--- /dev/null
--- /dev/null
++name: Blank Issue
++description: Create a blank issue.
++body:
++ - type: markdown
++ attributes:
++ value: Thank you for filing an issue!
++ - type: textarea
++ id: problem
++ attributes:
++ label: Description
++ description: >
++ Please provide a discription of the issue, along with any information
++ you feel relevant to replicate it.
++ validations:
++ required: true
++ - type: textarea
++ id: version
++ attributes:
++ label: Version
++ description: "Rust version (`rustc -Vv`)"
++ placeholder: |
++ rustc 1.46.0-nightly (f455e46ea 2020-06-20)
++ binary: rustc
++ commit-hash: f455e46eae1a227d735091091144601b467e1565
++ commit-date: 2020-06-20
++ host: x86_64-unknown-linux-gnu
++ release: 1.46.0-nightly
++ LLVM version: 10.0
++ render: text
++ - type: textarea
++ id: labels
++ attributes:
++ label: Additional Labels
++ description: >
++ Additional labels can be added to this issue by including the following
++ command
++ placeholder: |
++ @rustbot label +<label>
++
++ Common labels for this issue type are:
++ * C-an-interesting-project
++ * C-enhancement
++ * C-question
++ * C-tracking-issue
--- /dev/null
--- /dev/null
++name: Bug Report
++description: Create a bug report for Clippy
++labels: ["C-bug"]
++body:
++ - type: markdown
++ attributes:
++ value: Thank you for filing a bug report! 🐛
++ - type: textarea
++ id: problem
++ attributes:
++ label: Summary
++ description: >
++ Please provide a short summary of the bug, along with any information
++ you feel relevant to replicate the bug.
++ validations:
++ required: true
++ - type: textarea
++ id: reproducer
++ attributes:
++ label: Reproducer
++ description: Please provide the code and steps to repoduce the bug
++ value: |
++ I tried this code:
++
++ ```rust
++ <code>
++ ```
++
++ I expected to see this happen:
++
++ Instead, this happened:
++ - type: textarea
++ id: version
++ attributes:
++ label: Version
++ description: "Rust version (`rustc -Vv`)"
++ placeholder: |
++ rustc 1.46.0-nightly (f455e46ea 2020-06-20)
++ binary: rustc
++ commit-hash: f455e46eae1a227d735091091144601b467e1565
++ commit-date: 2020-06-20
++ host: x86_64-unknown-linux-gnu
++ release: 1.46.0-nightly
++ LLVM version: 10.0
++ render: text
++ - type: textarea
++ id: labels
++ attributes:
++ label: Additional Labels
++ description: >
++ Additional labels can be added to this issue by including the following
++ command
++ placeholder: |
++ @rustbot label +<label>
++
++ Common labels for this issue type are:
++ * `I-suggestion-causes-error`
--- /dev/null
--- /dev/null
++name: Bug Report (False Negative)
++description: Create a bug report about missing warnings from a lint
++labels: ["C-bug", "I-false-negative"]
++body:
++ - type: markdown
++ attributes:
++ value: Thank you for filing a bug report! 🐛
++ - type: textarea
++ id: problem
++ attributes:
++ label: Summary
++ description: >
++ Please provide a short summary of the bug, along with any information
++ you feel relevant to replicate the bug.
++ validations:
++ required: true
++ - type: input
++ id: lint-name
++ attributes:
++ label: Lint Name
++ description: Please provide the lint name.
++ - type: textarea
++ id: reproducer
++ attributes:
++ label: Reproducer
++ description: Please provide the code and steps to repoduce the bug
++ value: |
++ I tried this code:
++
++ ```rust
++ <code>
++ ```
++
++ I expected to see this happen:
++
++ Instead, this happened:
++ - type: textarea
++ id: version
++ attributes:
++ label: Version
++ description: "Rust version (`rustc -Vv`)"
++ placeholder: |
++ rustc 1.46.0-nightly (f455e46ea 2020-06-20)
++ binary: rustc
++ commit-hash: f455e46eae1a227d735091091144601b467e1565
++ commit-date: 2020-06-20
++ host: x86_64-unknown-linux-gnu
++ release: 1.46.0-nightly
++ LLVM version: 10.0
++ render: text
--- /dev/null
--- /dev/null
++name: Bug Report (False Positive)
++description: Create a bug report about a wrongly emitted lint warning
++labels: ["C-bug", "I-false-positive"]
++body:
++ - type: markdown
++ attributes:
++ value: Thank you for filing a bug report! 🐛
++ - type: textarea
++ id: problem
++ attributes:
++ label: Summary
++ description: >
++ Please provide a short summary of the bug, along with any information
++ you feel relevant to replicate the bug.
++ validations:
++ required: true
++ - type: input
++ id: lint-name
++ attributes:
++ label: Lint Name
++ description: Please provide the lint name.
++ - type: textarea
++ id: reproducer
++ attributes:
++ label: Reproducer
++ description: >
++ Please provide the code and steps to repoduce the bug together with the
++ output from Clippy.
++ value: |
++ I tried this code:
++
++ ```rust
++ <code>
++ ```
++
++ I saw this happen:
++
++ ```
++ <output>
++ ```
++
++ I expected to see this happen:
++ - type: textarea
++ id: version
++ attributes:
++ label: Version
++ description: "Rust version (`rustc -Vv`)"
++ placeholder: |
++ rustc 1.46.0-nightly (f455e46ea 2020-06-20)
++ binary: rustc
++ commit-hash: f455e46eae1a227d735091091144601b467e1565
++ commit-date: 2020-06-20
++ host: x86_64-unknown-linux-gnu
++ release: 1.46.0-nightly
++ LLVM version: 10.0
++ render: text
++ - type: textarea
++ id: labels
++ attributes:
++ label: Additional Labels
++ description: >
++ Additional labels can be added to this issue by including the following
++ command
++ placeholder: |
++ @rustbot label +<label>
++
++ Common labels for this issue type are:
++ * `I-suggestion-causes-error`
--- /dev/null
--- /dev/null
++name: Internal Compiler Error
++description: Create a report for an internal compiler error (ICE) in Clippy.
++labels: ["C-bug", "I-ICE"]
++body:
++ - type: markdown
++ attributes:
++ value: Thank you for finding an Internal Compiler Error! 🧊
++ - type: textarea
++ id: problem
++ attributes:
++ label: Summary
++ description: |
++ If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occured.
++
++ [mve]: http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/
++ validations:
++ required: true
++ - type: textarea
++ id: version
++ attributes:
++ label: Version
++ description: "Rust version (`rustc -Vv`)"
++ placeholder: |
++ rustc 1.46.0-nightly (f455e46ea 2020-06-20)
++ binary: rustc
++ commit-hash: f455e46eae1a227d735091091144601b467e1565
++ commit-date: 2020-06-20
++ host: x86_64-unknown-linux-gnu
++ release: 1.46.0-nightly
++ LLVM version: 10.0
++ render: text
++ - type: textarea
++ id: error
++ attributes:
++ label: Error output
++ description: >
++ Include a backtrace in the code block by setting `RUST_BACKTRACE=1` in
++ your environment. E.g. `RUST_BACKTRACE=1 cargo clippy`.
++ value: |
++ <details><summary>Backtrace</summary>
++ <p>
++
++ ```
++ <backtrace>
++ ```
++
++ </p>
++ </details>
--- /dev/null
--- /dev/null
++name: New lint suggestion
++description: Suggest a new Clippy lint.
++labels: ["A-lint"]
++body:
++ - type: markdown
++ attributes:
++ value: Thank you for your lint idea!
++ - type: textarea
++ id: what
++ attributes:
++ label: What it does
++ description: What does this lint do?
++ validations:
++ required: true
++ - type: input
++ id: lint-name
++ attributes:
++ label: Lint Name
++ description: Please provide the lint name.
++ - type: dropdown
++ id: category
++ attributes:
++ label: Category
++ description: >
++ What category should this lint go into? If you're unsure you can select
++ multiple categories. You can find a category description in the
++ `README`.
++ multiple: true
++ options:
++ - correctness
++ - suspicious
++ - style
++ - complexity
++ - perf
++ - pedantic
++ - restriction
++ - cargo
++ - type: textarea
++ id: advantage
++ attributes:
++ label: Advantage
++ description: >
++ What is the advantage of the recommended code over the original code?
++ placeholder: |
++ - Remove bounds check inserted by ...
++ - Remove the need to duplicate/store ...
++ - Remove typo ...
++ - type: textarea
++ id: drawbacks
++ attributes:
++ label: Drawbacks
++ description: What might be possible drawbacks of such a lint?
++ - type: textarea
++ id: example
++ attributes:
++ label: Example
++ description: >
++ Include a short example showing when the lint should trigger together
++ with the improved code.
++ value: |
++ ```rust
++ <code>
++ ```
++
++ Could be written as:
++
++ ```rust
++ <code>
++ ```
++ validations:
++ required: true
--- /dev/null
- - name: remove toolchain file
- run: rm rust-toolchain
-
- - name: rust-toolchain
- uses: actions-rs/toolchain@v1.0.6
- with:
- toolchain: nightly
- target: x86_64-unknown-linux-gnu
- profile: minimal
- components: rustfmt
- default: true
-
+name: Clippy Dev Test
+
+on:
+ push:
+ branches:
+ - auto
+ - try
+ pull_request:
+ # Only run on paths, that get checked by the clippy_dev tool
+ paths:
+ - 'CHANGELOG.md'
+ - 'README.md'
+ - '**.stderr'
+ - '**.rs'
+
+env:
+ RUST_BACKTRACE: 1
+
+jobs:
+ clippy_dev:
+ runs-on: ubuntu-latest
+
+ steps:
+ # Setup
+ - name: Checkout
+ uses: actions/checkout@v2.3.3
+
+ # Run
+ - name: Build
+ run: cargo build --features deny-warnings
+ working-directory: clippy_dev
+
+ - name: Test update_lints
+ run: cargo dev update_lints --check
+
+ - name: Test fmt
+ run: cargo dev fmt --check
+
+ # These jobs doesn't actually test anything, but they're only used to tell
+ # bors the build completed, as there is no practical way to detect when a
+ # workflow is successful listening to webhooks only.
+ #
+ # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
+
+ end-success:
+ name: bors dev test finished
+ if: github.event.pusher.name == 'bors' && success()
+ runs-on: ubuntu-latest
+ needs: [clippy_dev]
+
+ steps:
+ - name: Mark the job as successful
+ run: exit 0
+
+ end-failure:
+ name: bors dev test finished
+ if: github.event.pusher.name == 'bors' && (failure() || cancelled())
+ runs-on: ubuntu-latest
+ needs: [clippy_dev]
+
+ steps:
+ - name: Mark the job as a failure
+ run: exit 1
--- /dev/null
- * [`disallowed_method`]: Allow adding a reason that will be displayed with the
+# Changelog
+
+All notable changes to this project will be documented in this file.
+See [Changelog Update](doc/changelog_update.md) if you want to update this
+document.
+
+## Unreleased / In Rust Nightly
+
+[b7f3f7f...master](https://github.com/rust-lang/rust-clippy/compare/b7f3f7f...master)
+
+## Rust 1.57
+
+Current beta, release 2021-12-02
+
+[7bfc26e...b7f3f7f](https://github.com/rust-lang/rust-clippy/compare/7bfc26e...b7f3f7f)
+
+### New Lints
+
+* [`negative_feature_names`]
+ [#7539](https://github.com/rust-lang/rust-clippy/pull/7539)
+* [`redundant_feature_names`]
+ [#7539](https://github.com/rust-lang/rust-clippy/pull/7539)
+* [`mod_module_files`]
+ [#7543](https://github.com/rust-lang/rust-clippy/pull/7543)
+* [`self_named_module_files`]
+ [#7543](https://github.com/rust-lang/rust-clippy/pull/7543)
+* [`manual_split_once`]
+ [#7565](https://github.com/rust-lang/rust-clippy/pull/7565)
+* [`derivable_impls`]
+ [#7570](https://github.com/rust-lang/rust-clippy/pull/7570)
+* [`needless_option_as_deref`]
+ [#7596](https://github.com/rust-lang/rust-clippy/pull/7596)
+* [`iter_not_returning_iterator`]
+ [#7610](https://github.com/rust-lang/rust-clippy/pull/7610)
+* [`same_name_method`]
+ [#7653](https://github.com/rust-lang/rust-clippy/pull/7653)
+* [`manual_assert`] [#7669](https://github.com/rust-lang/rust-clippy/pull/7669)
+* [`non_send_fields_in_send_ty`]
+ [#7709](https://github.com/rust-lang/rust-clippy/pull/7709)
+* [`equatable_if_let`]
+ [#7762](https://github.com/rust-lang/rust-clippy/pull/7762)
+
+### Moves and Deprecations
+
+* Move [`shadow_unrelated`] to `restriction`
+ [#7338](https://github.com/rust-lang/rust-clippy/pull/7338)
+* Move [`option_if_let_else`] to `nursery`
+ [#7568](https://github.com/rust-lang/rust-clippy/pull/7568)
+* Move [`branches_sharing_code`] to `nursery`
+ [#7595](https://github.com/rust-lang/rust-clippy/pull/7595)
+* Rename `if_let_some_result` to [`match_result_ok`] which now also handles
+ `while let` cases [#7608](https://github.com/rust-lang/rust-clippy/pull/7608)
+* Move [`many_single_char_names`] to `pedantic`
+ [#7671](https://github.com/rust-lang/rust-clippy/pull/7671)
+* Move [`float_cmp`] to `pedantic`
+ [#7692](https://github.com/rust-lang/rust-clippy/pull/7692)
+* Rename `box_vec` to [`box_collection`] and lint on more general cases
+ [#7693](https://github.com/rust-lang/rust-clippy/pull/7693)
+* Uplift `invalid_atomic_ordering` to rustc
+ [rust-lang/rust#84039](https://github.com/rust-lang/rust/pull/84039)
+
+### Enhancements
+
+* Rewrite the `shadow*` lints, so that they find a lot more shadows and are not
+ limited to certain patterns
+ [#7338](https://github.com/rust-lang/rust-clippy/pull/7338)
+* The `avoid-breaking-exported-api` configuration now also works for
+ [`box_collection`], [`redundant_allocation`], [`rc_buffer`], [`vec_box`],
+ [`option_option`], [`linkedlist`], [`rc_mutex`]
+ [#7560](https://github.com/rust-lang/rust-clippy/pull/7560)
+* [`unnecessary_unwrap`]: Now also checks for `expect`s
+ [#7584](https://github.com/rust-lang/rust-clippy/pull/7584)
- * [`disallowed_type`]: Now also primitive types can be disallowed
++* [`disallowed_methods`]: Allow adding a reason that will be displayed with the
+ lint message
+ [#7621](https://github.com/rust-lang/rust-clippy/pull/7621)
+* [`approx_constant`]: Now checks the MSRV for `LOG10_2` and `LOG2_10`
+ [#7629](https://github.com/rust-lang/rust-clippy/pull/7629)
+* [`approx_constant`]: Add `TAU`
+ [#7642](https://github.com/rust-lang/rust-clippy/pull/7642)
+* [`needless_borrow`]: Now also lints on needless mutable borrows
+ [#7657](https://github.com/rust-lang/rust-clippy/pull/7657)
+* [`missing_safety_doc`]: Now also lints on unsafe traits
+ [#7734](https://github.com/rust-lang/rust-clippy/pull/7734)
+
+### False Positive Fixes
+
+* [`manual_map`]: No longer lints when the option is borrowed in the match and
+ also consumed in the arm
+ [#7531](https://github.com/rust-lang/rust-clippy/pull/7531)
+* [`filter_next`]: No longer lints if `filter` method is not the
+ `Iterator::filter` method
+ [#7562](https://github.com/rust-lang/rust-clippy/pull/7562)
+* [`manual_flatten`]: No longer lints if expression is used after `if let`
+ [#7566](https://github.com/rust-lang/rust-clippy/pull/7566)
+* [`option_if_let_else`]: Multiple fixes
+ [#7573](https://github.com/rust-lang/rust-clippy/pull/7573)
+ * `break` and `continue` statements local to the would-be closure are
+ allowed
+ * Don't lint in const contexts
+ * Don't lint when yield expressions are used
+ * Don't lint when the captures made by the would-be closure conflict with
+ the other branch
+ * Don't lint when a field of a local is used when the type could be
+ potentially moved from
+ * In some cases, don't lint when scrutinee expression conflicts with the
+ captures of the would-be closure
+* [`redundant_allocation`]: No longer lints on `Box<Box<dyn T>>` which replaces
+ wide pointers with thin pointers
+ [#7592](https://github.com/rust-lang/rust-clippy/pull/7592)
+* [`bool_assert_comparison`]: No longer lints on types that do not implement the
+ `Not` trait with `Output = bool`
+ [#7605](https://github.com/rust-lang/rust-clippy/pull/7605)
+* [`mut_range_bound`]: No longer lints on range bound mutations, that are
+ immediately followed by a `break;`
+ [#7607](https://github.com/rust-lang/rust-clippy/pull/7607)
+* [`mutable_key_type`]: Improve accuracy and document remaining false positives
+ and false negatives
+ [#7640](https://github.com/rust-lang/rust-clippy/pull/7640)
+* [`redundant_closure`]: Rewrite the lint to fix various false positives and
+ false negatives [#7661](https://github.com/rust-lang/rust-clippy/pull/7661)
+* [`large_enum_variant`]: No longer wrongly identifies the second largest
+ variant [#7677](https://github.com/rust-lang/rust-clippy/pull/7677)
+* [`needless_return`]: No longer lints on let-else expressions
+ [#7685](https://github.com/rust-lang/rust-clippy/pull/7685)
+* [`suspicious_else_formatting`]: No longer lints in proc-macros
+ [#7707](https://github.com/rust-lang/rust-clippy/pull/7707)
+* [`excessive_precision`]: No longer lints when in some cases the float was
+ already written in the shortest form
+ [#7722](https://github.com/rust-lang/rust-clippy/pull/7722)
+* [`doc_markdown`]: No longer lints on intra-doc links
+ [#7772](https://github.com/rust-lang/rust-clippy/pull/7772)
+
+### Suggestion Fixes/Improvements
+
+* [`unnecessary_operation`]: Recommend using an `assert!` instead of using a
+ function call in an indexing operation
+ [#7453](https://github.com/rust-lang/rust-clippy/pull/7453)
+* [`manual_split_once`]: Produce semantically equivalent suggestion when
+ `rsplitn` is used [#7663](https://github.com/rust-lang/rust-clippy/pull/7663)
+* [`while_let_on_iterator`]: Produce correct suggestion when using `&mut`
+ [#7690](https://github.com/rust-lang/rust-clippy/pull/7690)
+* [`manual_assert`]: No better handles complex conditions
+ [#7741](https://github.com/rust-lang/rust-clippy/pull/7741)
+* Correctly handle signs in exponents in numeric literals lints
+ [#7747](https://github.com/rust-lang/rust-clippy/pull/7747)
+* [`suspicious_map`]: Now also suggests to use `inspect` as an alternative
+ [#7770](https://github.com/rust-lang/rust-clippy/pull/7770)
+* Drop exponent from suggestion if it is 0 in numeric literals lints
+ [#7774](https://github.com/rust-lang/rust-clippy/pull/7774)
+
+### ICE Fixes
+
+* [`implicit_hasher`]
+ [#7761](https://github.com/rust-lang/rust-clippy/pull/7761)
+
+### Others
+
+* Clippy now uses the 2021
+ [Edition!](https://www.youtube.com/watch?v=q0aNduqb2Ro)
+ [#7664](https://github.com/rust-lang/rust-clippy/pull/7664)
+
+## Rust 1.56
+
+Current stable, released 2021-10-21
+
+[74d1561...7bfc26e](https://github.com/rust-lang/rust-clippy/compare/74d1561...7bfc26e)
+
+### New Lints
+
+* [`unwrap_or_else_default`]
+ [#7516](https://github.com/rust-lang/rust-clippy/pull/7516)
+
+### Enhancements
+
+* [`needless_continue`]: Now also lints in `loop { continue; }` case
+ [#7477](https://github.com/rust-lang/rust-clippy/pull/7477)
- * [`disallowed_type`]
++* [`disallowed_types`]: Now also primitive types can be disallowed
+ [#7488](https://github.com/rust-lang/rust-clippy/pull/7488)
+* [`manual_swap`]: Now also lints on xor swaps
+ [#7506](https://github.com/rust-lang/rust-clippy/pull/7506)
+* [`map_flatten`]: Now also lints on the `Result` type
+ [#7522](https://github.com/rust-lang/rust-clippy/pull/7522)
+* [`no_effect`]: Now also lints on inclusive ranges
+ [#7556](https://github.com/rust-lang/rust-clippy/pull/7556)
+
+### False Positive Fixes
+
+* [`nonstandard_macro_braces`]: No longer lints on similar named nested macros
+ [#7478](https://github.com/rust-lang/rust-clippy/pull/7478)
+* [`too_many_lines`]: No longer lints in closures to avoid duplicated diagnostics
+ [#7534](https://github.com/rust-lang/rust-clippy/pull/7534)
+* [`similar_names`]: No longer complains about `iter` and `item` being too
+ similar [#7546](https://github.com/rust-lang/rust-clippy/pull/7546)
+
+### Suggestion Fixes/Improvements
+
+* [`similar_names`]: No longer suggests to insert or add an underscore as a fix
+ [#7221](https://github.com/rust-lang/rust-clippy/pull/7221)
+* [`new_without_default`]: No longer shows the full qualified type path when
+ suggesting adding a `Default` implementation
+ [#7493](https://github.com/rust-lang/rust-clippy/pull/7493)
+* [`while_let_on_iterator`]: Now suggests re-borrowing mutable references
+ [#7520](https://github.com/rust-lang/rust-clippy/pull/7520)
+* [`extend_with_drain`]: Improve code suggestion for mutable and immutable
+ references [#7533](https://github.com/rust-lang/rust-clippy/pull/7533)
+* [`trivially_copy_pass_by_ref`]: Now properly handles `Self` type
+ [#7535](https://github.com/rust-lang/rust-clippy/pull/7535)
+* [`never_loop`]: Now suggests using `if let` instead of a `for` loop when
+ applicable [#7541](https://github.com/rust-lang/rust-clippy/pull/7541)
+
+### Documentation Improvements
+
+* Clippy now uses a lint to generate its lint documentation. [Lints all the way
+ down](https://en.wikipedia.org/wiki/Turtles_all_the_way_down).
+ [#7502](https://github.com/rust-lang/rust-clippy/pull/7502)
+* Reworked Clippy's website:
+ [#7172](https://github.com/rust-lang/rust-clippy/issues/7172)
+ [#7279](https://github.com/rust-lang/rust-clippy/pull/7279)
+ * Added applicability information about lints
+ * Added a link to jump into the implementation
+ * Improved loading times
+ * Adapted some styling
+* `cargo clippy --help` now also explains the `--fix` and `--no-deps` flag
+ [#7492](https://github.com/rust-lang/rust-clippy/pull/7492)
+* [`unnested_or_patterns`]: Removed `or_patterns` feature gate in the code
+ example [#7507](https://github.com/rust-lang/rust-clippy/pull/7507)
+
+## Rust 1.55
+
+Released 2021-09-09
+
+[3ae8faf...74d1561](https://github.com/rust-lang/rust-clippy/compare/3ae8faf...74d1561)
+
+### Important Changes
+
+* Stabilized `cargo clippy --fix` :tada:
+ [#7405](https://github.com/rust-lang/rust-clippy/pull/7405)
+
+### New Lints
+
+* [`rc_mutex`]
+ [#7316](https://github.com/rust-lang/rust-clippy/pull/7316)
+* [`nonstandard_macro_braces`]
+ [#7299](https://github.com/rust-lang/rust-clippy/pull/7299)
+* [`strlen_on_c_strings`]
+ [#7243](https://github.com/rust-lang/rust-clippy/pull/7243)
+* [`self_named_constructors`]
+ [#7403](https://github.com/rust-lang/rust-clippy/pull/7403)
+* [`disallowed_script_idents`]
+ [#7400](https://github.com/rust-lang/rust-clippy/pull/7400)
- * [`disallowed_method`], [`disallowed_type`]: The configuration values `disallowed-method` and `disallowed-type`
++* [`disallowed_types`]
+ [#7315](https://github.com/rust-lang/rust-clippy/pull/7315)
+* [`missing_enforced_import_renames`]
+ [#7300](https://github.com/rust-lang/rust-clippy/pull/7300)
+* [`extend_with_drain`]
+ [#7270](https://github.com/rust-lang/rust-clippy/pull/7270)
+
+### Moves and Deprecations
+
+* Moved [`from_iter_instead_of_collect`] to `pedantic`
+ [#7375](https://github.com/rust-lang/rust-clippy/pull/7375)
+* Added `suspicious` as a new lint group for *code that is most likely wrong or useless*
+ [#7350](https://github.com/rust-lang/rust-clippy/pull/7350)
+ * Moved [`blanket_clippy_restriction_lints`] to `suspicious`
+ * Moved [`empty_loop`] to `suspicious`
+ * Moved [`eval_order_dependence`] to `suspicious`
+ * Moved [`float_equality_without_abs`] to `suspicious`
+ * Moved [`for_loops_over_fallibles`] to `suspicious`
+ * Moved [`misrefactored_assign_op`] to `suspicious`
+ * Moved [`mut_range_bound`] to `suspicious`
+ * Moved [`mutable_key_type`] to `suspicious`
+ * Moved [`suspicious_arithmetic_impl`] to `suspicious`
+ * Moved [`suspicious_assignment_formatting`] to `suspicious`
+ * Moved [`suspicious_else_formatting`] to `suspicious`
+ * Moved [`suspicious_map`] to `suspicious`
+ * Moved [`suspicious_op_assign_impl`] to `suspicious`
+ * Moved [`suspicious_unary_op_formatting`] to `suspicious`
+
+### Enhancements
+
+* [`while_let_on_iterator`]: Now suggests `&mut iter` inside closures
+ [#7262](https://github.com/rust-lang/rust-clippy/pull/7262)
+* [`doc_markdown`]:
+ * Now detects unbalanced ticks
+ [#7357](https://github.com/rust-lang/rust-clippy/pull/7357)
+ * Add `FreeBSD` to the default configuration as an allowed identifier
+ [#7334](https://github.com/rust-lang/rust-clippy/pull/7334)
+* [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]: Now allows wildcards for enums with unstable
+ or hidden variants
+ [#7407](https://github.com/rust-lang/rust-clippy/pull/7407)
+* [`redundant_allocation`]: Now additionally supports the `Arc<>` type
+ [#7308](https://github.com/rust-lang/rust-clippy/pull/7308)
+* [`blacklisted_name`]: Now allows blacklisted names in test code
+ [#7379](https://github.com/rust-lang/rust-clippy/pull/7379)
+* [`redundant_closure`]: Suggests `&mut` for `FnMut`
+ [#7437](https://github.com/rust-lang/rust-clippy/pull/7437)
- * [`disallowed_method`]: Now supports functions in addition to methods
++* [`disallowed_methods`], [`disallowed_types`]: The configuration values `disallowed-method` and `disallowed-type`
+ no longer require fully qualified paths
+ [#7345](https://github.com/rust-lang/rust-clippy/pull/7345)
+* [`zst_offset`]: Fixed lint invocation after it was accidentally suppressed
+ [#7396](https://github.com/rust-lang/rust-clippy/pull/7396)
+
+### False Positive Fixes
+
+* [`default_numeric_fallback`]: No longer lints on float literals as function arguments
+ [#7446](https://github.com/rust-lang/rust-clippy/pull/7446)
+* [`use_self`]: No longer lints on type parameters
+ [#7288](https://github.com/rust-lang/rust-clippy/pull/7288)
+* [`unimplemented`]: Now ignores the `assert` and `debug_assert` macros
+ [#7439](https://github.com/rust-lang/rust-clippy/pull/7439)
+* [`branches_sharing_code`]: Now always checks for block expressions
+ [#7462](https://github.com/rust-lang/rust-clippy/pull/7462)
+* [`field_reassign_with_default`]: No longer triggers in macros
+ [#7160](https://github.com/rust-lang/rust-clippy/pull/7160)
+* [`redundant_clone`]: No longer lints on required clones for borrowed data
+ [#7346](https://github.com/rust-lang/rust-clippy/pull/7346)
+* [`default_numeric_fallback`]: No longer triggers in external macros
+ [#7325](https://github.com/rust-lang/rust-clippy/pull/7325)
+* [`needless_bool`]: No longer lints in macros
+ [#7442](https://github.com/rust-lang/rust-clippy/pull/7442)
+* [`useless_format`]: No longer triggers when additional text is being appended
+ [#7442](https://github.com/rust-lang/rust-clippy/pull/7442)
+* [`assertions_on_constants`]: `cfg!(...)` is no longer considered to be a constant
+ [#7319](https://github.com/rust-lang/rust-clippy/pull/7319)
+
+### Suggestion Fixes/Improvements
+
+* [`needless_collect`]: Now show correct lint messages for shadowed values
+ [#7289](https://github.com/rust-lang/rust-clippy/pull/7289)
+* [`wrong_pub_self_convention`]: The deprecated message now suggest the correct configuration value
+ [#7382](https://github.com/rust-lang/rust-clippy/pull/7382)
+* [`semicolon_if_nothing_returned`]: Allow missing semicolon in blocks with only one expression
+ [#7326](https://github.com/rust-lang/rust-clippy/pull/7326)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#7470](https://github.com/rust-lang/rust-clippy/pull/7470)
+* [`redundant_pattern_matching`]
+ [#7471](https://github.com/rust-lang/rust-clippy/pull/7471)
+* [`modulo_one`]
+ [#7473](https://github.com/rust-lang/rust-clippy/pull/7473)
+* [`use_self`]
+ [#7428](https://github.com/rust-lang/rust-clippy/pull/7428)
+
+## Rust 1.54
+
+Released 2021-07-29
+
+[7c7683c...3ae8faf](https://github.com/rust-lang/rust-clippy/compare/7c7683c...3ae8faf)
+
+### New Lints
+
+- [`ref_binding_to_reference`]
+ [#7105](https://github.com/rust-lang/rust-clippy/pull/7105)
+- [`needless_bitwise_bool`]
+ [#7133](https://github.com/rust-lang/rust-clippy/pull/7133)
+- [`unused_async`] [#7225](https://github.com/rust-lang/rust-clippy/pull/7225)
+- [`manual_str_repeat`]
+ [#7265](https://github.com/rust-lang/rust-clippy/pull/7265)
+- [`suspicious_splitn`]
+ [#7292](https://github.com/rust-lang/rust-clippy/pull/7292)
+
+### Moves and Deprecations
+
+- Deprecate `pub_enum_variant_names` and `wrong_pub_self_convention` in favor of
+ the new `avoid-breaking-exported-api` config option (see
+ [Enhancements](#1-54-enhancements))
+ [#7187](https://github.com/rust-lang/rust-clippy/pull/7187)
+- Move [`inconsistent_struct_constructor`] to `pedantic`
+ [#7193](https://github.com/rust-lang/rust-clippy/pull/7193)
+- Move [`needless_borrow`] to `style` (now warn-by-default)
+ [#7254](https://github.com/rust-lang/rust-clippy/pull/7254)
+- Move [`suspicious_operation_groupings`] to `nursery`
+ [#7266](https://github.com/rust-lang/rust-clippy/pull/7266)
+- Move [`semicolon_if_nothing_returned`] to `pedantic`
+ [#7268](https://github.com/rust-lang/rust-clippy/pull/7268)
+
+### Enhancements <a name="1-54-enhancements"></a>
+
+- [`while_let_on_iterator`]: Now also lints in nested loops
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`single_char_pattern`]: Now also lints on `strip_prefix` and `strip_suffix`
+ [#7156](https://github.com/rust-lang/rust-clippy/pull/7156)
+- [`needless_collect`]: Now also lints on assignments with type annotations
+ [#7163](https://github.com/rust-lang/rust-clippy/pull/7163)
+- [`if_then_some_else_none`]: Now works with the MSRV config
+ [#7177](https://github.com/rust-lang/rust-clippy/pull/7177)
+- Add `avoid-breaking-exported-api` config option for the lints
+ [`enum_variant_names`], [`large_types_passed_by_value`],
+ [`trivially_copy_pass_by_ref`], [`unnecessary_wraps`],
+ [`upper_case_acronyms`], and [`wrong_self_convention`]. We recommend to set
+ this configuration option to `false` before a major release (1.0/2.0/...) to
+ clean up the API [#7187](https://github.com/rust-lang/rust-clippy/pull/7187)
+- [`needless_collect`]: Now lints on even more data structures
+ [#7188](https://github.com/rust-lang/rust-clippy/pull/7188)
+- [`missing_docs_in_private_items`]: No longer sees `#[<name> = "<value>"]` like
+ attributes as sufficient documentation
+ [#7281](https://github.com/rust-lang/rust-clippy/pull/7281)
+- [`needless_collect`], [`short_circuit_statement`], [`unnecessary_operation`]:
+ Now work as expected when used with `allow`
+ [#7282](https://github.com/rust-lang/rust-clippy/pull/7282)
+
+### False Positive Fixes
+
+- [`implicit_return`]: Now takes all diverging functions in account to avoid
+ false positives [#6951](https://github.com/rust-lang/rust-clippy/pull/6951)
+- [`while_let_on_iterator`]: No longer lints when the iterator is a struct field
+ and the struct is used in the loop
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`multiple_inherent_impl`]: No longer lints with generic arguments
+ [#7089](https://github.com/rust-lang/rust-clippy/pull/7089)
+- [`comparison_chain`]: No longer lints in a `const` context
+ [#7118](https://github.com/rust-lang/rust-clippy/pull/7118)
+- [`while_immutable_condition`]: Fix false positive where mutation in the loop
+ variable wasn't picked up
+ [#7144](https://github.com/rust-lang/rust-clippy/pull/7144)
+- [`default_trait_access`]: No longer lints in macros
+ [#7150](https://github.com/rust-lang/rust-clippy/pull/7150)
+- [`needless_question_mark`]: No longer lints when the inner value is implicitly
+ dereferenced [#7165](https://github.com/rust-lang/rust-clippy/pull/7165)
+- [`unused_unit`]: No longer lints when multiple macro contexts are involved
+ [#7167](https://github.com/rust-lang/rust-clippy/pull/7167)
+- [`eval_order_dependence`]: Fix false positive in async context
+ [#7174](https://github.com/rust-lang/rust-clippy/pull/7174)
+- [`unnecessary_filter_map`]: No longer lints if the `filter_map` changes the
+ type [#7175](https://github.com/rust-lang/rust-clippy/pull/7175)
+- [`wrong_self_convention`]: No longer lints in trait implementations of
+ non-`Copy` types [#7182](https://github.com/rust-lang/rust-clippy/pull/7182)
+- [`suboptimal_flops`]: No longer lints on `powi(2)`
+ [#7201](https://github.com/rust-lang/rust-clippy/pull/7201)
+- [`wrong_self_convention`]: No longer lints if there is no implicit `self`
+ [#7215](https://github.com/rust-lang/rust-clippy/pull/7215)
+- [`option_if_let_else`]: No longer lints on `else if let` pattern
+ [#7216](https://github.com/rust-lang/rust-clippy/pull/7216)
+- [`use_self`], [`useless_conversion`]: Fix false positives when generic
+ arguments are involved
+ [#7223](https://github.com/rust-lang/rust-clippy/pull/7223)
+- [`manual_unwrap_or`]: Fix false positive with deref coercion
+ [#7233](https://github.com/rust-lang/rust-clippy/pull/7233)
+- [`similar_names`]: No longer lints on `wparam`/`lparam`
+ [#7255](https://github.com/rust-lang/rust-clippy/pull/7255)
+- [`redundant_closure`]: No longer lints on using the `vec![]` macro in a
+ closure [#7263](https://github.com/rust-lang/rust-clippy/pull/7263)
+
+### Suggestion Fixes/Improvements
+
+- [`implicit_return`]
+ [#6951](https://github.com/rust-lang/rust-clippy/pull/6951)
+ - Fix suggestion for async functions
+ - Improve suggestion with macros
+ - Suggest to change `break` to `return` when appropriate
+- [`while_let_on_iterator`]: Now suggests `&mut iter` when necessary
+ [#6966](https://github.com/rust-lang/rust-clippy/pull/6966)
+- [`match_single_binding`]: Improve suggestion when match scrutinee has side
+ effects [#7095](https://github.com/rust-lang/rust-clippy/pull/7095)
+- [`needless_borrow`]: Now suggests to also change usage sites as needed
+ [#7105](https://github.com/rust-lang/rust-clippy/pull/7105)
+- [`write_with_newline`]: Improve suggestion when only `\n` is written to the
+ buffer [#7183](https://github.com/rust-lang/rust-clippy/pull/7183)
+- [`from_iter_instead_of_collect`]: The suggestion is now auto applicable also
+ when a `<_ as Trait>::_` is involved
+ [#7264](https://github.com/rust-lang/rust-clippy/pull/7264)
+- [`not_unsafe_ptr_arg_deref`]: Improved error message
+ [#7294](https://github.com/rust-lang/rust-clippy/pull/7294)
+
+### ICE Fixes
+
+- Fix ICE when running Clippy on `libstd`
+ [#7140](https://github.com/rust-lang/rust-clippy/pull/7140)
+- [`implicit_return`]
+ [#7242](https://github.com/rust-lang/rust-clippy/pull/7242)
+
+## Rust 1.53
+
+Released 2021-06-17
+
+[6ed6f1e...7c7683c](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...7c7683c)
+
+### New Lints
+
+* [`option_filter_map`]
+ [#6342](https://github.com/rust-lang/rust-clippy/pull/6342)
+* [`branches_sharing_code`]
+ [#6463](https://github.com/rust-lang/rust-clippy/pull/6463)
+* [`needless_for_each`]
+ [#6706](https://github.com/rust-lang/rust-clippy/pull/6706)
+* [`if_then_some_else_none`]
+ [#6859](https://github.com/rust-lang/rust-clippy/pull/6859)
+* [`non_octal_unix_permissions`]
+ [#7001](https://github.com/rust-lang/rust-clippy/pull/7001)
+* [`unnecessary_self_imports`]
+ [#7072](https://github.com/rust-lang/rust-clippy/pull/7072)
+* [`bool_assert_comparison`]
+ [#7083](https://github.com/rust-lang/rust-clippy/pull/7083)
+* [`cloned_instead_of_copied`]
+ [#7098](https://github.com/rust-lang/rust-clippy/pull/7098)
+* [`flat_map_option`]
+ [#7101](https://github.com/rust-lang/rust-clippy/pull/7101)
+
+### Moves and Deprecations
+
+* Deprecate [`filter_map`] lint
+ [#7059](https://github.com/rust-lang/rust-clippy/pull/7059)
+* Move [`transmute_ptr_to_ptr`] to `pedantic`
+ [#7102](https://github.com/rust-lang/rust-clippy/pull/7102)
+
+### Enhancements
+
+* [`mem_replace_with_default`]: Also lint on common std constructors
+ [#6820](https://github.com/rust-lang/rust-clippy/pull/6820)
+* [`wrong_self_convention`]: Also lint on `to_*_mut` methods
+ [#6828](https://github.com/rust-lang/rust-clippy/pull/6828)
+* [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]:
+ [#6863](https://github.com/rust-lang/rust-clippy/pull/6863)
+ * Attempt to find a common path prefix in suggestion
+ * Don't lint on `Option` and `Result`
+ * Consider `Self` prefix
+* [`explicit_deref_methods`]: Also lint on chained `deref` calls
+ [#6865](https://github.com/rust-lang/rust-clippy/pull/6865)
+* [`or_fun_call`]: Also lint on `unsafe` blocks
+ [#6928](https://github.com/rust-lang/rust-clippy/pull/6928)
+* [`vec_box`], [`linkedlist`], [`option_option`]: Also lint in `const` and
+ `static` items [#6938](https://github.com/rust-lang/rust-clippy/pull/6938)
+* [`search_is_some`]: Also check for `is_none`
+ [#6942](https://github.com/rust-lang/rust-clippy/pull/6942)
+* [`string_lit_as_bytes`]: Also lint on `into_bytes`
+ [#6959](https://github.com/rust-lang/rust-clippy/pull/6959)
+* [`len_without_is_empty`]: Also lint if function signatures of `len` and
+ `is_empty` don't match
+ [#6980](https://github.com/rust-lang/rust-clippy/pull/6980)
+* [`redundant_pattern_matching`]: Also lint if the pattern is a `&` pattern
+ [#6991](https://github.com/rust-lang/rust-clippy/pull/6991)
+* [`clone_on_copy`]: Also lint on chained method calls taking `self` by value
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`missing_panics_doc`]: Also lint on `assert_eq!` and `assert_ne!`
+ [#7029](https://github.com/rust-lang/rust-clippy/pull/7029)
+* [`needless_return`]: Also lint in `async` functions
+ [#7067](https://github.com/rust-lang/rust-clippy/pull/7067)
+* [`unused_io_amount`]: Also lint on expressions like `_.read().ok()?`
+ [#7100](https://github.com/rust-lang/rust-clippy/pull/7100)
+* [`iter_cloned_collect`]: Also lint on large arrays, since const-generics are
+ now stable [#7138](https://github.com/rust-lang/rust-clippy/pull/7138)
+
+### False Positive Fixes
+
+* [`upper_case_acronyms`]: No longer lints on public items
+ [#6805](https://github.com/rust-lang/rust-clippy/pull/6805)
+* [`suspicious_map`]: No longer lints when side effects may occur inside the
+ `map` call [#6831](https://github.com/rust-lang/rust-clippy/pull/6831)
+* [`manual_map`], [`manual_unwrap_or`]: No longer lints in `const` functions
+ [#6917](https://github.com/rust-lang/rust-clippy/pull/6917)
+* [`wrong_self_convention`]: Now respects `Copy` types
+ [#6924](https://github.com/rust-lang/rust-clippy/pull/6924)
+* [`needless_question_mark`]: No longer lints if the `?` and the `Some(..)` come
+ from different macro contexts [#6935](https://github.com/rust-lang/rust-clippy/pull/6935)
+* [`map_entry`]: Better detect if the entry API can be used
+ [#6937](https://github.com/rust-lang/rust-clippy/pull/6937)
+* [`or_fun_call`]: No longer lints on some `len` function calls
+ [#6950](https://github.com/rust-lang/rust-clippy/pull/6950)
+* [`new_ret_no_self`]: No longer lints when `Self` is returned with different
+ generic arguments [#6952](https://github.com/rust-lang/rust-clippy/pull/6952)
+* [`upper_case_acronyms`]: No longer lints on public items
+ [#6981](https://github.com/rust-lang/rust-clippy/pull/6981)
+* [`explicit_into_iter_loop`]: Only lint when `into_iter` is an implementation
+ of `IntoIterator` [#6982](https://github.com/rust-lang/rust-clippy/pull/6982)
+* [`expl_impl_clone_on_copy`]: Take generic constraints into account before
+ suggesting to use `derive` instead
+ [#6993](https://github.com/rust-lang/rust-clippy/pull/6993)
+* [`missing_panics_doc`]: No longer lints when only debug-assertions are used
+ [#6996](https://github.com/rust-lang/rust-clippy/pull/6996)
+* [`clone_on_copy`]: Only lint when using the `Clone` trait
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`wrong_self_convention`]: No longer lints inside a trait implementation
+ [#7002](https://github.com/rust-lang/rust-clippy/pull/7002)
+* [`redundant_clone`]: No longer lints when the cloned value is modified while
+ the clone is in use
+ [#7011](https://github.com/rust-lang/rust-clippy/pull/7011)
+* [`same_item_push`]: No longer lints if the `Vec` is used in the loop body
+ [#7018](https://github.com/rust-lang/rust-clippy/pull/7018)
+* [`cargo_common_metadata`]: Remove author requirement
+ [#7026](https://github.com/rust-lang/rust-clippy/pull/7026)
+* [`panic_in_result_fn`]: No longer lints on `debug_assert` family
+ [#7060](https://github.com/rust-lang/rust-clippy/pull/7060)
+* [`panic`]: No longer wrongfully lints on `debug_assert` with message
+ [#7063](https://github.com/rust-lang/rust-clippy/pull/7063)
+* [`wrong_self_convention`]: No longer lints in trait implementations where no
+ `self` is involved [#7064](https://github.com/rust-lang/rust-clippy/pull/7064)
+* [`missing_const_for_fn`]: No longer lints when unstable `const` function is
+ involved [#7076](https://github.com/rust-lang/rust-clippy/pull/7076)
+* [`suspicious_else_formatting`]: Allow Allman style braces
+ [#7087](https://github.com/rust-lang/rust-clippy/pull/7087)
+* [`inconsistent_struct_constructor`]: No longer lints in macros
+ [#7097](https://github.com/rust-lang/rust-clippy/pull/7097)
+* [`single_component_path_imports`]: No longer lints on macro re-exports
+ [#7120](https://github.com/rust-lang/rust-clippy/pull/7120)
+
+### Suggestion Fixes/Improvements
+
+* [`redundant_pattern_matching`]: Add a note when applying this lint would
+ change the drop order
+ [#6568](https://github.com/rust-lang/rust-clippy/pull/6568)
+* [`write_literal`], [`print_literal`]: Add auto-applicable suggestion
+ [#6821](https://github.com/rust-lang/rust-clippy/pull/6821)
+* [`manual_map`]: Fix suggestion for complex `if let ... else` chains
+ [#6856](https://github.com/rust-lang/rust-clippy/pull/6856)
+* [`inconsistent_struct_constructor`]: Make lint description and message clearer
+ [#6892](https://github.com/rust-lang/rust-clippy/pull/6892)
+* [`map_entry`]: Now suggests `or_insert`, `insert_with` or `match _.entry(_)`
+ as appropriate [#6937](https://github.com/rust-lang/rust-clippy/pull/6937)
+* [`manual_flatten`]: Suggest to insert `copied` if necessary
+ [#6962](https://github.com/rust-lang/rust-clippy/pull/6962)
+* [`redundant_slicing`]: Fix suggestion when a re-borrow might be required or
+ when the value is from a macro call
+ [#6975](https://github.com/rust-lang/rust-clippy/pull/6975)
+* [`match_wildcard_for_single_variants`]: Fix suggestion for hidden variant
+ [#6988](https://github.com/rust-lang/rust-clippy/pull/6988)
+* [`clone_on_copy`]: Correct suggestion when the cloned value is a macro call
+ [#7000](https://github.com/rust-lang/rust-clippy/pull/7000)
+* [`manual_map`]: Fix suggestion at the end of an if chain
+ [#7004](https://github.com/rust-lang/rust-clippy/pull/7004)
+* Fix needless parenthesis output in multiple lint suggestions
+ [#7013](https://github.com/rust-lang/rust-clippy/pull/7013)
+* [`needless_collect`]: Better explanation in the lint message
+ [#7020](https://github.com/rust-lang/rust-clippy/pull/7020)
+* [`useless_vec`]: Now considers mutability
+ [#7036](https://github.com/rust-lang/rust-clippy/pull/7036)
+* [`useless_format`]: Wrap the content in braces if necessary
+ [#7092](https://github.com/rust-lang/rust-clippy/pull/7092)
+* [`single_match`]: Don't suggest an equality check for types which don't
+ implement `PartialEq`
+ [#7093](https://github.com/rust-lang/rust-clippy/pull/7093)
+* [`from_over_into`]: Mention type in help message
+ [#7099](https://github.com/rust-lang/rust-clippy/pull/7099)
+* [`manual_unwrap_or`]: Fix invalid code suggestion due to a macro call
+ [#7136](https://github.com/rust-lang/rust-clippy/pull/7136)
+
+### ICE Fixes
+
+* [`macro_use_imports`]
+ [#7022](https://github.com/rust-lang/rust-clippy/pull/7022)
+* [`missing_panics_doc`]
+ [#7034](https://github.com/rust-lang/rust-clippy/pull/7034)
+* [`tabs_in_doc_comments`]
+ [#7039](https://github.com/rust-lang/rust-clippy/pull/7039)
+* [`missing_const_for_fn`]
+ [#7128](https://github.com/rust-lang/rust-clippy/pull/7128)
+
+### Others
+
+* [Clippy's lint
+ list](https://rust-lang.github.io/rust-clippy/master/index.html) now supports
+ themes [#7030](https://github.com/rust-lang/rust-clippy/pull/7030)
+* Lints that were uplifted to `rustc` now mention the new `rustc` name in the
+ deprecation warning
+ [#7056](https://github.com/rust-lang/rust-clippy/pull/7056)
+
+## Rust 1.52
+
+Released 2021-05-06
+
+[3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e)
+
+### New Lints
+
+* [`from_str_radix_10`]
+ [#6717](https://github.com/rust-lang/rust-clippy/pull/6717)
+* [`implicit_clone`]
+ [#6730](https://github.com/rust-lang/rust-clippy/pull/6730)
+* [`semicolon_if_nothing_returned`]
+ [#6681](https://github.com/rust-lang/rust-clippy/pull/6681)
+* [`manual_flatten`]
+ [#6646](https://github.com/rust-lang/rust-clippy/pull/6646)
+* [`inconsistent_struct_constructor`]
+ [#6769](https://github.com/rust-lang/rust-clippy/pull/6769)
+* [`iter_count`]
+ [#6791](https://github.com/rust-lang/rust-clippy/pull/6791)
+* [`default_numeric_fallback`]
+ [#6662](https://github.com/rust-lang/rust-clippy/pull/6662)
+* [`bytes_nth`]
+ [#6695](https://github.com/rust-lang/rust-clippy/pull/6695)
+* [`filter_map_identity`]
+ [#6685](https://github.com/rust-lang/rust-clippy/pull/6685)
+* [`manual_map`]
+ [#6573](https://github.com/rust-lang/rust-clippy/pull/6573)
+
+### Moves and Deprecations
+
+* Moved [`upper_case_acronyms`] to `pedantic`
+ [#6775](https://github.com/rust-lang/rust-clippy/pull/6775)
+* Moved [`manual_map`] to `nursery`
+ [#6796](https://github.com/rust-lang/rust-clippy/pull/6796)
+* Moved [`unnecessary_wraps`] to `pedantic`
+ [#6765](https://github.com/rust-lang/rust-clippy/pull/6765)
+* Moved [`trivial_regex`] to `nursery`
+ [#6696](https://github.com/rust-lang/rust-clippy/pull/6696)
+* Moved [`naive_bytecount`] to `pedantic`
+ [#6825](https://github.com/rust-lang/rust-clippy/pull/6825)
+* Moved [`upper_case_acronyms`] to `style`
+ [#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
+* Moved [`manual_map`] to `style`
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+
+### Enhancements
+
- * [`disallowed_method`] [#6081](https://github.com/rust-lang/rust-clippy/pull/6081)
++* [`disallowed_methods`]: Now supports functions in addition to methods
+ [#6674](https://github.com/rust-lang/rust-clippy/pull/6674)
+* [`upper_case_acronyms`]: Added a new configuration `upper-case-acronyms-aggressive` to
+ trigger the lint if there is more than one uppercase character next to each other
+ [#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
+* [`collapsible_match`]: Now supports block comparison with different value names
+ [#6754](https://github.com/rust-lang/rust-clippy/pull/6754)
+* [`unnecessary_wraps`]: Will now suggest removing unnecessary wrapped return unit type, like `Option<()>`
+ [#6665](https://github.com/rust-lang/rust-clippy/pull/6665)
+* Improved value usage detection in closures
+ [#6698](https://github.com/rust-lang/rust-clippy/pull/6698)
+
+### False Positive Fixes
+
+* [`use_self`]: No longer lints in macros
+ [#6833](https://github.com/rust-lang/rust-clippy/pull/6833)
+* [`use_self`]: Fixed multiple false positives for: generics, associated types and derive implementations
+ [#6179](https://github.com/rust-lang/rust-clippy/pull/6179)
+* [`missing_inline_in_public_items`]: No longer lints for procedural macros
+ [#6814](https://github.com/rust-lang/rust-clippy/pull/6814)
+* [`inherent_to_string`]: No longer lints on functions with function generics
+ [#6771](https://github.com/rust-lang/rust-clippy/pull/6771)
+* [`doc_markdown`]: Add `OpenDNS` to the default configuration as an allowed identifier
+ [#6783](https://github.com/rust-lang/rust-clippy/pull/6783)
+* [`missing_panics_doc`]: No longer lints on [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html)
+ [#6700](https://github.com/rust-lang/rust-clippy/pull/6700)
+* [`collapsible_if`]: No longer lints on if statements with attributes
+ [#6701](https://github.com/rust-lang/rust-clippy/pull/6701)
+* [`match_same_arms`]: Only considers empty blocks as equal if the tokens contained are the same
+ [#6843](https://github.com/rust-lang/rust-clippy/pull/6843)
+* [`redundant_closure`]: Now ignores macros
+ [#6871](https://github.com/rust-lang/rust-clippy/pull/6871)
+* [`manual_map`]: Fixed false positives when control flow statements like `return`, `break` etc. are used
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+* [`vec_init_then_push`]: Fixed false positives for loops and if statements
+ [#6697](https://github.com/rust-lang/rust-clippy/pull/6697)
+* [`len_without_is_empty`]: Will now consider multiple impl blocks and `#[allow]` on
+ the `len` method as well as the type definition.
+ [#6853](https://github.com/rust-lang/rust-clippy/pull/6853)
+* [`let_underscore_drop`]: Only lints on types which implement `Drop`
+ [#6682](https://github.com/rust-lang/rust-clippy/pull/6682)
+* [`unit_arg`]: No longer lints on unit arguments when they come from a path expression.
+ [#6601](https://github.com/rust-lang/rust-clippy/pull/6601)
+* [`cargo_common_metadata`]: No longer lints if
+ [`publish = false`](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field)
+ is defined in the manifest
+ [#6650](https://github.com/rust-lang/rust-clippy/pull/6650)
+
+### Suggestion Fixes/Improvements
+
+* [`collapsible_match`]: Fixed lint message capitalization
+ [#6766](https://github.com/rust-lang/rust-clippy/pull/6766)
+* [`or_fun_call`]: Improved suggestions for `or_insert(vec![])`
+ [#6790](https://github.com/rust-lang/rust-clippy/pull/6790)
+* [`manual_map`]: No longer expands macros in the suggestions
+ [#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
+* Aligned Clippy's lint messages with the rustc dev guide
+ [#6787](https://github.com/rust-lang/rust-clippy/pull/6787)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#6866](https://github.com/rust-lang/rust-clippy/pull/6866)
+
+### Documentation Improvements
+
+* [`useless_format`]: Improved the documentation example
+ [#6854](https://github.com/rust-lang/rust-clippy/pull/6854)
+* Clippy's [`README.md`]: Includes a new subsection on running Clippy as a rustc wrapper
+ [#6782](https://github.com/rust-lang/rust-clippy/pull/6782)
+
+### Others
+* Running `cargo clippy` after `cargo check` now works as expected
+ (`cargo clippy` and `cargo check` no longer shares the same build cache)
+ [#6687](https://github.com/rust-lang/rust-clippy/pull/6687)
+* Cargo now re-runs Clippy if arguments after `--` provided to `cargo clippy` are changed.
+ [#6834](https://github.com/rust-lang/rust-clippy/pull/6834)
+* Extracted Clippy's `utils` module into the new `clippy_utils` crate
+ [#6756](https://github.com/rust-lang/rust-clippy/pull/6756)
+* Clippy lintcheck tool improvements
+ [#6800](https://github.com/rust-lang/rust-clippy/pull/6800)
+ [#6735](https://github.com/rust-lang/rust-clippy/pull/6735)
+ [#6764](https://github.com/rust-lang/rust-clippy/pull/6764)
+ [#6708](https://github.com/rust-lang/rust-clippy/pull/6708)
+ [#6780](https://github.com/rust-lang/rust-clippy/pull/6780)
+ [#6686](https://github.com/rust-lang/rust-clippy/pull/6686)
+
+## Rust 1.51
+
+Released 2021-03-25
+
+[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797)
+
+### New Lints
+
+* [`upper_case_acronyms`]
+ [#6475](https://github.com/rust-lang/rust-clippy/pull/6475)
+* [`from_over_into`] [#6476](https://github.com/rust-lang/rust-clippy/pull/6476)
+* [`case_sensitive_file_extension_comparisons`]
+ [#6500](https://github.com/rust-lang/rust-clippy/pull/6500)
+* [`needless_question_mark`]
+ [#6507](https://github.com/rust-lang/rust-clippy/pull/6507)
+* [`missing_panics_doc`]
+ [#6523](https://github.com/rust-lang/rust-clippy/pull/6523)
+* [`redundant_slicing`]
+ [#6528](https://github.com/rust-lang/rust-clippy/pull/6528)
+* [`vec_init_then_push`]
+ [#6538](https://github.com/rust-lang/rust-clippy/pull/6538)
+* [`ptr_as_ptr`] [#6542](https://github.com/rust-lang/rust-clippy/pull/6542)
+* [`collapsible_else_if`] (split out from `collapsible_if`)
+ [#6544](https://github.com/rust-lang/rust-clippy/pull/6544)
+* [`inspect_for_each`] [#6577](https://github.com/rust-lang/rust-clippy/pull/6577)
+* [`manual_filter_map`]
+ [#6591](https://github.com/rust-lang/rust-clippy/pull/6591)
+* [`exhaustive_enums`]
+ [#6617](https://github.com/rust-lang/rust-clippy/pull/6617)
+* [`exhaustive_structs`]
+ [#6617](https://github.com/rust-lang/rust-clippy/pull/6617)
+
+### Moves and Deprecations
+
+* Replace [`find_map`] with [`manual_find_map`]
+ [#6591](https://github.com/rust-lang/rust-clippy/pull/6591)
+* `unknown_clippy_lints` Now integrated in the `unknown_lints` rustc lint
+ [#6653](https://github.com/rust-lang/rust-clippy/pull/6653)
+
+### Enhancements
+
+* [`ptr_arg`] Now also suggests to use `&Path` instead of `&PathBuf`
+ [#6506](https://github.com/rust-lang/rust-clippy/pull/6506)
+* [`cast_ptr_alignment`] Also lint when the `pointer::cast` method is used
+ [#6557](https://github.com/rust-lang/rust-clippy/pull/6557)
+* [`collapsible_match`] Now also deals with `&` and `*` operators in the `match`
+ scrutinee [#6619](https://github.com/rust-lang/rust-clippy/pull/6619)
+
+### False Positive Fixes
+
+* [`similar_names`] Ignore underscore prefixed names
+ [#6403](https://github.com/rust-lang/rust-clippy/pull/6403)
+* [`print_literal`] and [`write_literal`] No longer lint numeric literals
+ [#6408](https://github.com/rust-lang/rust-clippy/pull/6408)
+* [`large_enum_variant`] No longer lints in external macros
+ [#6485](https://github.com/rust-lang/rust-clippy/pull/6485)
+* [`empty_enum`] Only lint if `never_type` feature is enabled
+ [#6513](https://github.com/rust-lang/rust-clippy/pull/6513)
+* [`field_reassign_with_default`] No longer lints in macros
+ [#6553](https://github.com/rust-lang/rust-clippy/pull/6553)
+* [`size_of_in_element_count`] No longer lints when dividing by element size
+ [#6578](https://github.com/rust-lang/rust-clippy/pull/6578)
+* [`needless_return`] No longer lints in macros
+ [#6586](https://github.com/rust-lang/rust-clippy/pull/6586)
+* [`match_overlapping_arm`] No longer lint when first arm is completely included
+ in second arm [#6603](https://github.com/rust-lang/rust-clippy/pull/6603)
+* [`doc_markdown`] Add `WebGL` to the default configuration as an allowed
+ identifier [#6605](https://github.com/rust-lang/rust-clippy/pull/6605)
+
+### Suggestion Fixes/Improvements
+
+* [`field_reassign_with_default`] Don't expand macro in lint suggestion
+ [#6531](https://github.com/rust-lang/rust-clippy/pull/6531)
+* [`match_like_matches_macro`] Strip references in suggestion
+ [#6532](https://github.com/rust-lang/rust-clippy/pull/6532)
+* [`single_match`] Suggest `if` over `if let` when possible
+ [#6574](https://github.com/rust-lang/rust-clippy/pull/6574)
+* [`ref_in_deref`] Use parentheses correctly in suggestion
+ [#6609](https://github.com/rust-lang/rust-clippy/pull/6609)
+* [`stable_sort_primitive`] Clarify error message
+ [#6611](https://github.com/rust-lang/rust-clippy/pull/6611)
+
+### ICE Fixes
+
+* [`zero_sized_map_values`]
+ [#6582](https://github.com/rust-lang/rust-clippy/pull/6582)
+
+### Documentation Improvements
+
+* Improve search performance on the Clippy website and make it possible to
+ directly search for lints on the GitHub issue tracker
+ [#6483](https://github.com/rust-lang/rust-clippy/pull/6483)
+* Clean up `README.md` by removing outdated paragraph
+ [#6488](https://github.com/rust-lang/rust-clippy/pull/6488)
+* [`await_holding_refcell_ref`] and [`await_holding_lock`]
+ [#6585](https://github.com/rust-lang/rust-clippy/pull/6585)
+* [`as_conversions`] [#6608](https://github.com/rust-lang/rust-clippy/pull/6608)
+
+### Others
+
+* Clippy now has a [Roadmap] for 2021. If you like to get involved in a bigger
+ project, take a look at the [Roadmap project page]. All issues listed there
+ are actively mentored
+ [#6462](https://github.com/rust-lang/rust-clippy/pull/6462)
+* The Clippy version number now corresponds to the Rust version number
+ [#6526](https://github.com/rust-lang/rust-clippy/pull/6526)
+* Fix oversight which caused Clippy to lint deps in some environments, where
+ `CLIPPY_TESTS=true` was set somewhere
+ [#6575](https://github.com/rust-lang/rust-clippy/pull/6575)
+* Add `cargo dev-lintcheck` tool to the Clippy Dev Tool
+ [#6469](https://github.com/rust-lang/rust-clippy/pull/6469)
+
+[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/doc/roadmap-2021.md
+[Roadmap project page]: https://github.com/rust-lang/rust-clippy/projects/3
+
+## Rust 1.50
+
+Released 2021-02-11
+
+[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1)
+
+### New Lints
+
+* [`suspicious_operation_groupings`] [#6086](https://github.com/rust-lang/rust-clippy/pull/6086)
+* [`size_of_in_element_count`] [#6394](https://github.com/rust-lang/rust-clippy/pull/6394)
+* [`unnecessary_wraps`] [#6070](https://github.com/rust-lang/rust-clippy/pull/6070)
+* [`let_underscore_drop`] [#6305](https://github.com/rust-lang/rust-clippy/pull/6305)
+* [`collapsible_match`] [#6402](https://github.com/rust-lang/rust-clippy/pull/6402)
+* [`redundant_else`] [#6330](https://github.com/rust-lang/rust-clippy/pull/6330)
+* [`zero_sized_map_values`] [#6218](https://github.com/rust-lang/rust-clippy/pull/6218)
+* [`print_stderr`] [#6367](https://github.com/rust-lang/rust-clippy/pull/6367)
+* [`string_from_utf8_as_bytes`] [#6134](https://github.com/rust-lang/rust-clippy/pull/6134)
+
+### Moves and Deprecations
+
+* Previously deprecated [`str_to_string`] and [`string_to_string`] have been un-deprecated
+ as `restriction` lints [#6333](https://github.com/rust-lang/rust-clippy/pull/6333)
+* Deprecate `panic_params` lint. This is now available in rustc as `non_fmt_panics`
+ [#6351](https://github.com/rust-lang/rust-clippy/pull/6351)
+* Move [`map_err_ignore`] to `restriction`
+ [#6416](https://github.com/rust-lang/rust-clippy/pull/6416)
+* Move [`await_holding_refcell_ref`] to `pedantic`
+ [#6354](https://github.com/rust-lang/rust-clippy/pull/6354)
+* Move [`await_holding_lock`] to `pedantic`
+ [#6354](https://github.com/rust-lang/rust-clippy/pull/6354)
+
+### Enhancements
+
+* Add the `unreadable-literal-lint-fractions` configuration to disable
+ the `unreadable_literal` lint for fractions
+ [#6421](https://github.com/rust-lang/rust-clippy/pull/6421)
+* [`clone_on_copy`]: Now shows the type in the lint message
+ [#6443](https://github.com/rust-lang/rust-clippy/pull/6443)
+* [`redundant_pattern_matching`]: Now also lints on `std::task::Poll`
+ [#6339](https://github.com/rust-lang/rust-clippy/pull/6339)
+* [`redundant_pattern_matching`]: Additionally also lints on `std::net::IpAddr`
+ [#6377](https://github.com/rust-lang/rust-clippy/pull/6377)
+* [`search_is_some`]: Now suggests `contains` instead of `find(foo).is_some()`
+ [#6119](https://github.com/rust-lang/rust-clippy/pull/6119)
+* [`clone_double_ref`]: Now prints the reference type in the lint message
+ [#6442](https://github.com/rust-lang/rust-clippy/pull/6442)
+* [`modulo_one`]: Now also lints on -1.
+ [#6360](https://github.com/rust-lang/rust-clippy/pull/6360)
+* [`empty_loop`]: Now lints no_std crates, too
+ [#6205](https://github.com/rust-lang/rust-clippy/pull/6205)
+* [`or_fun_call`]: Now also lints when indexing `HashMap` or `BTreeMap`
+ [#6267](https://github.com/rust-lang/rust-clippy/pull/6267)
+* [`wrong_self_convention`]: Now also lints in trait definitions
+ [#6316](https://github.com/rust-lang/rust-clippy/pull/6316)
+* [`needless_borrow`]: Print the type in the lint message
+ [#6449](https://github.com/rust-lang/rust-clippy/pull/6449)
+
+[msrv_readme]: https://github.com/rust-lang/rust-clippy#specifying-the-minimum-supported-rust-version
+
+### False Positive Fixes
+
+* [`manual_range_contains`]: No longer lints in `const fn`
+ [#6382](https://github.com/rust-lang/rust-clippy/pull/6382)
+* [`unnecessary_lazy_evaluations`]: No longer lints if closure argument is used
+ [#6370](https://github.com/rust-lang/rust-clippy/pull/6370)
+* [`match_single_binding`]: Now ignores cases with `#[cfg()]` macros
+ [#6435](https://github.com/rust-lang/rust-clippy/pull/6435)
+* [`match_like_matches_macro`]: No longer lints on arms with attributes
+ [#6290](https://github.com/rust-lang/rust-clippy/pull/6290)
+* [`map_clone`]: No longer lints with deref and clone
+ [#6269](https://github.com/rust-lang/rust-clippy/pull/6269)
+* [`map_clone`]: No longer lints in the case of &mut
+ [#6301](https://github.com/rust-lang/rust-clippy/pull/6301)
+* [`needless_update`]: Now ignores `non_exhaustive` structs
+ [#6464](https://github.com/rust-lang/rust-clippy/pull/6464)
+* [`needless_collect`]: No longer lints when a collect is needed multiple times
+ [#6313](https://github.com/rust-lang/rust-clippy/pull/6313)
+* [`unnecessary_cast`] No longer lints cfg-dependent types
+ [#6369](https://github.com/rust-lang/rust-clippy/pull/6369)
+* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]:
+ Both now ignore enums with frozen variants
+ [#6110](https://github.com/rust-lang/rust-clippy/pull/6110)
+* [`field_reassign_with_default`] No longer lint for private fields
+ [#6537](https://github.com/rust-lang/rust-clippy/pull/6537)
+
+
+### Suggestion Fixes/Improvements
+
+* [`vec_box`]: Provide correct type scope suggestion
+ [#6271](https://github.com/rust-lang/rust-clippy/pull/6271)
+* [`manual_range_contains`]: Give correct suggestion when using floats
+ [#6320](https://github.com/rust-lang/rust-clippy/pull/6320)
+* [`unnecessary_lazy_evaluations`]: Don't always mark suggestion as MachineApplicable
+ [#6272](https://github.com/rust-lang/rust-clippy/pull/6272)
+* [`manual_async_fn`]: Improve suggestion formatting
+ [#6294](https://github.com/rust-lang/rust-clippy/pull/6294)
+* [`unnecessary_cast`]: Fix incorrectly formatted float literal suggestion
+ [#6362](https://github.com/rust-lang/rust-clippy/pull/6362)
+
+### ICE Fixes
+
+* Fix a crash in [`from_iter_instead_of_collect`]
+ [#6304](https://github.com/rust-lang/rust-clippy/pull/6304)
+* Fix a silent crash when parsing doc comments in [`needless_doctest_main`]
+ [#6458](https://github.com/rust-lang/rust-clippy/pull/6458)
+
+### Documentation Improvements
+
+* The lint website search has been improved ([#6477](https://github.com/rust-lang/rust-clippy/pull/6477)):
+ * Searching for lints with dashes and spaces is possible now. For example
+ `missing-errors-doc` and `missing errors doc` are now valid aliases for lint names
+ * Improved fuzzy search in lint descriptions
+* Various README improvements
+ [#6287](https://github.com/rust-lang/rust-clippy/pull/6287)
+* Add known problems to [`comparison_chain`] documentation
+ [#6390](https://github.com/rust-lang/rust-clippy/pull/6390)
+* Fix example used in [`cargo_common_metadata`]
+ [#6293](https://github.com/rust-lang/rust-clippy/pull/6293)
+* Improve [`map_clone`] documentation
+ [#6340](https://github.com/rust-lang/rust-clippy/pull/6340)
+
+### Others
+
+* You can now tell Clippy about the MSRV your project supports. Please refer to
+ the specific README section to learn more about MSRV support [here][msrv_readme]
+ [#6201](https://github.com/rust-lang/rust-clippy/pull/6201)
+* Add `--no-deps` option to avoid running on path dependencies in workspaces
+ [#6188](https://github.com/rust-lang/rust-clippy/pull/6188)
+
+## Rust 1.49
+
+Released 2020-12-31
+
+[e636b88...b20d4c1](https://github.com/rust-lang/rust-clippy/compare/e636b88...b20d4c1)
+
+### New Lints
+
+* [`field_reassign_with_default`] [#5911](https://github.com/rust-lang/rust-clippy/pull/5911)
+* [`await_holding_refcell_ref`] [#6029](https://github.com/rust-lang/rust-clippy/pull/6029)
- [`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
++* [`disallowed_methods`] [#6081](https://github.com/rust-lang/rust-clippy/pull/6081)
+* [`inline_asm_x86_att_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092)
+* [`inline_asm_x86_intel_syntax`] [#6092](https://github.com/rust-lang/rust-clippy/pull/6092)
+* [`from_iter_instead_of_collect`] [#6101](https://github.com/rust-lang/rust-clippy/pull/6101)
+* [`mut_mutex_lock`] [#6103](https://github.com/rust-lang/rust-clippy/pull/6103)
+* [`single_element_loop`] [#6109](https://github.com/rust-lang/rust-clippy/pull/6109)
+* [`manual_unwrap_or`] [#6123](https://github.com/rust-lang/rust-clippy/pull/6123)
+* [`large_types_passed_by_value`] [#6135](https://github.com/rust-lang/rust-clippy/pull/6135)
+* [`result_unit_err`] [#6157](https://github.com/rust-lang/rust-clippy/pull/6157)
+* [`ref_option_ref`] [#6165](https://github.com/rust-lang/rust-clippy/pull/6165)
+* [`manual_range_contains`] [#6177](https://github.com/rust-lang/rust-clippy/pull/6177)
+* [`unusual_byte_groupings`] [#6183](https://github.com/rust-lang/rust-clippy/pull/6183)
+* [`comparison_to_empty`] [#6226](https://github.com/rust-lang/rust-clippy/pull/6226)
+* [`map_collect_result_unit`] [#6227](https://github.com/rust-lang/rust-clippy/pull/6227)
+* [`manual_ok_or`] [#6233](https://github.com/rust-lang/rust-clippy/pull/6233)
+
+### Moves and Deprecations
+
+* Rename `single_char_push_str` to [`single_char_add_str`]
+ [#6037](https://github.com/rust-lang/rust-clippy/pull/6037)
+* Rename `zero_width_space` to [`invisible_characters`]
+ [#6105](https://github.com/rust-lang/rust-clippy/pull/6105)
+* Deprecate `drop_bounds` (uplifted)
+ [#6111](https://github.com/rust-lang/rust-clippy/pull/6111)
+* Move [`string_lit_as_bytes`] to `nursery`
+ [#6117](https://github.com/rust-lang/rust-clippy/pull/6117)
+* Move [`rc_buffer`] to `restriction`
+ [#6128](https://github.com/rust-lang/rust-clippy/pull/6128)
+
+### Enhancements
+
+* [`manual_memcpy`]: Also lint when there are loop counters (and produce a
+ reliable suggestion)
+ [#5727](https://github.com/rust-lang/rust-clippy/pull/5727)
+* [`single_char_add_str`]: Also lint on `String::insert_str`
+ [#6037](https://github.com/rust-lang/rust-clippy/pull/6037)
+* [`invisible_characters`]: Also lint the characters `\u{AD}` and `\u{2060}`
+ [#6105](https://github.com/rust-lang/rust-clippy/pull/6105)
+* [`eq_op`]: Also lint on the `assert_*!` macro family
+ [#6167](https://github.com/rust-lang/rust-clippy/pull/6167)
+* [`items_after_statements`]: Also lint in local macro expansions
+ [#6176](https://github.com/rust-lang/rust-clippy/pull/6176)
+* [`unnecessary_cast`]: Also lint casts on integer and float literals
+ [#6187](https://github.com/rust-lang/rust-clippy/pull/6187)
+* [`manual_unwrap_or`]: Also lint `Result::unwrap_or`
+ [#6190](https://github.com/rust-lang/rust-clippy/pull/6190)
+* [`match_like_matches_macro`]: Also lint when `match` has more than two arms
+ [#6216](https://github.com/rust-lang/rust-clippy/pull/6216)
+* [`integer_arithmetic`]: Better handle `/` an `%` operators
+ [#6229](https://github.com/rust-lang/rust-clippy/pull/6229)
+
+### False Positive Fixes
+
+* [`needless_lifetimes`]: Bail out if the function has a `where` clause with the
+ lifetime [#5978](https://github.com/rust-lang/rust-clippy/pull/5978)
+* [`explicit_counter_loop`]: No longer lints, when loop counter is used after it
+ is incremented [#6076](https://github.com/rust-lang/rust-clippy/pull/6076)
+* [`or_fun_call`]: Revert changes addressing the handling of `const fn`
+ [#6077](https://github.com/rust-lang/rust-clippy/pull/6077)
+* [`needless_range_loop`]: No longer lints, when the iterable is used in the
+ range [#6102](https://github.com/rust-lang/rust-clippy/pull/6102)
+* [`inconsistent_digit_grouping`]: Fix bug when using floating point exponent
+ [#6104](https://github.com/rust-lang/rust-clippy/pull/6104)
+* [`mistyped_literal_suffixes`]: No longer lints on the fractional part of a
+ float (e.g. `713.32_64`)
+ [#6114](https://github.com/rust-lang/rust-clippy/pull/6114)
+* [`invalid_regex`]: No longer lint on unicode characters within `bytes::Regex`
+ [#6132](https://github.com/rust-lang/rust-clippy/pull/6132)
+* [`boxed_local`]: No longer lints on `extern fn` arguments
+ [#6133](https://github.com/rust-lang/rust-clippy/pull/6133)
+* [`needless_lifetimes`]: Fix regression, where lifetime is used in `where`
+ clause [#6198](https://github.com/rust-lang/rust-clippy/pull/6198)
+
+### Suggestion Fixes/Improvements
+
+* [`unnecessary_sort_by`]: Avoid dereferencing the suggested closure parameter
+ [#6078](https://github.com/rust-lang/rust-clippy/pull/6078)
+* [`needless_arbitrary_self_type`]: Correctly handle expanded code
+ [#6093](https://github.com/rust-lang/rust-clippy/pull/6093)
+* [`useless_format`]: Preserve raw strings in suggestion
+ [#6151](https://github.com/rust-lang/rust-clippy/pull/6151)
+* [`empty_loop`]: Suggest alternatives
+ [#6162](https://github.com/rust-lang/rust-clippy/pull/6162)
+* [`borrowed_box`]: Correctly add parentheses in suggestion
+ [#6200](https://github.com/rust-lang/rust-clippy/pull/6200)
+* [`unused_unit`]: Improve suggestion formatting
+ [#6247](https://github.com/rust-lang/rust-clippy/pull/6247)
+
+### Documentation Improvements
+
+* Some doc improvements:
+ * [`rc_buffer`] [#6090](https://github.com/rust-lang/rust-clippy/pull/6090)
+ * [`empty_loop`] [#6162](https://github.com/rust-lang/rust-clippy/pull/6162)
+* [`doc_markdown`]: Document problematic link text style
+ [#6107](https://github.com/rust-lang/rust-clippy/pull/6107)
+
+## Rust 1.48
+
+Released 2020-11-19
+
+[09bd400...e636b88](https://github.com/rust-lang/rust-clippy/compare/09bd400...e636b88)
+
+### New lints
+
+* [`self_assignment`] [#5894](https://github.com/rust-lang/rust-clippy/pull/5894)
+* [`unnecessary_lazy_evaluations`] [#5720](https://github.com/rust-lang/rust-clippy/pull/5720)
+* [`manual_strip`] [#6038](https://github.com/rust-lang/rust-clippy/pull/6038)
+* [`map_err_ignore`] [#5998](https://github.com/rust-lang/rust-clippy/pull/5998)
+* [`rc_buffer`] [#6044](https://github.com/rust-lang/rust-clippy/pull/6044)
+* [`to_string_in_display`] [#5831](https://github.com/rust-lang/rust-clippy/pull/5831)
+* `single_char_push_str` [#5881](https://github.com/rust-lang/rust-clippy/pull/5881)
+
+### Moves and Deprecations
+
+* Downgrade [`verbose_bit_mask`] to pedantic
+ [#6036](https://github.com/rust-lang/rust-clippy/pull/6036)
+
+### Enhancements
+
+* Extend [`precedence`] to handle chains of methods combined with unary negation
+ [#5928](https://github.com/rust-lang/rust-clippy/pull/5928)
+* [`useless_vec`]: add a configuration value for the maximum allowed size on the stack
+ [#5907](https://github.com/rust-lang/rust-clippy/pull/5907)
+* [`suspicious_arithmetic_impl`]: extend to implementations of `BitAnd`, `BitOr`, `BitXor`, `Rem`, `Shl`, and `Shr`
+ [#5884](https://github.com/rust-lang/rust-clippy/pull/5884)
+* `invalid_atomic_ordering`: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update`
+ [#6025](https://github.com/rust-lang/rust-clippy/pull/6025)
+* Avoid [`redundant_pattern_matching`] triggering in macros
+ [#6069](https://github.com/rust-lang/rust-clippy/pull/6069)
+* [`option_if_let_else`]: distinguish pure from impure `else` expressions
+ [#5937](https://github.com/rust-lang/rust-clippy/pull/5937)
+* [`needless_doctest_main`]: parse doctests instead of using textual search
+ [#5912](https://github.com/rust-lang/rust-clippy/pull/5912)
+* [`wildcard_imports`]: allow `prelude` to appear in any segment of an import
+ [#5929](https://github.com/rust-lang/rust-clippy/pull/5929)
+* Re-enable [`len_zero`] for ranges now that `range_is_empty` is stable
+ [#5961](https://github.com/rust-lang/rust-clippy/pull/5961)
+* [`option_as_ref_deref`]: catch fully-qualified calls to `Deref::deref` and `DerefMut::deref_mut`
+ [#5933](https://github.com/rust-lang/rust-clippy/pull/5933)
+
+### False Positive Fixes
+
+* [`useless_attribute`]: permit allowing [`wildcard_imports`] and [`enum_glob_use`]
+ [#5994](https://github.com/rust-lang/rust-clippy/pull/5994)
+* [`transmute_ptr_to_ptr`]: avoid suggesting dereferencing raw pointers in const contexts
+ [#5999](https://github.com/rust-lang/rust-clippy/pull/5999)
+* [`redundant_closure_call`]: take into account usages of the closure in nested functions and closures
+ [#5920](https://github.com/rust-lang/rust-clippy/pull/5920)
+* Fix false positive in [`borrow_interior_mutable_const`] when referencing a field behind a pointer
+ [#5949](https://github.com/rust-lang/rust-clippy/pull/5949)
+* [`doc_markdown`]: allow using "GraphQL" without backticks
+ [#5996](https://github.com/rust-lang/rust-clippy/pull/5996)
+* [`to_string_in_display`]: avoid linting when calling `to_string()` on anything that is not `self`
+ [#5971](https://github.com/rust-lang/rust-clippy/pull/5971)
+* [`indexing_slicing`] and [`out_of_bounds_indexing`] treat references to arrays as arrays
+ [#6034](https://github.com/rust-lang/rust-clippy/pull/6034)
+* [`should_implement_trait`]: ignore methods with lifetime parameters
+ [#5725](https://github.com/rust-lang/rust-clippy/pull/5725)
+* [`needless_return`]: avoid linting if a temporary borrows a local variable
+ [#5903](https://github.com/rust-lang/rust-clippy/pull/5903)
+* Restrict [`unnecessary_sort_by`] to non-reference, Copy types
+ [#6006](https://github.com/rust-lang/rust-clippy/pull/6006)
+* Avoid suggesting `from_bits`/`to_bits` in const contexts in [`transmute_int_to_float`]
+ [#5919](https://github.com/rust-lang/rust-clippy/pull/5919)
+* [`declare_interior_mutable_const`] and [`borrow_interior_mutable_const`]: improve detection of interior mutable types
+ [#6046](https://github.com/rust-lang/rust-clippy/pull/6046)
+
+### Suggestion Fixes/Improvements
+
+* [`let_and_return`]: add a cast to the suggestion when the return expression has adjustments
+ [#5946](https://github.com/rust-lang/rust-clippy/pull/5946)
+* [`useless_conversion`]: show the type in the error message
+ [#6035](https://github.com/rust-lang/rust-clippy/pull/6035)
+* [`unnecessary_mut_passed`]: discriminate between functions and methods in the error message
+ [#5892](https://github.com/rust-lang/rust-clippy/pull/5892)
+* [`float_cmp`] and [`float_cmp_const`]: change wording to make margin of error less ambiguous
+ [#6043](https://github.com/rust-lang/rust-clippy/pull/6043)
+* [`default_trait_access`]: do not use unnecessary type parameters in the suggestion
+ [#5993](https://github.com/rust-lang/rust-clippy/pull/5993)
+* [`collapsible_if`]: don't use expanded code in the suggestion
+ [#5992](https://github.com/rust-lang/rust-clippy/pull/5992)
+* Do not suggest empty format strings in [`print_with_newline`] and [`write_with_newline`]
+ [#6042](https://github.com/rust-lang/rust-clippy/pull/6042)
+* [`unit_arg`]: improve the readability of the suggestion
+ [#5931](https://github.com/rust-lang/rust-clippy/pull/5931)
+* [`stable_sort_primitive`]: print the type that is being sorted in the lint message
+ [#5935](https://github.com/rust-lang/rust-clippy/pull/5935)
+* Show line count and max lines in [`too_many_lines`] lint message
+ [#6009](https://github.com/rust-lang/rust-clippy/pull/6009)
+* Keep parentheses in the suggestion of [`useless_conversion`] where applicable
+ [#5900](https://github.com/rust-lang/rust-clippy/pull/5900)
+* [`option_map_unit_fn`] and [`result_map_unit_fn`]: print the unit type `()` explicitly
+ [#6024](https://github.com/rust-lang/rust-clippy/pull/6024)
+* [`redundant_allocation`]: suggest replacing `Rc<Box<T>>` with `Rc<T>`
+ [#5899](https://github.com/rust-lang/rust-clippy/pull/5899)
+* Make lint messages adhere to rustc dev guide conventions
+ [#5893](https://github.com/rust-lang/rust-clippy/pull/5893)
+
+### ICE Fixes
+
+* Fix ICE in [`repeat_once`]
+ [#5948](https://github.com/rust-lang/rust-clippy/pull/5948)
+
+### Documentation Improvements
+
+* [`mutable_key_type`]: explain potential for false positives when the interior mutable type is not accessed in the `Hash` implementation
+ [#6019](https://github.com/rust-lang/rust-clippy/pull/6019)
+* [`unnecessary_mut_passed`]: fix typo
+ [#5913](https://github.com/rust-lang/rust-clippy/pull/5913)
+* Add example of false positive to [`ptr_arg`] docs.
+ [#5885](https://github.com/rust-lang/rust-clippy/pull/5885)
+* [`box_vec`](https://rust-lang.github.io/rust-clippy/master/index.html#box_collection), [`vec_box`] and [`borrowed_box`]: add link to the documentation of `Box`
+ [#6023](https://github.com/rust-lang/rust-clippy/pull/6023)
+
+## Rust 1.47
+
+Released 2020-10-08
+
+[c2c07fa...09bd400](https://github.com/rust-lang/rust-clippy/compare/c2c07fa...09bd400)
+
+### New lints
+
+* [`derive_ord_xor_partial_ord`] [#5848](https://github.com/rust-lang/rust-clippy/pull/5848)
+* [`trait_duplication_in_bounds`] [#5852](https://github.com/rust-lang/rust-clippy/pull/5852)
+* [`map_identity`] [#5694](https://github.com/rust-lang/rust-clippy/pull/5694)
+* [`unit_return_expecting_ord`] [#5737](https://github.com/rust-lang/rust-clippy/pull/5737)
+* [`pattern_type_mismatch`] [#4841](https://github.com/rust-lang/rust-clippy/pull/4841)
+* [`repeat_once`] [#5773](https://github.com/rust-lang/rust-clippy/pull/5773)
+* [`same_item_push`] [#5825](https://github.com/rust-lang/rust-clippy/pull/5825)
+* [`needless_arbitrary_self_type`] [#5869](https://github.com/rust-lang/rust-clippy/pull/5869)
+* [`match_like_matches_macro`] [#5769](https://github.com/rust-lang/rust-clippy/pull/5769)
+* [`stable_sort_primitive`] [#5809](https://github.com/rust-lang/rust-clippy/pull/5809)
+* [`blanket_clippy_restriction_lints`] [#5750](https://github.com/rust-lang/rust-clippy/pull/5750)
+* [`option_if_let_else`] [#5301](https://github.com/rust-lang/rust-clippy/pull/5301)
+
+### Moves and Deprecations
+
+* Deprecate [`regex_macro`] lint
+ [#5760](https://github.com/rust-lang/rust-clippy/pull/5760)
+* Move [`range_minus_one`] to `pedantic`
+ [#5752](https://github.com/rust-lang/rust-clippy/pull/5752)
+
+### Enhancements
+
+* Improve [`needless_collect`] by catching `collect` calls followed by `iter` or `into_iter` calls
+ [#5837](https://github.com/rust-lang/rust-clippy/pull/5837)
+* [`panic`], [`todo`], [`unimplemented`] and [`unreachable`] now detect calls with formatting
+ [#5811](https://github.com/rust-lang/rust-clippy/pull/5811)
+* Detect more cases of [`suboptimal_flops`] and [`imprecise_flops`]
+ [#5443](https://github.com/rust-lang/rust-clippy/pull/5443)
+* Handle asymmetrical implementations of `PartialEq` in [`cmp_owned`]
+ [#5701](https://github.com/rust-lang/rust-clippy/pull/5701)
+* Make it possible to allow [`unsafe_derive_deserialize`]
+ [#5870](https://github.com/rust-lang/rust-clippy/pull/5870)
+* Catch `ord.min(a).max(b)` where a < b in [`min_max`]
+ [#5871](https://github.com/rust-lang/rust-clippy/pull/5871)
+* Make [`clone_on_copy`] suggestion machine applicable
+ [#5745](https://github.com/rust-lang/rust-clippy/pull/5745)
+* Enable [`len_zero`] on ranges now that `is_empty` is stable on them
+ [#5961](https://github.com/rust-lang/rust-clippy/pull/5961)
+
+### False Positive Fixes
+
+* Avoid triggering [`or_fun_call`] with const fns that take no arguments
+ [#5889](https://github.com/rust-lang/rust-clippy/pull/5889)
+* Fix [`redundant_closure_call`] false positive for closures that have multiple calls
+ [#5800](https://github.com/rust-lang/rust-clippy/pull/5800)
+* Don't lint cases involving `ManuallyDrop` in [`redundant_clone`]
+ [#5824](https://github.com/rust-lang/rust-clippy/pull/5824)
+* Treat a single expression the same as a single statement in the 2nd arm of a match in [`single_match_else`]
+ [#5771](https://github.com/rust-lang/rust-clippy/pull/5771)
+* Don't trigger [`unnested_or_patterns`] if the feature `or_patterns` is not enabled
+ [#5758](https://github.com/rust-lang/rust-clippy/pull/5758)
+* Avoid linting if key borrows in [`unnecessary_sort_by`]
+ [#5756](https://github.com/rust-lang/rust-clippy/pull/5756)
+* Consider `Try` impl for `Poll` when generating suggestions in [`try_err`]
+ [#5857](https://github.com/rust-lang/rust-clippy/pull/5857)
+* Take input lifetimes into account in `manual_async_fn`
+ [#5859](https://github.com/rust-lang/rust-clippy/pull/5859)
+* Fix multiple false positives in [`type_repetition_in_bounds`] and add a configuration option
+ [#5761](https://github.com/rust-lang/rust-clippy/pull/5761)
+* Limit the [`suspicious_arithmetic_impl`] lint to one binary operation
+ [#5820](https://github.com/rust-lang/rust-clippy/pull/5820)
+
+### Suggestion Fixes/Improvements
+
+* Improve readability of [`shadow_unrelated`] suggestion by truncating the RHS snippet
+ [#5788](https://github.com/rust-lang/rust-clippy/pull/5788)
+* Suggest `filter_map` instead of `flat_map` when mapping to `Option` in [`map_flatten`]
+ [#5846](https://github.com/rust-lang/rust-clippy/pull/5846)
+* Ensure suggestion is shown correctly for long method call chains in [`iter_nth_zero`]
+ [#5793](https://github.com/rust-lang/rust-clippy/pull/5793)
+* Drop borrow operator in suggestions of [`redundant_pattern_matching`]
+ [#5815](https://github.com/rust-lang/rust-clippy/pull/5815)
+* Add suggestion for [`iter_skip_next`]
+ [#5843](https://github.com/rust-lang/rust-clippy/pull/5843)
+* Improve [`collapsible_if`] fix suggestion
+ [#5732](https://github.com/rust-lang/rust-clippy/pull/5732)
+
+### ICE Fixes
+
+* Fix ICE caused by [`needless_collect`]
+ [#5877](https://github.com/rust-lang/rust-clippy/pull/5877)
+* Fix ICE caused by [`unnested_or_patterns`]
+ [#5784](https://github.com/rust-lang/rust-clippy/pull/5784)
+
+### Documentation Improvements
+
+* Fix grammar of [`await_holding_lock`] documentation
+ [#5748](https://github.com/rust-lang/rust-clippy/pull/5748)
+
+### Others
+
+* Make lints adhere to the rustc dev guide
+ [#5888](https://github.com/rust-lang/rust-clippy/pull/5888)
+
+## Rust 1.46
+
+Released 2020-08-27
+
+[7ea7cd1...c2c07fa](https://github.com/rust-lang/rust-clippy/compare/7ea7cd1...c2c07fa)
+
+### New lints
+
+* [`unnested_or_patterns`] [#5378](https://github.com/rust-lang/rust-clippy/pull/5378)
+* [`iter_next_slice`] [#5597](https://github.com/rust-lang/rust-clippy/pull/5597)
+* [`unnecessary_sort_by`] [#5623](https://github.com/rust-lang/rust-clippy/pull/5623)
+* [`vec_resize_to_zero`] [#5637](https://github.com/rust-lang/rust-clippy/pull/5637)
+
+### Moves and Deprecations
+
+* Move [`cast_ptr_alignment`] to pedantic [#5667](https://github.com/rust-lang/rust-clippy/pull/5667)
+
+### Enhancements
+
+* Improve [`mem_replace_with_uninit`] lint [#5695](https://github.com/rust-lang/rust-clippy/pull/5695)
+
+### False Positive Fixes
+
+* [`len_zero`]: Avoid linting ranges when the `range_is_empty` feature is not enabled
+ [#5656](https://github.com/rust-lang/rust-clippy/pull/5656)
+* [`let_and_return`]: Don't lint if a temporary borrow is involved
+ [#5680](https://github.com/rust-lang/rust-clippy/pull/5680)
+* [`reversed_empty_ranges`]: Avoid linting `N..N` in for loop arguments in
+ [#5692](https://github.com/rust-lang/rust-clippy/pull/5692)
+* [`if_same_then_else`]: Don't assume multiplication is always commutative
+ [#5702](https://github.com/rust-lang/rust-clippy/pull/5702)
+* [`blacklisted_name`]: Remove `bar` from the default configuration
+ [#5712](https://github.com/rust-lang/rust-clippy/pull/5712)
+* [`redundant_pattern_matching`]: Avoid suggesting non-`const fn` calls in const contexts
+ [#5724](https://github.com/rust-lang/rust-clippy/pull/5724)
+
+### Suggestion Fixes/Improvements
+
+* Fix suggestion of [`unit_arg`] lint, so that it suggest semantic equivalent code
+ [#4455](https://github.com/rust-lang/rust-clippy/pull/4455)
+* Add auto applicable suggestion to [`macro_use_imports`]
+ [#5279](https://github.com/rust-lang/rust-clippy/pull/5279)
+
+### ICE Fixes
+
+* Fix ICE in the `consts` module of Clippy [#5709](https://github.com/rust-lang/rust-clippy/pull/5709)
+
+### Documentation Improvements
+
+* Improve code examples across multiple lints [#5664](https://github.com/rust-lang/rust-clippy/pull/5664)
+
+### Others
+
+* Introduce a `--rustc` flag to `clippy-driver`, which turns `clippy-driver`
+ into `rustc` and passes all the given arguments to `rustc`. This is especially
+ useful for tools that need the `rustc` version Clippy was compiled with,
+ instead of the Clippy version. E.g. `clippy-driver --rustc --version` will
+ print the output of `rustc --version`.
+ [#5178](https://github.com/rust-lang/rust-clippy/pull/5178)
+* New issue templates now make it easier to complain if Clippy is too annoying
+ or not annoying enough! [#5735](https://github.com/rust-lang/rust-clippy/pull/5735)
+
+## Rust 1.45
+
+Released 2020-07-16
+
+[891e1a8...7ea7cd1](https://github.com/rust-lang/rust-clippy/compare/891e1a8...7ea7cd1)
+
+### New lints
+
+* [`match_wildcard_for_single_variants`] [#5582](https://github.com/rust-lang/rust-clippy/pull/5582)
+* [`unsafe_derive_deserialize`] [#5493](https://github.com/rust-lang/rust-clippy/pull/5493)
+* [`if_let_mutex`] [#5332](https://github.com/rust-lang/rust-clippy/pull/5332)
+* [`mismatched_target_os`] [#5506](https://github.com/rust-lang/rust-clippy/pull/5506)
+* [`await_holding_lock`] [#5439](https://github.com/rust-lang/rust-clippy/pull/5439)
+* [`match_on_vec_items`] [#5522](https://github.com/rust-lang/rust-clippy/pull/5522)
+* [`manual_async_fn`] [#5576](https://github.com/rust-lang/rust-clippy/pull/5576)
+* [`reversed_empty_ranges`] [#5583](https://github.com/rust-lang/rust-clippy/pull/5583)
+* [`manual_non_exhaustive`] [#5550](https://github.com/rust-lang/rust-clippy/pull/5550)
+
+### Moves and Deprecations
+
+* Downgrade [`match_bool`] to pedantic [#5408](https://github.com/rust-lang/rust-clippy/pull/5408)
+* Downgrade [`match_wild_err_arm`] to pedantic and update help messages. [#5622](https://github.com/rust-lang/rust-clippy/pull/5622)
+* Downgrade [`useless_let_if_seq`] to nursery. [#5599](https://github.com/rust-lang/rust-clippy/pull/5599)
+* Generalize `option_and_then_some` and rename to [`bind_instead_of_map`]. [#5529](https://github.com/rust-lang/rust-clippy/pull/5529)
+* Rename `identity_conversion` to [`useless_conversion`]. [#5568](https://github.com/rust-lang/rust-clippy/pull/5568)
+* Merge `block_in_if_condition_expr` and `block_in_if_condition_stmt` into [`blocks_in_if_conditions`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_map_unwrap_or`, `option_map_unwrap_or_else` and `result_map_unwrap_or_else` into [`map_unwrap_or`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_unwrap_used` and `result_unwrap_used` into [`unwrap_used`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `option_expect_used` and `result_expect_used` into [`expect_used`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+* Merge `for_loop_over_option` and `for_loop_over_result` into [`for_loops_over_fallibles`].
+[#5563](https://github.com/rust-lang/rust-clippy/pull/5563)
+
+### Enhancements
+
+* Avoid running cargo lints when not enabled to improve performance. [#5505](https://github.com/rust-lang/rust-clippy/pull/5505)
+* Extend [`useless_conversion`] with `TryFrom` and `TryInto`. [#5631](https://github.com/rust-lang/rust-clippy/pull/5631)
+* Lint also in type parameters and where clauses in [`unused_unit`]. [#5592](https://github.com/rust-lang/rust-clippy/pull/5592)
+* Do not suggest deriving `Default` in [`new_without_default`]. [#5616](https://github.com/rust-lang/rust-clippy/pull/5616)
+
+### False Positive Fixes
+
+* [`while_let_on_iterator`] [#5525](https://github.com/rust-lang/rust-clippy/pull/5525)
+* [`empty_line_after_outer_attr`] [#5609](https://github.com/rust-lang/rust-clippy/pull/5609)
+* [`unnecessary_unwrap`] [#5558](https://github.com/rust-lang/rust-clippy/pull/5558)
+* [`comparison_chain`] [#5596](https://github.com/rust-lang/rust-clippy/pull/5596)
+* Don't trigger [`used_underscore_binding`] in await desugaring. [#5535](https://github.com/rust-lang/rust-clippy/pull/5535)
+* Don't trigger [`borrowed_box`] on mutable references. [#5491](https://github.com/rust-lang/rust-clippy/pull/5491)
+* Allow `1 << 0` in [`identity_op`]. [#5602](https://github.com/rust-lang/rust-clippy/pull/5602)
+* Allow `use super::*;` glob imports in [`wildcard_imports`]. [#5564](https://github.com/rust-lang/rust-clippy/pull/5564)
+* Whitelist more words in [`doc_markdown`]. [#5611](https://github.com/rust-lang/rust-clippy/pull/5611)
+* Skip dev and build deps in [`multiple_crate_versions`]. [#5636](https://github.com/rust-lang/rust-clippy/pull/5636)
+* Honor `allow` attribute on arguments in [`ptr_arg`]. [#5647](https://github.com/rust-lang/rust-clippy/pull/5647)
+* Honor lint level attributes for [`redundant_field_names`], [`just_underscores_and_digits`], [`many_single_char_names`]
+and [`similar_names`]. [#5651](https://github.com/rust-lang/rust-clippy/pull/5651)
+* Ignore calls to `len` in [`or_fun_call`]. [#4429](https://github.com/rust-lang/rust-clippy/pull/4429)
+
+### Suggestion Improvements
+
+* Simplify suggestions in [`manual_memcpy`]. [#5536](https://github.com/rust-lang/rust-clippy/pull/5536)
+* Fix suggestion in [`redundant_pattern_matching`] for macros. [#5511](https://github.com/rust-lang/rust-clippy/pull/5511)
+* Avoid suggesting `copied()` for mutable references in [`map_clone`]. [#5530](https://github.com/rust-lang/rust-clippy/pull/5530)
+* Improve help message for [`clone_double_ref`]. [#5547](https://github.com/rust-lang/rust-clippy/pull/5547)
+
+### ICE Fixes
+
+* Fix ICE caused in unwrap module. [#5590](https://github.com/rust-lang/rust-clippy/pull/5590)
+* Fix ICE on rustc test issue-69020-assoc-const-arith-overflow.rs [#5499](https://github.com/rust-lang/rust-clippy/pull/5499)
+
+### Documentation
+
+* Clarify the documentation of [`unnecessary_mut_passed`]. [#5639](https://github.com/rust-lang/rust-clippy/pull/5639)
+* Extend example for [`unneeded_field_pattern`]. [#5541](https://github.com/rust-lang/rust-clippy/pull/5541)
+
+## Rust 1.44
+
+Released 2020-06-04
+
+[204bb9b...891e1a8](https://github.com/rust-lang/rust-clippy/compare/204bb9b...891e1a8)
+
+### New lints
+
+* [`explicit_deref_methods`] [#5226](https://github.com/rust-lang/rust-clippy/pull/5226)
+* [`implicit_saturating_sub`] [#5427](https://github.com/rust-lang/rust-clippy/pull/5427)
+* [`macro_use_imports`] [#5230](https://github.com/rust-lang/rust-clippy/pull/5230)
+* [`verbose_file_reads`] [#5272](https://github.com/rust-lang/rust-clippy/pull/5272)
+* [`future_not_send`] [#5423](https://github.com/rust-lang/rust-clippy/pull/5423)
+* [`redundant_pub_crate`] [#5319](https://github.com/rust-lang/rust-clippy/pull/5319)
+* [`large_const_arrays`] [#5248](https://github.com/rust-lang/rust-clippy/pull/5248)
+* [`result_map_or_into_option`] [#5415](https://github.com/rust-lang/rust-clippy/pull/5415)
+* [`redundant_allocation`] [#5349](https://github.com/rust-lang/rust-clippy/pull/5349)
+* [`fn_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294)
+* [`vtable_address_comparisons`] [#5294](https://github.com/rust-lang/rust-clippy/pull/5294)
+
+
+### Moves and Deprecations
+
+* Deprecate [`replace_consts`] lint [#5380](https://github.com/rust-lang/rust-clippy/pull/5380)
+* Move [`cognitive_complexity`] to nursery [#5428](https://github.com/rust-lang/rust-clippy/pull/5428)
+* Move [`useless_transmute`] to nursery [#5364](https://github.com/rust-lang/rust-clippy/pull/5364)
+* Downgrade [`inefficient_to_string`] to pedantic [#5412](https://github.com/rust-lang/rust-clippy/pull/5412)
+* Downgrade [`option_option`] to pedantic [#5401](https://github.com/rust-lang/rust-clippy/pull/5401)
+* Downgrade [`unreadable_literal`] to pedantic [#5419](https://github.com/rust-lang/rust-clippy/pull/5419)
+* Downgrade [`let_unit_value`] to pedantic [#5409](https://github.com/rust-lang/rust-clippy/pull/5409)
+* Downgrade [`trivially_copy_pass_by_ref`] to pedantic [#5410](https://github.com/rust-lang/rust-clippy/pull/5410)
+* Downgrade [`implicit_hasher`] to pedantic [#5411](https://github.com/rust-lang/rust-clippy/pull/5411)
+
+### Enhancements
+
+* On _nightly_ you can now use `cargo clippy --fix -Z unstable-options` to
+ auto-fix lints that support this [#5363](https://github.com/rust-lang/rust-clippy/pull/5363)
+* Make [`redundant_clone`] also trigger on cases where the cloned value is not
+ consumed. [#5304](https://github.com/rust-lang/rust-clippy/pull/5304)
+* Expand [`integer_arithmetic`] to also disallow bit-shifting [#5430](https://github.com/rust-lang/rust-clippy/pull/5430)
+* [`option_as_ref_deref`] now detects more deref cases [#5425](https://github.com/rust-lang/rust-clippy/pull/5425)
+* [`large_enum_variant`] now report the sizes of the largest and second-largest variants [#5466](https://github.com/rust-lang/rust-clippy/pull/5466)
+* [`bool_comparison`] now also checks for inequality comparisons that can be
+ written more concisely [#5365](https://github.com/rust-lang/rust-clippy/pull/5365)
+* Expand [`clone_on_copy`] to work in method call arguments as well [#5441](https://github.com/rust-lang/rust-clippy/pull/5441)
+* [`redundant_pattern_matching`] now also handles `while let` [#5483](https://github.com/rust-lang/rust-clippy/pull/5483)
+* [`integer_arithmetic`] now also lints references of integers [#5329](https://github.com/rust-lang/rust-clippy/pull/5329)
+* Expand [`float_cmp_const`] to also work on arrays [#5345](https://github.com/rust-lang/rust-clippy/pull/5345)
+* Trigger [`map_flatten`] when map is called on an `Option` [#5473](https://github.com/rust-lang/rust-clippy/pull/5473)
+
+### False Positive Fixes
+
+* [`many_single_char_names`] [#5468](https://github.com/rust-lang/rust-clippy/pull/5468)
+* [`should_implement_trait`] [#5437](https://github.com/rust-lang/rust-clippy/pull/5437)
+* [`unused_self`] [#5387](https://github.com/rust-lang/rust-clippy/pull/5387)
+* [`redundant_clone`] [#5453](https://github.com/rust-lang/rust-clippy/pull/5453)
+* [`precedence`] [#5445](https://github.com/rust-lang/rust-clippy/pull/5445)
+* [`suspicious_op_assign_impl`] [#5424](https://github.com/rust-lang/rust-clippy/pull/5424)
+* [`needless_lifetimes`] [#5293](https://github.com/rust-lang/rust-clippy/pull/5293)
+* [`redundant_pattern`] [#5287](https://github.com/rust-lang/rust-clippy/pull/5287)
+* [`inconsistent_digit_grouping`] [#5451](https://github.com/rust-lang/rust-clippy/pull/5451)
+
+
+### Suggestion Improvements
+
+* Improved [`question_mark`] lint suggestion so that it doesn't add redundant `as_ref()` [#5481](https://github.com/rust-lang/rust-clippy/pull/5481)
+* Improve the suggested placeholder in [`option_map_unit_fn`] [#5292](https://github.com/rust-lang/rust-clippy/pull/5292)
+* Improve suggestion for [`match_single_binding`] when triggered inside a closure [#5350](https://github.com/rust-lang/rust-clippy/pull/5350)
+
+### ICE Fixes
+
+* Handle the unstable `trivial_bounds` feature [#5296](https://github.com/rust-lang/rust-clippy/pull/5296)
+* `shadow_*` lints [#5297](https://github.com/rust-lang/rust-clippy/pull/5297)
+
+### Documentation
+
+* Fix documentation generation for configurable lints [#5353](https://github.com/rust-lang/rust-clippy/pull/5353)
+* Update documentation for [`new_ret_no_self`] [#5448](https://github.com/rust-lang/rust-clippy/pull/5448)
+* The documentation for [`option_option`] now suggest using a tri-state enum [#5403](https://github.com/rust-lang/rust-clippy/pull/5403)
+* Fix bit mask example in [`verbose_bit_mask`] documentation [#5454](https://github.com/rust-lang/rust-clippy/pull/5454)
+* [`wildcard_imports`] documentation now mentions that `use ...::prelude::*` is
+ not linted [#5312](https://github.com/rust-lang/rust-clippy/pull/5312)
+
+## Rust 1.43
+
+Released 2020-04-23
+
+[4ee1206...204bb9b](https://github.com/rust-lang/rust-clippy/compare/4ee1206...204bb9b)
+
+### New lints
+
+* [`imprecise_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897)
+* [`suboptimal_flops`] [#4897](https://github.com/rust-lang/rust-clippy/pull/4897)
+* [`wildcard_imports`] [#5029](https://github.com/rust-lang/rust-clippy/pull/5029)
+* [`single_component_path_imports`] [#5058](https://github.com/rust-lang/rust-clippy/pull/5058)
+* [`match_single_binding`] [#5061](https://github.com/rust-lang/rust-clippy/pull/5061)
+* [`let_underscore_lock`] [#5101](https://github.com/rust-lang/rust-clippy/pull/5101)
+* [`struct_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125)
+* [`fn_params_excessive_bools`] [#5125](https://github.com/rust-lang/rust-clippy/pull/5125)
+* [`option_env_unwrap`] [#5148](https://github.com/rust-lang/rust-clippy/pull/5148)
+* [`lossy_float_literal`] [#5202](https://github.com/rust-lang/rust-clippy/pull/5202)
+* [`rest_pat_in_fully_bound_structs`] [#5258](https://github.com/rust-lang/rust-clippy/pull/5258)
+
+### Moves and Deprecations
+
+* Move [`unneeded_field_pattern`] to pedantic group [#5200](https://github.com/rust-lang/rust-clippy/pull/5200)
+
+### Enhancements
+
+* Make [`missing_errors_doc`] lint also trigger on `async` functions
+ [#5181](https://github.com/rust-lang/rust-clippy/pull/5181)
+* Add more constants to [`approx_constant`] [#5193](https://github.com/rust-lang/rust-clippy/pull/5193)
+* Extend [`question_mark`] lint [#5266](https://github.com/rust-lang/rust-clippy/pull/5266)
+
+### False Positive Fixes
+
+* [`use_debug`] [#5047](https://github.com/rust-lang/rust-clippy/pull/5047)
+* [`unnecessary_unwrap`] [#5132](https://github.com/rust-lang/rust-clippy/pull/5132)
+* [`zero_prefixed_literal`] [#5170](https://github.com/rust-lang/rust-clippy/pull/5170)
+* [`missing_const_for_fn`] [#5216](https://github.com/rust-lang/rust-clippy/pull/5216)
+
+### Suggestion Improvements
+
+* Improve suggestion when blocks of code are suggested [#5134](https://github.com/rust-lang/rust-clippy/pull/5134)
+
+### ICE Fixes
+
+* `misc_early` lints [#5129](https://github.com/rust-lang/rust-clippy/pull/5129)
+* [`missing_errors_doc`] [#5213](https://github.com/rust-lang/rust-clippy/pull/5213)
+* Fix ICE when evaluating `usize`s [#5256](https://github.com/rust-lang/rust-clippy/pull/5256)
+
+### Documentation
+
+* Improve documentation of [`iter_nth_zero`]
+* Add documentation pages for stable releases [#5171](https://github.com/rust-lang/rust-clippy/pull/5171)
+
+### Others
+
+* Clippy now completely runs on GitHub Actions [#5190](https://github.com/rust-lang/rust-clippy/pull/5190)
+
+
+## Rust 1.42
+
+Released 2020-03-12
+
+[69f99e7...4ee1206](https://github.com/rust-lang/rust-clippy/compare/69f99e7...4ee1206)
+
+### New lints
+
+* [`filetype_is_file`] [#4543](https://github.com/rust-lang/rust-clippy/pull/4543)
+* [`let_underscore_must_use`] [#4823](https://github.com/rust-lang/rust-clippy/pull/4823)
+* [`modulo_arithmetic`] [#4867](https://github.com/rust-lang/rust-clippy/pull/4867)
+* [`mem_replace_with_default`] [#4881](https://github.com/rust-lang/rust-clippy/pull/4881)
+* [`mutable_key_type`] [#4885](https://github.com/rust-lang/rust-clippy/pull/4885)
+* [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945)
+* [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960)
+* [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966)
+* `invalid_atomic_ordering` [#4999](https://github.com/rust-lang/rust-clippy/pull/4999)
+* [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067)
+
+### Moves and Deprecations
+
+* Move [`transmute_float_to_int`] from nursery to complexity group
+ [#5015](https://github.com/rust-lang/rust-clippy/pull/5015)
+* Move [`range_plus_one`] to pedantic group [#5057](https://github.com/rust-lang/rust-clippy/pull/5057)
+* Move [`debug_assert_with_mut_call`] to nursery group [#5106](https://github.com/rust-lang/rust-clippy/pull/5106)
+* Deprecate `unused_label` [#4930](https://github.com/rust-lang/rust-clippy/pull/4930)
+
+### Enhancements
+
+* Lint vectored IO in [`unused_io_amount`] [#5027](https://github.com/rust-lang/rust-clippy/pull/5027)
+* Make [`vec_box`] configurable by adding a size threshold [#5081](https://github.com/rust-lang/rust-clippy/pull/5081)
+* Also lint constants in [`cmp_nan`] [#4910](https://github.com/rust-lang/rust-clippy/pull/4910)
+* Fix false negative in [`expect_fun_call`] [#4915](https://github.com/rust-lang/rust-clippy/pull/4915)
+* Fix false negative in [`redundant_clone`] [#5017](https://github.com/rust-lang/rust-clippy/pull/5017)
+
+### False Positive Fixes
+
+* [`map_clone`] [#4937](https://github.com/rust-lang/rust-clippy/pull/4937)
+* [`replace_consts`] [#4977](https://github.com/rust-lang/rust-clippy/pull/4977)
+* [`let_and_return`] [#5008](https://github.com/rust-lang/rust-clippy/pull/5008)
+* [`eq_op`] [#5079](https://github.com/rust-lang/rust-clippy/pull/5079)
+* [`possible_missing_comma`] [#5083](https://github.com/rust-lang/rust-clippy/pull/5083)
+* [`debug_assert_with_mut_call`] [#5106](https://github.com/rust-lang/rust-clippy/pull/5106)
+* Don't trigger [`let_underscore_must_use`] in external macros
+ [#5082](https://github.com/rust-lang/rust-clippy/pull/5082)
+* Don't trigger [`empty_loop`] in `no_std` crates [#5086](https://github.com/rust-lang/rust-clippy/pull/5086)
+
+### Suggestion Improvements
+
+* `option_map_unwrap_or` [#4634](https://github.com/rust-lang/rust-clippy/pull/4634)
+* [`wildcard_enum_match_arm`] [#4934](https://github.com/rust-lang/rust-clippy/pull/4934)
+* [`cognitive_complexity`] [#4935](https://github.com/rust-lang/rust-clippy/pull/4935)
+* [`decimal_literal_representation`] [#4956](https://github.com/rust-lang/rust-clippy/pull/4956)
+* `unknown_clippy_lints` [#4963](https://github.com/rust-lang/rust-clippy/pull/4963)
+* [`explicit_into_iter_loop`] [#4978](https://github.com/rust-lang/rust-clippy/pull/4978)
+* [`useless_attribute`] [#5022](https://github.com/rust-lang/rust-clippy/pull/5022)
+* `if_let_some_result` [#5032](https://github.com/rust-lang/rust-clippy/pull/5032)
+
+### ICE fixes
+
+* [`unsound_collection_transmute`] [#4975](https://github.com/rust-lang/rust-clippy/pull/4975)
+
+### Documentation
+
+* Improve documentation of [`empty_enum`], [`replace_consts`], [`redundant_clone`], and [`iterator_step_by_zero`]
+
+
+## Rust 1.41
+
+Released 2020-01-30
+
+[c8e3cfb...69f99e7](https://github.com/rust-lang/rust-clippy/compare/c8e3cfb...69f99e7)
+
+* New Lints:
+ * [`exit`] [#4697](https://github.com/rust-lang/rust-clippy/pull/4697)
+ * [`to_digit_is_some`] [#4801](https://github.com/rust-lang/rust-clippy/pull/4801)
+ * [`tabs_in_doc_comments`] [#4806](https://github.com/rust-lang/rust-clippy/pull/4806)
+ * [`large_stack_arrays`] [#4807](https://github.com/rust-lang/rust-clippy/pull/4807)
+ * [`same_functions_in_if_condition`] [#4814](https://github.com/rust-lang/rust-clippy/pull/4814)
+ * [`zst_offset`] [#4816](https://github.com/rust-lang/rust-clippy/pull/4816)
+ * [`as_conversions`] [#4821](https://github.com/rust-lang/rust-clippy/pull/4821)
+ * [`missing_errors_doc`] [#4884](https://github.com/rust-lang/rust-clippy/pull/4884)
+ * [`transmute_float_to_int`] [#4889](https://github.com/rust-lang/rust-clippy/pull/4889)
+* Remove plugin interface, see
+ [Inside Rust Blog](https://blog.rust-lang.org/inside-rust/2019/11/04/Clippy-removes-plugin-interface.html) for
+ details [#4714](https://github.com/rust-lang/rust-clippy/pull/4714)
+* Move [`use_self`] to nursery group [#4863](https://github.com/rust-lang/rust-clippy/pull/4863)
+* Deprecate `into_iter_on_array` [#4788](https://github.com/rust-lang/rust-clippy/pull/4788)
+* Expand [`string_lit_as_bytes`] to also trigger when literal has escapes
+ [#4808](https://github.com/rust-lang/rust-clippy/pull/4808)
+* Fix false positive in `comparison_chain` [#4842](https://github.com/rust-lang/rust-clippy/pull/4842)
+* Fix false positive in `while_immutable_condition` [#4730](https://github.com/rust-lang/rust-clippy/pull/4730)
+* Fix false positive in `explicit_counter_loop` [#4803](https://github.com/rust-lang/rust-clippy/pull/4803)
+* Fix false positive in `must_use_candidate` [#4794](https://github.com/rust-lang/rust-clippy/pull/4794)
+* Fix false positive in `print_with_newline` and `write_with_newline`
+ [#4769](https://github.com/rust-lang/rust-clippy/pull/4769)
+* Fix false positive in `derive_hash_xor_eq` [#4766](https://github.com/rust-lang/rust-clippy/pull/4766)
+* Fix false positive in `missing_inline_in_public_items` [#4870](https://github.com/rust-lang/rust-clippy/pull/4870)
+* Fix false positive in `string_add` [#4880](https://github.com/rust-lang/rust-clippy/pull/4880)
+* Fix false positive in `float_arithmetic` [#4851](https://github.com/rust-lang/rust-clippy/pull/4851)
+* Fix false positive in `cast_sign_loss` [#4883](https://github.com/rust-lang/rust-clippy/pull/4883)
+* Fix false positive in `manual_swap` [#4877](https://github.com/rust-lang/rust-clippy/pull/4877)
+* Fix ICEs occurring while checking some block expressions [#4772](https://github.com/rust-lang/rust-clippy/pull/4772)
+* Fix ICE in `use_self` [#4776](https://github.com/rust-lang/rust-clippy/pull/4776)
+* Fix ICEs related to `const_generics` [#4780](https://github.com/rust-lang/rust-clippy/pull/4780)
+* Display help when running `clippy-driver` without arguments, instead of ICEing
+ [#4810](https://github.com/rust-lang/rust-clippy/pull/4810)
+* Clippy has its own ICE message now [#4588](https://github.com/rust-lang/rust-clippy/pull/4588)
+* Show deprecated lints in the documentation again [#4757](https://github.com/rust-lang/rust-clippy/pull/4757)
+* Improve Documentation by adding positive examples to some lints
+ [#4832](https://github.com/rust-lang/rust-clippy/pull/4832)
+
+## Rust 1.40
+
+Released 2019-12-19
+
+[4e7e71b...c8e3cfb](https://github.com/rust-lang/rust-clippy/compare/4e7e71b...c8e3cfb)
+
+* New Lints:
+ * [`unneeded_wildcard_pattern`] [#4537](https://github.com/rust-lang/rust-clippy/pull/4537)
+ * [`needless_doctest_main`] [#4603](https://github.com/rust-lang/rust-clippy/pull/4603)
+ * [`suspicious_unary_op_formatting`] [#4615](https://github.com/rust-lang/rust-clippy/pull/4615)
+ * [`debug_assert_with_mut_call`] [#4680](https://github.com/rust-lang/rust-clippy/pull/4680)
+ * [`unused_self`] [#4619](https://github.com/rust-lang/rust-clippy/pull/4619)
+ * [`inefficient_to_string`] [#4683](https://github.com/rust-lang/rust-clippy/pull/4683)
+ * [`must_use_unit`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`must_use_candidate`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`double_must_use`] [#4560](https://github.com/rust-lang/rust-clippy/pull/4560)
+ * [`comparison_chain`] [#4569](https://github.com/rust-lang/rust-clippy/pull/4569)
+ * [`unsound_collection_transmute`] [#4592](https://github.com/rust-lang/rust-clippy/pull/4592)
+ * [`panic`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * [`unreachable`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * [`todo`] [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * `option_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+ * `result_expect_used` [#4657](https://github.com/rust-lang/rust-clippy/pull/4657)
+* Move `redundant_clone` to perf group [#4509](https://github.com/rust-lang/rust-clippy/pull/4509)
+* Move `manual_mul_add` to nursery group [#4736](https://github.com/rust-lang/rust-clippy/pull/4736)
+* Expand `unit_cmp` to also work with `assert_eq!`, `debug_assert_eq!`, `assert_ne!` and `debug_assert_ne!` [#4613](https://github.com/rust-lang/rust-clippy/pull/4613)
+* Expand `integer_arithmetic` to also detect mutating arithmetic like `+=` [#4585](https://github.com/rust-lang/rust-clippy/pull/4585)
+* Fix false positive in `nonminimal_bool` [#4568](https://github.com/rust-lang/rust-clippy/pull/4568)
+* Fix false positive in `missing_safety_doc` [#4611](https://github.com/rust-lang/rust-clippy/pull/4611)
+* Fix false positive in `cast_sign_loss` [#4614](https://github.com/rust-lang/rust-clippy/pull/4614)
+* Fix false positive in `redundant_clone` [#4509](https://github.com/rust-lang/rust-clippy/pull/4509)
+* Fix false positive in `try_err` [#4721](https://github.com/rust-lang/rust-clippy/pull/4721)
+* Fix false positive in `toplevel_ref_arg` [#4570](https://github.com/rust-lang/rust-clippy/pull/4570)
+* Fix false positive in `multiple_inherent_impl` [#4593](https://github.com/rust-lang/rust-clippy/pull/4593)
+* Improve more suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4575](https://github.com/rust-lang/rust-clippy/pull/4575)
+* Improve suggestion for `zero_ptr` [#4599](https://github.com/rust-lang/rust-clippy/pull/4599)
+* Improve suggestion for `explicit_counter_loop` [#4691](https://github.com/rust-lang/rust-clippy/pull/4691)
+* Improve suggestion for `mul_add` [#4602](https://github.com/rust-lang/rust-clippy/pull/4602)
+* Improve suggestion for `assertions_on_constants` [#4635](https://github.com/rust-lang/rust-clippy/pull/4635)
+* Fix ICE in `use_self` [#4671](https://github.com/rust-lang/rust-clippy/pull/4671)
+* Fix ICE when encountering const casts [#4590](https://github.com/rust-lang/rust-clippy/pull/4590)
+
+## Rust 1.39
+
+Released 2019-11-07
+
+[3aea860...4e7e71b](https://github.com/rust-lang/rust-clippy/compare/3aea860...4e7e71b)
+
+* New Lints:
+ * [`uninit_assumed_init`] [#4479](https://github.com/rust-lang/rust-clippy/pull/4479)
+ * [`flat_map_identity`] [#4231](https://github.com/rust-lang/rust-clippy/pull/4231)
+ * [`missing_safety_doc`] [#4535](https://github.com/rust-lang/rust-clippy/pull/4535)
+ * [`mem_replace_with_uninit`] [#4511](https://github.com/rust-lang/rust-clippy/pull/4511)
+ * [`suspicious_map`] [#4394](https://github.com/rust-lang/rust-clippy/pull/4394)
+ * `option_and_then_some` [#4386](https://github.com/rust-lang/rust-clippy/pull/4386)
+ * [`manual_saturating_arithmetic`] [#4498](https://github.com/rust-lang/rust-clippy/pull/4498)
+* Deprecate `unused_collect` lint. This is fully covered by rustc's `#[must_use]` on `collect` [#4348](https://github.com/rust-lang/rust-clippy/pull/4348)
+* Move `type_repetition_in_bounds` to pedantic group [#4403](https://github.com/rust-lang/rust-clippy/pull/4403)
+* Move `cast_lossless` to pedantic group [#4539](https://github.com/rust-lang/rust-clippy/pull/4539)
+* `temporary_cstring_as_ptr` now catches more cases [#4425](https://github.com/rust-lang/rust-clippy/pull/4425)
+* `use_self` now works in constructors, too [#4525](https://github.com/rust-lang/rust-clippy/pull/4525)
+* `cargo_common_metadata` now checks for license files [#4518](https://github.com/rust-lang/rust-clippy/pull/4518)
+* `cognitive_complexity` now includes the measured complexity in the warning message [#4469](https://github.com/rust-lang/rust-clippy/pull/4469)
+* Fix false positives in `block_in_if_*` lints [#4458](https://github.com/rust-lang/rust-clippy/pull/4458)
+* Fix false positive in `cast_lossless` [#4473](https://github.com/rust-lang/rust-clippy/pull/4473)
+* Fix false positive in `clone_on_copy` [#4411](https://github.com/rust-lang/rust-clippy/pull/4411)
+* Fix false positive in `deref_addrof` [#4487](https://github.com/rust-lang/rust-clippy/pull/4487)
+* Fix false positive in `too_many_lines` [#4490](https://github.com/rust-lang/rust-clippy/pull/4490)
+* Fix false positive in `new_ret_no_self` [#4365](https://github.com/rust-lang/rust-clippy/pull/4365)
+* Fix false positive in `manual_swap` [#4478](https://github.com/rust-lang/rust-clippy/pull/4478)
+* Fix false positive in `missing_const_for_fn` [#4450](https://github.com/rust-lang/rust-clippy/pull/4450)
+* Fix false positive in `extra_unused_lifetimes` [#4477](https://github.com/rust-lang/rust-clippy/pull/4477)
+* Fix false positive in `inherent_to_string` [#4460](https://github.com/rust-lang/rust-clippy/pull/4460)
+* Fix false positive in `map_entry` [#4495](https://github.com/rust-lang/rust-clippy/pull/4495)
+* Fix false positive in `unused_unit` [#4445](https://github.com/rust-lang/rust-clippy/pull/4445)
+* Fix false positive in `redundant_pattern` [#4489](https://github.com/rust-lang/rust-clippy/pull/4489)
+* Fix false positive in `wrong_self_convention` [#4369](https://github.com/rust-lang/rust-clippy/pull/4369)
+* Improve various suggestions and tests in preparation for the unstable `cargo fix --clippy` [#4558](https://github.com/rust-lang/rust-clippy/pull/4558)
+* Improve suggestions for `redundant_pattern_matching` [#4352](https://github.com/rust-lang/rust-clippy/pull/4352)
+* Improve suggestions for `explicit_write` [#4544](https://github.com/rust-lang/rust-clippy/pull/4544)
+* Improve suggestion for `or_fun_call` [#4522](https://github.com/rust-lang/rust-clippy/pull/4522)
+* Improve suggestion for `match_as_ref` [#4446](https://github.com/rust-lang/rust-clippy/pull/4446)
+* Improve suggestion for `unnecessary_fold_span` [#4382](https://github.com/rust-lang/rust-clippy/pull/4382)
+* Add suggestions for `unseparated_literal_suffix` [#4401](https://github.com/rust-lang/rust-clippy/pull/4401)
+* Add suggestions for `char_lit_as_u8` [#4418](https://github.com/rust-lang/rust-clippy/pull/4418)
+
+## Rust 1.38
+
+Released 2019-09-26
+
+[e3cb40e...3aea860](https://github.com/rust-lang/rust-clippy/compare/e3cb40e...3aea860)
+
+* New Lints:
+ * [`main_recursion`] [#4203](https://github.com/rust-lang/rust-clippy/pull/4203)
+ * [`inherent_to_string`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259)
+ * [`inherent_to_string_shadow_display`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259)
+ * [`type_repetition_in_bounds`] [#3766](https://github.com/rust-lang/rust-clippy/pull/3766)
+ * [`try_err`] [#4222](https://github.com/rust-lang/rust-clippy/pull/4222)
+* Move `{unnnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307)
+* Extend the `use_self` lint to suggest uses of `Self::Variant` [#4308](https://github.com/rust-lang/rust-clippy/pull/4308)
+* Improve suggestion for needless return [#4262](https://github.com/rust-lang/rust-clippy/pull/4262)
+* Add auto-fixable suggestion for `let_unit` [#4337](https://github.com/rust-lang/rust-clippy/pull/4337)
+* Fix false positive in `pub_enum_variant_names` and `enum_variant_names` [#4345](https://github.com/rust-lang/rust-clippy/pull/4345)
+* Fix false positive in `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257)
+* Fix false positive in `string_lit_as_bytes` [#4233](https://github.com/rust-lang/rust-clippy/pull/4233)
+* Fix false positive in `needless_lifetimes` [#4266](https://github.com/rust-lang/rust-clippy/pull/4266)
+* Fix false positive in `float_cmp` [#4275](https://github.com/rust-lang/rust-clippy/pull/4275)
+* Fix false positives in `needless_return` [#4274](https://github.com/rust-lang/rust-clippy/pull/4274)
+* Fix false negative in `match_same_arms` [#4246](https://github.com/rust-lang/rust-clippy/pull/4246)
+* Fix incorrect suggestion for `needless_bool` [#4335](https://github.com/rust-lang/rust-clippy/pull/4335)
+* Improve suggestion for `cast_ptr_alignment` [#4257](https://github.com/rust-lang/rust-clippy/pull/4257)
+* Improve suggestion for `single_char_literal` [#4361](https://github.com/rust-lang/rust-clippy/pull/4361)
+* Improve suggestion for `len_zero` [#4314](https://github.com/rust-lang/rust-clippy/pull/4314)
+* Fix ICE in `implicit_hasher` [#4268](https://github.com/rust-lang/rust-clippy/pull/4268)
+* Fix allow bug in `trivially_copy_pass_by_ref` [#4250](https://github.com/rust-lang/rust-clippy/pull/4250)
+
+## Rust 1.37
+
+Released 2019-08-15
+
+[082cfa7...e3cb40e](https://github.com/rust-lang/rust-clippy/compare/082cfa7...e3cb40e)
+
+* New Lints:
+ * [`checked_conversions`] [#4088](https://github.com/rust-lang/rust-clippy/pull/4088)
+ * [`get_last_with_len`] [#3832](https://github.com/rust-lang/rust-clippy/pull/3832)
+ * [`integer_division`] [#4195](https://github.com/rust-lang/rust-clippy/pull/4195)
+* Renamed Lint: `const_static_lifetime` is now called [`redundant_static_lifetimes`].
+ The lint now covers statics in addition to consts [#4162](https://github.com/rust-lang/rust-clippy/pull/4162)
+* [`match_same_arms`] now warns for all identical arms, instead of only the first one [#4102](https://github.com/rust-lang/rust-clippy/pull/4102)
+* [`needless_return`] now works with void functions [#4220](https://github.com/rust-lang/rust-clippy/pull/4220)
+* Fix false positive in [`redundant_closure`] [#4190](https://github.com/rust-lang/rust-clippy/pull/4190)
+* Fix false positive in [`useless_attribute`] [#4107](https://github.com/rust-lang/rust-clippy/pull/4107)
+* Fix incorrect suggestion for [`float_cmp`] [#4214](https://github.com/rust-lang/rust-clippy/pull/4214)
+* Add suggestions for [`print_with_newline`] and [`write_with_newline`] [#4136](https://github.com/rust-lang/rust-clippy/pull/4136)
+* Improve suggestions for `option_map_unwrap_or_else` and `result_map_unwrap_or_else` [#4164](https://github.com/rust-lang/rust-clippy/pull/4164)
+* Improve suggestions for [`non_ascii_literal`] [#4119](https://github.com/rust-lang/rust-clippy/pull/4119)
+* Improve diagnostics for [`let_and_return`] [#4137](https://github.com/rust-lang/rust-clippy/pull/4137)
+* Improve diagnostics for [`trivially_copy_pass_by_ref`] [#4071](https://github.com/rust-lang/rust-clippy/pull/4071)
+* Add macro check for [`unreadable_literal`] [#4099](https://github.com/rust-lang/rust-clippy/pull/4099)
+
+## Rust 1.36
+
+Released 2019-07-04
+
+[eb9f9b1...082cfa7](https://github.com/rust-lang/rust-clippy/compare/eb9f9b1...082cfa7)
+
+* New lints: [`find_map`], [`filter_map_next`] [#4039](https://github.com/rust-lang/rust-clippy/pull/4039)
+* New lint: [`path_buf_push_overwrite`] [#3954](https://github.com/rust-lang/rust-clippy/pull/3954)
+* Move `path_buf_push_overwrite` to the nursery [#4013](https://github.com/rust-lang/rust-clippy/pull/4013)
+* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
+* Allow allowing of [`toplevel_ref_arg`] lint [#4007](https://github.com/rust-lang/rust-clippy/pull/4007)
+* Fix false negative in [`or_fun_call`] pertaining to nested constructors [#4084](https://github.com/rust-lang/rust-clippy/pull/4084)
+* Fix false positive in [`or_fun_call`] pertaining to enum variant constructors [#4018](https://github.com/rust-lang/rust-clippy/pull/4018)
+* Fix false positive in [`useless_let_if_seq`] pertaining to interior mutability [#4035](https://github.com/rust-lang/rust-clippy/pull/4035)
+* Fix false positive in [`redundant_closure`] pertaining to non-function types [#4008](https://github.com/rust-lang/rust-clippy/pull/4008)
+* Fix false positive in [`let_and_return`] pertaining to attributes on `let`s [#4024](https://github.com/rust-lang/rust-clippy/pull/4024)
+* Fix false positive in [`module_name_repetitions`] lint pertaining to attributes [#4006](https://github.com/rust-lang/rust-clippy/pull/4006)
+* Fix false positive on [`assertions_on_constants`] pertaining to `debug_assert!` [#3989](https://github.com/rust-lang/rust-clippy/pull/3989)
+* Improve suggestion in [`map_clone`] to suggest `.copied()` where applicable [#3970](https://github.com/rust-lang/rust-clippy/pull/3970) [#4043](https://github.com/rust-lang/rust-clippy/pull/4043)
+* Improve suggestion for [`search_is_some`] [#4049](https://github.com/rust-lang/rust-clippy/pull/4049)
+* Improve suggestion applicability for [`naive_bytecount`] [#3984](https://github.com/rust-lang/rust-clippy/pull/3984)
+* Improve suggestion applicability for [`while_let_loop`] [#3975](https://github.com/rust-lang/rust-clippy/pull/3975)
+* Improve diagnostics for [`too_many_arguments`] [#4053](https://github.com/rust-lang/rust-clippy/pull/4053)
+* Improve diagnostics for [`cast_lossless`] [#4021](https://github.com/rust-lang/rust-clippy/pull/4021)
+* Deal with macro checks in desugarings better [#4082](https://github.com/rust-lang/rust-clippy/pull/4082)
+* Add macro check for [`unnecessary_cast`] [#4026](https://github.com/rust-lang/rust-clippy/pull/4026)
+* Remove [`approx_constant`]'s documentation's "Known problems" section. [#4027](https://github.com/rust-lang/rust-clippy/pull/4027)
+* Fix ICE in [`suspicious_else_formatting`] [#3960](https://github.com/rust-lang/rust-clippy/pull/3960)
+* Fix ICE in [`decimal_literal_representation`] [#3931](https://github.com/rust-lang/rust-clippy/pull/3931)
+
+
+## Rust 1.35
+
+Released 2019-05-20
+
+[1fac380..37f5c1e](https://github.com/rust-lang/rust-clippy/compare/1fac380...37f5c1e)
+
+* New lint: `drop_bounds` to detect `T: Drop` bounds
+* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
+* Rename `cyclomatic_complexity` to [`cognitive_complexity`], start work on making lint more practical for Rust code
+* Move [`get_unwrap`] to the restriction category
+* Improve suggestions for [`iter_cloned_collect`]
+* Improve suggestions for [`cast_lossless`] to suggest suffixed literals
+* Fix false positives in [`print_with_newline`] and [`write_with_newline`] pertaining to raw strings
+* Fix false positive in [`needless_range_loop`] pertaining to structs without a `.iter()`
+* Fix false positive in [`bool_comparison`] pertaining to non-bool types
+* Fix false positive in [`redundant_closure`] pertaining to differences in borrows
+* Fix false positive in `option_map_unwrap_or` on non-copy types
+* Fix false positives in [`missing_const_for_fn`] pertaining to macros and trait method impls
+* Fix false positive in [`needless_pass_by_value`] pertaining to procedural macros
+* Fix false positive in [`needless_continue`] pertaining to loop labels
+* Fix false positive for [`boxed_local`] pertaining to arguments moved into closures
+* Fix false positive for [`use_self`] in nested functions
+* Fix suggestion for [`expect_fun_call`] (https://github.com/rust-lang/rust-clippy/pull/3846)
+* Fix suggestion for [`explicit_counter_loop`] to deal with parenthesizing range variables
+* Fix suggestion for [`single_char_pattern`] to correctly escape single quotes
+* Avoid triggering [`redundant_closure`] in macros
+* ICE fixes: [#3805](https://github.com/rust-lang/rust-clippy/pull/3805), [#3772](https://github.com/rust-lang/rust-clippy/pull/3772), [#3741](https://github.com/rust-lang/rust-clippy/pull/3741)
+
+## Rust 1.34
+
+Released 2019-04-10
+
+[1b89724...1fac380](https://github.com/rust-lang/rust-clippy/compare/1b89724...1fac380)
+
+* New lint: [`assertions_on_constants`] to detect for example `assert!(true)`
+* New lint: [`dbg_macro`] to detect uses of the `dbg!` macro
+* New lint: [`missing_const_for_fn`] that can suggest functions to be made `const`
+* New lint: [`too_many_lines`] to detect functions with excessive LOC. It can be
+ configured using the `too-many-lines-threshold` configuration.
+* New lint: [`wildcard_enum_match_arm`] to check for wildcard enum matches using `_`
+* Expand `redundant_closure` to also work for methods (not only functions)
+* Fix ICEs in `vec_box`, `needless_pass_by_value` and `implicit_hasher`
+* Fix false positive in `cast_sign_loss`
+* Fix false positive in `integer_arithmetic`
+* Fix false positive in `unit_arg`
+* Fix false positives in `implicit_return`
+* Add suggestion to `explicit_write`
+* Improve suggestions for `question_mark` lint
+* Fix incorrect suggestion for `cast_lossless`
+* Fix incorrect suggestion for `expect_fun_call`
+* Fix incorrect suggestion for `needless_bool`
+* Fix incorrect suggestion for `needless_range_loop`
+* Fix incorrect suggestion for `use_self`
+* Fix incorrect suggestion for `while_let_on_iterator`
+* Clippy is now slightly easier to invoke in non-cargo contexts. See
+ [#3665][pull3665] for more details.
+* We now have [improved documentation][adding_lints] on how to add new lints
+
+## Rust 1.33
+
+Released 2019-02-26
+
+[b2601be...1b89724](https://github.com/rust-lang/rust-clippy/compare/b2601be...1b89724)
+
+* New lints: [`implicit_return`], [`vec_box`], [`cast_ref_to_mut`]
+* The `rust-clippy` repository is now part of the `rust-lang` org.
+* Rename `stutter` to `module_name_repetitions`
+* Merge `new_without_default_derive` into `new_without_default` lint
+* Move `large_digit_groups` from `style` group to `pedantic`
+* Expand `bool_comparison` to check for `<`, `<=`, `>`, `>=`, and `!=`
+ comparisons against booleans
+* Expand `no_effect` to detect writes to constants such as `A_CONST.field = 2`
+* Expand `redundant_clone` to work on struct fields
+* Expand `suspicious_else_formatting` to detect `if .. {..} {..}`
+* Expand `use_self` to work on tuple structs and also in local macros
+* Fix ICE in `result_map_unit_fn` and `option_map_unit_fn`
+* Fix false positives in `implicit_return`
+* Fix false positives in `use_self`
+* Fix false negative in `clone_on_copy`
+* Fix false positive in `doc_markdown`
+* Fix false positive in `empty_loop`
+* Fix false positive in `if_same_then_else`
+* Fix false positive in `infinite_iter`
+* Fix false positive in `question_mark`
+* Fix false positive in `useless_asref`
+* Fix false positive in `wildcard_dependencies`
+* Fix false positive in `write_with_newline`
+* Add suggestion to `explicit_write`
+* Improve suggestions for `question_mark` lint
+* Fix incorrect suggestion for `get_unwrap`
+
+## Rust 1.32
+
+Released 2019-01-17
+
+[2e26fdc2...b2601be](https://github.com/rust-lang/rust-clippy/compare/2e26fdc2...b2601be)
+
+* New lints: [`slow_vector_initialization`], `mem_discriminant_non_enum`,
+ [`redundant_clone`], [`wildcard_dependencies`],
+ [`into_iter_on_ref`], `into_iter_on_array`, [`deprecated_cfg_attr`],
+ [`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
+[`README.md`]: https://github.com/rust-lang/rust-clippy/blob/master/README.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
+[`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async
+[`await_holding_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_lock
+[`await_holding_refcell_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_refcell_ref
+[`bad_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
+[`bind_instead_of_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#bind_instead_of_map
+[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name
+[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
+[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
+[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
+[`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_collection`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_collection
+[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
+[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
+[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
+[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
+[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
+[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
+[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
+[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
+[`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap
+[`cast_precision_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_precision_loss
+[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
+[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
+[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
+[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
+[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
+[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
+[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
+[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
+[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
+[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
+[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
+[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
+[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
+[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
+[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
+[`collapsible_else_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if
+[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
+[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
+[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
+[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
+[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
+[`create_dir`]: https://rust-lang.github.io/rust-clippy/master/index.html#create_dir
+[`crosspointer_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#crosspointer_transmute
+[`dbg_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro
+[`debug_assert_with_mut_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#debug_assert_with_mut_call
+[`decimal_literal_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation
+[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
+[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback
+[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
+[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
+[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
+[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
+[`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
+[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
+[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
- [`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
++[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
+[`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents
++[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
+[`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_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
+[`equatable_if_let`]: https://rust-lang.github.io/rust-clippy/master/index.html#equatable_if_let
+[`erasing_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#erasing_op
+[`eval_order_dependence`]: https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence
+[`excessive_precision`]: https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
+[`exhaustive_enums`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums
+[`exhaustive_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_structs
+[`exit`]: https://rust-lang.github.io/rust-clippy/master/index.html#exit
+[`expect_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_fun_call
+[`expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#expect_used
+[`expl_impl_clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#expl_impl_clone_on_copy
+[`explicit_counter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_counter_loop
+[`explicit_deref_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods
+[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop
+[`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop
+[`explicit_write`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write
+[`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice
+[`extend_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_with_drain
+[`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes
+[`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from
+[`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default
+[`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file
+[`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map
+[`filter_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity
+[`filter_map_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next
+[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
+[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
+[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
+[`flat_map_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_option
+[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
+[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
+[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
+[`float_equality_without_abs`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_equality_without_abs
+[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
+[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
+[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
+[`fn_to_numeric_cast_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_any
+[`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
+[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
+[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
+[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
+[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
+[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
+[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
+[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
+[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op
+[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex
+[`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching
+[`if_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
+[`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none
+[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
+[`implicit_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone
+[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
+[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
+[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub
+[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
+[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
+[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
++[`index_refutable_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice
+[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
+[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
+[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
+[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
+[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
+[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string
+[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display
+[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always
+[`inline_asm_x86_att_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_att_syntax
+[`inline_asm_x86_intel_syntax`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_asm_x86_intel_syntax
+[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
+[`inspect_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#inspect_for_each
+[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
+[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
+[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
+[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
+[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
+[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
+[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
+[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
+[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
+[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
+[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
+[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
+[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
+[`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator
+[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth
+[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
+[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
+[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
+[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
+[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
+[`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups
+[`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
+[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays
+[`large_types_passed_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value
+[`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
+[`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
+[`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return
+[`let_underscore_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_drop
+[`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock
+[`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use
+[`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value
+[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
+[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
+[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
+[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
+[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
+[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
+[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
+[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
+[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
+[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
+[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
+[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
+[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
+[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
+[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
+[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
+[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
+[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
+[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
+[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
+[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
+[`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names
+[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
+[`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit
+[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
+[`map_err_ignore`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_err_ignore
+[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
+[`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity
+[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or
+[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref
+[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool
+[`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro
+[`match_on_vec_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_on_vec_items
+[`match_overlapping_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_overlapping_arm
+[`match_ref_pats`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_ref_pats
+[`match_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_result_ok
+[`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_str_case_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_str_case_mismatch
+[`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_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_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames
+[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc
+[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
+[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
+[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
+[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
+[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
+[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
+[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
+[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
+[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
+[`modulo_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_one
+[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
+[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
+[`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
+[`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit
+[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
+[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
+[`mut_mutex_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mutex_lock
+[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
+[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
+[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
+[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
+[`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount
+[`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type
+[`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool
+[`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_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
++[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
+[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
+[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
+[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
+[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
+[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
+[`needless_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
++[`needless_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_splitn
+[`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
+[`negative_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#negative_feature_names
+[`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
+[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
+[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
+[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
+[`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty
+[`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
+[`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces
+[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
++[`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes
+[`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_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_filter_map
+[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
+[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
+[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
+[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
+[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
+[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
+[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
+[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
+[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
+[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
+[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
+[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
+[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
+[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
+[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
+[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
+[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
+[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
+[`print_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_with_newline
+[`println_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#println_empty_string
+[`ptr_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg
+[`ptr_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr
+[`ptr_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_eq
+[`ptr_offset_with_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#ptr_offset_with_cast
+[`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names
+[`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark
+[`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one
+[`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one
+[`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero
+[`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len
+[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
+[`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex
+[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
+[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
+[`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
+[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
+[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
+[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
+[`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
+[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
+[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
+[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
+[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
+[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
+[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
+[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
+[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
+[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
+[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
+[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
+[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
+[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
+[`result_map_or_into_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_or_into_option
+[`result_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unit_fn
+[`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err
+[`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges
+[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition
+[`same_item_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push
+[`same_name_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_name_method
+[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
+[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
+[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
+[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files
+[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
+[`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix
+[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
+[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse
+[`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same
+[`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated
+[`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement
+[`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq
+[`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
+[`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names
+[`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str
+[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
+[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
+[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
+[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
+[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
+[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
+[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
+[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
+[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
+[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
+[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
+[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
+[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
+[`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes
+[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
+[`string_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_slice
+[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
+[`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings
+[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
+[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops
+[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
+[`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting
+[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
+[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
+[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
+[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
+[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
+[`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
+[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
+[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
+[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
+[`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
+[`trailing_empty_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#trailing_empty_array
+[`trait_duplication_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#trait_duplication_in_bounds
+[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str
+[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int
+[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool
+[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char
+[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float
+[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
+[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
+[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
+[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
+[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
+[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
+[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
+[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
+[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
+[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
+[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
+[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
+[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
+[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
+[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
+[`uninit_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_vec
+[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
+[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
+[`unit_hash`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_hash
+[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
+[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
+[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
+[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
+[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
+[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
+[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
+[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
+[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
+[`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
+[`unnecessary_wraps`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps
+[`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern
+[`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern
+[`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns
+[`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable
+[`unreadable_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal
+[`unsafe_derive_deserialize`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_derive_deserialize
+[`unsafe_removed_from_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_removed_from_name
+[`unsafe_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsafe_vector_initialization
+[`unseparated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#unseparated_literal_suffix
+[`unsound_collection_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsound_collection_transmute
+[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice
+[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
+[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
+[`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_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
+[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
+[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
+[`unwrap_in_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_in_result
+[`unwrap_or_else_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_else_default
+[`unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
+[`upper_case_acronyms`]: https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
+[`use_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_debug
+[`use_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#use_self
+[`used_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_binding
+[`useless_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_asref
+[`useless_attribute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_attribute
+[`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
+[`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format
+[`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq
+[`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute
+[`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec
+[`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box
+[`vec_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_init_then_push
+[`vec_resize_to_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_resize_to_zero
+[`verbose_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask
+[`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads
+[`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons
+[`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition
+[`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop
+[`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator
+[`wildcard_dependencies`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_dependencies
+[`wildcard_enum_match_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_enum_match_arm
+[`wildcard_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports
+[`wildcard_in_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_in_or_patterns
+[`write_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_literal
+[`write_with_newline`]: https://rust-lang.github.io/rust-clippy/master/index.html#write_with_newline
+[`writeln_empty_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#writeln_empty_string
+[`wrong_pub_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_pub_self_convention
+[`wrong_self_convention`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention
+[`wrong_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#wrong_transmute
+[`zero_divided_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_divided_by_zero
+[`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal
+[`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr
+[`zero_sized_map_values`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_sized_map_values
+[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset
+<!-- end autogenerated links to lint list -->
--- /dev/null
- version = "0.1.58"
+[package]
+name = "clippy"
++version = "0.1.59"
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+categories = ["development-tools", "development-tools::cargo-plugins"]
+build = "build.rs"
+edition = "2021"
+publish = false
+
+[[bin]]
+name = "cargo-clippy"
+test = false
+path = "src/main.rs"
+
+[[bin]]
+name = "clippy-driver"
+path = "src/driver.rs"
+
+[dependencies]
+clippy_lints = { version = "0.1", path = "clippy_lints" }
+semver = "1.0"
+rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
+tempfile = { version = "3.2", optional = true }
+
+[dev-dependencies]
+cargo_metadata = "0.14"
+compiletest_rs = { version = "0.7.1", features = ["tmp"] }
+tester = "0.9"
+regex = "1.5"
+# This is used by the `collect-metadata` alias.
+filetime = "0.2"
+
+# A noop dependency that changes in the Rust repository, it's a bit of a hack.
+# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
+# for more information.
+rustc-workspace-hack = "1.0"
+
+# UI test dependencies
+clippy_utils = { path = "clippy_utils" }
+derive-new = "0.5"
+if_chain = "1.0"
+itertools = "0.10"
+quote = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+syn = { version = "1.0", features = ["full"] }
++parking_lot = "0.11.2"
+
+[build-dependencies]
+rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }
+
+[features]
+deny-warnings = ["clippy_lints/deny-warnings"]
+integration = ["tempfile"]
+internal-lints = ["clippy_lints/internal-lints"]
+metadata-collector-lint = ["internal-lints", "clippy_lints/metadata-collector-lint"]
+
+[package.metadata.rust-analyzer]
+# This package uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
+[package]
+name = "clippy_dev"
+version = "0.0.1"
+edition = "2021"
+
+[dependencies]
+bytecount = "0.6"
+clap = "2.33"
+indoc = "1.0"
+itertools = "0.10"
+opener = "0.5"
+regex = "1.5"
+shell-escape = "0.1"
+walkdir = "2.3"
++cargo_metadata = "0.14"
+
+[features]
+deny-warnings = []
--- /dev/null
- use std::ffi::OsStr;
+use crate::clippy_project_root;
++use itertools::Itertools;
+use shell_escape::escape;
- for entry in WalkDir::new(project_root.join("tests")) {
- let entry = entry?;
- let path = entry.path();
-
- if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" {
- continue;
- }
-
- success &= rustfmt(context, path)?;
++use std::ffi::{OsStr, OsString};
+use std::path::Path;
+use std::process::{self, Command};
+use std::{fs, io};
+use walkdir::WalkDir;
+
+#[derive(Debug)]
+pub enum CliError {
+ CommandFailed(String, String),
+ IoError(io::Error),
+ RustfmtNotInstalled,
+ WalkDirError(walkdir::Error),
+ RaSetupActive,
+}
+
+impl From<io::Error> for CliError {
+ fn from(error: io::Error) -> Self {
+ Self::IoError(error)
+ }
+}
+
+impl From<walkdir::Error> for CliError {
+ fn from(error: walkdir::Error) -> Self {
+ Self::WalkDirError(error)
+ }
+}
+
+struct FmtContext {
+ check: bool,
+ verbose: bool,
+}
+
+// the "main" function of cargo dev fmt
+pub fn run(check: bool, verbose: bool) {
+ fn try_run(context: &FmtContext) -> Result<bool, CliError> {
+ let mut success = true;
+
+ let project_root = clippy_project_root();
+
+ // if we added a local rustc repo as path dependency to clippy for rust analyzer, we do NOT want to
+ // format because rustfmt would also format the entire rustc repo as it is a local
+ // dependency
+ if fs::read_to_string(project_root.join("Cargo.toml"))
+ .expect("Failed to read clippy Cargo.toml")
+ .contains(&"[target.'cfg(NOT_A_PLATFORM)'.dependencies]")
+ {
+ return Err(CliError::RaSetupActive);
+ }
+
+ rustfmt_test(context)?;
+
+ success &= cargo_fmt(context, project_root.as_path())?;
+ success &= cargo_fmt(context, &project_root.join("clippy_dev"))?;
+ success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?;
+ success &= cargo_fmt(context, &project_root.join("lintcheck"))?;
+
- let mut args = vec!["+nightly", "fmt", "--all"];
++ let chunks = WalkDir::new(project_root.join("tests"))
++ .into_iter()
++ .filter_map(|entry| {
++ let entry = entry.expect("failed to find tests");
++ let path = entry.path();
++
++ if path.extension() != Some("rs".as_ref()) || entry.file_name() == "ice-3891.rs" {
++ None
++ } else {
++ Some(entry.into_path().into_os_string())
++ }
++ })
++ .chunks(250);
++
++ for chunk in &chunks {
++ success &= rustfmt(context, chunk)?;
+ }
+
+ Ok(success)
+ }
+
+ fn output_err(err: CliError) {
+ match err {
+ CliError::CommandFailed(command, stderr) => {
+ eprintln!("error: A command failed! `{}`\nstderr: {}", command, stderr);
+ },
+ CliError::IoError(err) => {
+ eprintln!("error: {}", err);
+ },
+ CliError::RustfmtNotInstalled => {
+ eprintln!("error: rustfmt nightly is not installed.");
+ },
+ CliError::WalkDirError(err) => {
+ eprintln!("error: {}", err);
+ },
+ CliError::RaSetupActive => {
+ eprintln!(
+ "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`.
+Not formatting because that would format the local repo as well!
+Please revert the changes to Cargo.tomls first."
+ );
+ },
+ }
+ }
+
+ let context = FmtContext { check, verbose };
+ let result = try_run(&context);
+ let code = match result {
+ Ok(true) => 0,
+ Ok(false) => {
+ eprintln!();
+ eprintln!("Formatting check failed.");
+ eprintln!("Run `cargo dev fmt` to update formatting.");
+ 1
+ },
+ Err(err) => {
+ output_err(err);
+ 1
+ },
+ };
+ process::exit(code);
+}
+
+fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
+ let arg_display: Vec<_> = args.iter().map(|a| escape(a.as_ref().to_string_lossy())).collect();
+
+ format!(
+ "cd {} && {} {}",
+ escape(dir.as_ref().to_string_lossy()),
+ escape(program.as_ref().to_string_lossy()),
+ arg_display.join(" ")
+ )
+}
+
+fn exec(
+ context: &FmtContext,
+ program: impl AsRef<OsStr>,
+ dir: impl AsRef<Path>,
+ args: &[impl AsRef<OsStr>],
+) -> Result<bool, CliError> {
+ if context.verbose {
+ println!("{}", format_command(&program, &dir, args));
+ }
+
+ let child = Command::new(&program).current_dir(&dir).args(args.iter()).spawn()?;
+ let output = child.wait_with_output()?;
+ let success = output.status.success();
+
+ if !context.check && !success {
+ let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
+ return Err(CliError::CommandFailed(
+ format_command(&program, &dir, args),
+ String::from(stderr),
+ ));
+ }
+
+ Ok(success)
+}
+
+fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
- let args = &["+nightly", "--version"];
++ let mut args = vec!["fmt", "--all"];
+ if context.check {
+ args.push("--");
+ args.push("--check");
+ }
+ let success = exec(context, "cargo", path, &args)?;
+
+ Ok(success)
+}
+
+fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> {
+ let program = "rustfmt";
+ let dir = std::env::current_dir()?;
- fn rustfmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
- let mut args = vec!["+nightly".as_ref(), path.as_os_str()];
++ let args = &["--version"];
+
+ if context.verbose {
+ println!("{}", format_command(&program, &dir, args));
+ }
+
+ let output = Command::new(&program).current_dir(&dir).args(args.iter()).output()?;
+
+ if output.status.success() {
+ Ok(())
+ } else if std::str::from_utf8(&output.stderr)
+ .unwrap_or("")
+ .starts_with("error: 'rustfmt' is not installed")
+ {
+ Err(CliError::RustfmtNotInstalled)
+ } else {
+ Err(CliError::CommandFailed(
+ format_command(&program, &dir, args),
+ std::str::from_utf8(&output.stderr).unwrap_or("").to_string(),
+ ))
+ }
+}
+
- args.push("--check".as_ref());
++fn rustfmt(context: &FmtContext, paths: impl Iterator<Item = OsString>) -> Result<bool, CliError> {
++ let mut args = Vec::new();
+ if context.check {
- if !success {
- eprintln!("rustfmt failed on {}", path.display());
- }
++ args.push(OsString::from("--check"));
+ }
++ args.extend(paths);
++
+ let success = exec(context, "rustfmt", std::env::current_dir()?, &args)?;
++
+ Ok(success)
+}
--- /dev/null
+#![feature(once_cell)]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use std::path::PathBuf;
+
+pub mod bless;
+pub mod fmt;
++pub mod lint;
+pub mod new_lint;
+pub mod serve;
+pub mod setup;
+pub mod update_lints;
+
+/// Returns the path to the Clippy project directory
+///
+/// # Panics
+///
+/// Panics if the current directory could not be retrieved, there was an error reading any of the
+/// Cargo.toml files or ancestor directory is the clippy root directory
+#[must_use]
+pub fn clippy_project_root() -> PathBuf {
+ let current_dir = std::env::current_dir().unwrap();
+ for path in current_dir.ancestors() {
+ let result = std::fs::read_to_string(path.join("Cargo.toml"));
+ if let Err(err) = &result {
+ if err.kind() == std::io::ErrorKind::NotFound {
+ continue;
+ }
+ }
+
+ let content = result.unwrap();
+ if content.contains("[package]\nname = \"clippy\"") {
+ return path.to_path_buf();
+ }
+ }
+ panic!("error: Can't determine root of project. Please run inside a Clippy working dir.");
+}
--- /dev/null
--- /dev/null
++use std::process::{self, Command};
++
++pub fn run(filename: &str) {
++ let code = Command::new("cargo")
++ .args(["run", "--bin", "clippy-driver", "--"])
++ .args(["-L", "./target/debug"])
++ .args(["-Z", "no-codegen"])
++ .args(["--edition", "2021"])
++ .arg(filename)
++ .env("__CLIPPY_INTERNAL_TESTS", "true")
++ .status()
++ .expect("failed to run cargo")
++ .code();
++
++ if code.is_none() {
++ eprintln!("Killed by signal");
++ }
++
++ process::exit(code.unwrap_or(1));
++}
--- /dev/null
- use clippy_dev::{bless, fmt, new_lint, serve, setup, update_lints};
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
++use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
+fn main() {
+ let matches = get_clap_config();
+
+ match matches.subcommand() {
+ ("bless", Some(matches)) => {
+ bless::bless(matches.is_present("ignore-timestamp"));
+ },
+ ("fmt", Some(matches)) => {
+ fmt::run(matches.is_present("check"), matches.is_present("verbose"));
+ },
+ ("update_lints", Some(matches)) => {
+ if matches.is_present("print-only") {
+ update_lints::print_lints();
+ } else if matches.is_present("check") {
+ update_lints::run(update_lints::UpdateMode::Check);
+ } else {
+ update_lints::run(update_lints::UpdateMode::Change);
+ }
+ },
+ ("new_lint", Some(matches)) => {
+ match new_lint::create(
+ matches.value_of("pass"),
+ matches.value_of("name"),
+ matches.value_of("category"),
+ matches.is_present("msrv"),
+ ) {
+ Ok(_) => update_lints::run(update_lints::UpdateMode::Change),
+ Err(e) => eprintln!("Unable to create lint: {}", e),
+ }
+ },
+ ("setup", Some(sub_command)) => match sub_command.subcommand() {
+ ("intellij", Some(matches)) => setup::intellij::setup_rustc_src(
+ matches
+ .value_of("rustc-repo-path")
+ .expect("this field is mandatory and therefore always valid"),
+ ),
+ ("git-hook", Some(matches)) => setup::git_hook::install_hook(matches.is_present("force-override")),
+ ("vscode-tasks", Some(matches)) => setup::vscode::install_tasks(matches.is_present("force-override")),
+ _ => {},
+ },
+ ("remove", Some(sub_command)) => match sub_command.subcommand() {
+ ("git-hook", Some(_)) => setup::git_hook::remove_hook(),
+ ("intellij", Some(_)) => setup::intellij::remove_rustc_src(),
+ ("vscode-tasks", Some(_)) => setup::vscode::remove_tasks(),
+ _ => {},
+ },
+ ("serve", Some(matches)) => {
+ let port = matches.value_of("port").unwrap().parse().unwrap();
+ let lint = matches.value_of("lint");
+ serve::run(port, lint);
+ },
++ ("lint", Some(matches)) => {
++ let filename = matches.value_of("filename").unwrap();
++ lint::run(filename);
++ },
+ _ => {},
+ }
+}
+
+fn get_clap_config<'a>() -> ArgMatches<'a> {
+ App::new("Clippy developer tooling")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .subcommand(
+ SubCommand::with_name("bless")
+ .about("bless the test output changes")
+ .arg(
+ Arg::with_name("ignore-timestamp")
+ .long("ignore-timestamp")
+ .help("Include files updated before clippy was built"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("fmt")
+ .about("Run rustfmt on all projects and tests")
+ .arg(
+ Arg::with_name("check")
+ .long("check")
+ .help("Use the rustfmt --check option"),
+ )
+ .arg(
+ Arg::with_name("verbose")
+ .short("v")
+ .long("verbose")
+ .help("Echo commands run"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("update_lints")
+ .about("Updates lint registration and information from the source code")
+ .long_about(
+ "Makes sure that:\n \
+ * the lint count in README.md is correct\n \
+ * the changelog contains markdown link references at the bottom\n \
+ * all lint groups include the correct lints\n \
+ * lint modules in `clippy_lints/*` are visible in `src/lifb.rs` via `pub mod`\n \
+ * all lints are registered in the lint store",
+ )
+ .arg(Arg::with_name("print-only").long("print-only").help(
+ "Print a table of lints to STDOUT. \
+ This does not include deprecated and internal lints. \
+ (Does not modify any files)",
+ ))
+ .arg(
+ Arg::with_name("check")
+ .long("check")
+ .help("Checks that `cargo dev update_lints` has been run. Used on CI."),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("new_lint")
+ .about("Create new lint and run `cargo dev update_lints`")
+ .arg(
+ Arg::with_name("pass")
+ .short("p")
+ .long("pass")
+ .help("Specify whether the lint runs during the early or late pass")
+ .takes_value(true)
+ .possible_values(&["early", "late"])
+ .required(true),
+ )
+ .arg(
+ Arg::with_name("name")
+ .short("n")
+ .long("name")
+ .help("Name of the new lint in snake case, ex: fn_too_long")
+ .takes_value(true)
+ .required(true),
+ )
+ .arg(
+ Arg::with_name("category")
+ .short("c")
+ .long("category")
+ .help("What category the lint belongs to")
+ .default_value("nursery")
+ .possible_values(&[
+ "style",
+ "correctness",
+ "suspicious",
+ "complexity",
+ "perf",
+ "pedantic",
+ "restriction",
+ "cargo",
+ "nursery",
+ "internal",
+ "internal_warn",
+ ])
+ .takes_value(true),
+ )
+ .arg(
+ Arg::with_name("msrv")
+ .long("msrv")
+ .help("Add MSRV config code to the lint"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("setup")
+ .about("Support for setting up your personal development environment")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .subcommand(
+ SubCommand::with_name("intellij")
+ .about("Alter dependencies so Intellij Rust can find rustc internals")
+ .arg(
+ Arg::with_name("rustc-repo-path")
+ .long("repo-path")
+ .short("r")
+ .help("The path to a rustc repo that will be used for setting the dependencies")
+ .takes_value(true)
+ .value_name("path")
+ .required(true),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("git-hook")
+ .about("Add a pre-commit git hook that formats your code to make it look pretty")
+ .arg(
+ Arg::with_name("force-override")
+ .long("force-override")
+ .short("f")
+ .help("Forces the override of an existing git pre-commit hook")
+ .required(false),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("vscode-tasks")
+ .about("Add several tasks to vscode for formatting, validation and testing")
+ .arg(
+ Arg::with_name("force-override")
+ .long("force-override")
+ .short("f")
+ .help("Forces the override of existing vscode tasks")
+ .required(false),
+ ),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("remove")
+ .about("Support for undoing changes done by the setup command")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .subcommand(SubCommand::with_name("git-hook").about("Remove any existing pre-commit git hook"))
+ .subcommand(SubCommand::with_name("vscode-tasks").about("Remove any existing vscode tasks"))
+ .subcommand(
+ SubCommand::with_name("intellij")
+ .about("Removes rustc source paths added via `cargo dev setup intellij`"),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("serve")
+ .about("Launch a local 'ALL the Clippy Lints' website in a browser")
+ .arg(
+ Arg::with_name("port")
+ .long("port")
+ .short("p")
+ .help("Local port for the http server")
+ .default_value("8000")
+ .validator_os(serve::validate_port),
+ )
+ .arg(Arg::with_name("lint").help("Which lint's page to load initially (optional)")),
+ )
++ .subcommand(
++ SubCommand::with_name("lint")
++ .about("Manually run clippy on a file")
++ .arg(
++ Arg::with_name("filename")
++ .required(true)
++ .help("The path to a file to lint"),
++ ),
++ )
+ .get_matches()
+}
--- /dev/null
- indoc! {"
+use crate::clippy_project_root;
+use indoc::indoc;
+use std::fs::{self, OpenOptions};
+use std::io::prelude::*;
+use std::io::{self, ErrorKind};
+use std::path::{Path, PathBuf};
+
+struct LintData<'a> {
+ pass: &'a str,
+ name: &'a str,
+ category: &'a str,
+ project_root: PathBuf,
+}
+
+trait Context {
+ fn context<C: AsRef<str>>(self, text: C) -> Self;
+}
+
+impl<T> Context for io::Result<T> {
+ fn context<C: AsRef<str>>(self, text: C) -> Self {
+ match self {
+ Ok(t) => Ok(t),
+ Err(e) => {
+ let message = format!("{}: {}", text.as_ref(), e);
+ Err(io::Error::new(ErrorKind::Other, message))
+ },
+ }
+ }
+}
+
+/// Creates the files required to implement and test a new lint and runs `update_lints`.
+///
+/// # Errors
+///
+/// This function errors out if the files couldn't be created or written to.
+pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> {
+ let lint = LintData {
+ pass: pass.expect("`pass` argument is validated by clap"),
+ name: lint_name.expect("`name` argument is validated by clap"),
+ category: category.expect("`category` argument is validated by clap"),
+ project_root: clippy_project_root(),
+ };
+
+ create_lint(&lint, msrv).context("Unable to create lint implementation")?;
+ create_test(&lint).context("Unable to create a test for the new lint")?;
+ add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")
+}
+
+fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
+ let lint_contents = get_lint_file_contents(lint, enable_msrv);
+
+ let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
+ write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
+}
+
+fn create_test(lint: &LintData<'_>) -> io::Result<()> {
+ fn create_project_layout<P: Into<PathBuf>>(lint_name: &str, location: P, case: &str, hint: &str) -> io::Result<()> {
+ let mut path = location.into().join(case);
+ fs::create_dir(&path)?;
+ write_file(path.join("Cargo.toml"), get_manifest_contents(lint_name, hint))?;
+
+ path.push("src");
+ fs::create_dir(&path)?;
+ let header = format!("// compile-flags: --crate-name={}", lint_name);
+ write_file(path.join("main.rs"), get_test_file_contents(lint_name, Some(&header)))?;
+
+ Ok(())
+ }
+
+ if lint.category == "cargo" {
+ let relative_test_dir = format!("tests/ui-cargo/{}", lint.name);
+ let test_dir = lint.project_root.join(relative_test_dir);
+ fs::create_dir(&test_dir)?;
+
+ create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?;
+ create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")
+ } else {
+ let test_path = format!("tests/ui/{}.rs", lint.name);
+ let test_contents = get_test_file_contents(lint.name, None);
+ write_file(lint.project_root.join(test_path), test_contents)
+ }
+}
+
+fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
+ let path = "clippy_lints/src/lib.rs";
+ let mut lib_rs = fs::read_to_string(path).context("reading")?;
+
+ let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment");
+
+ let new_lint = if enable_msrv {
+ format!(
+ "store.register_{lint_pass}_pass(move || Box::new({module_name}::{camel_name}::new(msrv)));\n ",
+ lint_pass = lint.pass,
+ module_name = lint.name,
+ camel_name = to_camel_case(lint.name),
+ )
+ } else {
+ format!(
+ "store.register_{lint_pass}_pass(|| Box::new({module_name}::{camel_name}));\n ",
+ lint_pass = lint.pass,
+ module_name = lint.name,
+ camel_name = to_camel_case(lint.name),
+ )
+ };
+
+ lib_rs.insert_str(comment_start, &new_lint);
+
+ fs::write(path, lib_rs).context("writing")
+}
+
+fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
+ fn inner(path: &Path, contents: &[u8]) -> io::Result<()> {
+ OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(path)?
+ .write_all(contents)
+ }
+
+ inner(path.as_ref(), contents.as_ref()).context(format!("writing to file: {}", path.as_ref().display()))
+}
+
+fn to_camel_case(name: &str) -> String {
+ name.split('_')
+ .map(|s| {
+ if s.is_empty() {
+ String::from("")
+ } else {
+ [&s[0..1].to_uppercase(), &s[1..]].concat()
+ }
+ })
+ .collect()
+}
+
++fn get_stabilisation_version() -> String {
++ let mut command = cargo_metadata::MetadataCommand::new();
++ command.no_deps();
++ if let Ok(metadata) = command.exec() {
++ if let Some(pkg) = metadata.packages.iter().find(|pkg| pkg.name == "clippy") {
++ return format!("{}.{}.0", pkg.version.minor, pkg.version.patch);
++ }
++ }
++
++ String::from("<TODO set version(see doc/adding_lints.md)>")
++}
++
+fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
+ let mut contents = format!(
+ indoc! {"
+ #![warn(clippy::{})]
+
+ fn main() {{
+ // test code goes here
+ }}
+ "},
+ lint_name
+ );
+
+ if let Some(header) = header_commands {
+ contents = format!("{}\n{}", header, contents);
+ }
+
+ contents
+}
+
+fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
+ format!(
+ indoc! {r#"
+ # {}
+
+ [package]
+ name = "{}"
+ version = "0.1.0"
+ publish = false
+
+ [workspace]
+ "#},
+ hint, lint_name
+ )
+}
+
+fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
+ let mut result = String::new();
+
+ let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
+ "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
+ "late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
+ _ => {
+ unreachable!("`pass_type` should only ever be `early` or `late`!");
+ },
+ };
+
++ let version = get_stabilisation_version();
+ let lint_name = lint.name;
+ let category = lint.category;
+ let name_camel = to_camel_case(lint.name);
+ let name_upper = lint_name.to_uppercase();
+
+ result.push_str(&if enable_msrv {
+ format!(
+ indoc! {"
+ use clippy_utils::msrvs;
+ {pass_import}
+ use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
+ use rustc_semver::RustcVersion;
+ use rustc_session::{{declare_tool_lint, impl_lint_pass}};
+
+ "},
+ pass_type = pass_type,
+ pass_import = pass_import,
+ context_import = context_import,
+ )
+ } else {
+ format!(
+ indoc! {"
+ {pass_import}
+ use rustc_lint::{{{context_import}, {pass_type}}};
+ use rustc_session::{{declare_lint_pass, declare_tool_lint}};
+
+ "},
+ pass_import = pass_import,
+ pass_type = pass_type,
+ context_import = context_import
+ )
+ });
+
+ result.push_str(&format!(
- \"default lint description\"
++ indoc! {r#"
+ declare_clippy_lint! {{
+ /// ### What it does
+ ///
+ /// ### Why is this bad?
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code which does not raise clippy warning
+ /// ```
++ #[clippy::version = "{version}"]
+ pub {name_upper},
+ {category},
- "},
++ "default lint description"
+ }}
++ "#},
++ version = version,
+ name_upper = name_upper,
+ category = category,
+ ));
+
+ result.push_str(&if enable_msrv {
+ format!(
+ indoc! {"
+ pub struct {name_camel} {{
+ msrv: Option<RustcVersion>,
+ }}
+
+ impl {name_camel} {{
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {{
+ Self {{ msrv }}
+ }}
+ }}
+
+ impl_lint_pass!({name_camel} => [{name_upper}]);
+
+ impl {pass_type}{pass_lifetimes} for {name_camel} {{
+ extract_msrv_attr!({context_import});
+ }}
+
+ // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
+ // TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
+ // TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
+ "},
+ pass_type = pass_type,
+ pass_lifetimes = pass_lifetimes,
+ name_upper = name_upper,
+ name_camel = name_camel,
+ context_import = context_import,
+ )
+ } else {
+ format!(
+ indoc! {"
+ declare_lint_pass!({name_camel} => [{name_upper}]);
+
+ impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
+ "},
+ pass_type = pass_type,
+ pass_lifetimes = pass_lifetimes,
+ name_upper = name_upper,
+ name_camel = name_camel,
+ )
+ });
+
+ result
+}
+
+#[test]
+fn test_camel_case() {
+ let s = "a_lint";
+ let s2 = to_camel_case(s);
+ assert_eq!(s2, "ALint");
+
+ let name = "a_really_long_new_lint";
+ let name2 = to_camel_case(name);
+ assert_eq!(name2, "AReallyLongNewLint");
+
+ let name3 = "lint__name";
+ let name4 = to_camel_case(name3);
+ assert_eq!(name4, "LintName");
+}
--- /dev/null
+use itertools::Itertools;
+use regex::Regex;
+use std::collections::HashMap;
+use std::ffi::OsStr;
+use std::fs;
+use std::lazy::SyncLazy;
+use std::path::Path;
+use walkdir::WalkDir;
+
+use crate::clippy_project_root;
+
+const GENERATED_FILE_COMMENT: &str = "// This file was generated by `cargo dev update_lints`.\n\
+ // Use that command to update this file and do not edit by hand.\n\
+ // Manual edits will be overwritten.\n\n";
+
+static DEC_CLIPPY_LINT_RE: SyncLazy<Regex> = SyncLazy::new(|| {
+ Regex::new(
+ r#"(?x)
+ declare_clippy_lint!\s*[\{(]
+ (?:\s+///.*)*
++ (?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])?
+ \s+pub\s+(?P<name>[A-Z_][A-Z_0-9]*)\s*,\s*
+ (?P<cat>[a-z_]+)\s*,\s*
+ "(?P<desc>(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})]
+"#,
+ )
+ .unwrap()
+});
+
+static DEC_DEPRECATED_LINT_RE: SyncLazy<Regex> = SyncLazy::new(|| {
+ Regex::new(
+ r#"(?x)
+ declare_deprecated_lint!\s*[{(]\s*
+ (?:\s+///.*)*
++ (?:\s*\#\[clippy::version\s*=\s*"[^"]*"\])?
+ \s+pub\s+(?P<name>[A-Z_][A-Z_0-9]*)\s*,\s*
+ "(?P<desc>(?:[^"\\]+|\\(?s).(?-s))*)"\s*[})]
+"#,
+ )
+ .unwrap()
+});
+static NL_ESCAPE_RE: SyncLazy<Regex> = SyncLazy::new(|| Regex::new(r#"\\\n\s*"#).unwrap());
+
+static DOCS_LINK: &str = "https://rust-lang.github.io/rust-clippy/master/index.html";
+
+#[derive(Clone, Copy, PartialEq)]
+pub enum UpdateMode {
+ Check,
+ Change,
+}
+
+/// Runs the `update_lints` command.
+///
+/// This updates various generated values from the lint source code.
+///
+/// `update_mode` indicates if the files should be updated or if updates should be checked for.
+///
+/// # Panics
+///
+/// Panics if a file path could not read from or then written to
+#[allow(clippy::too_many_lines)]
+pub fn run(update_mode: UpdateMode) {
+ let lint_list: Vec<Lint> = gather_all().collect();
+
+ let internal_lints = Lint::internal_lints(&lint_list);
+ let deprecated_lints = Lint::deprecated_lints(&lint_list);
+ let usable_lints = Lint::usable_lints(&lint_list);
+ let mut sorted_usable_lints = usable_lints.clone();
+ sorted_usable_lints.sort_by_key(|lint| lint.name.clone());
+
+ let usable_lint_count = round_to_fifty(usable_lints.len());
+
+ let mut file_change = false;
+
+ file_change |= replace_region_in_file(
+ Path::new("README.md"),
+ &format!(
+ r#"\[There are over \d+ lints included in this crate!\]\({}\)"#,
+ DOCS_LINK
+ ),
+ "",
+ true,
+ update_mode == UpdateMode::Change,
+ || {
+ vec![format!(
+ "[There are over {} lints included in this crate!]({})",
+ usable_lint_count, DOCS_LINK
+ )]
+ },
+ )
+ .changed;
+
+ file_change |= replace_region_in_file(
+ Path::new("CHANGELOG.md"),
+ "<!-- begin autogenerated links to lint list -->",
+ "<!-- end autogenerated links to lint list -->",
+ false,
+ update_mode == UpdateMode::Change,
+ || gen_changelog_lint_list(usable_lints.iter().chain(deprecated_lints.iter())),
+ )
+ .changed;
+
+ // This has to be in lib.rs, otherwise rustfmt doesn't work
+ file_change |= replace_region_in_file(
+ Path::new("clippy_lints/src/lib.rs"),
+ "begin lints modules",
+ "end lints modules",
+ false,
+ update_mode == UpdateMode::Change,
+ || gen_modules_list(usable_lints.iter()),
+ )
+ .changed;
+
+ if file_change && update_mode == UpdateMode::Check {
+ exit_with_failure();
+ }
+
+ process_file(
+ "clippy_lints/src/lib.register_lints.rs",
+ update_mode,
+ &gen_register_lint_list(internal_lints.iter(), usable_lints.iter()),
+ );
+ process_file(
+ "clippy_lints/src/lib.deprecated.rs",
+ update_mode,
+ &gen_deprecated(deprecated_lints.iter()),
+ );
+
+ let all_group_lints = usable_lints.iter().filter(|l| {
+ matches!(
+ &*l.group,
+ "correctness" | "suspicious" | "style" | "complexity" | "perf"
+ )
+ });
+ let content = gen_lint_group_list("all", all_group_lints);
+ process_file("clippy_lints/src/lib.register_all.rs", update_mode, &content);
+
+ for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) {
+ let content = gen_lint_group_list(&lint_group, lints.iter());
+ process_file(
+ &format!("clippy_lints/src/lib.register_{}.rs", lint_group),
+ update_mode,
+ &content,
+ );
+ }
+}
+
+pub fn print_lints() {
+ let lint_list: Vec<Lint> = gather_all().collect();
+ let usable_lints = Lint::usable_lints(&lint_list);
+ let usable_lint_count = usable_lints.len();
+ let grouped_by_lint_group = Lint::by_lint_group(usable_lints.into_iter());
+
+ for (lint_group, mut lints) in grouped_by_lint_group {
+ if lint_group == "Deprecated" {
+ continue;
+ }
+ println!("\n## {}", lint_group);
+
+ lints.sort_by_key(|l| l.name.clone());
+
+ for lint in lints {
+ println!("* [{}]({}#{}) ({})", lint.name, DOCS_LINK, lint.name, lint.desc);
+ }
+ }
+
+ println!("there are {} lints", usable_lint_count);
+}
+
+fn round_to_fifty(count: usize) -> usize {
+ count / 50 * 50
+}
+
+fn process_file(path: impl AsRef<Path>, update_mode: UpdateMode, content: &str) {
+ if update_mode == UpdateMode::Check {
+ let old_content =
+ fs::read_to_string(&path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.as_ref().display(), e));
+ if content != old_content {
+ exit_with_failure();
+ }
+ } else {
+ fs::write(&path, content.as_bytes())
+ .unwrap_or_else(|e| panic!("Cannot write to {}: {}", path.as_ref().display(), e));
+ }
+}
+
+fn exit_with_failure() {
+ println!(
+ "Not all lints defined properly. \
+ Please run `cargo dev update_lints` to make sure all lints are defined properly."
+ );
+ std::process::exit(1);
+}
+
+/// Lint data parsed from the Clippy source code.
+#[derive(Clone, PartialEq, Debug)]
+struct Lint {
+ name: String,
+ group: String,
+ desc: String,
+ deprecation: Option<String>,
+ module: String,
+}
+
+impl Lint {
+ #[must_use]
+ fn new(name: &str, group: &str, desc: &str, deprecation: Option<&str>, module: &str) -> Self {
+ Self {
+ name: name.to_lowercase(),
+ group: group.to_string(),
+ desc: NL_ESCAPE_RE.replace(&desc.replace("\\\"", "\""), "").to_string(),
+ deprecation: deprecation.map(ToString::to_string),
+ module: module.to_string(),
+ }
+ }
+
+ /// Returns all non-deprecated lints and non-internal lints
+ #[must_use]
+ fn usable_lints(lints: &[Self]) -> Vec<Self> {
+ lints
+ .iter()
+ .filter(|l| l.deprecation.is_none() && !l.group.starts_with("internal"))
+ .cloned()
+ .collect()
+ }
+
+ /// Returns all internal lints (not `internal_warn` lints)
+ #[must_use]
+ fn internal_lints(lints: &[Self]) -> Vec<Self> {
+ lints.iter().filter(|l| l.group == "internal").cloned().collect()
+ }
+
+ /// Returns all deprecated lints
+ #[must_use]
+ fn deprecated_lints(lints: &[Self]) -> Vec<Self> {
+ lints.iter().filter(|l| l.deprecation.is_some()).cloned().collect()
+ }
+
+ /// Returns the lints in a `HashMap`, grouped by the different lint groups
+ #[must_use]
+ fn by_lint_group(lints: impl Iterator<Item = Self>) -> HashMap<String, Vec<Self>> {
+ lints.map(|lint| (lint.group.to_string(), lint)).into_group_map()
+ }
+}
+
+/// Generates the code for registering a group
+fn gen_lint_group_list<'a>(group_name: &str, lints: impl Iterator<Item = &'a Lint>) -> String {
+ let mut details: Vec<_> = lints.map(|l| (&l.module, l.name.to_uppercase())).collect();
+ details.sort_unstable();
+
+ let mut output = GENERATED_FILE_COMMENT.to_string();
+
+ output.push_str(&format!(
+ "store.register_group(true, \"clippy::{0}\", Some(\"clippy_{0}\"), vec![\n",
+ group_name
+ ));
+ for (module, name) in details {
+ output.push_str(&format!(" LintId::of({}::{}),\n", module, name));
+ }
+ output.push_str("])\n");
+
+ output
+}
+
+/// Generates the module declarations for `lints`
+#[must_use]
+fn gen_modules_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
+ lints
+ .map(|l| &l.module)
+ .unique()
+ .map(|module| format!("mod {};", module))
+ .sorted()
+ .collect::<Vec<String>>()
+}
+
+/// Generates the list of lint links at the bottom of the CHANGELOG
+#[must_use]
+fn gen_changelog_lint_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
+ lints
+ .sorted_by_key(|l| &l.name)
+ .map(|l| format!("[`{}`]: {}#{}", l.name, DOCS_LINK, l.name))
+ .collect()
+}
+
+/// Generates the `register_removed` code
+#[must_use]
+fn gen_deprecated<'a>(lints: impl Iterator<Item = &'a Lint>) -> String {
+ let mut output = GENERATED_FILE_COMMENT.to_string();
+ output.push_str("{\n");
+ for Lint { name, deprecation, .. } in lints {
+ output.push_str(&format!(
+ concat!(
+ " store.register_removed(\n",
+ " \"clippy::{}\",\n",
+ " \"{}\",\n",
+ " );\n"
+ ),
+ name,
+ deprecation.as_ref().expect("`lints` are deprecated")
+ ));
+ }
+ output.push_str("}\n");
+
+ output
+}
+
+/// Generates the code for registering lints
+#[must_use]
+fn gen_register_lint_list<'a>(
+ internal_lints: impl Iterator<Item = &'a Lint>,
+ usable_lints: impl Iterator<Item = &'a Lint>,
+) -> String {
+ let mut details: Vec<_> = internal_lints
+ .map(|l| (false, &l.module, l.name.to_uppercase()))
+ .chain(usable_lints.map(|l| (true, &l.module, l.name.to_uppercase())))
+ .collect();
+ details.sort_unstable();
+
+ let mut output = GENERATED_FILE_COMMENT.to_string();
+ output.push_str("store.register_lints(&[\n");
+
+ for (is_public, module_name, lint_name) in details {
+ if !is_public {
+ output.push_str(" #[cfg(feature = \"internal-lints\")]\n");
+ }
+ output.push_str(&format!(" {}::{},\n", module_name, lint_name));
+ }
+ output.push_str("])\n");
+
+ output
+}
+
+/// Gathers all files in `src/clippy_lints` and gathers all lints inside
+fn gather_all() -> impl Iterator<Item = Lint> {
+ lint_files().flat_map(|f| gather_from_file(&f))
+}
+
+fn gather_from_file(dir_entry: &walkdir::DirEntry) -> impl Iterator<Item = Lint> {
+ let content = fs::read_to_string(dir_entry.path()).unwrap();
+ let path = dir_entry.path();
+ let filename = path.file_stem().unwrap();
+ let path_buf = path.with_file_name(filename);
+ let mut rel_path = path_buf
+ .strip_prefix(clippy_project_root().join("clippy_lints/src"))
+ .expect("only files in `clippy_lints/src` should be looked at");
+ // If the lints are stored in mod.rs, we get the module name from
+ // the containing directory:
+ if filename == "mod" {
+ rel_path = rel_path.parent().unwrap();
+ }
+
+ let module = rel_path
+ .components()
+ .map(|c| c.as_os_str().to_str().unwrap())
+ .collect::<Vec<_>>()
+ .join("::");
+
+ parse_contents(&content, &module)
+}
+
+fn parse_contents(content: &str, module: &str) -> impl Iterator<Item = Lint> {
+ let lints = DEC_CLIPPY_LINT_RE
+ .captures_iter(content)
+ .map(|m| Lint::new(&m["name"], &m["cat"], &m["desc"], None, module));
+ let deprecated = DEC_DEPRECATED_LINT_RE
+ .captures_iter(content)
+ .map(|m| Lint::new(&m["name"], "Deprecated", &m["desc"], Some(&m["desc"]), module));
+ // Removing the `.collect::<Vec<Lint>>().into_iter()` causes some lifetime issues due to the map
+ lints.chain(deprecated).collect::<Vec<Lint>>().into_iter()
+}
+
+/// Collects all .rs files in the `clippy_lints/src` directory
+fn lint_files() -> impl Iterator<Item = walkdir::DirEntry> {
+ // We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories.
+ // Otherwise we would not collect all the lints, for example in `clippy_lints/src/methods/`.
+ let path = clippy_project_root().join("clippy_lints/src");
+ WalkDir::new(path)
+ .into_iter()
+ .filter_map(Result::ok)
+ .filter(|f| f.path().extension() == Some(OsStr::new("rs")))
+}
+
+/// Whether a file has had its text changed or not
+#[derive(PartialEq, Debug)]
+struct FileChange {
+ changed: bool,
+ new_lines: String,
+}
+
+/// Replaces a region in a file delimited by two lines matching regexes.
+///
+/// `path` is the relative path to the file on which you want to perform the replacement.
+///
+/// See `replace_region_in_text` for documentation of the other options.
+///
+/// # Panics
+///
+/// Panics if the path could not read or then written
+fn replace_region_in_file<F>(
+ path: &Path,
+ start: &str,
+ end: &str,
+ replace_start: bool,
+ write_back: bool,
+ replacements: F,
+) -> FileChange
+where
+ F: FnOnce() -> Vec<String>,
+{
+ let contents = fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read from {}: {}", path.display(), e));
+ let file_change = replace_region_in_text(&contents, start, end, replace_start, replacements);
+
+ if write_back {
+ if let Err(e) = fs::write(path, file_change.new_lines.as_bytes()) {
+ panic!("Cannot write to {}: {}", path.display(), e);
+ }
+ }
+ file_change
+}
+
+/// Replaces a region in a text delimited by two lines matching regexes.
+///
+/// * `text` is the input text on which you want to perform the replacement
+/// * `start` is a `&str` that describes the delimiter line before the region you want to replace.
+/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too.
+/// * `end` is a `&str` that describes the delimiter line until where the replacement should happen.
+/// As the `&str` will be converted to a `Regex`, this can contain regex syntax, too.
+/// * If `replace_start` is true, the `start` delimiter line is replaced as well. The `end`
+/// delimiter line is never replaced.
+/// * `replacements` is a closure that has to return a `Vec<String>` which contains the new text.
+///
+/// If you want to perform the replacement on files instead of already parsed text,
+/// use `replace_region_in_file`.
+///
+/// # Example
+///
+/// ```ignore
+/// let the_text = "replace_start\nsome text\nthat will be replaced\nreplace_end";
+/// let result =
+/// replace_region_in_text(the_text, "replace_start", "replace_end", false, || {
+/// vec!["a different".to_string(), "text".to_string()]
+/// })
+/// .new_lines;
+/// assert_eq!("replace_start\na different\ntext\nreplace_end", result);
+/// ```
+///
+/// # Panics
+///
+/// Panics if start or end is not valid regex
+fn replace_region_in_text<F>(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> FileChange
+where
+ F: FnOnce() -> Vec<String>,
+{
+ let replace_it = replacements();
+ let mut in_old_region = false;
+ let mut found = false;
+ let mut new_lines = vec![];
+ let start = Regex::new(start).unwrap();
+ let end = Regex::new(end).unwrap();
+
+ for line in text.lines() {
+ if in_old_region {
+ if end.is_match(line) {
+ in_old_region = false;
+ new_lines.extend(replace_it.clone());
+ new_lines.push(line.to_string());
+ }
+ } else if start.is_match(line) {
+ if !replace_start {
+ new_lines.push(line.to_string());
+ }
+ in_old_region = true;
+ found = true;
+ } else {
+ new_lines.push(line.to_string());
+ }
+ }
+
+ if !found {
+ // This happens if the provided regex in `clippy_dev/src/main.rs` does not match in the
+ // given text or file. Most likely this is an error on the programmer's side and the Regex
+ // is incorrect.
+ eprintln!("error: regex \n{:?}\ndoesn't match. You may have to update it.", start);
+ std::process::exit(1);
+ }
+
+ let mut new_lines = new_lines.join("\n");
+ if text.ends_with('\n') {
+ new_lines.push('\n');
+ }
+ let changed = new_lines != text;
+ FileChange { changed, new_lines }
+}
+
+#[test]
+fn test_parse_contents() {
+ let result: Vec<Lint> = parse_contents(
+ r#"
+declare_clippy_lint! {
++ #[clippy::version = "Hello Clippy!"]
+ pub PTR_ARG,
+ style,
+ "really long \
+ text"
+}
+
+declare_clippy_lint!{
++ #[clippy::version = "Test version"]
+ pub DOC_MARKDOWN,
+ pedantic,
+ "single line"
+}
+
+/// some doc comment
+declare_deprecated_lint! {
++ #[clippy::version = "I'm a version"]
+ pub SHOULD_ASSERT_EQ,
+ "`assert!()` will be more flexible with RFC 2011"
+}
+ "#,
+ "module_name",
+ )
+ .collect();
+
+ let expected = vec![
+ Lint::new("ptr_arg", "style", "really long text", None, "module_name"),
+ Lint::new("doc_markdown", "pedantic", "single line", None, "module_name"),
+ Lint::new(
+ "should_assert_eq",
+ "Deprecated",
+ "`assert!()` will be more flexible with RFC 2011",
+ Some("`assert!()` will be more flexible with RFC 2011"),
+ "module_name",
+ ),
+ ];
+ assert_eq!(expected, result);
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_replace_region() {
+ let text = "\nabc\n123\n789\ndef\nghi";
+ let expected = FileChange {
+ changed: true,
+ new_lines: "\nabc\nhello world\ndef\nghi".to_string(),
+ };
+ let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, false, || {
+ vec!["hello world".to_string()]
+ });
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_replace_region_with_start() {
+ let text = "\nabc\n123\n789\ndef\nghi";
+ let expected = FileChange {
+ changed: true,
+ new_lines: "\nhello world\ndef\nghi".to_string(),
+ };
+ let result = replace_region_in_text(text, r#"^\s*abc$"#, r#"^\s*def"#, true, || {
+ vec!["hello world".to_string()]
+ });
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_replace_region_no_changes() {
+ let text = "123\n456\n789";
+ let expected = FileChange {
+ changed: false,
+ new_lines: "123\n456\n789".to_string(),
+ };
+ let result = replace_region_in_text(text, r#"^\s*123$"#, r#"^\s*456"#, false, Vec::new);
+ assert_eq!(expected, result);
+ }
+
+ #[test]
+ fn test_usable_lints() {
+ let lints = vec![
+ Lint::new("should_assert_eq", "Deprecated", "abc", Some("Reason"), "module_name"),
+ Lint::new("should_assert_eq2", "Not Deprecated", "abc", None, "module_name"),
+ Lint::new("should_assert_eq2", "internal", "abc", None, "module_name"),
+ Lint::new("should_assert_eq2", "internal_style", "abc", None, "module_name"),
+ ];
+ let expected = vec![Lint::new(
+ "should_assert_eq2",
+ "Not Deprecated",
+ "abc",
+ None,
+ "module_name",
+ )];
+ assert_eq!(expected, Lint::usable_lints(&lints));
+ }
+
+ #[test]
+ fn test_by_lint_group() {
+ let lints = vec![
+ Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
+ Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"),
+ Lint::new("incorrect_match", "group1", "abc", None, "module_name"),
+ ];
+ let mut expected: HashMap<String, Vec<Lint>> = HashMap::new();
+ expected.insert(
+ "group1".to_string(),
+ vec![
+ Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
+ Lint::new("incorrect_match", "group1", "abc", None, "module_name"),
+ ],
+ );
+ expected.insert(
+ "group2".to_string(),
+ vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")],
+ );
+ assert_eq!(expected, Lint::by_lint_group(lints.into_iter()));
+ }
+
+ #[test]
+ fn test_gen_changelog_lint_list() {
+ let lints = vec![
+ Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
+ Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"),
+ ];
+ let expected = vec![
+ format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK),
+ format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK),
+ ];
+ assert_eq!(expected, gen_changelog_lint_list(lints.iter()));
+ }
+
+ #[test]
+ fn test_gen_deprecated() {
+ let lints = vec![
+ Lint::new(
+ "should_assert_eq",
+ "group1",
+ "abc",
+ Some("has been superseded by should_assert_eq2"),
+ "module_name",
+ ),
+ Lint::new(
+ "another_deprecated",
+ "group2",
+ "abc",
+ Some("will be removed"),
+ "module_name",
+ ),
+ ];
+
+ let expected = GENERATED_FILE_COMMENT.to_string()
+ + &[
+ "{",
+ " store.register_removed(",
+ " \"clippy::should_assert_eq\",",
+ " \"has been superseded by should_assert_eq2\",",
+ " );",
+ " store.register_removed(",
+ " \"clippy::another_deprecated\",",
+ " \"will be removed\",",
+ " );",
+ "}",
+ ]
+ .join("\n")
+ + "\n";
+
+ assert_eq!(expected, gen_deprecated(lints.iter()));
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_gen_deprecated_fail() {
+ let lints = vec![Lint::new("should_assert_eq2", "group2", "abc", None, "module_name")];
+ let _deprecated_lints = gen_deprecated(lints.iter());
+ }
+
+ #[test]
+ fn test_gen_modules_list() {
+ let lints = vec![
+ Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
+ Lint::new("incorrect_stuff", "group3", "abc", None, "another_module"),
+ ];
+ let expected = vec!["mod another_module;".to_string(), "mod module_name;".to_string()];
+ assert_eq!(expected, gen_modules_list(lints.iter()));
+ }
+
+ #[test]
+ fn test_gen_lint_group_list() {
+ let lints = vec![
+ Lint::new("abc", "group1", "abc", None, "module_name"),
+ Lint::new("should_assert_eq", "group1", "abc", None, "module_name"),
+ Lint::new("internal", "internal_style", "abc", None, "module_name"),
+ ];
+ let expected = GENERATED_FILE_COMMENT.to_string()
+ + &[
+ "store.register_group(true, \"clippy::group1\", Some(\"clippy_group1\"), vec![",
+ " LintId::of(module_name::ABC),",
+ " LintId::of(module_name::INTERNAL),",
+ " LintId::of(module_name::SHOULD_ASSERT_EQ),",
+ "])",
+ ]
+ .join("\n")
+ + "\n";
+
+ let result = gen_lint_group_list("group1", lints.iter());
+
+ assert_eq!(expected, result);
+ }
+}
--- /dev/null
- version = "0.1.58"
+[package]
+name = "clippy_lints"
++version = "0.1.59"
+description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+repository = "https://github.com/rust-lang/rust-clippy"
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+keywords = ["clippy", "lint", "plugin"]
+edition = "2021"
+
+[dependencies]
+cargo_metadata = "0.14"
+clippy_utils = { path = "../clippy_utils" }
+if_chain = "1.0"
+itertools = "0.10"
+pulldown-cmark = { version = "0.8", default-features = false }
+quine-mc_cluskey = "0.2"
+regex-syntax = "0.6"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = { version = "1.0", optional = true }
+toml = "0.5"
+unicode-normalization = "0.1"
+unicode-script = { version = "0.5", default-features = false }
+semver = "1.0"
+rustc-semver = "1.1"
+# NOTE: cargo requires serde feat in its url dep
+# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
+url = { version = "2.2", features = ["serde"] }
+
+[features]
+deny-warnings = ["clippy_utils/deny-warnings"]
+# build clippy with internal lints enabled, off by default
+internal-lints = ["clippy_utils/internal-lints"]
+metadata-collector-lint = ["serde_json", "clippy_utils/metadata-collector-lint"]
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+use clippy_utils::comparisons::{normalize_comparison, Rel};
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_isize_or_usize;
+use clippy_utils::{clip, int_bits, unsext};
+
+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 {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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]);
+
+impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref cmp, lhs, 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 {
+ AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
+ AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
+ AbsurdComparisonResult::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 {
+ ExtremeType::Minimum => "minimum",
+ ExtremeType::Maximum => "maximum",
+ },
+ conclusion
+ );
+
+ span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
+ }
+ }
+ }
+ }
+}
+
+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(cast_exp, _) = expr.kind {
+ let precast_ty = cx.typeck_results().expr_ty(cast_exp);
+ let cast_ty = cx.typeck_results().expr_ty(expr);
+
+ return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty);
+ }
+
+ false
+}
+
+fn detect_absurd_comparison<'tcx>(
+ cx: &LateContext<'tcx>,
+ op: BinOpKind,
+ lhs: &'tcx Expr<'_>,
+ rhs: &'tcx Expr<'_>,
+) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> {
+ use AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible};
+ use ExtremeType::{Maximum, Minimum};
+ // absurd comparison only makes sense on primitive types
+ // primitive types don't implement comparison operators with each other
+ if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) {
+ return None;
+ }
+
+ // comparisons between fix sized types and target sized types are considered unanalyzable
+ if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) {
+ return None;
+ }
+
+ let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?;
+
+ let lx = detect_extreme_expr(cx, normalized_lhs);
+ let rx = detect_extreme_expr(cx, normalized_rhs);
+
+ Some(match rel {
+ Rel::Lt => {
+ match (lx, rx) {
+ (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x
+ (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min
+ _ => return None,
+ }
+ },
+ Rel::Le => {
+ match (lx, rx) {
+ (Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x
+ (Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x
+ (_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min
+ (_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max
+ _ => return None,
+ }
+ },
+ Rel::Ne | Rel::Eq => return None,
+ })
+}
+
+fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
+ let ty = cx.typeck_results().expr_ty(expr);
+
+ let cv = constant(cx, cx.typeck_results(), expr)?.0;
+
+ let which = match (ty.kind(), cv) {
+ (&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum,
+ (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => {
+ ExtremeType::Minimum
+ },
+
+ (&ty::Bool, Constant::Bool(true)) => ExtremeType::Maximum,
+ (&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => {
+ ExtremeType::Maximum
+ },
+ (&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => ExtremeType::Maximum,
+
+ _ => return None,
+ };
+ Some(ExtremeExpr { which, expr })
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol;
+use std::f64::consts as f64;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for floating point literals that approximate
+ /// constants which are defined in
+ /// [`std::f32::consts`](https://doc.rust-lang.org/stable/std/f32/consts/#constants)
+ /// or
+ /// [`std::f64::consts`](https://doc.rust-lang.org/stable/std/f64/consts/#constants),
+ /// respectively, suggesting to use the predefined constant.
+ ///
+ /// ### Why is this bad?
+ /// Usually, the definition in the standard library is more
+ /// precise than what people come up with. If you find that your definition is
+ /// actually more precise, please [file a Rust
+ /// issue](https://github.com/rust-lang/rust/issues).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 3.14;
+ /// let y = 1_f64 / x;
+ /// ```
+ /// Use predefined constants instead:
+ /// ```rust
+ /// let x = std::f32::consts::PI;
+ /// let y = std::f64::consts::FRAC_1_PI;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub APPROX_CONSTANT,
+ correctness,
+ "the approximate of a known float constant (in `std::fXX::consts`)"
+}
+
+// Tuples are of the form (constant, name, min_digits, msrv)
+const KNOWN_CONSTS: [(f64, &str, usize, Option<RustcVersion>); 19] = [
+ (f64::E, "E", 4, None),
+ (f64::FRAC_1_PI, "FRAC_1_PI", 4, None),
+ (f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5, None),
+ (f64::FRAC_2_PI, "FRAC_2_PI", 5, None),
+ (f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5, None),
+ (f64::FRAC_PI_2, "FRAC_PI_2", 5, None),
+ (f64::FRAC_PI_3, "FRAC_PI_3", 5, None),
+ (f64::FRAC_PI_4, "FRAC_PI_4", 5, None),
+ (f64::FRAC_PI_6, "FRAC_PI_6", 5, None),
+ (f64::FRAC_PI_8, "FRAC_PI_8", 5, None),
+ (f64::LN_2, "LN_2", 5, None),
+ (f64::LN_10, "LN_10", 5, None),
+ (f64::LOG2_10, "LOG2_10", 5, Some(msrvs::LOG2_10)),
+ (f64::LOG2_E, "LOG2_E", 5, None),
+ (f64::LOG10_2, "LOG10_2", 5, Some(msrvs::LOG10_2)),
+ (f64::LOG10_E, "LOG10_E", 5, None),
+ (f64::PI, "PI", 3, None),
+ (f64::SQRT_2, "SQRT_2", 5, None),
+ (f64::TAU, "TAU", 3, Some(msrvs::TAU)),
+];
+
+pub struct ApproxConstant {
+ msrv: Option<RustcVersion>,
+}
+
+impl ApproxConstant {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+
+ fn check_lit(&self, cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) {
+ match *lit {
+ LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
+ FloatTy::F32 => self.check_known_consts(cx, e, s, "f32"),
+ FloatTy::F64 => self.check_known_consts(cx, e, s, "f64"),
+ },
+ LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, e, s, "f{32, 64}"),
+ _ => (),
+ }
+ }
+
+ fn check_known_consts(&self, cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) {
+ let s = s.as_str();
+ if s.parse::<f64>().is_ok() {
+ for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
+ if is_approx_const(constant, &s, min_digits)
+ && msrv.as_ref().map_or(true, |msrv| meets_msrv(self.msrv.as_ref(), msrv))
+ {
+ span_lint_and_help(
+ cx,
+ APPROX_CONSTANT,
+ e.span,
+ &format!("approximate value of `{}::consts::{}` found", module, &name),
+ None,
+ "consider using the constant directly",
+ );
+ return;
+ }
+ }
+ }
+ }
+}
+
+impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
+
+impl<'tcx> LateLintPass<'tcx> for ApproxConstant {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Lit(lit) = &e.kind {
+ self.check_lit(cx, &lit.node, e);
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+/// Returns `false` if the number of significant figures in `value` are
+/// less than `min_digits`; otherwise, returns true if `value` is equal
+/// to `constant`, rounded to the number of digits present in `value`.
+#[must_use]
+fn is_approx_const(constant: f64, value: &str, min_digits: usize) -> bool {
+ if value.len() <= min_digits {
+ false
+ } else if constant.to_string().starts_with(value) {
+ // The value is a truncated constant
+ true
+ } else {
+ let round_const = format!("{:.*}", value.len() - 2, constant);
+ value == round_const
+ }
+}
--- /dev/null
+use clippy_utils::consts::constant_simple;
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for integer arithmetic operations which could overflow or panic.
+ ///
+ /// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable
+ /// of overflowing according to the [Rust
+ /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
+ /// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is
+ /// attempted.
+ ///
+ /// ### Why is this bad?
+ /// Integer overflow will trigger a panic in debug builds or will wrap in
+ /// release mode. Division by zero will cause a panic in either mode. In some applications one
+ /// wants explicitly checked, wrapping or saturating arithmetic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = 0;
+ /// a + 1;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INTEGER_ARITHMETIC,
+ restriction,
+ "any integer arithmetic expression which could overflow or panic"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for float arithmetic.
+ ///
+ /// ### Why is this bad?
+ /// For some embedded systems or kernel development, it
+ /// can be useful to rule out floating-point numbers.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = 0.0;
+ /// a + 1.0;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_ARITHMETIC,
+ restriction,
+ "any floating-point arithmetic statement"
+}
+
+#[derive(Copy, Clone, Default)]
+pub struct Arithmetic {
+ expr_span: Option<Span>,
+ /// This field is used to check whether expressions are constants, such as in enum discriminants
+ /// and consts
+ const_span: Option<Span>,
+}
+
+impl_lint_pass!(Arithmetic => [INTEGER_ARITHMETIC, FLOAT_ARITHMETIC]);
+
+impl<'tcx> LateLintPass<'tcx> for Arithmetic {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if self.expr_span.is_some() {
+ return;
+ }
+
+ if let Some(span) = self.const_span {
+ if span.contains(expr.span) {
+ return;
+ }
+ }
+ match &expr.kind {
+ hir::ExprKind::Binary(op, l, r) | hir::ExprKind::AssignOp(op, l, r) => {
+ match op.node {
+ hir::BinOpKind::And
+ | hir::BinOpKind::Or
+ | hir::BinOpKind::BitAnd
+ | hir::BinOpKind::BitOr
+ | hir::BinOpKind::BitXor
+ | hir::BinOpKind::Eq
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt => return,
+ _ => (),
+ }
+
+ let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
+ if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() {
+ match op.node {
+ hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind {
+ hir::ExprKind::Lit(_lit) => (),
+ hir::ExprKind::Unary(hir::UnOp::Neg, expr) => {
+ if let hir::ExprKind::Lit(lit) = &expr.kind {
+ if let rustc_ast::ast::LitKind::Int(1, _) = lit.node {
+ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
+ self.expr_span = Some(expr.span);
+ }
+ }
+ },
+ _ => {
+ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
+ self.expr_span = Some(expr.span);
+ },
+ },
+ _ => {
+ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
+ self.expr_span = Some(expr.span);
+ },
+ }
+ } else if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
+ span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
+ self.expr_span = Some(expr.span);
+ }
+ },
+ hir::ExprKind::Unary(hir::UnOp::Neg, arg) => {
+ let ty = cx.typeck_results().expr_ty(arg);
+ if constant_simple(cx, cx.typeck_results(), expr).is_none() {
+ if ty.is_integral() {
+ span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
+ self.expr_span = Some(expr.span);
+ } else if ty.is_floating_point() {
+ span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
+ self.expr_span = Some(expr.span);
+ }
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if Some(expr.span) == self.expr_span {
+ self.expr_span = None;
+ }
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
+ let body_owner = cx.tcx.hir().body_owner(body.id());
+
+ match cx.tcx.hir().body_owner_kind(body_owner) {
+ hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => {
+ let body_span = cx.tcx.hir().span(body_owner);
+
+ if let Some(span) = self.const_span {
+ if span.contains(body_span) {
+ return;
+ }
+ }
+ self.const_span = Some(body_span);
+ },
+ hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => (),
+ }
+ }
+
+ fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) {
+ let body_owner = cx.tcx.hir().body_owner(body.id());
+ let body_span = cx.tcx.hir().span(body_owner);
+
+ if let Some(span) = self.const_span {
+ if span.contains(body_span) {
+ return;
+ }
+ }
+ self.const_span = None;
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_ast::ast::{Expr, ExprKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `as` conversions.
+ ///
+ /// Note that this lint is specialized in linting *every single* use of `as`
+ /// regardless of whether good alternatives exist or not.
+ /// If you want more precise lints for `as`, please consider using these separate lints:
+ /// `unnecessary_cast`, `cast_lossless/possible_truncation/possible_wrap/precision_loss/sign_loss`,
+ /// `fn_to_numeric_cast(_with_truncation)`, `char_lit_as_u8`, `ref_to_mut` and `ptr_as_ptr`.
+ /// There is a good explanation the reason why this lint should work in this way and how it is useful
+ /// [in this issue](https://github.com/rust-lang/rust-clippy/issues/5122).
+ ///
+ /// ### Why is this bad?
+ /// `as` conversions will perform many kinds of
+ /// conversions, including silently lossy conversions and dangerous coercions.
+ /// There are cases when it makes sense to use `as`, so the lint is
+ /// Allow by default.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let a: u32;
+ /// ...
+ /// f(a as u16);
+ /// ```
+ ///
+ /// Usually better represents the semantics you expect:
+ /// ```rust,ignore
+ /// f(a.try_into()?);
+ /// ```
+ /// or
+ /// ```rust,ignore
+ /// f(a.try_into().expect("Unexpected u16 overflow in f"));
+ /// ```
+ ///
++ #[clippy::version = "1.41.0"]
+ pub AS_CONVERSIONS,
+ restriction,
+ "using a potentially dangerous silent `as` conversion"
+}
+
+declare_lint_pass!(AsConversions => [AS_CONVERSIONS]);
+
+impl EarlyLintPass for AsConversions {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess, expr.span) {
+ return;
+ }
+
+ if let ExprKind::Cast(_, _) = expr.kind {
+ span_lint_and_help(
+ cx,
+ AS_CONVERSIONS,
+ expr.span,
+ "using a potentially dangerous silent `as` conversion",
+ None,
+ "consider using a safe wrapper for this conversion",
+ );
+ }
+ }
+}
--- /dev/null
+use std::fmt;
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_ast::ast::{Expr, ExprKind, InlineAsmOptions};
+use rustc_lint::{EarlyContext, EarlyLintPass, Lint};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum AsmStyle {
+ Intel,
+ Att,
+}
+
+impl fmt::Display for AsmStyle {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ AsmStyle::Intel => f.write_str("Intel"),
+ AsmStyle::Att => f.write_str("AT&T"),
+ }
+ }
+}
+
+impl std::ops::Not for AsmStyle {
+ type Output = AsmStyle;
+
+ fn not(self) -> AsmStyle {
+ match self {
+ AsmStyle::Intel => AsmStyle::Att,
+ AsmStyle::Att => AsmStyle::Intel,
+ }
+ }
+}
+
+fn check_expr_asm_syntax(lint: &'static Lint, cx: &EarlyContext<'_>, expr: &Expr, check_for: AsmStyle) {
+ if let ExprKind::InlineAsm(ref inline_asm) = expr.kind {
+ let style = if inline_asm.options.contains(InlineAsmOptions::ATT_SYNTAX) {
+ AsmStyle::Att
+ } else {
+ AsmStyle::Intel
+ };
+
+ if style == check_for {
+ span_lint_and_help(
+ cx,
+ lint,
+ expr.span,
+ &format!("{} x86 assembly syntax used", style),
+ None,
+ &format!("use {} x86 assembly syntax", !style),
+ );
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of Intel x86 assembly syntax.
+ ///
+ /// ### Why is this bad?
+ /// The lint has been enabled to indicate a preference
+ /// for AT&T x86 assembly syntax.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run
+ /// # #![feature(asm)]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr);
+ /// # }
+ /// ```
+ /// Use instead:
+ /// ```rust,no_run
+ /// # #![feature(asm)]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax));
+ /// # }
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub INLINE_ASM_X86_INTEL_SYNTAX,
+ restriction,
+ "prefer AT&T x86 assembly syntax"
+}
+
+declare_lint_pass!(InlineAsmX86IntelSyntax => [INLINE_ASM_X86_INTEL_SYNTAX]);
+
+impl EarlyLintPass for InlineAsmX86IntelSyntax {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Intel);
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of AT&T x86 assembly syntax.
+ ///
+ /// ### Why is this bad?
+ /// The lint has been enabled to indicate a preference
+ /// for Intel x86 assembly syntax.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run
+ /// # #![feature(asm)]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax));
+ /// # }
+ /// ```
+ /// Use instead:
+ /// ```rust,no_run
+ /// # #![feature(asm)]
+ /// # unsafe { let ptr = "".as_ptr();
+ /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr);
+ /// # }
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub INLINE_ASM_X86_ATT_SYNTAX,
+ restriction,
+ "prefer Intel x86 assembly syntax"
+}
+
+declare_lint_pass!(InlineAsmX86AttSyntax => [INLINE_ASM_X86_ATT_SYNTAX]);
+
+impl EarlyLintPass for InlineAsmX86AttSyntax {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ check_expr_asm_syntax(Self::get_lints()[0], cx, expr, AsmStyle::Att);
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::higher;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::{is_direct_expn_of, is_expn_of, match_panic_call};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `assert!(true)` and `assert!(false)` calls.
+ ///
+ /// ### Why is this bad?
+ /// Will be optimized out by the compiler or should probably be replaced by a
+ /// `panic!()` or `unreachable!()`
+ ///
+ /// ### Known problems
+ /// None
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// assert!(false)
+ /// assert!(true)
+ /// const B: bool = false;
+ /// assert!(B)
+ /// ```
++ #[clippy::version = "1.34.0"]
+ pub ASSERTIONS_ON_CONSTANTS,
+ style,
+ "`assert!(true)` / `assert!(false)` will be optimized out by the compiler, and should probably be replaced by a `panic!()` or `unreachable!()`"
+}
+
+declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]);
+
+impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ let lint_true = |is_debug: bool| {
+ span_lint_and_help(
+ cx,
+ ASSERTIONS_ON_CONSTANTS,
+ e.span,
+ if is_debug {
+ "`debug_assert!(true)` will be optimized out by the compiler"
+ } else {
+ "`assert!(true)` will be optimized out by the compiler"
+ },
+ None,
+ "remove it",
+ );
+ };
+ let lint_false_without_message = || {
+ span_lint_and_help(
+ cx,
+ ASSERTIONS_ON_CONSTANTS,
+ e.span,
+ "`assert!(false)` should probably be replaced",
+ None,
+ "use `panic!()` or `unreachable!()`",
+ );
+ };
+ let lint_false_with_message = |panic_message: String| {
+ span_lint_and_help(
+ cx,
+ ASSERTIONS_ON_CONSTANTS,
+ e.span,
+ &format!("`assert!(false, {})` should probably be replaced", panic_message),
+ None,
+ &format!("use `panic!({})` or `unreachable!({})`", panic_message, panic_message),
+ );
+ };
+
+ if let Some(debug_assert_span) = is_expn_of(e.span, "debug_assert") {
+ if debug_assert_span.from_expansion() {
+ return;
+ }
+ if_chain! {
+ if let ExprKind::Unary(_, lit) = e.kind;
+ if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit);
+ if is_true;
+ then {
+ lint_true(true);
+ }
+ };
+ } else if let Some(assert_span) = is_direct_expn_of(e.span, "assert") {
+ if assert_span.from_expansion() {
+ return;
+ }
+ if let Some(assert_match) = match_assert_with_message(cx, e) {
+ match assert_match {
+ // matched assert but not message
+ AssertKind::WithoutMessage(false) => lint_false_without_message(),
+ AssertKind::WithoutMessage(true) | AssertKind::WithMessage(_, true) => lint_true(false),
+ AssertKind::WithMessage(panic_message, false) => lint_false_with_message(panic_message),
+ };
+ }
+ }
+ }
+}
+
+/// Result of calling `match_assert_with_message`.
+enum AssertKind {
+ WithMessage(String, bool),
+ WithoutMessage(bool),
+}
+
+/// Check if the expression matches
+///
+/// ```rust,ignore
+/// if !c {
+/// {
+/// ::std::rt::begin_panic(message, _)
+/// }
+/// }
+/// ```
+///
+/// where `message` is any expression and `c` is a constant bool.
+fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
+ if_chain! {
+ if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
+ if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
+ // bind the first argument of the `assert!` macro
+ if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
+ // block
+ if let ExprKind::Block(block, _) = then.kind;
+ if block.stmts.is_empty();
+ if let Some(block_expr) = &block.expr;
+ // inner block is optional. unwrap it if it exists, or use the expression as is otherwise.
+ if let Some(begin_panic_call) = match block_expr.kind {
+ ExprKind::Block(inner_block, _) => &inner_block.expr,
+ _ => &block.expr,
+ };
+ // function call
+ if let Some(arg) = match_panic_call(cx, begin_panic_call);
+ // bind the second argument of the `assert!` macro if it exists
+ if let panic_message = snippet_opt(cx, arg.span);
+ // second argument of begin_panic is irrelevant
+ // as is the second match arm
+ then {
+ // an empty message occurs when it was generated by the macro
+ // (and not passed by the user)
+ return panic_message
+ .filter(|msg| !msg.is_empty())
+ .map(|msg| AssertKind::WithMessage(msg, is_true))
+ .or(Some(AssertKind::WithoutMessage(is_true)));
+ }
+ }
+ None
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{binop_traits, sugg};
+use clippy_utils::{eq_expr_value, trait_ref_of_method};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `a = a op b` or `a = b commutative_op a`
+ /// patterns.
+ ///
+ /// ### Why is this bad?
+ /// These can be written as the shorter `a op= b`.
+ ///
+ /// ### Known problems
+ /// While forbidden by the spec, `OpAssign` traits may have
+ /// implementations that differ from the regular `Op` impl.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 5;
+ /// let b = 0;
+ /// // ...
+ /// // Bad
+ /// a = a + b;
+ ///
+ /// // Good
+ /// a += b;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ASSIGN_OP_PATTERN,
+ style,
+ "assigning the result of an operation on a variable to that same variable"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `a op= a op b` or `a op= b op a` patterns.
+ ///
+ /// ### Why is this bad?
+ /// Most likely these are bugs where one meant to write `a
+ /// op= b`.
+ ///
+ /// ### Known problems
+ /// Clippy cannot know for sure if `a op= a op b` should have
+ /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
+ /// If `a op= a op b` is really the correct behaviour it should be
+ /// written as `a = a op a op b` as it's less confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 5;
+ /// let b = 2;
+ /// // ...
+ /// a += a + b;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MISREFACTORED_ASSIGN_OP,
+ suspicious,
+ "having a variable on both sides of an assign op"
+}
+
+declare_lint_pass!(AssignOps => [ASSIGN_OP_PATTERN, MISREFACTORED_ASSIGN_OP]);
+
+impl<'tcx> LateLintPass<'tcx> for AssignOps {
+ #[allow(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ match &expr.kind {
+ hir::ExprKind::AssignOp(op, lhs, rhs) => {
+ if let hir::ExprKind::Binary(binop, l, r) = &rhs.kind {
+ if op.node != binop.node {
+ return;
+ }
+ // lhs op= l op r
+ if eq_expr_value(cx, lhs, l) {
+ lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r);
+ }
+ // lhs op= l commutative_op r
+ if is_commutative(op.node) && eq_expr_value(cx, lhs, r) {
+ lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l);
+ }
+ }
+ },
+ hir::ExprKind::Assign(assignee, e, _) => {
+ if let hir::ExprKind::Binary(op, l, r) = &e.kind {
+ let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
+ let ty = cx.typeck_results().expr_ty(assignee);
+ let rty = cx.typeck_results().expr_ty(rhs);
+ if_chain! {
+ if let Some((_, lang_item)) = binop_traits(op.node);
+ if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item);
+ let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id);
+ if trait_ref_of_method(cx, parent_fn)
+ .map_or(true, |t| t.path.res.def_id() != trait_id);
+ if implements_trait(cx, ty, trait_id, &[rty.into()]);
+ then {
+ span_lint_and_then(
+ cx,
+ ASSIGN_OP_PATTERN,
+ expr.span,
+ "manual implementation of an assign operation",
+ |diag| {
+ if let (Some(snip_a), Some(snip_r)) =
+ (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
+ {
+ diag.span_suggestion(
+ expr.span,
+ "replace it with",
+ format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+ }
+ };
+
+ let mut visitor = ExprVisitor {
+ assignee,
+ counter: 0,
+ cx,
+ };
+
+ walk_expr(&mut visitor, e);
+
+ if visitor.counter == 1 {
+ // a = a op b
+ if eq_expr_value(cx, assignee, l) {
+ lint(assignee, r);
+ }
+ // a = b commutative_op a
+ // Limited to primitive type as these ops are know to be commutative
+ if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
+ match op.node {
+ hir::BinOpKind::Add
+ | hir::BinOpKind::Mul
+ | hir::BinOpKind::And
+ | hir::BinOpKind::Or
+ | hir::BinOpKind::BitXor
+ | hir::BinOpKind::BitAnd
+ | hir::BinOpKind::BitOr => {
+ lint(assignee, l);
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+fn lint_misrefactored_assign_op(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ op: hir::BinOp,
+ rhs: &hir::Expr<'_>,
+ assignee: &hir::Expr<'_>,
+ rhs_other: &hir::Expr<'_>,
+) {
+ span_lint_and_then(
+ cx,
+ MISREFACTORED_ASSIGN_OP,
+ expr.span,
+ "variable appears on both sides of an assignment operation",
+ |diag| {
+ if let (Some(snip_a), Some(snip_r)) = (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs_other.span)) {
+ let a = &sugg::Sugg::hir(cx, assignee, "..");
+ let r = &sugg::Sugg::hir(cx, rhs, "..");
+ let long = format!("{} = {}", snip_a, sugg::make_binop(op.node.into(), a, r));
+ diag.span_suggestion(
+ expr.span,
+ &format!(
+ "did you mean `{} = {} {} {}` or `{}`? Consider replacing it with",
+ snip_a,
+ snip_a,
+ op.node.as_str(),
+ snip_r,
+ long
+ ),
+ format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
+ Applicability::MaybeIncorrect,
+ );
+ diag.span_suggestion(
+ expr.span,
+ "or",
+ long,
+ Applicability::MaybeIncorrect, // snippet
+ );
+ }
+ },
+ );
+}
+
+#[must_use]
+fn is_commutative(op: hir::BinOpKind) -> bool {
+ use rustc_hir::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ match op {
+ Add | Mul | And | Or | BitXor | BitAnd | BitOr | Eq | Ne => true,
+ Sub | Div | Rem | Shl | Shr | Lt | Le | Ge | Gt => false,
+ }
+}
+
+struct ExprVisitor<'a, 'tcx> {
+ assignee: &'a hir::Expr<'a>,
+ counter: u8,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if eq_expr_value(self.cx, self.assignee, expr) {
+ self.counter += 1;
+ }
+
+ walk_expr(self, expr);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::implements_trait;
+use rustc_errors::Applicability;
+use rustc_hir::{AsyncGeneratorKind, Body, BodyId, ExprKind, GeneratorKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for async blocks that yield values of types
+ /// that can themselves be awaited.
+ ///
+ /// ### Why is this bad?
+ /// An await is likely missing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// async fn foo() {}
+ ///
+ /// fn bar() {
+ /// let x = async {
+ /// foo()
+ /// };
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// async fn foo() {}
+ ///
+ /// fn bar() {
+ /// let x = async {
+ /// foo().await
+ /// };
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub ASYNC_YIELDS_ASYNC,
+ correctness,
+ "async blocks that return a type that can be awaited"
+}
+
+declare_lint_pass!(AsyncYieldsAsync => [ASYNC_YIELDS_ASYNC]);
+
+impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync {
+ fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ use AsyncGeneratorKind::{Block, Closure};
+ // For functions, with explicitly defined types, don't warn.
+ // XXXkhuey maybe we should?
+ if let Some(GeneratorKind::Async(Block | Closure)) = body.generator_kind {
+ if let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() {
+ let body_id = BodyId {
+ hir_id: body.value.hir_id,
+ };
+ let typeck_results = cx.tcx.typeck_body(body_id);
+ let expr_ty = typeck_results.expr_ty(&body.value);
+
+ if implements_trait(cx, expr_ty, future_trait_def_id, &[]) {
+ let return_expr_span = match &body.value.kind {
+ // XXXkhuey there has to be a better way.
+ ExprKind::Block(block, _) => block.expr.map(|e| e.span),
+ ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span),
+ _ => None,
+ };
+ if let Some(return_expr_span) = return_expr_span {
+ span_lint_and_then(
+ cx,
+ ASYNC_YIELDS_ASYNC,
+ return_expr_span,
+ "an async construct yields a type which is itself awaitable",
+ |db| {
+ db.span_label(body.value.span, "outer async construct");
+ db.span_label(return_expr_span, "awaitable value not awaited");
+ db.span_suggestion(
+ return_expr_span,
+ "consider awaiting this value",
+ format!("{}.await", snippet(cx, return_expr_span, "..")),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::match_panic_def_id;
+//! checks for attributes
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
- use rustc_session::{declare_lint_pass, declare_tool_lint};
++use clippy_utils::msrvs;
+use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
++use clippy_utils::{extract_msrv_attr, match_panic_def_id, meets_msrv};
+use if_chain::if_chain;
+use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
+use rustc_errors::Applicability;
+use rustc_hir::{
+ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
+};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
- declare_lint_pass!(EarlyAttributes => [
++use rustc_semver::RustcVersion;
++use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+use rustc_span::symbol::{Symbol, SymbolStr};
+use semver::Version;
+
+static UNIX_SYSTEMS: &[&str] = &[
+ "android",
+ "dragonfly",
+ "emscripten",
+ "freebsd",
+ "fuchsia",
+ "haiku",
+ "illumos",
+ "ios",
+ "l4re",
+ "linux",
+ "macos",
+ "netbsd",
+ "openbsd",
+ "redox",
+ "solaris",
+ "vxworks",
+];
+
+// NOTE: windows is excluded from the list because it's also a valid target family.
+static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for items annotated with `#[inline(always)]`,
+ /// unless the annotated function is empty or simply panics.
+ ///
+ /// ### Why is this bad?
+ /// While there are valid uses of this annotation (and once
+ /// you know when to use it, by all means `allow` this lint), it's a common
+ /// newbie-mistake to pepper one's code with it.
+ ///
+ /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
+ /// measure if that additional function call really affects your runtime profile
+ /// sufficiently to make up for the increase in compile time.
+ ///
+ /// ### Known problems
+ /// False positives, big time. This lint is meant to be
+ /// deactivated by everyone doing serious performance work. This means having
+ /// done the measurement.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// #[inline(always)]
+ /// fn not_quite_hot_code(..) { ... }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INLINE_ALWAYS,
+ pedantic,
+ "use of `#[inline(always)]`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `extern crate` and `use` items annotated with
+ /// lint attributes.
+ ///
+ /// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`,
+ /// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and
+ /// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on
+ /// `extern crate` items with a `#[macro_use]` attribute.
+ ///
+ /// ### Why is this bad?
+ /// Lint attributes have no effect on crate imports. Most
+ /// likely a `!` was forgotten.
+ ///
+ /// ### 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;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[deprecated(since = "forever")]
+ /// fn something_else() { /* ... */ }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DEPRECATED_SEMVER,
+ correctness,
+ "use of `#[deprecated(since = \"x\")]` where x is not semver"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for empty lines after outer attributes
+ ///
+ /// ### Why is this bad?
+ /// Most likely the attribute was meant to be an inner attribute using a '!'.
+ /// If it was meant to be an outer attribute, then the following item
+ /// should not be separated by empty lines.
+ ///
+ /// ### Known problems
+ /// Can cause false positives.
+ ///
+ /// From the clippy side it's difficult to detect empty lines between an attributes and the
+ /// following item because empty lines and comments are not part of the AST. The parsing
+ /// currently works for basic cases but is not perfect.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Good (as inner attribute)
+ /// #![allow(dead_code)]
+ ///
+ /// fn this_is_fine() { }
+ ///
+ /// // Bad
+ /// #[allow(dead_code)]
+ ///
+ /// fn not_quite_good_code() { }
+ ///
+ /// // Good (as outer attribute)
+ /// #[allow(dead_code)]
+ /// fn this_is_fine_too() { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EMPTY_LINE_AFTER_OUTER_ATTR,
+ nursery,
+ "empty line after outer attribute"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
+ ///
+ /// ### Why is this bad?
+ /// Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.
+ /// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// #![deny(clippy::restriction)]
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// #![deny(clippy::as_conversions)]
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub BLANKET_CLIPPY_RESTRICTION_LINTS,
+ suspicious,
+ "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() { }
+ /// ```
++ #[clippy::version = "1.32.0"]
+ 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.
+ ///
+ /// ### 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.
++ #[clippy::version = "1.45.0"]
+ pub MISMATCHED_TARGET_OS,
+ correctness,
+ "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
+}
+
+declare_lint_pass!(Attributes => [
+ INLINE_ALWAYS,
+ DEPRECATED_SEMVER,
+ USELESS_ATTRIBUTE,
+ BLANKET_CLIPPY_RESTRICTION_LINTS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Attributes {
+ fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
+ if let Some(items) = &attr.meta_item_list() {
+ if let Some(ident) = attr.ident() {
+ if is_lint_level(ident.name) {
+ check_clippy_lint_names(cx, ident.name, items);
+ }
+ if items.is_empty() || !attr.has_name(sym::deprecated) {
+ return;
+ }
+ for item in items {
+ if_chain! {
+ if let NestedMetaItem::MetaItem(mi) = &item;
+ if let MetaItemKind::NameValue(lit) = &mi.kind;
+ if mi.has_name(sym::since);
+ then {
+ check_semver(cx, item.span(), lit);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if is_relevant_item(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, attrs);
+ }
+ match item.kind {
+ ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
+ let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use));
+
+ for attr in attrs {
+ if in_external_macro(cx.sess(), attr.span) {
+ return;
+ }
+ if let Some(lint_list) = &attr.meta_item_list() {
+ if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
+ // permit `unused_imports`, `deprecated`, `unreachable_pub`,
+ // `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items
+ // and `unused_imports` for `extern crate` items with `macro_use`
+ for lint in lint_list {
+ match item.kind {
+ ItemKind::Use(..) => {
+ if is_word(lint, sym!(unused_imports))
+ || is_word(lint, sym::deprecated)
+ || is_word(lint, sym!(unreachable_pub))
+ || is_word(lint, sym!(unused))
+ || extract_clippy_lint(lint).map_or(false, |s| s == "wildcard_imports")
+ || extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use")
+ {
+ return;
+ }
+ },
+ ItemKind::ExternCrate(..) => {
+ if is_word(lint, sym!(unused_imports)) && skip_unused_imports {
+ return;
+ }
+ if is_word(lint, sym!(unused_extern_crates)) {
+ return;
+ }
+ },
+ _ => {},
+ }
+ }
+ let line_span = first_line_of_span(cx, attr.span);
+
+ if let Some(mut sugg) = snippet_opt(cx, line_span) {
+ if sugg.contains("#[") {
+ span_lint_and_then(
+ cx,
+ USELESS_ATTRIBUTE,
+ line_span,
+ "useless lint attribute",
+ |diag| {
+ sugg = sugg.replacen("#[", "#![", 1);
+ diag.span_suggestion(
+ line_span,
+ "if you just forgot a `!`, use",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if is_relevant_impl(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if is_relevant_trait(cx, item) {
+ check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
+ }
+ }
+}
+
+/// Returns the lint name if it is clippy lint.
+fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<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.name == sym::clippy;
+ then {
+ let lint_name = meta_item.path.segments.last().unwrap().ident.name;
+ return Some(lint_name.as_str());
+ }
+ }
+ None
+}
+
+fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
+ for lint in items {
+ if let Some(lint_name) = extract_clippy_lint(lint) {
+ if lint_name == "restriction" && name != sym::allow {
+ span_lint_and_help(
+ cx,
+ BLANKET_CLIPPY_RESTRICTION_LINTS,
+ lint.span(),
+ "restriction lints are not meant to be all enabled",
+ None,
+ "try enabling only the lints you really need",
+ );
+ }
+ }
+ }
+}
+
+fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if let ItemKind::Fn(_, _, eid) = item.kind {
+ is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
+ } else {
+ true
+ }
+}
+
+fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
+ match item.kind {
+ ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value),
+ _ => false,
+ }
+}
+
+fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
+ match item.kind {
+ TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
+ TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
+ is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
+ },
+ _ => false,
+ }
+}
+
+fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool {
+ block.stmts.first().map_or(
+ block
+ .expr
+ .as_ref()
+ .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)),
+ |stmt| match &stmt.kind {
+ StmtKind::Local(_) => true,
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
+ StmtKind::Item(_) => false,
+ },
+ )
+}
+
+fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
+ match &expr.kind {
+ ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
+ ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
+ ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
+ ExprKind::Call(path_expr, _) => {
+ if let ExprKind::Path(qpath) = &path_expr.kind {
+ typeck_results
+ .qpath_res(qpath, path_expr.hir_id)
+ .opt_def_id()
+ .map_or(true, |fun_id| !match_panic_def_id(cx, fun_id))
+ } else {
+ true
+ }
+ },
+ _ => true,
+ }
+}
+
+fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
+ if span.from_expansion() {
+ return;
+ }
+
+ for attr in attrs {
+ if let Some(values) = attr.meta_item_list() {
+ if values.len() != 1 || !attr.has_name(sym::inline) {
+ continue;
+ }
+ if is_word(&values[0], sym::always) {
+ span_lint(
+ cx,
+ INLINE_ALWAYS,
+ attr.span,
+ &format!(
+ "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea",
+ name
+ ),
+ );
+ }
+ }
+ }
+}
+
+fn check_semver(cx: &LateContext<'_>, span: Span, lit: &Lit) {
+ if let LitKind::Str(is, _) = lit.kind {
+ if Version::parse(&is.as_str()).is_ok() {
+ return;
+ }
+ }
+ span_lint(
+ cx,
+ DEPRECATED_SEMVER,
+ span,
+ "the since field must contain a semver-compliant version",
+ );
+}
+
+fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
+ if let NestedMetaItem::MetaItem(mi) = &nmi {
+ mi.is_word() && mi.has_name(expected)
+ } else {
+ false
+ }
+}
+
- check_deprecated_cfg_attr(cx, attr);
++pub struct EarlyAttributes {
++ pub msrv: Option<RustcVersion>,
++}
++
++impl_lint_pass!(EarlyAttributes => [
+ DEPRECATED_CFG_ATTR,
+ MISMATCHED_TARGET_OS,
+ EMPTY_LINE_AFTER_OUTER_ATTR,
+]);
+
+impl EarlyLintPass for EarlyAttributes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
+ check_empty_line_after_outer_attr(cx, item);
+ }
+
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
- fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) {
++ check_deprecated_cfg_attr(cx, attr, self.msrv);
+ check_mismatched_target_os(cx, attr);
+ }
++
++ extract_msrv_attr!(EarlyContext);
+}
+
+fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
+ for attr in &item.attrs {
+ let attr_item = if let AttrKind::Normal(ref attr, _) = attr.kind {
+ attr
+ } else {
+ return;
+ };
+
+ if attr.style == AttrStyle::Outer {
+ if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) {
+ return;
+ }
+
+ let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
+ let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt(), item.span.parent());
+
+ 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, msrv: Option<RustcVersion>) {
+ if_chain! {
++ if meets_msrv(msrv.as_ref(), &msrvs::TOOL_ATTRIBUTES);
+ // check cfg_attr
+ if attr.has_name(sym::cfg_attr);
+ if let Some(items) = attr.meta_item_list();
+ if items.len() == 2;
+ // check for `rustfmt`
+ if let Some(feature_item) = items[0].meta_item();
+ if feature_item.has_name(sym::rustfmt);
+ // check for `rustfmt_skip` and `rustfmt::skip`
+ if let Some(skip_item) = &items[1].meta_item();
+ if skip_item.has_name(sym!(rustfmt_skip)) ||
+ skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym::skip;
+ // Only lint outer attributes, because custom inner attributes are unstable
+ // Tracking issue: https://github.com/rust-lang/rust/issues/54726
+ if attr.style == AttrStyle::Outer;
+ 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));
+ }
+ }
+ },
+ MetaItemKind::NameValue(..) => {},
+ }
+ }
+ }
+
+ mismatched
+ }
+
+ if_chain! {
+ if attr.has_name(sym::cfg);
+ if let Some(list) = attr.meta_item_list();
+ let mismatched = find_mismatched_target_os(&list);
+ if !mismatched.is_empty();
+ then {
+ let mess = "operating system used in target family position";
+
+ span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
+ // Avoid showing the unix suggestion multiple times in case
+ // we have more than one mismatch for unix-like systems
+ let mut unix_suggested = false;
+
+ for (os, span) in mismatched {
+ let sugg = format!("target_os = \"{}\"", os);
+ diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
+
+ if !unix_suggested && is_unix(os) {
+ diag.help("did you mean `unix`?");
+ unix_suggested = true;
+ }
+ }
+ });
+ }
+ }
+}
+
+fn is_lint_level(symbol: Symbol) -> bool {
+ matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_note;
+use clippy_utils::{match_def_path, paths};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::GeneratorInteriorTypeCause;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to await while holding a
+ /// non-async-aware MutexGuard.
+ ///
+ /// ### Why is this bad?
+ /// The Mutex types found in std::sync and parking_lot
+ /// are not designed to operate in an async context across await points.
+ ///
+ /// There are two potential solutions. One is to use an async-aware Mutex
+ /// type. Many asynchronous foundation crates provide such a Mutex type. The
+ /// other solution is to ensure the mutex is unlocked before calling await,
+ /// either by introducing a scope or an explicit call to Drop::drop.
+ ///
+ /// ### Known problems
+ /// Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::sync::Mutex;
+ ///
+ /// async fn foo(x: &Mutex<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;
+ /// }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub AWAIT_HOLDING_LOCK,
+ pedantic,
+ "Inside an async function, holding a MutexGuard while calling await"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to await while holding a
+ /// `RefCell` `Ref` or `RefMut`.
+ ///
+ /// ### Why is this bad?
+ /// `RefCell` refs only check for exclusive mutable access
+ /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
+ /// risks panics from a mutable ref shared while other refs are outstanding.
+ ///
+ /// ### Known problems
+ /// Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::cell::RefCell;
+ ///
+ /// async fn foo(x: &RefCell<u32>) {
+ /// let mut y = x.borrow_mut();
+ /// *y += 1;
+ /// bar.await;
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust,ignore
+ /// use std::cell::RefCell;
+ ///
+ /// async fn foo(x: &RefCell<u32>) {
+ /// {
+ /// let mut y = x.borrow_mut();
+ /// *y += 1;
+ /// }
+ /// bar.await;
+ /// }
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub AWAIT_HOLDING_REFCELL_REF,
+ pedantic,
+ "Inside an async function, holding a RefCell ref while calling await"
+}
+
+declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);
+
+impl LateLintPass<'_> for AwaitHolding {
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
+ use AsyncGeneratorKind::{Block, Closure, Fn};
+ if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
+ let body_id = BodyId {
+ hir_id: body.value.hir_id,
+ };
+ let typeck_results = cx.tcx.typeck_body(body_id);
+ check_interior_types(
+ cx,
+ typeck_results.generator_interior_types.as_ref().skip_binder(),
+ body.value.span,
+ );
+ }
+ }
+}
+
+fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
+ for ty_cause in ty_causes {
+ if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
+ if is_mutex_guard(cx, adt.did) {
+ span_lint_and_note(
+ cx,
+ AWAIT_HOLDING_LOCK,
+ ty_cause.span,
+ "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await",
+ ty_cause.scope_span.or(Some(span)),
+ "these are all the await points this lock is held through",
+ );
+ }
+ if is_refcell_ref(cx, adt.did) {
+ span_lint_and_note(
+ cx,
+ AWAIT_HOLDING_REFCELL_REF,
+ ty_cause.span,
+ "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await",
+ ty_cause.scope_span.or(Some(span)),
+ "these are all the await points this ref is held through",
+ );
+ }
+ }
+ }
+}
+
+fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ match_def_path(cx, def_id, &paths::MUTEX_GUARD)
+ || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
+ || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
+ || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
+}
+
+fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
+}
--- /dev/null
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::sugg::Sugg;
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for incompatible bit masks in comparisons.
+ ///
+ /// The formula for detecting if an expression of the type `_ <bit_op> m
+ /// <cmp_op> c` (where `<bit_op>` is one of {`&`, `|`} and `<cmp_op>` is one of
+ /// {`!=`, `>=`, `>`, `!=`, `>=`, `>`}) can be determined from the following
+ /// table:
+ ///
+ /// |Comparison |Bit Op|Example |is always|Formula |
+ /// |------------|------|------------|---------|----------------------|
+ /// |`==` or `!=`| `&` |`x & 2 == 3`|`false` |`c & m != c` |
+ /// |`<` or `>=`| `&` |`x & 2 < 3` |`true` |`m < c` |
+ /// |`>` or `<=`| `&` |`x & 1 > 1` |`false` |`m <= c` |
+ /// |`==` or `!=`| `|` |`x | 1 == 0`|`false` |`c | m != c` |
+ /// |`<` or `>=`| `|` |`x | 1 < 1` |`false` |`m >= c` |
+ /// |`<=` or `>` | `|` |`x | 1 > 0` |`true` |`m > c` |
+ ///
+ /// ### Why is this bad?
+ /// If the bits that the comparison cares about are always
+ /// set to zero or one by the bit mask, the comparison is constant `true` or
+ /// `false` (depending on mask, compared value, and operators).
+ ///
+ /// So the code is actively misleading, and the only reason someone would write
+ /// this intentionally is to win an underhanded Rust contest or create a
+ /// test-case for this lint.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if (x & 1 == 2) { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub BAD_BIT_MASK,
+ correctness,
+ "expressions of the form `_ & mask == select` that will only ever return `true` or `false`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bit masks in comparisons which can be removed
+ /// without changing the outcome. The basic structure can be seen in the
+ /// following table:
+ ///
+ /// |Comparison| Bit Op |Example |equals |
+ /// |----------|---------|-----------|-------|
+ /// |`>` / `<=`|`|` / `^`|`x | 2 > 3`|`x > 3`|
+ /// |`<` / `>=`|`|` / `^`|`x ^ 1 < 4`|`x < 4`|
+ ///
+ /// ### Why is this bad?
+ /// Not equally evil as [`bad_bit_mask`](#bad_bit_mask),
+ /// but still a bit misleading, because the bit mask is ineffective.
+ ///
+ /// ### Known problems
+ /// False negatives: This lint will only match instances
+ /// where we have figured out the math (which is for a power-of-two compared
+ /// value). This means things like `x | 1 >= 7` (which would be better written
+ /// as `x >= 6`) will not be reported (but bit masks like this are fairly
+ /// uncommon).
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if (x | 1 > 3) { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INEFFECTIVE_BIT_MASK,
+ correctness,
+ "expressions where a bit mask will be rendered useless by a comparison, e.g., `(x | 1) > 2`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bit masks that can be replaced by a call
+ /// to `trailing_zeros`
+ ///
+ /// ### Why is this bad?
+ /// `x.trailing_zeros() > 4` is much clearer than `x & 15
+ /// == 0`
+ ///
+ /// ### Known problems
+ /// llvm generates better code for `x & 15 == 0` on x86
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if x & 0b1111 == 0 { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub VERBOSE_BIT_MASK,
+ pedantic,
+ "expressions where a bit mask is less readable than the corresponding method call"
+}
+
+#[derive(Copy, Clone)]
+pub struct BitMask {
+ verbose_bit_mask_threshold: u64,
+}
+
+impl BitMask {
+ #[must_use]
+ pub fn new(verbose_bit_mask_threshold: u64) -> Self {
+ Self {
+ verbose_bit_mask_threshold,
+ }
+ }
+}
+
+impl_lint_pass!(BitMask => [BAD_BIT_MASK, INEFFECTIVE_BIT_MASK, VERBOSE_BIT_MASK]);
+
+impl<'tcx> LateLintPass<'tcx> for BitMask {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(cmp, left, right) = &e.kind {
+ if cmp.node.is_comparison() {
+ if let Some(cmp_opt) = fetch_int_literal(cx, right) {
+ check_compare(cx, left, cmp.node, cmp_opt, e.span);
+ } else if let Some(cmp_val) = fetch_int_literal(cx, left) {
+ check_compare(cx, right, invert_cmp(cmp.node), cmp_val, e.span);
+ }
+ }
+ }
+ if_chain! {
+ if let ExprKind::Binary(op, left, right) = &e.kind;
+ if BinOpKind::Eq == op.node;
+ if let ExprKind::Binary(op1, left1, right1) = &left.kind;
+ if BinOpKind::BitAnd == op1.node;
+ if let ExprKind::Lit(lit) = &right1.kind;
+ if let LitKind::Int(n, _) = lit.node;
+ if let ExprKind::Lit(lit1) = &right.kind;
+ if let LitKind::Int(0, _) = lit1.node;
+ if n.leading_zeros() == n.count_zeros();
+ if n > u128::from(self.verbose_bit_mask_threshold);
+ then {
+ span_lint_and_then(cx,
+ VERBOSE_BIT_MASK,
+ e.span,
+ "bit mask could be simplified with a call to `trailing_zeros`",
+ |diag| {
+ let sugg = Sugg::hir(cx, left1, "...").maybe_par();
+ diag.span_suggestion(
+ e.span,
+ "try",
+ format!("{}.trailing_zeros() >= {}", sugg, n.count_ones()),
+ Applicability::MaybeIncorrect,
+ );
+ });
+ }
+ }
+ }
+}
+
+#[must_use]
+fn invert_cmp(cmp: BinOpKind) -> BinOpKind {
+ match cmp {
+ BinOpKind::Eq => BinOpKind::Eq,
+ BinOpKind::Ne => BinOpKind::Ne,
+ BinOpKind::Lt => BinOpKind::Gt,
+ BinOpKind::Gt => BinOpKind::Lt,
+ BinOpKind::Le => BinOpKind::Ge,
+ BinOpKind::Ge => BinOpKind::Le,
+ _ => BinOpKind::Or, // Dummy
+ }
+}
+
+fn check_compare(cx: &LateContext<'_>, bit_op: &Expr<'_>, cmp_op: BinOpKind, cmp_value: u128, span: Span) {
+ if let ExprKind::Binary(op, left, right) = &bit_op.kind {
+ if op.node != BinOpKind::BitAnd && op.node != BinOpKind::BitOr {
+ return;
+ }
+ fetch_int_literal(cx, right)
+ .or_else(|| fetch_int_literal(cx, left))
+ .map_or((), |mask| check_bit_mask(cx, op.node, cmp_op, mask, cmp_value, span));
+ }
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_bit_mask(
+ cx: &LateContext<'_>,
+ bit_op: BinOpKind,
+ cmp_op: BinOpKind,
+ mask_value: u128,
+ cmp_value: u128,
+ span: Span,
+) {
+ match cmp_op {
+ BinOpKind::Eq | BinOpKind::Ne => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value & cmp_value != cmp_value {
+ if cmp_value != 0 {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ & {}` can never be equal to `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ }
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value | cmp_value != cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ | {}` can never be equal to `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ }
+ },
+ _ => (),
+ },
+ BinOpKind::Lt | BinOpKind::Ge => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value < cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ & {}` will always be lower than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value >= cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ | {}` will never be lower than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else {
+ check_ineffective_lt(cx, span, mask_value, cmp_value, "|");
+ }
+ },
+ BinOpKind::BitXor => check_ineffective_lt(cx, span, mask_value, cmp_value, "^"),
+ _ => (),
+ },
+ BinOpKind::Le | BinOpKind::Gt => match bit_op {
+ BinOpKind::BitAnd => {
+ if mask_value <= cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ & {}` will never be higher than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else if mask_value == 0 {
+ span_lint(cx, BAD_BIT_MASK, span, "&-masking with zero");
+ }
+ },
+ BinOpKind::BitOr => {
+ if mask_value > cmp_value {
+ span_lint(
+ cx,
+ BAD_BIT_MASK,
+ span,
+ &format!(
+ "incompatible bit mask: `_ | {}` will always be higher than `{}`",
+ mask_value, cmp_value
+ ),
+ );
+ } else {
+ check_ineffective_gt(cx, span, mask_value, cmp_value, "|");
+ }
+ },
+ BinOpKind::BitXor => check_ineffective_gt(cx, span, mask_value, cmp_value, "^"),
+ _ => (),
+ },
+ _ => (),
+ }
+}
+
+fn check_ineffective_lt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) {
+ if c.is_power_of_two() && m < c {
+ span_lint(
+ cx,
+ INEFFECTIVE_BIT_MASK,
+ span,
+ &format!(
+ "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
+ op, m, c
+ ),
+ );
+ }
+}
+
+fn check_ineffective_gt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op: &str) {
+ if (c + 1).is_power_of_two() && m <= c {
+ span_lint(
+ cx,
+ INEFFECTIVE_BIT_MASK,
+ span,
+ &format!(
+ "ineffective bit mask: `x {} {}` compared to `{}`, is the same as x compared directly",
+ op, m, c
+ ),
+ );
+ }
+}
+
+fn fetch_int_literal(cx: &LateContext<'_>, lit: &Expr<'_>) -> Option<u128> {
+ match constant(cx, cx.typeck_results(), lit)?.0 {
+ Constant::Int(n) => Some(n),
+ _ => None,
+ }
+}
--- /dev/null
+use clippy_utils::{diagnostics::span_lint, is_test_module_or_function};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_hir::{Item, Pat, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of blacklisted names for variables, such
+ /// as `foo`.
+ ///
+ /// ### Why is this bad?
+ /// These names are usually placeholder names and should be
+ /// avoided.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let foo = 3.14;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub BLACKLISTED_NAME,
+ style,
+ "usage of a blacklisted/placeholder name"
+}
+
+#[derive(Clone, Debug)]
+pub struct BlacklistedName {
+ blacklist: FxHashSet<String>,
+ test_modules_deep: u32,
+}
+
+impl BlacklistedName {
+ pub fn new(blacklist: FxHashSet<String>) -> Self {
+ Self {
+ blacklist,
+ test_modules_deep: 0,
+ }
+ }
+
+ fn in_test_module(&self) -> bool {
+ self.test_modules_deep != 0
+ }
+}
+
+impl_lint_pass!(BlacklistedName => [BLACKLISTED_NAME]);
+
+impl<'tcx> LateLintPass<'tcx> for BlacklistedName {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_test_module_or_function(cx.tcx, item) {
+ self.test_modules_deep = self.test_modules_deep.saturating_add(1);
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ // Check whether we are under the `test` attribute.
+ if self.in_test_module() {
+ return;
+ }
+
+ if let PatKind::Binding(.., ident, _) = pat.kind {
+ if self.blacklist.contains(&ident.name.to_string()) {
+ span_lint(
+ cx,
+ BLACKLISTED_NAME,
+ ident.span,
+ &format!("use of a blacklisted/placeholder name `{}`", ident.name),
+ );
+ }
+ }
+ }
+
+ fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_test_module_or_function(cx.tcx, item) {
+ self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::higher;
+use clippy_utils::source::snippet_block_with_applicability;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{differing_macro_contexts, get_parent_expr};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{BlockCheckMode, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `if` conditions that use blocks containing an
+ /// expression, statements or conditions that use closures with blocks.
+ ///
+ /// ### Why is this bad?
+ /// Style, using blocks in the condition makes it hard to read.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// // Bad
+ /// if { true } { /* ... */ }
+ ///
+ /// // Good
+ /// if true { /* ... */ }
+ /// ```
+ ///
+ /// // or
+ ///
+ /// ```rust
+ /// # fn somefunc() -> bool { true };
+ /// // Bad
+ /// if { let x = somefunc(); x } { /* ... */ }
+ ///
+ /// // Good
+ /// let res = { let x = somefunc(); x };
+ /// if res { /* ... */ }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub BLOCKS_IN_IF_CONDITIONS,
+ style,
+ "useless or complex blocks that can be eliminated in conditions"
+}
+
+declare_lint_pass!(BlocksInIfConditions => [BLOCKS_IN_IF_CONDITIONS]);
+
+struct ExVisitor<'a, 'tcx> {
+ found_block: Option<&'tcx Expr<'tcx>>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ if let ExprKind::Closure(_, _, eid, _, _) = expr.kind {
+ // do not lint if the closure is called using an iterator (see #1141)
+ if_chain! {
+ if let Some(parent) = get_parent_expr(self.cx, expr);
+ if let ExprKind::MethodCall(_, _, [self_arg, ..], _) = &parent.kind;
+ let caller = self.cx.typeck_results().expr_ty(self_arg);
+ if let Some(iter_id) = self.cx.tcx.get_diagnostic_item(sym::Iterator);
+ if implements_trait(self.cx, caller, iter_id, &[]);
+ then {
+ return;
+ }
+ }
+
+ let body = self.cx.tcx.hir().body(eid);
+ let ex = &body.value;
+ if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() {
+ self.found_block = Some(ex);
+ return;
+ }
+ }
+ walk_expr(self, expr);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition";
+const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \
+ instead, move the block or closure higher and bind it with a `let`";
+
+impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+ if let Some(higher::If { cond, .. }) = higher::If::hir(expr) {
+ if let ExprKind::Block(block, _) = &cond.kind {
+ if block.rules == BlockCheckMode::DefaultBlock {
+ if block.stmts.is_empty() {
+ if let Some(ex) = &block.expr {
+ // don't dig into the expression here, just suggest that they remove
+ // the block
+ if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BLOCKS_IN_IF_CONDITIONS,
+ cond.span,
+ BRACED_EXPR_MESSAGE,
+ "try",
+ format!(
+ "{}",
+ snippet_block_with_applicability(
+ cx,
+ ex.span,
+ "..",
+ Some(expr.span),
+ &mut applicability
+ )
+ ),
+ applicability,
+ );
+ }
+ } else {
+ let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
+ if span.from_expansion() || differing_macro_contexts(expr.span, span) {
+ return;
+ }
+ // move block higher
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ BLOCKS_IN_IF_CONDITIONS,
+ expr.span.with_hi(cond.span.hi()),
+ COMPLEX_BLOCK_MESSAGE,
+ "try",
+ format!(
+ "let res = {}; if res",
+ snippet_block_with_applicability(
+ cx,
+ block.span,
+ "..",
+ Some(expr.span),
+ &mut applicability
+ ),
+ ),
+ applicability,
+ );
+ }
+ }
+ } else {
+ let mut visitor = ExVisitor { found_block: None, cx };
+ walk_expr(&mut visitor, cond);
+ if let Some(block) = visitor.found_block {
+ span_lint(cx, BLOCKS_IN_IF_CONDITIONS, block.span, COMPLEX_BLOCK_MESSAGE);
+ }
+ }
+ }
+ }
+}
--- /dev/null
- let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize;
+use clippy_utils::{diagnostics::span_lint_and_sugg, higher, is_direct_expn_of, ty::implements_trait};
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Lit};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Ident;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about boolean comparisons in assert-like macros.
+ ///
+ /// ### Why is this bad?
+ /// It is shorter to use the equivalent.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// assert_eq!("a".is_empty(), false);
+ /// assert_ne!("a".is_empty(), true);
+ ///
+ /// // Good
+ /// assert!(!"a".is_empty());
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub BOOL_ASSERT_COMPARISON,
+ style,
+ "Using a boolean as comparison value in an assert_* macro when there is no need"
+}
+
+declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]);
+
+fn is_bool_lit(e: &Expr<'_>) -> bool {
+ matches!(
+ e.kind,
+ ExprKind::Lit(Lit {
+ node: LitKind::Bool(_),
+ ..
+ })
+ ) && !e.span.from_expansion()
+}
+
+fn is_impl_not_trait_with_bool_out(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(e);
+
+ cx.tcx
+ .lang_items()
+ .not_trait()
+ .filter(|trait_id| implements_trait(cx, ty, *trait_id, &[]))
+ .and_then(|trait_id| {
+ cx.tcx.associated_items(trait_id).find_by_name_and_kind(
+ cx.tcx,
+ Ident::from_str("Output"),
+ ty::AssocKind::Type,
+ trait_id,
+ )
+ })
+ .map_or(false, |assoc_item| {
+ let proj = cx.tcx.mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(ty, &[]));
+ let nty = cx.tcx.normalize_erasing_regions(cx.param_env, proj);
+
+ nty.is_bool()
+ })
+}
+
+impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let macros = ["assert_eq", "debug_assert_eq"];
+ let inverted_macros = ["assert_ne", "debug_assert_ne"];
+
+ for mac in macros.iter().chain(inverted_macros.iter()) {
+ if let Some(span) = is_direct_expn_of(expr.span, mac) {
+ if let Some(args) = higher::extract_assert_macro_args(expr) {
+ if let [a, b, ..] = args[..] {
++ let nb_bool_args = usize::from(is_bool_lit(a)) + usize::from(is_bool_lit(b));
+
+ if nb_bool_args != 1 {
+ // If there are two boolean arguments, we definitely don't understand
+ // what's going on, so better leave things as is...
+ //
+ // Or there is simply no boolean and then we can leave things as is!
+ return;
+ }
+
+ if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) {
+ // At this point the expression which is not a boolean
+ // literal does not implement Not trait with a bool output,
+ // so we cannot suggest to rewrite our code
+ return;
+ }
+
+ let non_eq_mac = &mac[..mac.len() - 3];
+ span_lint_and_sugg(
+ cx,
+ BOOL_ASSERT_COMPARISON,
+ span,
+ &format!("used `{}!` with a literal bool", mac),
+ "replace it with",
+ format!("{}!(..)", non_eq_mac),
+ Applicability::MaybeIncorrect,
+ );
+ return;
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{eq_expr_value, get_trait_def_id, in_macro, paths};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
- if in_macro(e.span) {
- return;
- }
- match &e.kind {
- ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => {
- self.bool_expr(e);
- },
- ExprKind::Unary(UnOp::Not, inner) => {
- if self.cx.typeck_results().node_types()[inner.hir_id].is_bool() {
++use clippy_utils::{eq_expr_value, get_trait_def_id, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for boolean expressions that can be written more
+ /// concisely.
+ ///
+ /// ### Why is this bad?
+ /// Readability of boolean expressions suffers from
+ /// unnecessary duplication.
+ ///
+ /// ### Known problems
+ /// Ignores short circuiting behavior of `||` and
+ /// `&&`. Ignores `|`, `&` and `^`.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if a && true // should be: if a
+ /// if !(a == b) // should be: if a != b
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NONMINIMAL_BOOL,
+ complexity,
+ "boolean expressions that can be written more concisely"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for boolean expressions that contain terminals that
+ /// can be eliminated.
+ ///
+ /// ### Why is this bad?
+ /// This is most likely a logic bug.
+ ///
+ /// ### Known problems
+ /// Ignores short circuiting behavior.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if a && b || a { ... }
+ /// ```
+ /// The `b` is unnecessary, the expression is equivalent to `if a`.
++ #[clippy::version = "pre 1.29.0"]
+ pub LOGIC_BUG,
+ correctness,
+ "boolean expressions that contain terminals which can be eliminated"
+}
+
+// For each pairs, both orders are considered.
+const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")];
+
+declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, LOGIC_BUG]);
+
+impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ NonminimalBoolVisitor { cx }.visit_body(body);
+ }
+}
+
+struct NonminimalBoolVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+use quine_mc_cluskey::Bool;
+struct Hir2Qmm<'a, 'tcx, 'v> {
+ terminals: Vec<&'v Expr<'v>>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
+ fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
+ for a in a {
+ if let ExprKind::Binary(binop, lhs, rhs) = &a.kind {
+ if binop.node == op {
+ v = self.extract(op, &[lhs, rhs], v)?;
+ continue;
+ }
+ }
+ v.push(self.run(a)?);
+ }
+ Ok(v)
+ }
+
+ fn run(&mut self, e: &'v Expr<'_>) -> Result<Bool, String> {
+ fn negate(bin_op_kind: BinOpKind) -> Option<BinOpKind> {
+ match bin_op_kind {
+ BinOpKind::Eq => Some(BinOpKind::Ne),
+ BinOpKind::Ne => Some(BinOpKind::Eq),
+ BinOpKind::Gt => Some(BinOpKind::Le),
+ BinOpKind::Ge => Some(BinOpKind::Lt),
+ BinOpKind::Lt => Some(BinOpKind::Ge),
+ BinOpKind::Le => Some(BinOpKind::Gt),
+ _ => None,
+ }
+ }
+
+ // prevent folding of `cfg!` macros and the like
+ if !e.span.from_expansion() {
+ match &e.kind {
+ ExprKind::Unary(UnOp::Not, inner) => return Ok(Bool::Not(Box::new(self.run(inner)?))),
+ ExprKind::Binary(binop, lhs, rhs) => match &binop.node {
+ BinOpKind::Or => {
+ return Ok(Bool::Or(self.extract(BinOpKind::Or, &[lhs, rhs], Vec::new())?));
+ },
+ BinOpKind::And => {
+ return Ok(Bool::And(self.extract(BinOpKind::And, &[lhs, rhs], Vec::new())?));
+ },
+ _ => (),
+ },
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Bool(true) => return Ok(Bool::True),
+ LitKind::Bool(false) => return Ok(Bool::False),
+ _ => (),
+ },
+ _ => (),
+ }
+ }
+ for (n, expr) in self.terminals.iter().enumerate() {
+ if eq_expr_value(self.cx, e, expr) {
+ #[allow(clippy::cast_possible_truncation)]
+ return Ok(Bool::Term(n as u8));
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(e_binop, e_lhs, e_rhs) = &e.kind;
+ if implements_ord(self.cx, e_lhs);
+ if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
+ if negate(e_binop.node) == Some(expr_binop.node);
+ if eq_expr_value(self.cx, e_lhs, expr_lhs);
+ if eq_expr_value(self.cx, e_rhs, expr_rhs);
+ then {
+ #[allow(clippy::cast_possible_truncation)]
+ return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
+ }
+ }
+ }
+ let n = self.terminals.len();
+ self.terminals.push(e);
+ if n < 32 {
+ #[allow(clippy::cast_possible_truncation)]
+ Ok(Bool::Term(n as u8))
+ } else {
+ Err("too many literals".to_owned())
+ }
+ }
+}
+
+struct SuggestContext<'a, 'tcx, 'v> {
+ terminals: &'v [&'v Expr<'v>],
+ cx: &'a LateContext<'tcx>,
+ output: String,
+}
+
+impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> {
+ fn recurse(&mut self, suggestion: &Bool) -> Option<()> {
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ match suggestion {
+ True => {
+ self.output.push_str("true");
+ },
+ False => {
+ self.output.push_str("false");
+ },
+ Not(inner) => match **inner {
+ And(_) | Or(_) => {
+ self.output.push('!');
+ self.output.push('(');
+ self.recurse(inner);
+ self.output.push(')');
+ },
+ Term(n) => {
+ let terminal = self.terminals[n as usize];
+ if let Some(str) = simplify_not(self.cx, terminal) {
+ self.output.push_str(&str);
+ } else {
+ self.output.push('!');
+ let snip = snippet_opt(self.cx, terminal.span)?;
+ self.output.push_str(&snip);
+ }
+ },
+ True | False | Not(_) => {
+ self.output.push('!');
+ self.recurse(inner)?;
+ },
+ },
+ And(v) => {
+ for (index, inner) in v.iter().enumerate() {
+ if index > 0 {
+ self.output.push_str(" && ");
+ }
+ if let Or(_) = *inner {
+ self.output.push('(');
+ self.recurse(inner);
+ self.output.push(')');
+ } else {
+ self.recurse(inner);
+ }
+ }
+ },
+ Or(v) => {
+ for (index, inner) in v.iter().rev().enumerate() {
+ if index > 0 {
+ self.output.push_str(" || ");
+ }
+ self.recurse(inner);
+ }
+ },
+ &Term(n) => {
+ let snip = snippet_opt(self.cx, self.terminals[n as usize].span)?;
+ self.output.push_str(&snip);
+ },
+ }
+ Some(())
+ }
+}
+
+fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ match &expr.kind {
+ ExprKind::Binary(binop, lhs, rhs) => {
+ if !implements_ord(cx, lhs) {
+ return None;
+ }
+
+ match binop.node {
+ BinOpKind::Eq => Some(" != "),
+ BinOpKind::Ne => Some(" == "),
+ BinOpKind::Lt => Some(" >= "),
+ BinOpKind::Gt => Some(" <= "),
+ BinOpKind::Le => Some(" > "),
+ BinOpKind::Ge => Some(" < "),
+ _ => None,
+ }
+ .and_then(|op| {
+ Some(format!(
+ "{}{}{}",
+ snippet_opt(cx, lhs.span)?,
+ op,
+ snippet_opt(cx, rhs.span)?
+ ))
+ })
+ },
+ ExprKind::MethodCall(path, _, args, _) if args.len() == 1 => {
+ let type_of_receiver = cx.typeck_results().expr_ty(&args[0]);
+ if !is_type_diagnostic_item(cx, type_of_receiver, sym::Option)
+ && !is_type_diagnostic_item(cx, type_of_receiver, sym::Result)
+ {
+ return None;
+ }
+ METHODS_WITH_NEGATION
+ .iter()
+ .copied()
+ .flat_map(|(a, b)| vec![(a, b), (b, a)])
+ .find(|&(a, _)| {
+ let path: &str = &path.ident.name.as_str();
+ a == path
+ })
+ .and_then(|(_, neg_method)| Some(format!("{}.{}()", snippet_opt(cx, args[0].span)?, neg_method)))
+ },
+ _ => None,
+ }
+}
+
+fn suggest(cx: &LateContext<'_>, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String {
+ let mut suggest_context = SuggestContext {
+ terminals,
+ cx,
+ output: String::new(),
+ };
+ suggest_context.recurse(suggestion);
+ suggest_context.output
+}
+
+fn simple_negate(b: Bool) -> Bool {
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ match b {
+ True => False,
+ False => True,
+ t @ Term(_) => Not(Box::new(t)),
+ And(mut v) => {
+ for el in &mut v {
+ *el = simple_negate(::std::mem::replace(el, True));
+ }
+ Or(v)
+ },
+ Or(mut v) => {
+ for el in &mut v {
+ *el = simple_negate(::std::mem::replace(el, True));
+ }
+ And(v)
+ },
+ Not(inner) => *inner,
+ }
+}
+
+#[derive(Default)]
+struct Stats {
+ terminals: [usize; 32],
+ negations: usize,
+ ops: usize,
+}
+
+fn terminal_stats(b: &Bool) -> Stats {
+ fn recurse(b: &Bool, stats: &mut Stats) {
+ match b {
+ True | False => stats.ops += 1,
+ Not(inner) => {
+ match **inner {
+ And(_) | Or(_) => stats.ops += 1, // brackets are also operations
+ _ => stats.negations += 1,
+ }
+ recurse(inner, stats);
+ },
+ And(v) | Or(v) => {
+ stats.ops += v.len() - 1;
+ for inner in v {
+ recurse(inner, stats);
+ }
+ },
+ &Term(n) => stats.terminals[n as usize] += 1,
+ }
+ }
+ use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True};
+ let mut stats = Stats::default();
+ recurse(b, &mut stats);
+ stats
+}
+
+impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
+ fn bool_expr(&self, e: &'tcx Expr<'_>) {
+ let mut h2q = Hir2Qmm {
+ terminals: Vec::new(),
+ cx: self.cx,
+ };
+ if let Ok(expr) = h2q.run(e) {
+ if h2q.terminals.len() > 8 {
+ // QMC has exponentially slow behavior as the number of terminals increases
+ // 8 is reasonable, it takes approximately 0.2 seconds.
+ // See #825
+ return;
+ }
+
+ let stats = terminal_stats(&expr);
+ let mut simplified = expr.simplify();
+ for simple in Bool::Not(Box::new(expr)).simplify() {
+ match simple {
+ Bool::Not(_) | Bool::True | Bool::False => {},
+ _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
+ }
+ let simple_negated = simple_negate(simple);
+ if simplified.iter().any(|s| *s == simple_negated) {
+ continue;
+ }
+ simplified.push(simple_negated);
+ }
+ let mut improvements = Vec::with_capacity(simplified.len());
+ 'simplified: for suggestion in &simplified {
+ let simplified_stats = terminal_stats(suggestion);
+ let mut improvement = false;
+ for i in 0..32 {
+ // ignore any "simplifications" that end up requiring a terminal more often
+ // than in the original expression
+ if stats.terminals[i] < simplified_stats.terminals[i] {
+ continue 'simplified;
+ }
+ if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
+ span_lint_and_then(
+ self.cx,
+ LOGIC_BUG,
+ e.span,
+ "this boolean expression contains a logic bug",
+ |diag| {
+ diag.span_help(
+ h2q.terminals[i].span,
+ "this expression can be optimized out by applying boolean operations to the \
+ outer expression",
+ );
+ diag.span_suggestion(
+ e.span,
+ "it would look like the following",
+ suggest(self.cx, suggestion, &h2q.terminals),
+ // nonminimal_bool can produce minimal but
+ // not human readable expressions (#3141)
+ Applicability::Unspecified,
+ );
+ },
+ );
+ // don't also lint `NONMINIMAL_BOOL`
+ return;
+ }
+ // if the number of occurrences of a terminal decreases or any of the stats
+ // decreases while none increases
+ improvement |= (stats.terminals[i] > simplified_stats.terminals[i])
+ || (stats.negations > simplified_stats.negations && stats.ops == simplified_stats.ops)
+ || (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
+ }
+ if improvement {
+ improvements.push(suggestion);
+ }
+ }
+ let nonminimal_bool_lint = |suggestions: Vec<_>| {
+ span_lint_and_then(
+ self.cx,
+ NONMINIMAL_BOOL,
+ e.span,
+ "this boolean expression can be simplified",
+ |diag| {
+ diag.span_suggestions(
+ e.span,
+ "try",
+ suggestions.into_iter(),
+ // nonminimal_bool can produce minimal but
+ // not human readable expressions (#3141)
+ Applicability::Unspecified,
+ );
+ },
+ );
+ };
+ if improvements.is_empty() {
+ let mut visitor = NotSimplificationVisitor { cx: self.cx };
+ visitor.visit_expr(e);
+ } else {
+ nonminimal_bool_lint(
+ improvements
+ .into_iter()
+ .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals))
+ .collect(),
+ );
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
- } else {
- walk_expr(self, e);
- }
- },
- _ => walk_expr(self, e),
++ if !e.span.from_expansion() {
++ match &e.kind {
++ ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => {
+ self.bool_expr(e);
++ },
++ ExprKind::Unary(UnOp::Not, inner) => {
++ if self.cx.typeck_results().node_types()[inner.hir_id].is_bool() {
++ self.bool_expr(e);
++ }
++ },
++ _ => {},
++ }
+ }
++ walk_expr(self, e);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn implements_ord<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]))
+}
+
+struct NotSimplificationVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind {
+ if let Some(suggestion) = simplify_not(self.cx, inner) {
+ span_lint_and_sugg(
+ self.cx,
+ NONMINIMAL_BOOL,
+ expr.span,
+ "this boolean expression can be simplified",
+ "try",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ walk_expr(self, expr);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- &filter_recv
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::match_type;
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, UintTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for naive byte counts
+ ///
+ /// ### Why is this bad?
+ /// The [`bytecount`](https://crates.io/crates/bytecount)
+ /// crate has methods to count your bytes faster, especially for large slices.
+ ///
+ /// ### Known problems
+ /// If you have predominantly small slices, the
+ /// `bytecount::count(..)` method may actually be slower. However, if you can
+ /// ensure that less than 2³²-1 matches arise, the `naive_count_32(..)` can be
+ /// faster in those cases.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let vec = vec![1_u8];
+ /// &vec.iter().filter(|x| **x == 0u8).count(); // use bytecount::count instead
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NAIVE_BYTECOUNT,
+ pedantic,
+ "use of naive `<slice>.filter(|&x| x == y).count()` to count byte values"
+}
+
+declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]);
+
+impl<'tcx> LateLintPass<'tcx> for ByteCount {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(count, _, [count_recv], _) = expr.kind;
+ if count.ident.name == sym!(count);
+ if let ExprKind::MethodCall(filter, _, [filter_recv, filter_arg], _) = count_recv.kind;
+ if filter.ident.name == sym!(filter);
+ if let ExprKind::Closure(_, _, body_id, _, _) = filter_arg.kind;
+ let body = cx.tcx.hir().body(body_id);
+ if let [param] = body.params;
+ if let PatKind::Binding(_, arg_id, _, _) = strip_pat_refs(param.pat).kind;
+ if let ExprKind::Binary(ref op, l, r) = body.value.kind;
+ if op.node == BinOpKind::Eq;
+ if match_type(cx,
+ cx.typeck_results().expr_ty(filter_recv).peel_refs(),
+ &paths::SLICE_ITER);
+ let operand_is_arg = |expr| {
+ let expr = peel_ref_operators(cx, remove_blocks(expr));
+ path_to_local_id(expr, arg_id)
+ };
+ let needle = if operand_is_arg(l) {
+ r
+ } else if operand_is_arg(r) {
+ l
+ } else {
+ return;
+ };
+ if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind();
+ if !is_local_used(cx, needle, arg_id);
+ then {
+ let haystack = if let ExprKind::MethodCall(path, _, args, _) =
+ filter_recv.kind {
+ let p = path.ident.name;
+ if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 {
+ &args[0]
+ } else {
- &filter_recv
++ filter_recv
+ }
+ } else {
++ filter_recv
+ };
+ let mut applicability = Applicability::MaybeIncorrect;
+ span_lint_and_sugg(
+ cx,
+ NAIVE_BYTECOUNT,
+ expr.span,
+ "you appear to be counting bytes the naive way",
+ "consider using the bytecount crate",
+ format!("bytecount::count({}, {})",
+ snippet_with_applicability(cx, haystack.span, "..", &mut applicability),
+ snippet_with_applicability(cx, needle.span, "..", &mut applicability)),
+ applicability,
+ );
+ }
+ };
+ }
+}
--- /dev/null
+//! lint on missing cargo common metadata
+
+use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::DUMMY_SP;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks to see if all common metadata is defined in
+ /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata
+ ///
+ /// ### Why is this bad?
+ /// It will be more difficult for users to discover the
+ /// purpose of the crate, and key information related to it.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # This `Cargo.toml` is missing a description field:
+ /// [package]
+ /// name = "clippy"
+ /// version = "0.0.212"
+ /// repository = "https://github.com/rust-lang/rust-clippy"
+ /// readme = "README.md"
+ /// license = "MIT OR Apache-2.0"
+ /// keywords = ["clippy", "lint", "plugin"]
+ /// categories = ["development-tools", "development-tools::cargo-plugins"]
+ /// ```
+ ///
+ /// Should include a description field like:
+ ///
+ /// ```toml
+ /// # This `Cargo.toml` includes all common metadata
+ /// [package]
+ /// name = "clippy"
+ /// version = "0.0.212"
+ /// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
+ /// repository = "https://github.com/rust-lang/rust-clippy"
+ /// readme = "README.md"
+ /// license = "MIT OR Apache-2.0"
+ /// keywords = ["clippy", "lint", "plugin"]
+ /// categories = ["development-tools", "development-tools::cargo-plugins"]
+ /// ```
++ #[clippy::version = "1.32.0"]
+ pub CARGO_COMMON_METADATA,
+ cargo,
+ "common metadata is defined in `Cargo.toml`"
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct CargoCommonMetadata {
+ ignore_publish: bool,
+}
+
+impl CargoCommonMetadata {
+ pub fn new(ignore_publish: bool) -> Self {
+ Self { ignore_publish }
+ }
+}
+
+impl_lint_pass!(CargoCommonMetadata => [
+ CARGO_COMMON_METADATA
+]);
+
+fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) {
+ let message = format!("package `{}` is missing `{}` metadata", package.name, field);
+ span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message);
+}
+
+fn is_empty_str<T: AsRef<std::ffi::OsStr>>(value: &Option<T>) -> bool {
+ value.as_ref().map_or(true, |s| s.as_ref().is_empty())
+}
+
+fn is_empty_vec(value: &[String]) -> bool {
+ // This works because empty iterators return true
+ value.iter().all(String::is_empty)
+}
+
+impl LateLintPass<'_> for CargoCommonMetadata {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ if is_lint_allowed(cx, CARGO_COMMON_METADATA, CRATE_HIR_ID) {
+ return;
+ }
+
+ let metadata = unwrap_cargo_metadata!(cx, CARGO_COMMON_METADATA, false);
+
+ for package in metadata.packages {
+ // only run the lint if publish is `None` (`publish = true` or skipped entirely)
+ // or if the vector isn't empty (`publish = ["something"]`)
+ if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish {
+ if is_empty_str(&package.description) {
+ missing_warning(cx, &package, "package.description");
+ }
+
+ if is_empty_str(&package.license) && is_empty_str(&package.license_file) {
+ missing_warning(cx, &package, "either package.license or package.license_file");
+ }
+
+ if is_empty_str(&package.repository) {
+ missing_warning(cx, &package, "package.repository");
+ }
+
+ if is_empty_str(&package.readme) {
+ missing_warning(cx, &package, "package.readme");
+ }
+
+ if is_empty_vec(&package.keywords) {
+ missing_warning(cx, &package, "package.keywords");
+ }
+
+ if is_empty_vec(&package.categories) {
+ missing_warning(cx, &package, "package.categories");
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_hir::{Expr, ExprKind, PathSegment};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{source_map::Spanned, symbol::sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `ends_with` with possible file extensions
+ /// and suggests to use a case-insensitive approach instead.
+ ///
+ /// ### Why is this bad?
+ /// `ends_with` is case-sensitive and may not detect files with a valid extension.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn is_rust_file(filename: &str) -> bool {
+ /// filename.ends_with(".rs")
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn is_rust_file(filename: &str) -> bool {
+ /// filename.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rs")) == Some(true)
+ /// }
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ pedantic,
+ "Checks for calls to ends_with with case-sensitive file extensions"
+}
+
+declare_lint_pass!(CaseSensitiveFileExtensionComparisons => [CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS]);
+
+fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Span> {
+ if_chain! {
+ if let ExprKind::MethodCall(PathSegment { ident, .. }, _, [obj, extension, ..], span) = expr.kind;
+ if ident.as_str() == "ends_with";
+ if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind;
+ if (2..=6).contains(&ext_literal.as_str().len());
+ if ext_literal.as_str().starts_with('.');
+ if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_digit(10))
+ || ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_digit(10));
+ then {
+ let mut ty = ctx.typeck_results().expr_ty(obj);
+ ty = match ty.kind() {
+ ty::Ref(_, ty, ..) => ty,
+ _ => ty
+ };
+
+ match ty.kind() {
+ ty::Str => {
+ return Some(span);
+ },
+ ty::Adt(&ty::AdtDef { did, .. }, _) => {
+ if ctx.tcx.is_diagnostic_item(sym::String, did) {
+ return Some(span);
+ }
+ },
+ _ => { return None; }
+ }
+ }
+ }
+ None
+}
+
+impl LateLintPass<'tcx> for CaseSensitiveFileExtensionComparisons {
+ fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if let Some(span) = check_case_sensitive_file_extension_comparison(ctx, expr) {
+ span_lint_and_help(
+ ctx,
+ CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ span,
+ "case-sensitive file extension comparison",
+ None,
+ "consider using a case-insensitive comparison instead",
+ );
+ }
+ }
+}
--- /dev/null
- use clippy_utils::in_constant;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
- if !should_lint(cx, expr, cast_from, cast_to) {
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_isize_or_usize;
++use clippy_utils::{in_constant, meets_msrv, msrvs};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, FloatTy, Ty};
++use rustc_semver::RustcVersion;
+
+use super::{utils, CAST_LOSSLESS};
+
- &format!(
- "casting `{}` to `{}` may become silently lossy if you later change the type",
- cast_from, cast_to
- ),
++pub(super) fn check(
++ cx: &LateContext<'_>,
++ expr: &Expr<'_>,
++ cast_op: &Expr<'_>,
++ cast_from: Ty<'_>,
++ cast_to: Ty<'_>,
++ msrv: &Option<RustcVersion>,
++) {
++ if !should_lint(cx, expr, cast_from, cast_to, msrv) {
+ return;
+ }
+
+ // The suggestion is to use a function call, so if the original expression
+ // has parens on the outside, they are no longer needed.
+ let mut applicability = Applicability::MachineApplicable;
+ let opt = snippet_opt(cx, cast_op.span);
+ let sugg = opt.as_ref().map_or_else(
+ || {
+ applicability = Applicability::HasPlaceholders;
+ ".."
+ },
+ |snip| {
+ if should_strip_parens(cast_op, snip) {
+ &snip[1..snip.len() - 1]
+ } else {
+ snip.as_str()
+ }
+ },
+ );
+
++ let message = if cast_from.is_bool() {
++ format!(
++ "casting `{0:}` to `{1:}` is more cleanly stated with `{1:}::from(_)`",
++ cast_from, cast_to
++ )
++ } else {
++ format!(
++ "casting `{}` to `{}` may become silently lossy if you later change the type",
++ cast_from, cast_to
++ )
++ };
++
+ span_lint_and_sugg(
+ cx,
+ CAST_LOSSLESS,
+ expr.span,
- fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool {
++ &message,
+ "try",
+ format!("{}::from({})", cast_to, sugg),
+ applicability,
+ );
+}
+
-
++fn should_lint(
++ cx: &LateContext<'_>,
++ expr: &Expr<'_>,
++ cast_from: Ty<'_>,
++ cast_to: Ty<'_>,
++ msrv: &Option<RustcVersion>,
++) -> bool {
+ // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
+ if in_constant(cx, expr.hir_id) {
+ return false;
+ }
+
+ match (cast_from.is_integral(), cast_to.is_integral()) {
+ (true, true) => {
+ let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
+ let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
+ !is_isize_or_usize(cast_from)
+ && !is_isize_or_usize(cast_to)
+ && from_nbits < to_nbits
+ && !cast_signed_to_unsigned
+ },
+
+ (true, false) => {
+ let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
+ let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() {
+ 32
+ } else {
+ 64
+ };
+ from_nbits < to_nbits
+ },
++ (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv.as_ref(), &msrvs::FROM_BOOL) => true,
+ (_, _) => {
+ matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
+ },
+ }
+}
+
+fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool {
+ if let ExprKind::Binary(_, _, _) = cast_expr.kind {
+ if snip.starts_with('(') && snip.ends_with(')') {
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
- if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
- cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
- cast_possible_wrap::check(cx, expr, cast_from, cast_to);
- cast_precision_loss::check(cx, expr, cast_from, cast_to);
- cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to);
- cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
+mod cast_lossless;
+mod cast_possible_truncation;
+mod cast_possible_wrap;
+mod cast_precision_loss;
+mod cast_ptr_alignment;
+mod cast_ref_to_mut;
+mod cast_sign_loss;
+mod char_lit_as_u8;
+mod fn_to_numeric_cast;
+mod fn_to_numeric_cast_any;
+mod fn_to_numeric_cast_with_truncation;
+mod ptr_as_ptr;
+mod unnecessary_cast;
+mod utils;
+
+use clippy_utils::is_hir_ty_cfg_dependant;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = u64::MAX;
+ /// x as f64;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let y: i8 = -1;
+ /// y as u128; // will return 18446744073709551615
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn as_u8(x: u64) -> u8 {
+ /// x as u8
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// u32::MAX as i32; // will yield a value of `-1`
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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)
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub CAST_LOSSLESS,
+ pedantic,
+ "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts to the same type, casts of int literals to integer types
+ /// and casts of float literals to float types.
+ ///
+ /// ### Why is this bad?
+ /// It's just unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = 2i32 as i32;
+ /// let _ = 0.5 as f32;
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// let _ = 2_i32;
+ /// let _ = 0.5_f32;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_CAST,
+ complexity,
+ "cast to the same type, e.g., `x as i32` where `x: i32`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts, using `as` or `pointer::cast`,
+ /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer
+ ///
+ /// ### Why is this bad?
+ /// Dereferencing the resulting pointer may be undefined
+ /// behavior.
+ ///
+ /// ### Known problems
+ /// Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar
+ /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like
+ /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (&1u8 as *const u8) as *const u16;
+ /// let _ = (&mut 1u8 as *mut u8) as *mut u16;
+ ///
+ /// (&1u8 as *const u8).cast::<u16>();
+ /// (&mut 1u8 as *mut u8).cast::<u16>();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ style,
+ "casting a function pointer to a numeric type not wide enough to store the address"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts of a function pointer to any integer type.
+ ///
+ /// ### Why is this bad?
+ /// Casting a function pointer to an integer can have surprising results and can occur
+ /// accidentally if parantheses are omitted from a function call. If you aren't doing anything
+ /// low-level with function pointers then you can opt-out of casting functions to integers in
+ /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
+ /// pointer casts in your code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad: fn1 is cast as `usize`
+ /// fn fn1() -> u16 {
+ /// 1
+ /// };
+ /// let _ = fn1 as usize;
+ ///
+ /// // Good: maybe you intended to call the function?
+ /// fn fn2() -> u16 {
+ /// 1
+ /// };
+ /// let _ = fn2() as usize;
+ ///
+ /// // Good: maybe you intended to cast it to a function type?
+ /// fn fn3() -> u16 {
+ /// 1
+ /// }
+ /// let _ = fn3 as fn() -> u16;
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub FN_TO_NUMERIC_CAST_ANY,
+ restriction,
+ "casting a function pointer to any integer type"
+}
+
+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.
+ ///
+ /// ### 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;
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.33.0"]
+ pub CAST_REF_TO_MUT,
+ correctness,
+ "a cast of reference to a mutable pointer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions where a character literal is cast
+ /// to `u8` and suggests using a byte literal instead.
+ ///
+ /// ### Why is this bad?
+ /// In general, casting values to smaller types is
+ /// error-prone and should be avoided where possible. In the particular case of
+ /// converting a character literal to u8, it is easy to avoid by just using a
+ /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter
+ /// than `'a' as u8`.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// 'x' as u8
+ /// ```
+ ///
+ /// A better version, using the byte literal:
+ ///
+ /// ```rust,ignore
+ /// b'x'
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub CHAR_LIT_AS_U8,
+ complexity,
+ "casting a character literal to `u8` truncates"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `as` casts between raw pointers without changing its mutability,
+ /// namely `*const T` to `*const U` and `*mut T` to `*mut U`.
+ ///
+ /// ### Why is this bad?
+ /// Though `as` casts between raw pointers is not terrible, `pointer::cast` is safer because
+ /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let ptr: *const u32 = &42_u32;
+ /// let mut_ptr: *mut u32 = &mut 42_u32;
+ /// let _ = ptr as *const i32;
+ /// let _ = mut_ptr as *mut i32;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let ptr: *const u32 = &42_u32;
+ /// let mut_ptr: *mut u32 = &mut 42_u32;
+ /// let _ = ptr.cast::<i32>();
+ /// let _ = mut_ptr.cast::<i32>();
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub PTR_AS_PTR,
+ pedantic,
+ "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`"
+}
+
+pub struct Casts {
+ msrv: Option<RustcVersion>,
+}
+
+impl Casts {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(Casts => [
+ CAST_PRECISION_LOSS,
+ CAST_SIGN_LOSS,
+ CAST_POSSIBLE_TRUNCATION,
+ CAST_POSSIBLE_WRAP,
+ CAST_LOSSLESS,
+ CAST_REF_TO_MUT,
+ CAST_PTR_ALIGNMENT,
+ UNNECESSARY_CAST,
+ FN_TO_NUMERIC_CAST_ANY,
+ FN_TO_NUMERIC_CAST,
+ FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ CHAR_LIT_AS_U8,
+ PTR_AS_PTR,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Casts {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
+ if is_hir_ty_cfg_dependant(cx, cast_to) {
+ return;
+ }
+ let (cast_from, cast_to) = (
+ cx.typeck_results().expr_ty(cast_expr),
+ cx.typeck_results().expr_ty(expr),
+ );
+
+ if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) {
+ return;
+ }
+
+ fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
+ fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
+ fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
++
++ if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
++ if cast_from.is_numeric() {
++ cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
++ cast_possible_wrap::check(cx, expr, cast_from, cast_to);
++ cast_precision_loss::check(cx, expr, cast_from, cast_to);
++ cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
++ }
++
++ cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
+ }
+ }
+
+ cast_ref_to_mut::check(cx, expr);
+ cast_ptr_alignment::check(cx, expr);
+ char_lit_as_u8::check(cx, expr);
+ ptr_as_ptr::check(cx, expr, &self.msrv);
+ }
+
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
+//! lint on manually implemented checked conversions that could be transformed into `try_from`
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{meets_msrv, msrvs, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit bounds checking when casting.
+ ///
+ /// ### Why is this bad?
+ /// Reduces the readability of statements & is error prone.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo: u32 = 5;
+ /// # let _ =
+ /// foo <= i32::MAX as u32
+ /// # ;
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # use std::convert::TryFrom;
+ /// # let foo = 1;
+ /// # let _ =
+ /// i32::try_from(foo).is_ok()
+ /// # ;
+ /// ```
++ #[clippy::version = "1.37.0"]
+ pub CHECKED_CONVERSIONS,
+ pedantic,
+ "`try_from` could replace manual bounds checking when casting"
+}
+
+pub struct CheckedConversions {
+ msrv: Option<RustcVersion>,
+}
+
+impl CheckedConversions {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
+ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::TRY_FROM) {
+ return;
+ }
+
+ let result = if_chain! {
+ if !in_external_macro(cx.sess(), item.span);
+ if let ExprKind::Binary(op, left, right) = &item.kind;
+
+ then {
+ match op.node {
+ BinOpKind::Ge | BinOpKind::Le => single_check(item),
+ BinOpKind::And => double_check(cx, left, right),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ };
+
+ if let Some(cv) = result {
+ if let Some(to_type) = cv.to_type {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = snippet_with_applicability(cx, cv.expr_to_cast.span, "_", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ CHECKED_CONVERSIONS,
+ item.span,
+ "checked cast can be simplified",
+ "try",
+ format!("{}::try_from({}).is_ok()", to_type, snippet),
+ applicability,
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+/// Searches for a single check from unsigned to _ is done
+/// todo: check for case signed -> larger unsigned == only x >= 0
+fn single_check<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ check_upper_bound(expr).filter(|cv| cv.cvt == ConversionType::FromUnsigned)
+}
+
+/// Searches for a combination of upper & lower bound checks
+fn double_check<'a>(cx: &LateContext<'_>, left: &'a Expr<'_>, right: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ let upper_lower = |l, r| {
+ let upper = check_upper_bound(l);
+ let lower = check_lower_bound(r);
+
+ upper.zip(lower).and_then(|(l, r)| l.combine(r, cx))
+ };
+
+ upper_lower(left, right).or_else(|| upper_lower(right, left))
+}
+
+/// Contains the result of a tried conversion check
+#[derive(Clone, Debug)]
+struct Conversion<'a> {
+ cvt: ConversionType,
+ expr_to_cast: &'a Expr<'a>,
+ to_type: Option<&'a str>,
+}
+
+/// The kind of conversion that is checked
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum ConversionType {
+ SignedToUnsigned,
+ SignedToSigned,
+ FromUnsigned,
+}
+
+impl<'a> Conversion<'a> {
+ /// Combine multiple conversions if the are compatible
+ pub fn combine(self, other: Self, cx: &LateContext<'_>) -> Option<Conversion<'a>> {
+ if self.is_compatible(&other, cx) {
+ // Prefer a Conversion that contains a type-constraint
+ Some(if self.to_type.is_some() { self } else { other })
+ } else {
+ None
+ }
+ }
+
+ /// Checks if two conversions are compatible
+ /// same type of conversion, same 'castee' and same 'to type'
+ pub fn is_compatible(&self, other: &Self, cx: &LateContext<'_>) -> bool {
+ (self.cvt == other.cvt)
+ && (SpanlessEq::new(cx).eq_expr(self.expr_to_cast, other.expr_to_cast))
+ && (self.has_compatible_to_type(other))
+ }
+
+ /// Checks if the to-type is the same (if there is a type constraint)
+ fn has_compatible_to_type(&self, other: &Self) -> bool {
+ match (self.to_type, other.to_type) {
+ (Some(l), Some(r)) => l == r,
+ _ => true,
+ }
+ }
+
+ /// Try to construct a new conversion if the conversion type is valid
+ fn try_new(expr_to_cast: &'a Expr<'_>, from_type: &str, to_type: &'a str) -> Option<Conversion<'a>> {
+ ConversionType::try_new(from_type, to_type).map(|cvt| Conversion {
+ cvt,
+ expr_to_cast,
+ to_type: Some(to_type),
+ })
+ }
+
+ /// Construct a new conversion without type constraint
+ fn new_any(expr_to_cast: &'a Expr<'_>) -> Conversion<'a> {
+ Conversion {
+ cvt: ConversionType::SignedToUnsigned,
+ expr_to_cast,
+ to_type: None,
+ }
+ }
+}
+
+impl ConversionType {
+ /// Creates a conversion type if the type is allowed & conversion is valid
+ #[must_use]
+ fn try_new(from: &str, to: &str) -> Option<Self> {
+ if UINTS.contains(&from) {
+ Some(Self::FromUnsigned)
+ } else if SINTS.contains(&from) {
+ if UINTS.contains(&to) {
+ Some(Self::SignedToUnsigned)
+ } else if SINTS.contains(&to) {
+ Some(Self::SignedToSigned)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr <= (to_type::MAX as from_type)`
+fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ if_chain! {
+ if let ExprKind::Binary(ref op, left, right) = &expr.kind;
+ if let Some((candidate, check)) = normalize_le_ge(op, left, right);
+ if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
+
+ then {
+ Conversion::try_new(candidate, from, to)
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr >= 0|(to_type::MIN as from_type)`
+fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
+ fn check_function<'a>(candidate: &'a Expr<'a>, check: &'a Expr<'a>) -> Option<Conversion<'a>> {
+ (check_lower_bound_zero(candidate, check)).or_else(|| (check_lower_bound_min(candidate, check)))
+ }
+
+ // First of we need a binary containing the expression & the cast
+ if let ExprKind::Binary(ref op, left, right) = &expr.kind {
+ normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r))
+ } else {
+ None
+ }
+}
+
+/// Check for `expr >= 0`
+fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ if_chain! {
+ if let ExprKind::Lit(ref lit) = &check.kind;
+ if let LitKind::Int(0, _) = &lit.node;
+
+ then {
+ Some(Conversion::new_any(candidate))
+ } else {
+ None
+ }
+ }
+}
+
+/// Check for `expr >= (to_type::MIN as from_type)`
+fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option<Conversion<'a>> {
+ if let Some((from, to)) = get_types_from_cast(check, SINTS, "min_value", "MIN") {
+ Conversion::try_new(candidate, from, to)
+ } else {
+ None
+ }
+}
+
+/// Tries to extract the from- and to-type from a cast expression
+fn get_types_from_cast<'a>(
+ expr: &'a Expr<'_>,
+ types: &'a [&str],
+ func: &'a str,
+ assoc_const: &'a str,
+) -> Option<(&'a str, &'a str)> {
+ // `to_type::max_value() as from_type`
+ // or `to_type::MAX as from_type`
+ let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
+ // to_type::max_value(), from_type
+ if let ExprKind::Cast(limit, from_type) = &expr.kind;
+ if let TyKind::Path(ref from_type_path) = &from_type.kind;
+ if let Some(from_sym) = int_ty_to_sym(from_type_path);
+
+ then {
+ Some((limit, from_sym))
+ } else {
+ None
+ }
+ };
+
+ // `from_type::from(to_type::max_value())`
+ let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
+ if_chain! {
+ // `from_type::from, to_type::max_value()`
+ if let ExprKind::Call(from_func, args) = &expr.kind;
+ // `to_type::max_value()`
+ if args.len() == 1;
+ if let limit = &args[0];
+ // `from_type::from`
+ if let ExprKind::Path(ref path) = &from_func.kind;
+ if let Some(from_sym) = get_implementing_type(path, INTS, "from");
+
+ then {
+ Some((limit, from_sym))
+ } else {
+ None
+ }
+ }
+ });
+
+ if let Some((limit, from_type)) = limit_from {
+ match limit.kind {
+ // `from_type::from(_)`
+ ExprKind::Call(path, _) => {
+ if let ExprKind::Path(ref path) = path.kind {
+ // `to_type`
+ if let Some(to_type) = get_implementing_type(path, types, func) {
+ return Some((from_type, to_type));
+ }
+ }
+ },
+ // `to_type::MAX`
+ ExprKind::Path(ref path) => {
+ if let Some(to_type) = get_implementing_type(path, types, assoc_const) {
+ return Some((from_type, to_type));
+ }
+ },
+ _ => {},
+ }
+ };
+ None
+}
+
+/// Gets the type which implements the called function
+fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
+ if_chain! {
+ if let QPath::TypeRelative(ty, path) = &path;
+ if path.ident.name.as_str() == function;
+ if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
+ if let [int] = &*tp.segments;
+ then {
+ let name = &int.ident.name.as_str();
+ candidates.iter().find(|c| name == *c).copied()
+ } else {
+ None
+ }
+ }
+}
+
+/// Gets the type as a string, if it is a supported integer
+fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
+ if_chain! {
+ if let QPath::Resolved(_, path) = *path;
+ if let [ty] = &*path.segments;
+ then {
+ let name = &ty.ident.name.as_str();
+ INTS.iter().find(|c| name == *c).copied()
+ } else {
+ None
+ }
+ }
+}
+
+/// Will return the expressions as if they were expr1 <= expr2
+fn normalize_le_ge<'a>(op: &BinOp, left: &'a Expr<'a>, right: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
+ match op.node {
+ BinOpKind::Le => Some((left, right)),
+ BinOpKind::Ge => Some((right, left)),
+ _ => None,
+ }
+}
+
+// Constants
+const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"];
+const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"];
+const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"];
--- /dev/null
+//! calculate cognitive complexity and warn about overly complex functions
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::LimitStack;
+use rustc_ast::ast::Attribute;
+use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::{sym, BytePos};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for methods with high cognitive complexity.
+ ///
+ /// ### Why is this bad?
+ /// Methods of high cognitive complexity tend to be hard to
+ /// both read and maintain. Also LLVM will tend to optimize small methods better.
+ ///
+ /// ### Known problems
+ /// Sometimes it's hard to find a way to reduce the
+ /// complexity.
+ ///
+ /// ### Example
+ /// No. You'll see it when you get the warning.
++ #[clippy::version = "1.35.0"]
+ pub COGNITIVE_COMPLEXITY,
+ nursery,
+ "functions that should be split up into multiple functions"
+}
+
+pub struct CognitiveComplexity {
+ limit: LimitStack,
+}
+
+impl CognitiveComplexity {
+ #[must_use]
+ pub fn new(limit: u64) -> Self {
+ Self {
+ limit: LimitStack::new(limit),
+ }
+ }
+}
+
+impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
+
+impl CognitiveComplexity {
+ #[allow(clippy::cast_possible_truncation)]
+ fn check<'tcx>(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ body_span: Span,
+ ) {
+ if body_span.from_expansion() {
+ return;
+ }
+
+ let expr = &body.value;
+
+ let mut helper = CcHelper { cc: 1, returns: 0 };
+ helper.visit_expr(expr);
+ let CcHelper { cc, returns } = helper;
+ let ret_ty = cx.typeck_results().node_type(expr.hir_id);
+ let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
+ returns
+ } else {
+ #[allow(clippy::integer_division)]
+ (returns / 2)
+ };
+
+ let mut rust_cc = cc;
+ // prevent degenerate cases where unreachable code contains `return` statements
+ if rust_cc >= ret_adjust {
+ rust_cc -= ret_adjust;
+ }
+
+ if rust_cc > self.limit.limit() {
+ let fn_span = match kind {
+ FnKind::ItemFn(ident, _, _, _) | FnKind::Method(ident, _, _) => ident.span,
+ FnKind::Closure => {
+ let header_span = body_span.with_hi(decl.output.span().lo());
+ let pos = snippet_opt(cx, header_span).and_then(|snip| {
+ let low_offset = snip.find('|')?;
+ let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?;
+ let low = header_span.lo() + BytePos(low_offset as u32);
+ let high = low + BytePos(high_offset as u32 + 1);
+
+ Some((low, high))
+ });
+
+ if let Some((low, high)) = pos {
+ Span::new(low, high, header_span.ctxt(), header_span.parent())
+ } else {
+ return;
+ }
+ },
+ };
+
+ span_lint_and_help(
+ cx,
+ COGNITIVE_COMPLEXITY,
+ fn_span,
+ &format!(
+ "the function has a cognitive complexity of ({}/{})",
+ rust_cc,
+ self.limit.limit()
+ ),
+ None,
+ "you could split it up into multiple smaller functions",
+ );
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+ if !cx.tcx.has_attr(def_id.to_def_id(), sym::test) {
+ self.check(cx, kind, decl, body, span);
+ }
+ }
+
+ fn enter_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
+ self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
+ }
+ fn exit_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
+ self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
+ }
+}
+
+struct CcHelper {
+ cc: u64,
+ returns: u64,
+}
+
+impl<'tcx> Visitor<'tcx> for CcHelper {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ walk_expr(self, e);
+ match e.kind {
+ ExprKind::If(_, _, _) => {
+ self.cc += 1;
+ },
+ ExprKind::Match(_, arms, _) => {
+ if arms.len() > 1 {
+ self.cc += 1;
+ }
+ self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
+ },
+ ExprKind::Ret(_) => self.returns += 1,
+ _ => {},
+ }
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
+//! 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 clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet_block, snippet_block_with_applicability};
+use clippy_utils::sugg::Sugg;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for nested `if` statements which can be collapsed
+ /// by `&&`-combining their conditions.
+ ///
+ /// ### Why is this bad?
+ /// Each `if`-statement adds one level of nesting, which
+ /// makes code look more complex than it really is.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// if x {
+ /// if y {
+ /// …
+ /// }
+ /// }
+ ///
+ /// ```
+ ///
+ /// Should be written:
+ ///
+ /// ```rust.ignore
+ /// if x && y {
+ /// …
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub COLLAPSIBLE_IF,
+ style,
+ "nested `if`s that can be collapsed (e.g., `if x { if y { ... } }`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for collapsible `else { if ... }` expressions
+ /// that can be collapsed to `else if ...`.
+ ///
+ /// ### Why is this bad?
+ /// Each `if`-statement adds one level of nesting, which
+ /// makes code look more complex than it really is.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ ///
+ /// if x {
+ /// …
+ /// } else {
+ /// if y {
+ /// …
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Should be written:
+ ///
+ /// ```rust.ignore
+ /// if x {
+ /// …
+ /// } else if y {
+ /// …
+ /// }
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub COLLAPSIBLE_ELSE_IF,
+ style,
+ "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)"
+}
+
+declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]);
+
+impl EarlyLintPass for CollapsibleIf {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if !expr.span.from_expansion() {
+ check_if(cx, expr);
+ }
+ }
+}
+
+fn check_if(cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if let ast::ExprKind::If(check, then, else_) = &expr.kind {
+ if let Some(else_) = else_ {
+ check_collapsible_maybe_if_let(cx, else_);
+ } else if let ast::ExprKind::Let(..) = check.kind {
+ // Prevent triggering on `if let a = b { if c { .. } }`.
+ } else {
+ check_collapsible_no_if_let(cx, expr, check, then);
+ }
+ }
+}
+
+fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool {
+ // We trim all opening braces and whitespaces and then check if the next string is a comment.
+ let trimmed_block_text = snippet_block(cx, expr.span, "..", None)
+ .trim_start_matches(|c: char| c.is_whitespace() || c == '{')
+ .to_owned();
+ trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*")
+}
+
+fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
+ if_chain! {
+ if let ast::ExprKind::Block(ref block, _) = else_.kind;
+ if !block_starts_with_comment(cx, block);
+ if let Some(else_) = expr_block(block);
+ if else_.attrs.is_empty();
+ if !else_.span.from_expansion();
+ if let ast::ExprKind::If(..) = else_.kind;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_ELSE_IF,
+ block.span,
+ "this `else { if .. }` block can be collapsed",
+ "collapse nested if block",
+ snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability).into_owned(),
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) {
+ if_chain! {
+ if !block_starts_with_comment(cx, then);
+ if let Some(inner) = expr_block(then);
+ if inner.attrs.is_empty();
+ if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
+ // Prevent triggering on `if c { if let a = b { .. } }`.
+ if !matches!(check_inner.kind, ast::ExprKind::Let(..));
+ if expr.span.ctxt() == inner.span.ctxt();
+ then {
+ span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
+ let lhs = Sugg::ast(cx, check, "..");
+ let rhs = Sugg::ast(cx, check_inner, "..");
+ diag.span_suggestion(
+ expr.span,
+ "collapse nested if block",
+ format!(
+ "if {} {}",
+ lhs.and(&rhs),
+ snippet_block(cx, content.span, "..", Some(expr.span)),
+ ),
+ Applicability::MachineApplicable, // snippet
+ );
+ });
+ }
+ }
+}
+
+/// If the block contains only one expression, return it.
+fn expr_block(block: &ast::Block) -> Option<&ast::Expr> {
+ let mut it = block.stmts.iter();
+
+ if let (Some(stmt), None) = (it.next(), it.next()) {
+ match stmt.kind {
+ ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => Some(expr),
+ _ => None,
+ }
+ } else {
+ None
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher::IfLetOrMatch;
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_ref_operators, SpanlessEq};
+use if_chain::if_chain;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{MultiSpan, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
+ /// without adding any branches.
+ ///
+ /// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
+ /// cases where merging would most likely make the code more readable.
+ ///
+ /// ### Why is this bad?
+ /// It is unnecessarily verbose and complex.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn func(opt: Option<Result<u64, String>>) {
+ /// let n = match opt {
+ /// Some(n) => match n {
+ /// Ok(n) => n,
+ /// _ => return,
+ /// }
+ /// None => return,
+ /// };
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn func(opt: Option<Result<u64, String>>) {
+ /// let n = match opt {
+ /// Some(Ok(n)) => n,
+ /// _ => return,
+ /// };
+ /// }
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub COLLAPSIBLE_MATCH,
+ style,
+ "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
+}
+
+declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
+
+impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+ match IfLetOrMatch::parse(cx, expr) {
+ Some(IfLetOrMatch::Match(_, arms, _)) => {
+ if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
+ for arm in arms {
+ check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
+ }
+ }
+ },
+ Some(IfLetOrMatch::IfLet(_, pat, body, els)) => {
+ check_arm(cx, false, pat, body, None, els);
+ },
+ None => {},
+ }
+ }
+}
+
+fn check_arm<'tcx>(
+ cx: &LateContext<'tcx>,
+ outer_is_match: bool,
+ outer_pat: &'tcx Pat<'tcx>,
+ outer_then_body: &'tcx Expr<'tcx>,
+ outer_guard: Option<&'tcx Guard<'tcx>>,
+ outer_else_body: Option<&'tcx Expr<'tcx>>,
+) {
+ let inner_expr = strip_singleton_blocks(outer_then_body);
+ if_chain! {
+ if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr);
+ if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner {
+ IfLetOrMatch::IfLet(scrutinee, pat, _, els) => Some((scrutinee, pat, els)),
+ IfLetOrMatch::Match(scrutinee, arms, ..) => if_chain! {
+ // if there are more than two arms, collapsing would be non-trivial
+ if arms.len() == 2 && arms.iter().all(|a| a.guard.is_none());
+ // one of the arms must be "wild-like"
+ if let Some(wild_idx) = arms.iter().rposition(|a| arm_is_wild_like(cx, a));
+ then {
+ let (then, els) = (&arms[1 - wild_idx], &arms[wild_idx]);
+ Some((scrutinee, then.pat, Some(els.body)))
+ } else {
+ None
+ }
+ },
+ };
+ if outer_pat.span.ctxt() == inner_scrutinee.span.ctxt();
+ // match expression must be a local binding
+ // match <local> { .. }
+ if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee));
+ if !pat_contains_or(inner_then_pat);
+ // the binding must come from the pattern of the containing match arm
+ // ..<local>.. => match <local> { .. }
+ if let Some(binding_span) = find_pat_binding(outer_pat, binding_id);
+ // the "else" branches must be equal
+ if match (outer_else_body, inner_else_body) {
+ (None, None) => true,
+ (None, Some(e)) | (Some(e), None) => is_unit_expr(e),
+ (Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
+ };
+ // the binding must not be used in the if guard
+ if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(_, e))| !is_local_used(cx, *e, binding_id));
+ // ...or anywhere in the inner expression
+ if match inner {
+ IfLetOrMatch::IfLet(_, _, body, els) => {
+ !is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
+ },
+ IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
+ };
+ then {
+ let msg = format!(
+ "this `{}` can be collapsed into the outer `{}`",
+ if matches!(inner, IfLetOrMatch::Match(..)) { "match" } else { "if let" },
+ if outer_is_match { "match" } else { "if let" },
+ );
+ span_lint_and_then(
+ cx,
+ COLLAPSIBLE_MATCH,
+ inner_expr.span,
+ &msg,
+ |diag| {
+ let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]);
+ help_span.push_span_label(binding_span, "replace this binding".into());
+ help_span.push_span_label(inner_then_pat.span, "with this pattern".into());
+ diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
+ },
+ );
+ }
+ }
+}
+
+fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
+ while let ExprKind::Block(block, _) = expr.kind {
+ match (block.stmts, block.expr) {
+ ([stmt], None) => match stmt.kind {
+ StmtKind::Expr(e) | StmtKind::Semi(e) => expr = e,
+ _ => break,
+ },
+ ([], Some(e)) => expr = e,
+ _ => break,
+ }
+ }
+ expr
+}
+
+/// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed"
+/// into a single wild arm without any significant loss in semantics or readability.
+fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if arm.guard.is_some() {
+ return false;
+ }
+ match arm.pat.kind {
+ PatKind::Binding(..) | PatKind::Wild => true,
+ PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
+ _ => false,
+ }
+}
+
+fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
+ let mut span = None;
+ pat.walk_short(|p| match &p.kind {
+ // ignore OR patterns
+ PatKind::Or(_) => false,
+ PatKind::Binding(_bm, _, _ident, _) => {
+ let found = p.hir_id == hir_id;
+ if found {
+ span = Some(p.span);
+ }
+ !found
+ },
+ _ => true,
+ });
+ span
+}
+
+fn pat_contains_or(pat: &Pat<'_>) -> bool {
+ let mut result = false;
+ pat.walk(|p| {
+ let is_or = matches!(p.kind, PatKind::Or(_));
+ result |= is_or;
+ !is_or
+ });
+ result
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{get_trait_def_id, if_sequence, in_constant, is_else_clause, paths, SpanlessEq};
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks comparison chains written with `if` that can be
+ /// rewritten with `match` and `cmp`.
+ ///
+ /// ### Why is this bad?
+ /// `if` is not guaranteed to be exhaustive and conditionals can get
+ /// repetitive
+ ///
+ /// ### Known problems
+ /// The match statement may be slower due to the compiler
+ /// not inlining the call to cmp. See issue [#5354](https://github.com/rust-lang/rust-clippy/issues/5354)
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # fn c() {}
+ /// fn f(x: u8, y: u8) {
+ /// if x > y {
+ /// a()
+ /// } else if x < y {
+ /// b()
+ /// } else {
+ /// c()
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust,ignore
+ /// use std::cmp::Ordering;
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # fn c() {}
+ /// fn f(x: u8, y: u8) {
+ /// match x.cmp(&y) {
+ /// Ordering::Greater => a(),
+ /// Ordering::Less => b(),
+ /// Ordering::Equal => c()
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.40.0"]
+ 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 is_else_clause(cx.tcx, expr) {
+ return;
+ }
+
+ if in_constant(cx, expr.hir_id) {
+ 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, lhs1, rhs1), &ExprKind::Binary(ref kind2, lhs2, rhs2)) =
+ (&cond[0].kind, &cond[1].kind)
+ {
+ if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) {
+ return;
+ }
+
+ // Check that both sets of operands are equal
+ let mut spanless_eq = SpanlessEq::new(cx);
+ let same_fixed_operands = spanless_eq.eq_expr(lhs1, lhs2) && spanless_eq.eq_expr(rhs1, rhs2);
+ let same_transposed_operands = spanless_eq.eq_expr(lhs1, rhs2) && spanless_eq.eq_expr(rhs1, lhs2);
+
+ if !same_fixed_operands && !same_transposed_operands {
+ return;
+ }
+
+ // Check that if the operation is the same, either it's not `==` or the operands are transposed
+ if kind1.node == kind2.node {
+ if kind1.node == BinOpKind::Eq {
+ return;
+ }
+ if !same_transposed_operands {
+ return;
+ }
+ }
+
+ // Check that the type being compared implements `core::cmp::Ord`
+ let ty = cx.typeck_results().expr_ty(lhs1);
+ let is_ord = get_trait_def_id(cx, &paths::ORD).map_or(false, |id| implements_trait(cx, ty, id, &[]));
+
+ if !is_ord {
+ return;
+ }
+ } else {
+ // We only care about comparison chains
+ return;
+ }
+ }
+ span_lint_and_help(
+ cx,
+ COMPARISON_CHAIN,
+ expr.span,
+ "`if` chain can be rewritten with `match`",
+ None,
+ "consider rewriting the `if` chain to use `cmp` and `match`",
+ );
+ }
+}
+
+fn kind_is_cmp(kind: BinOpKind) -> bool {
+ matches!(kind, BinOpKind::Lt | BinOpKind::Gt | BinOpKind::Eq)
+}
--- /dev/null
- both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, in_macro, is_else_clause,
- is_lint_allowed, search_same, ContainsName, SpanlessEq, SpanlessHash,
+use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
+use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
+use clippy_utils::{
- if in_macro(lhs.span) || in_macro(rhs.span) {
++ both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, is_else_clause, is_lint_allowed,
++ search_same, ContainsName, SpanlessEq, SpanlessHash,
+};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
+use rustc_hir::{Block, Expr, ExprKind, HirId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{source_map::Span, symbol::Symbol, BytePos};
+use std::borrow::Cow;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for consecutive `if`s with the same condition.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a copy & paste error.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if a == b {
+ /// …
+ /// } else if a == b {
+ /// …
+ /// }
+ /// ```
+ ///
+ /// Note that this lint ignores all conditions with a function call as it could
+ /// have side effects:
+ ///
+ /// ```ignore
+ /// if foo() {
+ /// …
+ /// } else if foo() { // not linted
+ /// …
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub IFS_SAME_COND,
+ correctness,
+ "consecutive `if`s with the same condition"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for consecutive `if`s with the same function call.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a copy & paste error.
+ /// Despite the fact that function can have side effects and `if` works as
+ /// intended, such an approach is implicit and can be considered a "code smell".
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if foo() == bar {
+ /// …
+ /// } else if foo() == bar {
+ /// …
+ /// }
+ /// ```
+ ///
+ /// This probably should be:
+ /// ```ignore
+ /// if foo() == bar {
+ /// …
+ /// } else if foo() == baz {
+ /// …
+ /// }
+ /// ```
+ ///
+ /// or if the original code was not a typo and called function mutates a state,
+ /// consider move the mutation out of the `if` condition to avoid similarity to
+ /// a copy & paste error:
+ ///
+ /// ```ignore
+ /// let first = foo();
+ /// if first == bar {
+ /// …
+ /// } else {
+ /// let second = foo();
+ /// if second == bar {
+ /// …
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.41.0"]
+ pub SAME_FUNCTIONS_IN_IF_CONDITION,
+ pedantic,
+ "consecutive `if`s with the same function call"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `if/else` with the same body as the *then* part
+ /// and the *else* part.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a copy & paste error.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let foo = if … {
+ /// 42
+ /// } else {
+ /// 42
+ /// };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub IF_SAME_THEN_ELSE,
+ correctness,
+ "`if` with the same `then` and `else` blocks"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks if the `if` and `else` block contain shared code that can be
+ /// moved out of the blocks.
+ ///
+ /// ### Why is this bad?
+ /// Duplicate code is less maintainable.
+ ///
+ /// ### Known problems
+ /// * The lint doesn't check if the moved expressions modify values that are beeing used in
+ /// the if condition. The suggestion can in that case modify the behavior of the program.
+ /// See [rust-clippy#7452](https://github.com/rust-lang/rust-clippy/issues/7452)
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let foo = if … {
+ /// println!("Hello World");
+ /// 13
+ /// } else {
+ /// println!("Hello World");
+ /// 42
+ /// };
+ /// ```
+ ///
+ /// Could be written as:
+ /// ```ignore
+ /// println!("Hello World");
+ /// let foo = if … {
+ /// 13
+ /// } else {
+ /// 42
+ /// };
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub BRANCHES_SHARING_CODE,
+ nursery,
+ "`if` statement with shared code in all blocks"
+}
+
+declare_lint_pass!(CopyAndPaste => [
+ IFS_SAME_COND,
+ SAME_FUNCTIONS_IN_IF_CONDITION,
+ IF_SAME_THEN_ELSE,
+ BRANCHES_SHARING_CODE
+]);
+
+impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !expr.span.from_expansion() {
+ if let ExprKind::If(_, _, _) = expr.kind {
+ // skip ifs directly in else, it will be checked in the parent if
+ if let Some(&Expr {
+ kind: ExprKind::If(_, _, Some(else_expr)),
+ ..
+ }) = get_parent_expr(cx, expr)
+ {
+ if else_expr.hir_id == expr.hir_id {
+ return;
+ }
+ }
+
+ let (conds, blocks) = if_sequence(expr);
+ // Conditions
+ lint_same_cond(cx, &conds);
+ lint_same_fns_in_if_cond(cx, &conds);
+ // Block duplication
+ lint_same_then_else(cx, &blocks, conds.len() == blocks.len(), expr);
+ }
+ }
+ }
+}
+
+/// Implementation of `BRANCHES_SHARING_CODE` and `IF_SAME_THEN_ELSE` if the blocks are equal.
+fn lint_same_then_else<'tcx>(
+ cx: &LateContext<'tcx>,
+ blocks: &[&Block<'tcx>],
+ has_conditional_else: bool,
+ expr: &'tcx Expr<'_>,
+) {
+ // We only lint ifs with multiple blocks
+ if blocks.len() < 2 || is_else_clause(cx.tcx, expr) {
+ return;
+ }
+
+ // Check if each block has shared code
+ let has_expr = blocks[0].expr.is_some();
+
+ let (start_eq, mut end_eq, expr_eq) = if let Some(block_eq) = scan_block_for_eq(cx, blocks) {
+ (block_eq.start_eq, block_eq.end_eq, block_eq.expr_eq)
+ } else {
+ return;
+ };
+
+ // BRANCHES_SHARING_CODE prerequisites
+ if has_conditional_else || (start_eq == 0 && end_eq == 0 && (has_expr && !expr_eq)) {
+ return;
+ }
+
+ // Only the start is the same
+ if start_eq != 0 && end_eq == 0 && (!has_expr || !expr_eq) {
+ let block = blocks[0];
+ let start_stmts = block.stmts.split_at(start_eq).0;
+
+ let mut start_walker = UsedValueFinderVisitor::new(cx);
+ for stmt in start_stmts {
+ intravisit::walk_stmt(&mut start_walker, stmt);
+ }
+
+ emit_branches_sharing_code_lint(
+ cx,
+ start_eq,
+ 0,
+ false,
+ check_for_warn_of_moved_symbol(cx, &start_walker.def_symbols, expr),
+ blocks,
+ expr,
+ );
+ } else if end_eq != 0 || (has_expr && expr_eq) {
+ let block = blocks[blocks.len() - 1];
+ let (start_stmts, block_stmts) = block.stmts.split_at(start_eq);
+ let (block_stmts, end_stmts) = block_stmts.split_at(block_stmts.len() - end_eq);
+
+ // Scan start
+ let mut start_walker = UsedValueFinderVisitor::new(cx);
+ for stmt in start_stmts {
+ intravisit::walk_stmt(&mut start_walker, stmt);
+ }
+ let mut moved_syms = start_walker.def_symbols;
+
+ // Scan block
+ let mut block_walker = UsedValueFinderVisitor::new(cx);
+ for stmt in block_stmts {
+ intravisit::walk_stmt(&mut block_walker, stmt);
+ }
+ let mut block_defs = block_walker.defs;
+
+ // Scan moved stmts
+ let mut moved_start: Option<usize> = None;
+ let mut end_walker = UsedValueFinderVisitor::new(cx);
+ for (index, stmt) in end_stmts.iter().enumerate() {
+ intravisit::walk_stmt(&mut end_walker, stmt);
+
+ for value in &end_walker.uses {
+ // Well we can't move this and all prev statements. So reset
+ if block_defs.contains(value) {
+ moved_start = Some(index + 1);
+ end_walker.defs.drain().for_each(|x| {
+ block_defs.insert(x);
+ });
+
+ end_walker.def_symbols.clear();
+ }
+ }
+
+ end_walker.uses.clear();
+ }
+
+ if let Some(moved_start) = moved_start {
+ end_eq -= moved_start;
+ }
+
+ let end_linable = block.expr.map_or_else(
+ || end_eq != 0,
+ |expr| {
+ intravisit::walk_expr(&mut end_walker, expr);
+ end_walker.uses.iter().any(|x| !block_defs.contains(x))
+ },
+ );
+
+ if end_linable {
+ end_walker.def_symbols.drain().for_each(|x| {
+ moved_syms.insert(x);
+ });
+ }
+
+ emit_branches_sharing_code_lint(
+ cx,
+ start_eq,
+ end_eq,
+ end_linable,
+ check_for_warn_of_moved_symbol(cx, &moved_syms, expr),
+ blocks,
+ expr,
+ );
+ }
+}
+
+struct BlockEqual {
+ /// The amount statements that are equal from the start
+ start_eq: usize,
+ /// The amount statements that are equal from the end
+ end_eq: usize,
+ /// An indication if the block expressions are the same. This will also be true if both are
+ /// `None`
+ expr_eq: bool,
+}
+
+/// This function can also trigger the `IF_SAME_THEN_ELSE` in which case it'll return `None` to
+/// abort any further processing and avoid duplicate lint triggers.
+fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> Option<BlockEqual> {
+ let mut start_eq = usize::MAX;
+ let mut end_eq = usize::MAX;
+ let mut expr_eq = true;
+ let mut iter = blocks.windows(2);
+ while let Some(&[win0, win1]) = iter.next() {
+ let l_stmts = win0.stmts;
+ let r_stmts = win1.stmts;
+
+ // `SpanlessEq` now keeps track of the locals and is therefore context sensitive clippy#6752.
+ // The comparison therefore needs to be done in a way that builds the correct context.
+ let mut evaluator = SpanlessEq::new(cx);
+ let mut evaluator = evaluator.inter_expr();
+
+ let current_start_eq = count_eq(&mut l_stmts.iter(), &mut r_stmts.iter(), |l, r| evaluator.eq_stmt(l, r));
+
+ let current_end_eq = {
+ // We skip the middle statements which can't be equal
+ let end_comparison_count = l_stmts.len().min(r_stmts.len()) - current_start_eq;
+ let it1 = l_stmts.iter().skip(l_stmts.len() - end_comparison_count);
+ let it2 = r_stmts.iter().skip(r_stmts.len() - end_comparison_count);
+ it1.zip(it2)
+ .fold(0, |acc, (l, r)| if evaluator.eq_stmt(l, r) { acc + 1 } else { 0 })
+ };
+ let block_expr_eq = both(&win0.expr, &win1.expr, |l, r| evaluator.eq_expr(l, r));
+
+ // IF_SAME_THEN_ELSE
+ if_chain! {
+ if block_expr_eq;
+ if l_stmts.len() == r_stmts.len();
+ if l_stmts.len() == current_start_eq;
+ if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, win0.hir_id);
+ if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, win1.hir_id);
+ then {
+ span_lint_and_note(
+ cx,
+ IF_SAME_THEN_ELSE,
+ win0.span,
+ "this `if` has identical blocks",
+ Some(win1.span),
+ "same as this",
+ );
+
+ return None;
+ }
+ }
+
+ start_eq = start_eq.min(current_start_eq);
+ end_eq = end_eq.min(current_end_eq);
+ expr_eq &= block_expr_eq;
+ }
+
+ if !expr_eq {
+ end_eq = 0;
+ }
+
+ // Check if the regions are overlapping. Set `end_eq` to prevent the overlap
+ let min_block_size = blocks.iter().map(|x| x.stmts.len()).min().unwrap();
+ if (start_eq + end_eq) > min_block_size {
+ end_eq = min_block_size - start_eq;
+ }
+
+ Some(BlockEqual {
+ start_eq,
+ end_eq,
+ expr_eq,
+ })
+}
+
+fn check_for_warn_of_moved_symbol(
+ cx: &LateContext<'tcx>,
+ symbols: &FxHashSet<Symbol>,
+ if_expr: &'tcx Expr<'_>,
+) -> bool {
+ get_enclosing_block(cx, if_expr.hir_id).map_or(false, |block| {
+ let ignore_span = block.span.shrink_to_lo().to(if_expr.span);
+
+ symbols
+ .iter()
+ .filter(|sym| !sym.as_str().starts_with('_'))
+ .any(move |sym| {
+ let mut walker = ContainsName {
+ name: *sym,
+ result: false,
+ };
+
+ // Scan block
+ block
+ .stmts
+ .iter()
+ .filter(|stmt| !ignore_span.overlaps(stmt.span))
+ .for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
+
+ if let Some(expr) = block.expr {
+ intravisit::walk_expr(&mut walker, expr);
+ }
+
+ walker.result
+ })
+ })
+}
+
+fn emit_branches_sharing_code_lint(
+ cx: &LateContext<'tcx>,
+ start_stmts: usize,
+ end_stmts: usize,
+ lint_end: bool,
+ warn_about_moved_symbol: bool,
+ blocks: &[&Block<'tcx>],
+ if_expr: &'tcx Expr<'_>,
+) {
+ if start_stmts == 0 && !lint_end {
+ return;
+ }
+
+ // (help, span, suggestion)
+ let mut suggestions: Vec<(&str, Span, String)> = vec![];
+ let mut add_expr_note = false;
+
+ // Construct suggestions
+ let sm = cx.sess().source_map();
+ if start_stmts > 0 {
+ let block = blocks[0];
+ let span_start = first_line_of_span(cx, if_expr.span).shrink_to_lo();
+ let span_end = sm.stmt_span(block.stmts[start_stmts - 1].span, block.span);
+
+ let cond_span = first_line_of_span(cx, if_expr.span).until(block.span);
+ let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None);
+ let cond_indent = indent_of(cx, cond_span);
+ let moved_span = block.stmts[0].span.source_callsite().to(span_end);
+ let moved_snippet = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
+ let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{";
+ let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent);
+
+ let span = span_start.to(span_end);
+ suggestions.push(("start", span, suggestion.to_string()));
+ }
+
+ if lint_end {
+ let block = blocks[blocks.len() - 1];
+ let span_end = block.span.shrink_to_hi();
+
+ let moved_start = if end_stmts == 0 && block.expr.is_some() {
+ block.expr.unwrap().span.source_callsite()
+ } else {
+ sm.stmt_span(block.stmts[block.stmts.len() - end_stmts].span, block.span)
+ };
+ let moved_end = block.expr.map_or_else(
+ || sm.stmt_span(block.stmts[block.stmts.len() - 1].span, block.span),
+ |expr| expr.span.source_callsite(),
+ );
+
+ let moved_span = moved_start.to(moved_end);
+ let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
+ let indent = indent_of(cx, if_expr.span.shrink_to_hi());
+ let suggestion = "}\n".to_string() + &moved_snipped;
+ let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent);
+
+ let mut span = moved_start.to(span_end);
+ // Improve formatting if the inner block has indention (i.e. normal Rust formatting)
+ let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt(), span.parent());
+ if snippet_opt(cx, test_span)
+ .map(|snip| snip == " ")
+ .unwrap_or_default()
+ {
+ span = span.with_lo(test_span.lo());
+ }
+
+ suggestions.push(("end", span, suggestion.to_string()));
+ add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit();
+ }
+
+ let add_optional_msgs = |diag: &mut DiagnosticBuilder<'_>| {
+ if add_expr_note {
+ diag.note("The end suggestion probably needs some adjustments to use the expression result correctly");
+ }
+
+ if warn_about_moved_symbol {
+ diag.warn("Some moved values might need to be renamed to avoid wrong references");
+ }
+ };
+
+ // Emit lint
+ if suggestions.len() == 1 {
+ let (place_str, span, sugg) = suggestions.pop().unwrap();
+ let msg = format!("all if blocks contain the same code at the {}", place_str);
+ let help = format!("consider moving the {} statements out like this", place_str);
+ span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg.as_str(), |diag| {
+ diag.span_suggestion(span, help.as_str(), sugg, Applicability::Unspecified);
+
+ add_optional_msgs(diag);
+ });
+ } else if suggestions.len() == 2 {
+ let (_, end_span, end_sugg) = suggestions.pop().unwrap();
+ let (_, start_span, start_sugg) = suggestions.pop().unwrap();
+ span_lint_and_then(
+ cx,
+ BRANCHES_SHARING_CODE,
+ start_span,
+ "all if blocks contain the same code at the start and the end. Here at the start",
+ move |diag| {
+ diag.span_note(end_span, "and here at the end");
+
+ diag.span_suggestion(
+ start_span,
+ "consider moving the start statements out like this",
+ start_sugg,
+ Applicability::Unspecified,
+ );
+
+ diag.span_suggestion(
+ end_span,
+ "and consider moving the end statements out like this",
+ end_sugg,
+ Applicability::Unspecified,
+ );
+
+ add_optional_msgs(diag);
+ },
+ );
+ }
+}
+
+/// This visitor collects `HirId`s and Symbols of defined symbols and `HirId`s of used values.
+struct UsedValueFinderVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+
+ /// The `HirId`s of defined values in the scanned statements
+ defs: FxHashSet<HirId>,
+
+ /// The Symbols of the defined symbols in the scanned statements
+ def_symbols: FxHashSet<Symbol>,
+
+ /// The `HirId`s of the used values
+ uses: FxHashSet<HirId>,
+}
+
+impl<'a, 'tcx> UsedValueFinderVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ UsedValueFinderVisitor {
+ cx,
+ defs: FxHashSet::default(),
+ def_symbols: FxHashSet::default(),
+ uses: FxHashSet::default(),
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UsedValueFinderVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+
+ fn visit_local(&mut self, l: &'tcx rustc_hir::Local<'tcx>) {
+ let local_id = l.pat.hir_id;
+ self.defs.insert(local_id);
+
+ if let Some(sym) = l.pat.simple_ident() {
+ self.def_symbols.insert(sym.name);
+ }
+
+ if let Some(expr) = l.init {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn visit_qpath(&mut self, qpath: &'tcx rustc_hir::QPath<'tcx>, id: HirId, _span: rustc_span::Span) {
+ if let rustc_hir::QPath::Resolved(_, path) = *qpath {
+ if path.segments.len() == 1 {
+ if let rustc_hir::def::Res::Local(var) = self.cx.qpath_res(qpath, id) {
+ self.uses.insert(var);
+ }
+ }
+ }
+ }
+}
+
+/// Implementation of `IFS_SAME_COND`.
+fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
+ let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(expr);
+ h.finish()
+ };
+
+ let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, lhs, rhs) };
+
+ for (i, j) in search_same(conds, hash, eq) {
+ span_lint_and_note(
+ cx,
+ IFS_SAME_COND,
+ j.span,
+ "this `if` has the same condition as a previous `if`",
+ Some(i.span),
+ "same as this",
+ );
+ }
+}
+
+/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`.
+fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
+ let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(expr);
+ h.finish()
+ };
+
+ let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool {
+ // Do not lint if any expr originates from a macro
++ if lhs.span.from_expansion() || rhs.span.from_expansion() {
+ return false;
+ }
+ // Do not spawn warning if `IFS_SAME_COND` already produced it.
+ if eq_expr_value(cx, lhs, rhs) {
+ return false;
+ }
+ SpanlessEq::new(cx).eq_expr(lhs, rhs)
+ };
+
+ for (i, j) in search_same(conds, hash, eq) {
+ span_lint_and_note(
+ cx,
+ SAME_FUNCTIONS_IN_IF_CONDITION,
+ j.span,
+ "this `if` has the same function call as a previous `if`",
+ Some(i.span),
+ "same as this",
+ );
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_note;
+use clippy_utils::ty::is_copy;
+use rustc_hir::{Impl, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+use if_chain::if_chain;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for types that implement `Copy` as well as
+ /// `Iterator`.
+ ///
+ /// ### Why is this bad?
+ /// Implicit copies can be confusing when working with
+ /// iterator combinators.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[derive(Copy, Clone)]
+ /// struct Countdown(u8);
+ ///
+ /// impl Iterator for Countdown {
+ /// // ...
+ /// }
+ ///
+ /// let a: Vec<_> = my_iterator.take(1).collect();
+ /// let b: Vec<_> = my_iterator.collect();
+ /// ```
++ #[clippy::version = "1.30.0"]
+ pub COPY_ITERATOR,
+ pedantic,
+ "implementing `Iterator` on a `Copy` type"
+}
+
+declare_lint_pass!(CopyIterator => [COPY_ITERATOR]);
+
+impl<'tcx> LateLintPass<'tcx> for CopyIterator {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if_chain! {
+ if let ItemKind::Impl(Impl {
+ of_trait: Some(ref trait_ref),
+ ..
+ }) = item.kind;
+ let ty = cx.tcx.type_of(item.def_id);
+ if is_copy(cx, ty);
+ if let Some(trait_id) = trait_ref.trait_def_id();
+ if cx.tcx.is_diagnostic_item(sym::Iterator, trait_id);
+ then {
+ span_lint_and_note(
+ cx,
+ COPY_ITERATOR,
+ item.span,
+ "you are implementing `Iterator` on a `Copy` type",
+ None,
+ "consider implementing `IntoIterator` instead",
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks usage of `std::fs::create_dir` and suggest using `std::fs::create_dir_all` instead.
+ ///
+ /// ### Why is this bad?
+ /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// std::fs::create_dir("foo");
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// std::fs::create_dir_all("foo");
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub CREATE_DIR,
+ restriction,
+ "calling `std::fs::create_dir` instead of `std::fs::create_dir_all`"
+}
+
+declare_lint_pass!(CreateDir => [CREATE_DIR]);
+
+impl LateLintPass<'_> for CreateDir {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(func, args) = expr.kind;
+ if let ExprKind::Path(ref path) = func.kind;
+ if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR);
+ then {
+ span_lint_and_sugg(
+ cx,
+ CREATE_DIR,
+ expr.span,
+ "calling `std::fs::create_dir` where there may be a better way",
+ "consider calling `std::fs::create_dir_all` instead",
+ format!("create_dir_all({})", snippet(cx, args[0].span, "..")),
+ Applicability::MaybeIncorrect,
+ )
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::snippet_opt;
+use rustc_ast::ast;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of dbg!() macro.
+ ///
+ /// ### Why is this bad?
+ /// `dbg!` macro is intended as a debugging tool. It
+ /// should not be in version control.
+ ///
++ /// ### Known problems
++ /// * The lint level is unaffected by crate attributes. The level can still
++ /// be set for functions, modules and other items. To change the level for
++ /// the entire crate, please use command line flags. More information and a
++ /// configuration example can be found in [clippy#6610].
++ ///
++ /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558
++ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// dbg!(true)
+ ///
+ /// // Good
+ /// true
+ /// ```
++ #[clippy::version = "1.34.0"]
+ pub DBG_MACRO,
+ restriction,
+ "`dbg!` macro is intended as a debugging tool"
+}
+
+declare_lint_pass!(DbgMacro => [DBG_MACRO]);
+
+impl EarlyLintPass for DbgMacro {
+ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) {
+ if mac.path == sym!(dbg) {
+ if let Some(sugg) = tts_span(mac.args.inner_tokens()).and_then(|span| snippet_opt(cx, span)) {
+ span_lint_and_sugg(
+ cx,
+ DBG_MACRO,
+ mac.span(),
+ "`dbg!` macro is intended as a debugging tool",
+ "ensure to avoid having uses of it in version control",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ span_lint_and_help(
+ cx,
+ DBG_MACRO,
+ mac.span(),
+ "`dbg!` macro is intended as a debugging tool",
+ None,
+ "ensure to avoid having uses of it in version control",
+ );
+ }
+ }
+ }
+}
+
+// Get span enclosing entire the token stream.
+fn tts_span(tts: TokenStream) -> Option<Span> {
+ let mut cursor = tts.into_trees();
+ let first = cursor.next()?.span();
+ let span = cursor.last().map_or(first, |tree| first.to(tree.span()));
+ Some(span)
+}
--- /dev/null
- use clippy_utils::{any_parent_is_automatically_derived, contains_name, in_macro, match_def_path, paths};
+use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::ty::{has_drop, is_copy};
- if !in_macro(expr.span);
++use clippy_utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for literal calls to `Default::default()`.
+ ///
+ /// ### Why is this bad?
+ /// It's more clear to the reader to use the name of the type whose default is
+ /// being gotten than the generic `Default`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let s: String = Default::default();
+ ///
+ /// // Good
+ /// let s = String::default();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DEFAULT_TRAIT_ACCESS,
+ pedantic,
+ "checks for literal calls to `Default::default()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for immediate reassignment of fields initialized
+ /// with Default::default().
+ ///
+ /// ### Why is this bad?
+ ///It's more idiomatic to use the [functional update syntax](https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax).
+ ///
+ /// ### Known problems
+ /// Assignments to patterns that are of tuple type are not linted.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```
+ /// # #[derive(Default)]
+ /// # struct A { i: i32 }
+ /// let mut a: A = Default::default();
+ /// a.i = 42;
+ /// ```
+ /// Use instead:
+ /// ```
+ /// # #[derive(Default)]
+ /// # struct A { i: i32 }
+ /// let a = A {
+ /// i: 42,
+ /// .. Default::default()
+ /// };
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub FIELD_REASSIGN_WITH_DEFAULT,
+ style,
+ "binding initialized with Default should have its fields set in the initializer"
+}
+
+#[derive(Default)]
+pub struct Default {
+ // Spans linted by `field_reassign_with_default`.
+ reassigned_linted: FxHashSet<Span>,
+}
+
+impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
+
+impl LateLintPass<'_> for Default {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
- if !in_macro(expr.span);
++ if !expr.span.from_expansion();
+ // Avoid cases already linted by `field_reassign_with_default`
+ if !self.reassigned_linted.contains(&expr.span);
+ if let ExprKind::Call(path, ..) = expr.kind;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
+ // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
+ if let QPath::Resolved(None, _path) = qpath;
+ let expr_ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(def, ..) = expr_ty.kind();
+ then {
+ // TODO: Work out a way to put "whatever the imported way of referencing
+ // this type in this file" rather than a fully-qualified type.
+ let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
+ span_lint_and_sugg(
+ cx,
+ DEFAULT_TRAIT_ACCESS,
+ expr.span,
+ &format!("calling `{}` is more clear than this expression", replacement),
+ "try",
+ replacement,
+ Applicability::Unspecified, // First resolve the TODO above
+ );
+ }
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
+ // start from the `let mut _ = _::default();` and look at all the following
+ // statements, see if they re-assign the fields of the binding
+ let stmts_head = match block.stmts {
+ // Skip the last statement since there cannot possibly be any following statements that re-assign fields.
+ [head @ .., _] if !head.is_empty() => head,
+ _ => return,
+ };
+ for (stmt_idx, stmt) in stmts_head.iter().enumerate() {
+ // find all binding statements like `let mut _ = T::default()` where `T::default()` is the
+ // `default` method of the `Default` trait, and store statement index in current block being
+ // checked and the name of the bound variable
+ let (local, variant, binding_name, binding_type, span) = if_chain! {
+ // only take `let ...` statements
+ if let StmtKind::Local(local) = stmt.kind;
+ if let Some(expr) = local.init;
+ if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
++ if !expr.span.from_expansion();
+ // only take bindings to identifiers
+ if let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind;
+ // only when assigning `... = Default::default()`
+ if is_expr_default(expr, cx);
+ let binding_type = cx.typeck_results().node_type(binding_id);
+ if let Some(adt) = binding_type.ty_adt_def();
+ if adt.is_struct();
+ let variant = adt.non_enum_variant();
+ if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
+ let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
+ if variant
+ .fields
+ .iter()
+ .all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
+ let all_fields_are_copy = variant
+ .fields
+ .iter()
+ .all(|field| {
+ is_copy(cx, cx.tcx.type_of(field.did))
+ });
+ if !has_drop(cx, binding_type) || all_fields_are_copy;
+ then {
+ (local, variant, ident.name, binding_type, expr.span)
+ } else {
+ continue;
+ }
+ };
+
+ // find all "later statement"'s where the fields of the binding set as
+ // Default::default() get reassigned, unless the reassignment refers to the original binding
+ let mut first_assign = None;
+ let mut assigned_fields = Vec::new();
+ let mut cancel_lint = false;
+ for consecutive_statement in &block.stmts[stmt_idx + 1..] {
+ // find out if and which field was set by this `consecutive_statement`
+ if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
+ // interrupt and cancel lint if assign_rhs references the original binding
+ if contains_name(binding_name, assign_rhs) {
+ cancel_lint = true;
+ break;
+ }
+
+ // if the field was previously assigned, replace the assignment, otherwise insert the assignment
+ if let Some(prev) = assigned_fields
+ .iter_mut()
+ .find(|(field_name, _)| field_name == &field_ident.name)
+ {
+ *prev = (field_ident.name, assign_rhs);
+ } else {
+ assigned_fields.push((field_ident.name, assign_rhs));
+ }
+
+ // also set first instance of error for help message
+ if first_assign.is_none() {
+ first_assign = Some(consecutive_statement);
+ }
+ }
+ // interrupt if no field was assigned, since we only want to look at consecutive statements
+ else {
+ break;
+ }
+ }
+
+ // if there are incorrectly assigned fields, do a span_lint_and_note to suggest
+ // construction using `Ty { fields, ..Default::default() }`
+ if !assigned_fields.is_empty() && !cancel_lint {
+ // if all fields of the struct are not assigned, add `.. Default::default()` to the suggestion.
+ let ext_with_default = !variant
+ .fields
+ .iter()
+ .all(|field| assigned_fields.iter().any(|(a, _)| a == &field.ident.name));
+
+ let field_list = assigned_fields
+ .into_iter()
+ .map(|(field, rhs)| {
+ // extract and store the assigned value for help message
+ let value_snippet = snippet_with_macro_callsite(cx, rhs.span, "..");
+ format!("{}: {}", field, value_snippet)
+ })
+ .collect::<Vec<String>>()
+ .join(", ");
+
+ // give correct suggestion if generics are involved (see #6944)
+ let binding_type = if_chain! {
+ if let ty::Adt(adt_def, substs) = binding_type.kind();
+ if !substs.is_empty();
+ then {
+ let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
+ let generic_args = substs.iter().collect::<Vec<_>>();
+ let tys_str = generic_args
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>()
+ .join(", ");
+ format!("{}::<{}>", adt_def_ty_name, &tys_str)
+ } else {
+ binding_type.to_string()
+ }
+ };
+
+ let sugg = if ext_with_default {
+ if field_list.is_empty() {
+ format!("{}::default()", binding_type)
+ } else {
+ format!("{} {{ {}, ..Default::default() }}", binding_type, field_list)
+ }
+ } else {
+ format!("{} {{ {} }}", binding_type, field_list)
+ };
+
+ // span lint once per statement that binds default
+ span_lint_and_note(
+ cx,
+ FIELD_REASSIGN_WITH_DEFAULT,
+ first_assign.unwrap().span,
+ "field assignment outside of initializer for an instance created with Default::default()",
+ Some(local.span),
+ &format!(
+ "consider initializing the variable with `{}` and removing relevant reassignments",
+ sugg
+ ),
+ );
+ self.reassigned_linted.insert(span);
+ }
+ }
+ }
+}
+
+/// Checks if the given expression is the `default` method belonging to the `Default` trait.
+fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
+ if_chain! {
+ if let ExprKind::Call(fn_expr, _) = &expr.kind;
+ if let ExprKind::Path(qpath) = &fn_expr.kind;
+ if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
+ then {
+ // right hand side of assignment is `Default::default`
+ match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD)
+ } else {
+ false
+ }
+ }
+}
+
+/// Returns the reassigned field and the assigning expression (right-hand side of assign).
+fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
+ if_chain! {
+ // only take assignments
+ if let StmtKind::Semi(later_expr) = this.kind;
+ if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
+ // only take assignments to fields where the left-hand side field is a field of
+ // the same binding as the previous statement
+ if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
+ if let Some(second_binding_name) = path.segments.last();
+ if second_binding_name.ident.name == binding_name;
+ then {
+ Some((field_ident, assign_rhs))
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::numeric_literal;
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::{
+ intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor},
+ Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::{
+ hir::map::Map,
+ lint::in_external_macro,
+ ty::{self, FloatTy, IntTy, PolyFnSig, Ty},
+};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type
+ /// inference.
+ ///
+ /// Default numeric fallback means that if numeric types have not yet been bound to concrete
+ /// types at the end of type inference, then integer type is bound to `i32`, and similarly
+ /// floating type is bound to `f64`.
+ ///
+ /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback.
+ ///
+ /// ### Why is this bad?
+ /// For those who are very careful about types, default numeric fallback
+ /// can be a pitfall that cause unexpected runtime behavior.
+ ///
+ /// ### Known problems
+ /// This lint can only be allowed at the function level or above.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let i = 10;
+ /// let f = 1.23;
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let i = 10i32;
+ /// let f = 1.23f64;
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub DEFAULT_NUMERIC_FALLBACK,
+ restriction,
+ "usage of unconstrained numeric literals which may cause default numeric fallback."
+}
+
+declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
+
+impl LateLintPass<'_> for DefaultNumericFallback {
+ fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+ let mut visitor = NumericFallbackVisitor::new(cx);
+ visitor.visit_body(body);
+ }
+}
+
+struct NumericFallbackVisitor<'a, 'tcx> {
+ /// Stack manages type bound of exprs. The top element holds current expr type.
+ ty_bounds: Vec<TyBound<'tcx>>,
+
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ ty_bounds: vec![TyBound::Nothing],
+ cx,
+ }
+ }
+
+ /// Check whether a passed literal has potential to cause fallback or not.
+ fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) {
+ if_chain! {
+ if !in_external_macro(self.cx.sess(), lit.span);
+ if let Some(ty_bound) = self.ty_bounds.last();
+ if matches!(lit.node,
+ LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
+ if !ty_bound.is_numeric();
+ then {
+ let (suffix, is_float) = match lit_ty.kind() {
+ ty::Int(IntTy::I32) => ("i32", false),
+ ty::Float(FloatTy::F64) => ("f64", true),
+ // Default numeric fallback never results in other types.
+ _ => return,
+ };
+
+ let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
+ src
+ } else {
+ match lit.node {
+ LitKind::Int(src, _) => format!("{}", src),
+ LitKind::Float(src, _) => format!("{}", src),
+ _ => return,
+ }
+ };
+ let sugg = numeric_literal::format(&src, Some(suffix), is_float);
+ span_lint_and_sugg(
+ self.cx,
+ DEFAULT_NUMERIC_FALLBACK,
+ lit.span,
+ "default numeric fallback might occur",
+ "consider adding suffix",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ #[allow(clippy::too_many_lines)]
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ match &expr.kind {
+ ExprKind::Call(func, args) => {
+ if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) {
+ for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) {
+ // Push found arg type, then visit arg.
+ self.ty_bounds.push(TyBound::Ty(bound));
+ self.visit_expr(expr);
+ self.ty_bounds.pop();
+ }
+ return;
+ }
+ },
+
+ ExprKind::MethodCall(_, _, args, _) => {
+ if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+ let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder();
+ for (expr, bound) in iter::zip(*args, fn_sig.inputs()) {
+ self.ty_bounds.push(TyBound::Ty(bound));
+ self.visit_expr(expr);
+ self.ty_bounds.pop();
+ }
+ return;
+ }
+ },
+
+ ExprKind::Struct(_, fields, base) => {
+ let ty = self.cx.typeck_results().expr_ty(expr);
+ if_chain! {
+ if let Some(adt_def) = ty.ty_adt_def();
+ if adt_def.is_struct();
+ if let Some(variant) = adt_def.variants.iter().next();
+ then {
+ let fields_def = &variant.fields;
+
+ // Push field type then visit each field expr.
+ for field in fields.iter() {
+ let bound =
+ fields_def
+ .iter()
+ .find_map(|f_def| {
+ if f_def.ident == field.ident
+ { Some(self.cx.tcx.type_of(f_def.did)) }
+ else { None }
+ });
+ self.ty_bounds.push(bound.into());
+ self.visit_expr(field.expr);
+ self.ty_bounds.pop();
+ }
+
+ // Visit base with no bound.
+ if let Some(base) = base {
+ self.ty_bounds.push(TyBound::Nothing);
+ self.visit_expr(base);
+ self.ty_bounds.pop();
+ }
+ return;
+ }
+ }
+ },
+
+ ExprKind::Lit(lit) => {
+ let ty = self.cx.typeck_results().expr_ty(expr);
+ self.check_lit(lit, ty);
+ return;
+ },
+
+ _ => {},
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if local.ty.is_some() {
+ self.ty_bounds.push(TyBound::Any);
+ } else {
+ self.ty_bounds.push(TyBound::Nothing);
+ }
+ },
+
+ _ => self.ty_bounds.push(TyBound::Nothing),
+ }
+
+ walk_stmt(self, stmt);
+ self.ty_bounds.pop();
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'tcx>> {
+ let node_ty = cx.typeck_results().node_type_opt(hir_id)?;
+ // We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs.
+ match node_ty.kind() {
+ ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)),
+ ty::FnPtr(fn_sig) => Some(*fn_sig),
+ _ => None,
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum TyBound<'tcx> {
+ Any,
+ Ty(Ty<'tcx>),
+ Nothing,
+}
+
+impl<'tcx> TyBound<'tcx> {
+ fn is_numeric(self) -> bool {
+ match self {
+ TyBound::Any => true,
+ TyBound::Ty(t) => t.is_numeric(),
+ TyBound::Nothing => false,
+ }
+ }
+}
+
+impl<'tcx> From<Option<Ty<'tcx>>> for TyBound<'tcx> {
+ fn from(v: Option<Ty<'tcx>>) -> Self {
+ match v {
+ Some(t) => TyBound::Ty(t),
+ None => TyBound::Nothing,
+ }
+ }
+}
--- /dev/null
+// NOTE: if you add a deprecated lint in this file, please add a corresponding test in
+// tests/ui/deprecated.rs
+
+/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This
+/// enables the simple extraction of the metadata without changing the current deprecation
+/// declaration.
+pub struct ClippyDeprecatedLint;
+
+macro_rules! declare_deprecated_lint {
+ { $(#[$attr:meta])* pub $name: ident, $_reason: expr} => {
+ $(#[$attr])*
+ #[allow(dead_code)]
+ pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {};
+ }
+}
+
+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.
++ #[clippy::version = "pre 1.29.0"]
+ 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.
++ #[clippy::version = "pre 1.29.0"]
+ 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
++ #[clippy::version = "pre 1.29.0"]
+ 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.
++ #[clippy::version = "pre 1.29.0"]
+ 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.
++ #[clippy::version = "pre 1.29.0"]
+ pub UNSTABLE_AS_MUT_SLICE,
+ "`Vec::as_mut_slice` has been stabilized in 1.7"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint should never have applied to non-pointer types, as transmuting
+ /// between non-pointer types of differing alignment is well-defined behavior (it's semantically
+ /// equivalent to a memcpy). This lint has thus been refactored into two separate lints:
+ /// cast_ptr_alignment and transmute_ptr_to_ptr.
++ #[clippy::version = "pre 1.29.0"]
+ 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.
++ #[clippy::version = "1.30.0"]
+ 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.
++ #[clippy::version = "pre 1.29.0"]
+ 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.
++ #[clippy::version = "pre 1.29.0"]
+ 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 #[must_use] in rustc.
++ #[clippy::version = "1.39.0"]
+ 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
+ /// Associated-constants are now preferred.
++ #[clippy::version = "1.44.0"]
+ pub REPLACE_CONSTS,
+ "associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// The regex! macro does not exist anymore.
++ #[clippy::version = "1.47.0"]
+ pub REGEX_MACRO,
+ "the regex! macro has been removed from the regex crate in 2018"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint has been replaced by `manual_find_map`, a
+ /// more specific lint.
++ #[clippy::version = "1.51.0"]
+ pub FIND_MAP,
+ "this lint has been replaced by `manual_find_map`, a more specific lint"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// This lint has been replaced by `manual_filter_map`, a
+ /// more specific lint.
++ #[clippy::version = "1.53.0"]
+ pub FILTER_MAP,
+ "this lint has been replaced by `manual_filter_map`, a more specific lint"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// The `avoid_breaking_exported_api` config option was added, which
+ /// enables the `enum_variant_names` lint for public items.
+ /// ```
++ #[clippy::version = "1.54.0"]
+ pub PUB_ENUM_VARIANT_NAMES,
+ "set the `avoid-breaking-exported-api` config option to `false` to enable the `enum_variant_names` lint for public items"
+}
+
+declare_deprecated_lint! {
+ /// ### What it does
+ /// Nothing. This lint has been deprecated.
+ ///
+ /// ### Deprecation reason
+ /// The `avoid_breaking_exported_api` config option was added, which
+ /// enables the `wrong_self_conversion` lint for public items.
++ #[clippy::version = "1.54.0"]
+ pub WRONG_PUB_SELF_CONVENTION,
+ "set the `avoid-breaking-exported-api` config option to `false` to enable the `wrong_self_convention` lint for public items"
+}
--- /dev/null
- use clippy_utils::diagnostics::span_lint_and_sugg;
- use clippy_utils::source::snippet_with_context;
++use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
++use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
+use clippy_utils::ty::peel_mid_ty_refs;
- use clippy_utils::{get_parent_node, in_macro, is_lint_allowed};
- use rustc_ast::util::parser::PREC_PREFIX;
++use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local};
++use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
++use rustc_data_structures::fx::FxIndexMap;
+use rustc_errors::Applicability;
- use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, MatchSource, Mutability, Node, UnOp};
++use rustc_hir::{
++ BindingAnnotation, Body, BodyId, BorrowKind, Destination, Expr, ExprKind, HirId, MatchSource, Mutability, Node,
++ Pat, PatKind, UnOp,
++};
+use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
+use rustc_middle::ty::{self, Ty, TyCtxt, TyS, TypeckResults};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{symbol::sym, Span};
++use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit `deref()` or `deref_mut()` method calls.
+ ///
+ /// ### Why is this bad?
+ /// Dereferencing by `&*x` or `&mut *x` is clearer and more concise,
+ /// when not part of a method chain.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::ops::Deref;
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b: &str = a.deref();
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let a: &mut String = &mut String::from("foo");
+ /// let b = &*a;
+ /// ```
+ ///
+ /// This lint excludes
+ /// ```rust,ignore
+ /// let _ = d.unwrap().deref();
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub EXPLICIT_DEREF_METHODS,
+ pedantic,
+ "Explicit use of deref or deref_mut method while not in a method chain."
+}
+
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for address of operations (`&`) that are going to
++ /// be dereferenced immediately by the compiler.
++ ///
++ /// ### Why is this bad?
++ /// Suggests that the receiver of the expression borrows
++ /// the expression.
++ ///
++ /// ### Example
++ /// ```rust
++ /// fn fun(_a: &i32) {}
++ ///
++ /// // Bad
++ /// let x: &i32 = &&&&&&5;
++ /// fun(&x);
++ ///
++ /// // Good
++ /// let x: &i32 = &5;
++ /// fun(x);
++ /// ```
++ #[clippy::version = "pre 1.29.0"]
++ pub NEEDLESS_BORROW,
++ style,
++ "taking a reference that is going to be automatically dereferenced"
++}
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for `ref` bindings which create a reference to a reference.
++ ///
++ /// ### Why is this bad?
++ /// The address-of operator at the use site is clearer about the need for a reference.
++ ///
++ /// ### Example
++ /// ```rust
++ /// // Bad
++ /// let x = Some("");
++ /// if let Some(ref x) = x {
++ /// // use `x` here
++ /// }
++ ///
++ /// // Good
++ /// let x = Some("");
++ /// if let Some(x) = x {
++ /// // use `&x` here
++ /// }
++ /// ```
++ #[clippy::version = "1.54.0"]
++ pub REF_BINDING_TO_REFERENCE,
++ pedantic,
++ "`ref` binding to a reference"
++}
++
+impl_lint_pass!(Dereferencing => [
+ EXPLICIT_DEREF_METHODS,
++ NEEDLESS_BORROW,
++ REF_BINDING_TO_REFERENCE,
+]);
+
+#[derive(Default)]
+pub struct Dereferencing {
+ state: Option<(State, StateData)>,
+
+ // While parsing a `deref` method call in ufcs form, the path to the function is itself an
+ // expression. This is to store the id of that expression so it can be skipped when
+ // `check_expr` is called for it.
+ skip_expr: Option<HirId>,
++
++ /// The body the first local was found in. Used to emit lints when the traversal of the body has
++ /// been finished. Note we can't lint at the end of every body as they can be nested within each
++ /// other.
++ current_body: Option<BodyId>,
++ /// The list of locals currently being checked by the lint.
++ /// If the value is `None`, then the binding has been seen as a ref pattern, but is not linted.
++ /// This is needed for or patterns where one of the branches can be linted, but another can not
++ /// be.
++ ///
++ /// e.g. `m!(x) | Foo::Bar(ref x)`
++ ref_locals: FxIndexMap<HirId, Option<RefPat>>,
+}
+
+struct StateData {
+ /// Span of the top level expression
+ span: Span,
+ /// The required mutability
+ target_mut: Mutability,
+}
+
+enum State {
+ // Any number of deref method calls.
+ DerefMethod {
+ // The number of calls in a sequence which changed the referenced type
+ ty_changed_count: usize,
+ is_final_ufcs: bool,
+ },
++ DerefedBorrow {
++ count: u32,
++ },
+}
+
+// A reference operation considered by this lint pass
+enum RefOp {
+ Method(Mutability),
+ Deref,
+ AddrOf,
+}
+
++struct RefPat {
++ /// Whether every usage of the binding is dereferenced.
++ always_deref: bool,
++ /// The spans of all the ref bindings for this local.
++ spans: Vec<Span>,
++ /// The applicability of this suggestion.
++ app: Applicability,
++ /// All the replacements which need to be made.
++ replacements: Vec<(Span, String)>,
++}
++
+impl<'tcx> LateLintPass<'tcx> for Dereferencing {
++ #[allow(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Skip path expressions from deref calls. e.g. `Deref::deref(e)`
+ if Some(expr.hir_id) == self.skip_expr.take() {
+ return;
+ }
+
++ if let Some(local) = path_to_local(expr) {
++ self.check_local_usage(cx, expr, local);
++ }
++
+ // Stop processing sub expressions when a macro call is seen
- if in_macro(expr.span) {
++ if expr.span.from_expansion() {
+ if let Some((state, data)) = self.state.take() {
+ report(cx, expr, state, data);
+ }
+ return;
+ }
+
+ let typeck = cx.typeck_results();
+ let (kind, sub_expr) = if let Some(x) = try_parse_ref_op(cx.tcx, typeck, expr) {
+ x
+ } else {
+ // The whole chain of reference operations has been seen
+ if let Some((state, data)) = self.state.take() {
+ report(cx, expr, state, data);
+ }
+ return;
+ };
+
+ match (self.state.take(), kind) {
+ (None, kind) => {
+ let parent = get_parent_node(cx.tcx, expr.hir_id);
+ let expr_ty = typeck.expr_ty(expr);
+
+ match kind {
+ RefOp::Method(target_mut)
+ if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
+ && is_linted_explicit_deref_position(parent, expr.hir_id, expr.span) =>
+ {
+ self.state = Some((
+ State::DerefMethod {
+ ty_changed_count: if deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr)) {
+ 0
+ } else {
+ 1
+ },
+ is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
+ },
+ StateData {
+ span: expr.span,
+ target_mut,
+ },
+ ));
+ },
++ RefOp::AddrOf => {
++ // Find the number of times the borrow is auto-derefed.
++ let mut iter = find_adjustments(cx.tcx, typeck, expr).iter();
++ if let Some((i, adjust)) = iter.by_ref().enumerate().find_map(|(i, adjust)| {
++ if !matches!(adjust.kind, Adjust::Deref(_)) {
++ Some((i, adjust))
++ } else if !adjust.target.is_ref() {
++ // Add one to the number of references found.
++ Some((i + 1, adjust))
++ } else {
++ None
++ }
++ }) {
++ // Found two consecutive derefs. At least one can be removed.
++ if i > 1 {
++ let target_mut = iter::once(adjust)
++ .chain(iter)
++ .find_map(|adjust| match adjust.kind {
++ Adjust::Borrow(AutoBorrow::Ref(_, m)) => Some(m.into()),
++ _ => None,
++ })
++ // This default should never happen. Auto-deref always reborrows.
++ .unwrap_or(Mutability::Not);
++ self.state = Some((
++ // Subtract one for the current borrow expression, and one to cover the last
++ // reference which can't be removed (it's either reborrowed, or needed for
++ // auto-deref to happen).
++ State::DerefedBorrow {
++ count:
++ // Truncation here would require more than a `u32::MAX` level reference. The compiler
++ // does not support this.
++ #[allow(clippy::cast_possible_truncation)]
++ { i as u32 - 2 }
++ },
++ StateData {
++ span: expr.span,
++ target_mut,
++ },
++ ));
++ }
++ }
++ },
+ _ => (),
+ }
+ },
+ (Some((State::DerefMethod { ty_changed_count, .. }, data)), RefOp::Method(_)) => {
+ self.state = Some((
+ State::DerefMethod {
+ ty_changed_count: if deref_method_same_type(typeck.expr_ty(expr), typeck.expr_ty(sub_expr)) {
+ ty_changed_count
+ } else {
+ ty_changed_count + 1
+ },
+ is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)),
+ },
+ data,
+ ));
+ },
++ (Some((State::DerefedBorrow { count }, data)), RefOp::AddrOf) if count != 0 => {
++ self.state = Some((State::DerefedBorrow { count: count - 1 }, data));
++ },
+
+ (Some((state, data)), _) => report(cx, expr, state, data),
+ }
+ }
++
++ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
++ if let PatKind::Binding(BindingAnnotation::Ref, id, name, _) = pat.kind {
++ if let Some(opt_prev_pat) = self.ref_locals.get_mut(&id) {
++ // This binding id has been seen before. Add this pattern to the list of changes.
++ if let Some(prev_pat) = opt_prev_pat {
++ if pat.span.from_expansion() {
++ // Doesn't match the context of the previous pattern. Can't lint here.
++ *opt_prev_pat = None;
++ } else {
++ prev_pat.spans.push(pat.span);
++ prev_pat.replacements.push((
++ pat.span,
++ snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut prev_pat.app)
++ .0
++ .into(),
++ ));
++ }
++ }
++ return;
++ }
++
++ if_chain! {
++ if !pat.span.from_expansion();
++ if let ty::Ref(_, tam, _) = *cx.typeck_results().pat_ty(pat).kind();
++ // only lint immutable refs, because borrowed `&mut T` cannot be moved out
++ if let ty::Ref(_, _, Mutability::Not) = *tam.kind();
++ then {
++ let mut app = Applicability::MachineApplicable;
++ let snip = snippet_with_context(cx, name.span, pat.span.ctxt(), "..", &mut app).0;
++ self.current_body = self.current_body.or(cx.enclosing_body);
++ self.ref_locals.insert(
++ id,
++ Some(RefPat {
++ always_deref: true,
++ spans: vec![pat.span],
++ app,
++ replacements: vec![(pat.span, snip.into())],
++ }),
++ );
++ }
++ }
++ }
++ }
++
++ fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
++ if Some(body.id()) == self.current_body {
++ for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
++ let replacements = pat.replacements;
++ let app = pat.app;
++ span_lint_and_then(
++ cx,
++ if pat.always_deref {
++ NEEDLESS_BORROW
++ } else {
++ REF_BINDING_TO_REFERENCE
++ },
++ pat.spans,
++ "this pattern creates a reference to a reference",
++ |diag| {
++ diag.multipart_suggestion("try this", replacements, app);
++ },
++ );
++ }
++ self.current_body = None;
++ }
++ }
+}
+
+fn try_parse_ref_op(
+ tcx: TyCtxt<'tcx>,
+ typeck: &'tcx TypeckResults<'_>,
+ expr: &'tcx Expr<'_>,
+) -> Option<(RefOp, &'tcx Expr<'tcx>)> {
+ let (def_id, arg) = match expr.kind {
+ ExprKind::MethodCall(_, _, [arg], _) => (typeck.type_dependent_def_id(expr.hir_id)?, arg),
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(path),
+ hir_id,
+ ..
+ },
+ [arg],
+ ) => (typeck.qpath_res(path, *hir_id).opt_def_id()?, arg),
+ ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => {
+ return Some((RefOp::Deref, sub_expr));
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, _, sub_expr) => return Some((RefOp::AddrOf, sub_expr)),
+ _ => return None,
+ };
+ if tcx.is_diagnostic_item(sym::deref_method, def_id) {
+ Some((RefOp::Method(Mutability::Not), arg))
+ } else if tcx.trait_of_item(def_id)? == tcx.lang_items().deref_mut_trait()? {
+ Some((RefOp::Method(Mutability::Mut), arg))
+ } else {
+ None
+ }
+}
+
+// Checks whether the type for a deref call actually changed the type, not just the mutability of
+// the reference.
+fn deref_method_same_type(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
+ match (result_ty.kind(), arg_ty.kind()) {
+ (ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => TyS::same_type(result_ty, arg_ty),
+
+ // The result type for a deref method is always a reference
+ // Not matching the previous pattern means the argument type is not a reference
+ // This means that the type did change
+ _ => false,
+ }
+}
+
+// Checks whether the parent node is a suitable context for switching from a deref method to the
+// deref operator.
+fn is_linted_explicit_deref_position(parent: Option<Node<'_>>, child_id: HirId, child_span: Span) -> bool {
+ let parent = match parent {
+ Some(Node::Expr(e)) if e.span.ctxt() == child_span.ctxt() => e,
+ _ => return true,
+ };
+ match parent.kind {
+ // Leave deref calls in the middle of a method chain.
+ // e.g. x.deref().foo()
+ ExprKind::MethodCall(_, _, [self_arg, ..], _) if self_arg.hir_id == child_id => false,
+
+ // Leave deref calls resulting in a called function
+ // e.g. (x.deref())()
+ ExprKind::Call(func_expr, _) if func_expr.hir_id == child_id => false,
+
+ // Makes an ugly suggestion
+ // e.g. *x.deref() => *&*x
+ ExprKind::Unary(UnOp::Deref, _)
+ // Postfix expressions would require parens
+ | ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
+ | ExprKind::Field(..)
+ | ExprKind::Index(..)
+ | ExprKind::Err => false,
+
+ ExprKind::Box(..)
+ | ExprKind::ConstBlock(..)
+ | ExprKind::Array(_)
+ | ExprKind::Call(..)
+ | ExprKind::MethodCall(..)
+ | ExprKind::Tup(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Unary(..)
+ | ExprKind::Lit(..)
+ | ExprKind::Cast(..)
+ | ExprKind::Type(..)
+ | ExprKind::DropTemps(..)
+ | ExprKind::If(..)
+ | ExprKind::Loop(..)
+ | ExprKind::Match(..)
+ | ExprKind::Let(..)
+ | ExprKind::Closure(..)
+ | ExprKind::Block(..)
+ | ExprKind::Assign(..)
+ | ExprKind::AssignOp(..)
+ | ExprKind::Path(..)
+ | ExprKind::AddrOf(..)
+ | ExprKind::Break(..)
+ | ExprKind::Continue(..)
+ | ExprKind::Ret(..)
+ | ExprKind::InlineAsm(..)
+ | ExprKind::LlvmInlineAsm(..)
+ | ExprKind::Struct(..)
+ | ExprKind::Repeat(..)
+ | ExprKind::Yield(..) => true,
+ }
+}
+
++/// Adjustments are sometimes made in the parent block rather than the expression itself.
++fn find_adjustments(
++ tcx: TyCtxt<'tcx>,
++ typeck: &'tcx TypeckResults<'_>,
++ expr: &'tcx Expr<'_>,
++) -> &'tcx [Adjustment<'tcx>] {
++ let map = tcx.hir();
++ let mut iter = map.parent_iter(expr.hir_id);
++ let mut prev = expr;
++
++ loop {
++ match typeck.expr_adjustments(prev) {
++ [] => (),
++ a => break a,
++ };
++
++ match iter.next().map(|(_, x)| x) {
++ Some(Node::Block(_)) => {
++ if let Some((_, Node::Expr(e))) = iter.next() {
++ prev = e;
++ } else {
++ // This shouldn't happen. Blocks are always contained in an expression.
++ break &[];
++ }
++ },
++ Some(Node::Expr(&Expr {
++ kind: ExprKind::Break(Destination { target_id: Ok(id), .. }, _),
++ ..
++ })) => {
++ if let Some(Node::Expr(e)) = map.find(id) {
++ prev = e;
++ iter = map.parent_iter(id);
++ } else {
++ // This shouldn't happen. The destination should exist.
++ break &[];
++ }
++ },
++ _ => break &[],
++ }
++ }
++}
++
+#[allow(clippy::needless_pass_by_value)]
+fn report(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data: StateData) {
+ match state {
+ State::DerefMethod {
+ ty_changed_count,
+ is_final_ufcs,
+ } => {
+ let mut app = Applicability::MachineApplicable;
+ let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
+ let ty = cx.typeck_results().expr_ty(expr);
+ let (_, ref_count) = peel_mid_ty_refs(ty);
+ let deref_str = if ty_changed_count >= ref_count && ref_count != 0 {
+ // a deref call changing &T -> &U requires two deref operators the first time
+ // this occurs. One to remove the reference, a second to call the deref impl.
+ "*".repeat(ty_changed_count + 1)
+ } else {
+ "*".repeat(ty_changed_count)
+ };
+ let addr_of_str = if ty_changed_count < ref_count {
+ // Check if a reborrow from &mut T -> &T is required.
+ if data.target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) {
+ "&*"
+ } else {
+ ""
+ }
+ } else if data.target_mut == Mutability::Mut {
+ "&mut "
+ } else {
+ "&"
+ };
+
+ let expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX {
+ format!("({})", expr_str)
+ } else {
+ expr_str.into_owned()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_DEREF_METHODS,
+ data.span,
+ match data.target_mut {
+ Mutability::Not => "explicit `deref` method call",
+ Mutability::Mut => "explicit `deref_mut` method call",
+ },
+ "try this",
+ format!("{}{}{}", addr_of_str, deref_str, expr_str),
+ app,
+ );
+ },
++ State::DerefedBorrow { .. } => {
++ let mut app = Applicability::MachineApplicable;
++ let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0;
++ span_lint_and_sugg(
++ cx,
++ NEEDLESS_BORROW,
++ data.span,
++ &format!(
++ "this expression borrows a reference (`{}`) that is immediately dereferenced by the compiler",
++ cx.typeck_results().expr_ty(expr),
++ ),
++ "change this to",
++ snip.into(),
++ app,
++ );
++ },
++ }
++}
++
++impl Dereferencing {
++ fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, local: HirId) {
++ if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
++ if let Some(pat) = outer_pat {
++ // Check for auto-deref
++ if !matches!(
++ cx.typeck_results().expr_adjustments(e),
++ [
++ Adjustment {
++ kind: Adjust::Deref(_),
++ ..
++ },
++ Adjustment {
++ kind: Adjust::Deref(_),
++ ..
++ },
++ ..
++ ]
++ ) {
++ match get_parent_expr(cx, e) {
++ // Field accesses are the same no matter the number of references.
++ Some(Expr {
++ kind: ExprKind::Field(..),
++ ..
++ }) => (),
++ Some(&Expr {
++ span,
++ kind: ExprKind::Unary(UnOp::Deref, _),
++ ..
++ }) if !span.from_expansion() => {
++ // Remove explicit deref.
++ let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0;
++ pat.replacements.push((span, snip.into()));
++ },
++ Some(parent) if !parent.span.from_expansion() => {
++ // Double reference might be needed at this point.
++ if parent.precedence().order() == PREC_POSTFIX {
++ // Parentheses would be needed here, don't lint.
++ *outer_pat = None;
++ } else {
++ pat.always_deref = false;
++ let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0;
++ pat.replacements.push((e.span, format!("&{}", snip)));
++ }
++ },
++ _ if !e.span.from_expansion() => {
++ // Double reference might be needed at this point.
++ pat.always_deref = false;
++ let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app);
++ pat.replacements.push((e.span, format!("&{}", snip)));
++ },
++ // Edge case for macros. The span of the identifier will usually match the context of the
++ // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
++ // macros
++ _ => *outer_pat = None,
++ }
++ }
++ }
++ }
+ }
+}
--- /dev/null
- use clippy_utils::{in_macro, is_automatically_derived, is_default_equivalent, remove_blocks};
+use clippy_utils::diagnostics::span_lint_and_help;
- if !in_macro(item.span);
++use clippy_utils::{is_automatically_derived, is_default_equivalent, remove_blocks};
+use rustc_hir::{
+ def::{DefKind, Res},
+ Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects manual `std::default::Default` implementations that are identical to a derived implementation.
+ ///
+ /// ### Why is this bad?
+ /// It is less concise.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo {
+ /// bar: bool
+ /// }
+ ///
+ /// impl std::default::Default for Foo {
+ /// fn default() -> Self {
+ /// Self {
+ /// bar: false
+ /// }
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Could be written as:
+ ///
+ /// ```rust
+ /// #[derive(Default)]
+ /// struct Foo {
+ /// bar: bool
+ /// }
+ /// ```
+ ///
+ /// ### Known problems
+ /// Derive macros [sometimes use incorrect bounds](https://github.com/rust-lang/rust/issues/26925)
+ /// in generic types and the user defined `impl` maybe is more generalized or
+ /// specialized than what derive will produce. This lint can't detect the manual `impl`
+ /// has exactly equal bounds, and therefore this lint is disabled for types with
+ /// generic parameters.
+ ///
++ #[clippy::version = "1.57.0"]
+ pub DERIVABLE_IMPLS,
+ complexity,
+ "manual implementation of the `Default` trait which is equal to a derive"
+}
+
+declare_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]);
+
+fn is_path_self(e: &Expr<'_>) -> bool {
+ if let ExprKind::Path(QPath::Resolved(_, p)) = e.kind {
+ matches!(p.res, Res::SelfCtor(..) | Res::Def(DefKind::Ctor(..), _))
+ } else {
+ false
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if_chain! {
+ if let ItemKind::Impl(Impl {
+ of_trait: Some(ref trait_ref),
+ items: [child],
+ self_ty,
+ ..
+ }) = item.kind;
+ if let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if !is_automatically_derived(attrs);
++ if !item.span.from_expansion();
+ if let Some(def_id) = trait_ref.trait_def_id();
+ if cx.tcx.is_diagnostic_item(sym::Default, def_id);
+ if let impl_item_hir = child.id.hir_id();
+ if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
+ if let ImplItemKind::Fn(_, b) = &impl_item.kind;
+ if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
+ if let Some(adt_def) = cx.tcx.type_of(item.def_id).ty_adt_def();
+ if !attrs.iter().any(|attr| attr.doc_str().is_some());
+ if let child_attrs = cx.tcx.hir().attrs(impl_item_hir);
+ if !child_attrs.iter().any(|attr| attr.doc_str().is_some());
+ if adt_def.is_struct();
+ then {
+ if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind {
+ if let Some(PathSegment { args: Some(a), .. }) = p.segments.last() {
+ for arg in a.args {
+ if !matches!(arg, GenericArg::Lifetime(_)) {
+ return;
+ }
+ }
+ }
+ }
+ let should_emit = match remove_blocks(func_expr).kind {
+ ExprKind::Tup(fields) => fields.iter().all(|e| is_default_equivalent(cx, e)),
+ ExprKind::Call(callee, args)
+ if is_path_self(callee) => args.iter().all(|e| is_default_equivalent(cx, e)),
+ ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_equivalent(cx, ef.expr)),
+ _ => false,
+ };
+ if should_emit {
+ let path_string = cx.tcx.def_path_str(adt_def.did);
+ span_lint_and_help(
+ cx,
+ DERIVABLE_IMPLS,
+ item.span,
+ "this `impl` can be derived",
+ None,
+ &format!("try annotating `{}` with `#[derive(Default)]`", path_string),
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
+use clippy_utils::paths;
+use clippy_utils::ty::{implements_trait, is_copy};
+use clippy_utils::{get_trait_def_id, is_automatically_derived, is_lint_allowed, match_def_path};
+use if_chain::if_chain;
+use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{
+ BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `Hash` but implementing `PartialEq`
+ /// explicitly or vice versa.
+ ///
+ /// ### Why is this bad?
+ /// The implementation of these traits must agree (for
+ /// example for use with `HashMap`) so it’s probably a bad idea to use a
+ /// default-generated `Hash` implementation with an explicitly defined
+ /// `PartialEq`. In particular, the following must hold for any type:
+ ///
+ /// ```text
+ /// k1 == k2 ⇒ hash(k1) == hash(k2)
+ /// ```
+ ///
+ /// ### Example
+ /// ```ignore
+ /// #[derive(Hash)]
+ /// struct Foo;
+ ///
+ /// impl PartialEq for Foo {
+ /// ...
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DERIVE_HASH_XOR_EQ,
+ correctness,
+ "deriving `Hash` but implementing `PartialEq` explicitly"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `Ord` but implementing `PartialOrd`
+ /// explicitly or vice versa.
+ ///
+ /// ### Why is this bad?
+ /// The implementation of these traits must agree (for
+ /// example for use with `sort`) so it’s probably a bad idea to use a
+ /// default-generated `Ord` implementation with an explicitly defined
+ /// `PartialOrd`. In particular, the following must hold for any type
+ /// implementing `Ord`:
+ ///
+ /// ```text
+ /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap()
+ /// ```
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[derive(Ord, PartialEq, Eq)]
+ /// struct Foo;
+ ///
+ /// impl PartialOrd for Foo {
+ /// ...
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// #[derive(PartialEq, Eq)]
+ /// struct Foo;
+ ///
+ /// impl PartialOrd for Foo {
+ /// fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
+ /// Some(self.cmp(other))
+ /// }
+ /// }
+ ///
+ /// impl Ord for Foo {
+ /// ...
+ /// }
+ /// ```
+ /// or, if you don't need a custom ordering:
+ /// ```rust,ignore
+ /// #[derive(Ord, PartialOrd, PartialEq, Eq)]
+ /// struct Foo;
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub DERIVE_ORD_XOR_PARTIAL_ORD,
+ correctness,
+ "deriving `Ord` but implementing `PartialOrd` explicitly"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit `Clone` implementations for `Copy`
+ /// types.
+ ///
+ /// ### Why is this bad?
+ /// To avoid surprising behaviour, these traits should
+ /// agree and the behaviour of `Copy` cannot be overridden. In almost all
+ /// situations a `Copy` type should have a `Clone` implementation that does
+ /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
+ /// gets you.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[derive(Copy)]
+ /// struct Foo;
+ ///
+ /// impl Clone for Foo {
+ /// // ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EXPL_IMPL_CLONE_ON_COPY,
+ pedantic,
+ "implementing `Clone` explicitly on `Copy` types"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for deriving `serde::Deserialize` on a type that
+ /// has methods using `unsafe`.
+ ///
+ /// ### Why is this bad?
+ /// Deriving `serde::Deserialize` will create a constructor
+ /// that may violate invariants hold by another constructor.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use serde::Deserialize;
+ ///
+ /// #[derive(Deserialize)]
+ /// pub struct Foo {
+ /// // ..
+ /// }
+ ///
+ /// impl Foo {
+ /// pub fn new() -> Self {
+ /// // setup here ..
+ /// }
+ ///
+ /// pub unsafe fn parts() -> (&str, &str) {
+ /// // assumes invariants hold
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub UNSAFE_DERIVE_DESERIALIZE,
+ pedantic,
+ "deriving `serde::Deserialize` on a type that has methods using `unsafe`"
+}
+
+declare_lint_pass!(Derive => [
+ EXPL_IMPL_CLONE_ON_COPY,
+ DERIVE_HASH_XOR_EQ,
+ DERIVE_ORD_XOR_PARTIAL_ORD,
+ UNSAFE_DERIVE_DESERIALIZE
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Derive {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if let ItemKind::Impl(Impl {
+ of_trait: Some(ref trait_ref),
+ ..
+ }) = item.kind
+ {
+ let ty = cx.tcx.type_of(item.def_id);
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let is_automatically_derived = is_automatically_derived(attrs);
+
+ check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
+ check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
+
+ if is_automatically_derived {
+ check_unsafe_derive_deserialize(cx, item, trait_ref, ty);
+ } else {
+ check_copy_clone(cx, item, trait_ref, ty);
+ }
+ }
+ }
+}
+
+/// Implementation of the `DERIVE_HASH_XOR_EQ` lint.
+fn check_hash_peq<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ trait_ref: &TraitRef<'_>,
+ ty: Ty<'tcx>,
+ hash_is_automatically_derived: bool,
+) {
+ if_chain! {
+ if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait();
+ if let Some(def_id) = trait_ref.trait_def_id();
+ if match_def_path(cx, def_id, &paths::HASH);
+ then {
+ // Look for the PartialEq implementations for `ty`
+ cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
+ let peq_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
+
+ if peq_is_automatically_derived == hash_is_automatically_derived {
+ return;
+ }
+
+ let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
+
+ // Only care about `impl PartialEq<Foo> for Foo`
+ // For `impl PartialEq<B> for A, input_types is [A, B]
+ if trait_ref.substs.type_at(1) == ty {
+ let mess = if peq_is_automatically_derived {
+ "you are implementing `Hash` explicitly but have derived `PartialEq`"
+ } else {
+ "you are deriving `Hash` but have implemented `PartialEq` explicitly"
+ };
+
+ span_lint_and_then(
+ cx,
+ DERIVE_HASH_XOR_EQ,
+ span,
+ mess,
+ |diag| {
+ if let Some(local_def_id) = impl_id.as_local() {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ diag.span_note(
+ cx.tcx.hir().span(hir_id),
+ "`PartialEq` implemented here"
+ );
+ }
+ }
+ );
+ }
+ });
+ }
+ }
+}
+
+/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
+fn check_ord_partial_ord<'tcx>(
+ cx: &LateContext<'tcx>,
+ span: Span,
+ trait_ref: &TraitRef<'_>,
+ ty: Ty<'tcx>,
+ ord_is_automatically_derived: bool,
+) {
+ if_chain! {
+ if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD);
+ if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait();
+ if let Some(def_id) = &trait_ref.trait_def_id();
+ if *def_id == ord_trait_def_id;
+ then {
+ // Look for the PartialOrd implementations for `ty`
+ cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
+ let partial_ord_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
+
+ if partial_ord_is_automatically_derived == ord_is_automatically_derived {
+ return;
+ }
+
+ let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation");
+
+ // Only care about `impl PartialOrd<Foo> for Foo`
+ // For `impl PartialOrd<B> for A, input_types is [A, B]
+ if trait_ref.substs.type_at(1) == ty {
+ let mess = if partial_ord_is_automatically_derived {
+ "you are implementing `Ord` explicitly but have derived `PartialOrd`"
+ } else {
+ "you are deriving `Ord` but have implemented `PartialOrd` explicitly"
+ };
+
+ span_lint_and_then(
+ cx,
+ DERIVE_ORD_XOR_PARTIAL_ORD,
+ span,
+ mess,
+ |diag| {
+ if let Some(local_def_id) = impl_id.as_local() {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ diag.span_note(
+ cx.tcx.hir().span(hir_id),
+ "`PartialOrd` implemented here"
+ );
+ }
+ }
+ );
+ }
+ });
+ }
+ }
+}
+
+/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
+fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
+ let clone_id = match cx.tcx.lang_items().clone_trait() {
+ Some(id) if trait_ref.trait_def_id() == Some(id) => id,
+ _ => return,
+ };
+ let copy_id = match cx.tcx.lang_items().copy_trait() {
+ Some(id) => id,
+ None => return,
+ };
+ let (ty_adt, ty_subs) = match *ty.kind() {
+ // Unions can't derive clone.
+ ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),
+ _ => return,
+ };
+ // If the current self type doesn't implement Copy (due to generic constraints), search to see if
+ // there's a Copy impl for any instance of the adt.
+ if !is_copy(cx, ty) {
+ if ty_subs.non_erasable_generics().next().is_some() {
+ let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| {
+ impls
+ .iter()
+ .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did == adt.did))
+ });
+ if !has_copy_impl {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+ // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
+ // this impl.
+ if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
+ return;
+ }
+
+ span_lint_and_note(
+ cx,
+ EXPL_IMPL_CLONE_ON_COPY,
+ item.span,
+ "you are implementing `Clone` explicitly on a `Copy` type",
+ Some(item.span),
+ "consider deriving `Clone` or removing `Copy`",
+ );
+}
+
+/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
+fn check_unsafe_derive_deserialize<'tcx>(
+ cx: &LateContext<'tcx>,
+ item: &Item<'_>,
+ trait_ref: &TraitRef<'_>,
+ ty: Ty<'tcx>,
+) {
+ fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
+ let mut visitor = UnsafeVisitor { cx, has_unsafe: false };
+ walk_item(&mut visitor, item);
+ visitor.has_unsafe
+ }
+
+ if_chain! {
+ if let Some(trait_def_id) = trait_ref.trait_def_id();
+ if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
+ if let ty::Adt(def, _) = ty.kind();
+ if let Some(local_def_id) = def.did.as_local();
+ let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
+ if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
+ if cx.tcx.inherent_impls(def.did)
+ .iter()
+ .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
+ .any(|imp| has_unsafe(cx, imp));
+ then {
+ span_lint_and_help(
+ cx,
+ UNSAFE_DERIVE_DESERIALIZE,
+ item.span,
+ "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
+ None,
+ "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html"
+ );
+ }
+ }
+}
+
+struct UnsafeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ has_unsafe: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) {
+ if self.has_unsafe {
+ return;
+ }
+
+ if_chain! {
+ if let Some(header) = kind.header();
+ if header.unsafety == Unsafety::Unsafe;
+ then {
+ self.has_unsafe = true;
+ }
+ }
+
+ walk_fn(self, kind, decl, body_id, span, id);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.has_unsafe {
+ return;
+ }
+
+ if let ExprKind::Block(block, _) = expr.kind {
+ if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
+ self.has_unsafe = true;
+ }
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use clippy_utils::fn_def_id;
++
++use rustc_hir::{def::Res, def_id::DefIdMap, Expr};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++
++use crate::utils::conf;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Denies the configured methods and functions in clippy.toml
++ ///
++ /// ### Why is this bad?
++ /// Some methods are undesirable in certain contexts, and it's beneficial to
++ /// lint for them as needed.
++ ///
++ /// ### Example
++ /// An example clippy.toml configuration:
++ /// ```toml
++ /// # clippy.toml
++ /// disallowed-methods = [
++ /// # Can use a string as the path of the disallowed method.
++ /// "std::boxed::Box::new",
++ /// # Can also use an inline table with a `path` key.
++ /// { path = "std::time::Instant::now" },
++ /// # When using an inline table, can add a `reason` for why the method
++ /// # is disallowed.
++ /// { path = "std::vec::Vec::leak", reason = "no leaking memory" },
++ /// ]
++ /// ```
++ ///
++ /// ```rust,ignore
++ /// // Example code where clippy issues a warning
++ /// let xs = vec![1, 2, 3, 4];
++ /// xs.leak(); // Vec::leak is disallowed in the config.
++ /// // The diagnostic contains the message "no leaking memory".
++ ///
++ /// let _now = Instant::now(); // Instant::now is disallowed in the config.
++ ///
++ /// let _box = Box::new(3); // Box::new is disallowed in the config.
++ /// ```
++ ///
++ /// Use instead:
++ /// ```rust,ignore
++ /// // Example code which does not raise clippy warning
++ /// let mut xs = Vec::new(); // Vec::new is _not_ disallowed in the config.
++ /// xs.push(123); // Vec::push is _not_ disallowed in the config.
++ /// ```
++ #[clippy::version = "1.49.0"]
++ pub DISALLOWED_METHODS,
++ nursery,
++ "use of a disallowed method call"
++}
++
++#[derive(Clone, Debug)]
++pub struct DisallowedMethods {
++ conf_disallowed: Vec<conf::DisallowedMethod>,
++ disallowed: DefIdMap<Option<String>>,
++}
++
++impl DisallowedMethods {
++ pub fn new(conf_disallowed: Vec<conf::DisallowedMethod>) -> Self {
++ Self {
++ conf_disallowed,
++ disallowed: DefIdMap::default(),
++ }
++ }
++}
++
++impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]);
++
++impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
++ fn check_crate(&mut self, cx: &LateContext<'_>) {
++ for conf in &self.conf_disallowed {
++ let (path, reason) = match conf {
++ conf::DisallowedMethod::Simple(path) => (path, None),
++ conf::DisallowedMethod::WithReason { path, reason } => (
++ path,
++ reason.as_ref().map(|reason| format!("{} (from clippy.toml)", reason)),
++ ),
++ };
++ let segs: Vec<_> = path.split("::").collect();
++ if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &segs) {
++ self.disallowed.insert(id, reason);
++ }
++ }
++ }
++
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ let def_id = match fn_def_id(cx, expr) {
++ Some(def_id) => def_id,
++ None => return,
++ };
++ let reason = match self.disallowed.get(&def_id) {
++ Some(reason) => reason,
++ None => return,
++ };
++ let func_path = cx.tcx.def_path_str(def_id);
++ let msg = format!("use of a disallowed method `{}`", func_path);
++ span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, &msg, |diag| {
++ if let Some(reason) = reason {
++ diag.note(reason);
++ }
++ });
++ }
++}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_lint::{EarlyContext, EarlyLintPass, Level};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use unicode_script::{Script, UnicodeScript};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of unicode scripts other than those explicitly allowed
+ /// by the lint config.
+ ///
+ /// This lint doesn't take into account non-text scripts such as `Unknown` and `Linear_A`.
+ /// It also ignores the `Common` script type.
+ /// While configuring, be sure to use official script name [aliases] from
+ /// [the list of supported scripts][supported_scripts].
+ ///
+ /// See also: [`non_ascii_idents`].
+ ///
+ /// [aliases]: http://www.unicode.org/reports/tr24/tr24-31.html#Script_Value_Aliases
+ /// [supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html
+ ///
+ /// ### Why is this bad?
+ /// It may be not desired to have many different scripts for
+ /// identifiers in the codebase.
+ ///
+ /// Note that if you only want to allow plain English, you might want to use
+ /// built-in [`non_ascii_idents`] lint instead.
+ ///
+ /// [`non_ascii_idents`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#non-ascii-idents
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Assuming that `clippy.toml` contains the following line:
+ /// // allowed-locales = ["Latin", "Cyrillic"]
+ /// let counter = 10; // OK, latin is allowed.
+ /// let счётчик = 10; // OK, cyrillic is allowed.
+ /// let zähler = 10; // OK, it's still latin.
+ /// let カウンタ = 10; // Will spawn the lint.
+ /// ```
++ #[clippy::version = "1.55.0"]
+ pub DISALLOWED_SCRIPT_IDENTS,
+ restriction,
+ "usage of non-allowed Unicode scripts"
+}
+
+#[derive(Clone, Debug)]
+pub struct DisallowedScriptIdents {
+ whitelist: FxHashSet<Script>,
+}
+
+impl DisallowedScriptIdents {
+ pub fn new(whitelist: &[String]) -> Self {
+ let whitelist = whitelist
+ .iter()
+ .map(String::as_str)
+ .filter_map(Script::from_full_name)
+ .collect();
+ Self { whitelist }
+ }
+}
+
+impl_lint_pass!(DisallowedScriptIdents => [DISALLOWED_SCRIPT_IDENTS]);
+
+impl EarlyLintPass for DisallowedScriptIdents {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+ // Implementation is heavily inspired by the implementation of [`non_ascii_idents`] lint:
+ // https://github.com/rust-lang/rust/blob/master/compiler/rustc_lint/src/non_ascii_idents.rs
+
+ let check_disallowed_script_idents = cx.builder.lint_level(DISALLOWED_SCRIPT_IDENTS).0 != Level::Allow;
+ if !check_disallowed_script_idents {
+ return;
+ }
+
+ let symbols = cx.sess.parse_sess.symbol_gallery.symbols.lock();
+ // Sort by `Span` so that error messages make sense with respect to the
+ // order of identifier locations in the code.
+ let mut symbols: Vec<_> = symbols.iter().collect();
+ symbols.sort_unstable_by_key(|k| k.1);
+
+ for (symbol, &span) in &symbols {
+ // Note: `symbol.as_str()` is an expensive operation, thus should not be called
+ // more than once for a single symbol.
+ let symbol_str = symbol.as_str();
+ if symbol_str.is_ascii() {
+ continue;
+ }
+
+ for c in symbol_str.chars() {
+ // We want to iterate through all the scripts associated with this character
+ // and check whether at least of one scripts is in the whitelist.
+ let forbidden_script = c
+ .script_extension()
+ .iter()
+ .find(|script| !self.whitelist.contains(script));
+ if let Some(script) = forbidden_script {
+ span_lint(
+ cx,
+ DISALLOWED_SCRIPT_IDENTS,
+ span,
+ &format!(
+ "identifier `{}` has a Unicode script that is not allowed by configuration: {}",
+ symbol_str,
+ script.full_name()
+ ),
+ );
+ // We don't want to spawn warning multiple times over a single identifier.
+ break;
+ }
+ }
+ }
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++
++use rustc_data_structures::fx::FxHashMap;
++use rustc_hir::{
++ def::Res, def_id::DefId, Item, ItemKind, PolyTraitRef, PrimTy, TraitBoundModifier, Ty, TyKind, UseKind,
++};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::Span;
++
++use crate::utils::conf;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Denies the configured types in clippy.toml.
++ ///
++ /// ### Why is this bad?
++ /// Some types are undesirable in certain contexts.
++ ///
++ /// ### Example:
++ /// An example clippy.toml configuration:
++ /// ```toml
++ /// # clippy.toml
++ /// disallowed-types = [
++ /// # Can use a string as the path of the disallowed type.
++ /// "std::collections::BTreeMap",
++ /// # Can also use an inline table with a `path` key.
++ /// { path = "std::net::TcpListener" },
++ /// # When using an inline table, can add a `reason` for why the type
++ /// # is disallowed.
++ /// { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
++ /// ]
++ /// ```
++ ///
++ /// ```rust,ignore
++ /// use std::collections::BTreeMap;
++ /// // or its use
++ /// let x = std::collections::BTreeMap::new();
++ /// ```
++ /// Use instead:
++ /// ```rust,ignore
++ /// // A similar type that is allowed by the config
++ /// use std::collections::HashMap;
++ /// ```
++ #[clippy::version = "1.55.0"]
++ pub DISALLOWED_TYPES,
++ nursery,
++ "use of disallowed types"
++}
++#[derive(Clone, Debug)]
++pub struct DisallowedTypes {
++ conf_disallowed: Vec<conf::DisallowedType>,
++ def_ids: FxHashMap<DefId, Option<String>>,
++ prim_tys: FxHashMap<PrimTy, Option<String>>,
++}
++
++impl DisallowedTypes {
++ pub fn new(conf_disallowed: Vec<conf::DisallowedType>) -> Self {
++ Self {
++ conf_disallowed,
++ def_ids: FxHashMap::default(),
++ prim_tys: FxHashMap::default(),
++ }
++ }
++
++ fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) {
++ match res {
++ Res::Def(_, did) => {
++ if let Some(reason) = self.def_ids.get(did) {
++ emit(cx, &cx.tcx.def_path_str(*did), span, reason.as_deref());
++ }
++ },
++ Res::PrimTy(prim) => {
++ if let Some(reason) = self.prim_tys.get(prim) {
++ emit(cx, prim.name_str(), span, reason.as_deref());
++ }
++ },
++ _ => {},
++ }
++ }
++}
++
++impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]);
++
++impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
++ fn check_crate(&mut self, cx: &LateContext<'_>) {
++ for conf in &self.conf_disallowed {
++ let (path, reason) = match conf {
++ conf::DisallowedType::Simple(path) => (path, None),
++ conf::DisallowedType::WithReason { path, reason } => (
++ path,
++ reason.as_ref().map(|reason| format!("{} (from clippy.toml)", reason)),
++ ),
++ };
++ let segs: Vec<_> = path.split("::").collect();
++ match clippy_utils::path_to_res(cx, &segs) {
++ Res::Def(_, id) => {
++ self.def_ids.insert(id, reason);
++ },
++ Res::PrimTy(ty) => {
++ self.prim_tys.insert(ty, reason);
++ },
++ _ => {},
++ }
++ }
++ }
++
++ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
++ if let ItemKind::Use(path, UseKind::Single) = &item.kind {
++ self.check_res_emit(cx, &path.res, item.span);
++ }
++ }
++
++ fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) {
++ if let TyKind::Path(path) = &ty.kind {
++ self.check_res_emit(cx, &cx.qpath_res(path, ty.hir_id), ty.span);
++ }
++ }
++
++ fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>, _: TraitBoundModifier) {
++ self.check_res_emit(cx, &poly.trait_ref.path.res, poly.trait_ref.path.span);
++ }
++}
++
++fn emit(cx: &LateContext<'_>, name: &str, span: Span, reason: Option<&str>) {
++ span_lint_and_then(
++ cx,
++ DISALLOWED_TYPES,
++ span,
++ &format!("`{}` is not allowed according to config", name),
++ |diag| {
++ if let Some(reason) = reason {
++ diag.note(reason);
++ }
++ },
++ );
++}
--- /dev/null
- use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg};
+use clippy_utils::attrs::is_doc_hidden;
- use rustc_errors::{Applicability, Handler};
++use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_then};
+use clippy_utils::source::{first_line_of_span, snippet_with_applicability};
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{is_entrypoint_fn, is_expn_of, match_panic_def_id, method_chain_args, return_ty};
+use if_chain::if_chain;
+use itertools::Itertools;
+use rustc_ast::ast::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind};
+use rustc_ast::token::CommentKind;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::emitter::EmitterWriter;
- ItemKind::Fn(box Fn { sig, body: Some(block), .. }) if item.ident.name == sym::main => {
++use rustc_errors::{Applicability, Handler, SuggestionStyle};
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
+use rustc_hir::{AnonConst, Expr, ExprKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_parse::maybe_new_parser_from_source_str;
+use rustc_parse::parser::ForceCollect;
+use rustc_session::parse::ParseSess;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::edition::Edition;
+use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span};
+use rustc_span::{sym, FileName, Pos};
+use std::io;
+use std::ops::Range;
+use std::thread;
+use url::Url;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the presence of `_`, `::` or camel-case words
+ /// outside ticks in documentation.
+ ///
+ /// ### Why is this bad?
+ /// *Rustdoc* supports markdown formatting, `_`, `::` and
+ /// camel-case probably indicates some code which should be included between
+ /// ticks. `_` can also be used for emphasis in markdown, this lint tries to
+ /// consider that.
+ ///
+ /// ### Known problems
+ /// Lots of bad docs won’t be fixed, what the lint checks
+ /// for is limited, and there are still false positives. HTML elements and their
+ /// content are not linted.
+ ///
+ /// In addition, when writing documentation comments, including `[]` brackets
+ /// inside a link text would trip the parser. Therefore, documenting link with
+ /// `[`SmallVec<[T; INLINE_CAPACITY]>`]` and then [`SmallVec<[T; INLINE_CAPACITY]>`]: SmallVec
+ /// would fail.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// /// Do something with the foo_bar parameter. See also
+ /// /// that::other::module::foo.
+ /// // ^ `foo_bar` and `that::other::module::foo` should be ticked.
+ /// fn doit(foo_bar: usize) {}
+ /// ```
+ ///
+ /// ```rust
+ /// // Link text with `[]` brackets should be written as following:
+ /// /// Consume the array and return the inner
+ /// /// [`SmallVec<[T; INLINE_CAPACITY]>`][SmallVec].
+ /// /// [SmallVec]: SmallVec
+ /// fn main() {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DOC_MARKDOWN,
+ pedantic,
+ "presence of `_`, `::` or camel-case outside backticks in documentation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the doc comments of publicly visible
+ /// unsafe functions and warns if there is no `# Safety` section.
+ ///
+ /// ### Why is this bad?
+ /// Unsafe functions should document their safety
+ /// preconditions, so that users can be sure they are using them safely.
+ ///
+ /// ### Examples
+ /// ```rust
+ ///# type Universe = ();
+ /// /// This function should really be documented
+ /// pub unsafe fn start_apocalypse(u: &mut Universe) {
+ /// unimplemented!();
+ /// }
+ /// ```
+ ///
+ /// At least write a line about safety:
+ ///
+ /// ```rust
+ ///# type Universe = ();
+ /// /// # Safety
+ /// ///
+ /// /// This function should not be called before the horsemen are ready.
+ /// pub unsafe fn start_apocalypse(u: &mut Universe) {
+ /// unimplemented!();
+ /// }
+ /// ```
++ #[clippy::version = "1.39.0"]
+ pub MISSING_SAFETY_DOC,
+ style,
+ "`pub unsafe fn` without `# Safety` docs"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the doc comments of publicly visible functions that
+ /// return a `Result` type and warns if there is no `# Errors` section.
+ ///
+ /// ### Why is this bad?
+ /// Documenting the type of errors that can be returned from a
+ /// function can help callers write code to handle the errors appropriately.
+ ///
+ /// ### Examples
+ /// Since the following function returns a `Result` it has an `# Errors` section in
+ /// its doc comment:
+ ///
+ /// ```rust
+ ///# use std::io;
+ /// /// # Errors
+ /// ///
+ /// /// Will return `Err` if `filename` does not exist or the user does not have
+ /// /// permission to read it.
+ /// pub fn read(filename: String) -> io::Result<String> {
+ /// unimplemented!();
+ /// }
+ /// ```
++ #[clippy::version = "1.41.0"]
+ pub MISSING_ERRORS_DOC,
+ pedantic,
+ "`pub fn` returns `Result` without `# Errors` in doc comment"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the doc comments of publicly visible functions that
+ /// may panic and warns if there is no `# Panics` section.
+ ///
+ /// ### Why is this bad?
+ /// Documenting the scenarios in which panicking occurs
+ /// can help callers who do not want to panic to avoid those situations.
+ ///
+ /// ### Examples
+ /// Since the following function may panic it has a `# Panics` section in
+ /// its doc comment:
+ ///
+ /// ```rust
+ /// /// # Panics
+ /// ///
+ /// /// Will panic if y is 0
+ /// pub fn divide_by(x: i32, y: i32) -> i32 {
+ /// if y == 0 {
+ /// panic!("Cannot divide by 0")
+ /// } else {
+ /// x / y
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub MISSING_PANICS_DOC,
+ pedantic,
+ "`pub fn` may panic without `# Panics` in doc comment"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `fn main() { .. }` in doctests
+ ///
+ /// ### Why is this bad?
+ /// The test can be shorter (and likely more readable)
+ /// if the `fn main()` is left implicit.
+ ///
+ /// ### Examples
+ /// ``````rust
+ /// /// An example of a doctest with a `main()` function
+ /// ///
+ /// /// # Examples
+ /// ///
+ /// /// ```
+ /// /// fn main() {
+ /// /// // this needs not be in an `fn`
+ /// /// }
+ /// /// ```
+ /// fn needless_main() {
+ /// unimplemented!();
+ /// }
+ /// ``````
++ #[clippy::version = "1.40.0"]
+ pub NEEDLESS_DOCTEST_MAIN,
+ style,
+ "presence of `fn main() {` in code examples"
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone)]
+pub struct DocMarkdown {
+ valid_idents: FxHashSet<String>,
+ in_trait_impl: bool,
+}
+
+impl DocMarkdown {
+ pub fn new(valid_idents: FxHashSet<String>) -> Self {
+ Self {
+ valid_idents,
+ in_trait_impl: false,
+ }
+ }
+}
+
+impl_lint_pass!(DocMarkdown =>
+ [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, NEEDLESS_DOCTEST_MAIN]
+);
+
+impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
+ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
+ let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
+ check_attrs(cx, &self.valid_idents, attrs);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ match item.kind {
+ hir::ItemKind::Fn(ref sig, _, body_id) => {
+ if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ cx,
+ typeck_results: cx.tcx.typeck(item.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(&body.value);
+ lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
+ }
+ },
+ hir::ItemKind::Impl(ref impl_) => {
+ self.in_trait_impl = impl_.of_trait.is_some();
+ },
+ hir::ItemKind::Trait(_, unsafety, ..) => {
+ if !headers.safety && unsafety == hir::Unsafety::Unsafe {
+ span_lint(
+ cx,
+ MISSING_SAFETY_DOC,
+ item.span,
+ "docs for unsafe trait missing `# Safety` section",
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if let hir::ItemKind::Impl { .. } = item.kind {
+ self.in_trait_impl = false;
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
+ if !in_external_macro(cx.tcx.sess, item.span) {
+ lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, None, None);
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let headers = check_attrs(cx, &self.valid_idents, attrs);
+ if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+ if let hir::ImplItemKind::Fn(ref sig, body_id) = item.kind {
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ cx,
+ typeck_results: cx.tcx.typeck(item.def_id),
+ panic_span: None,
+ };
+ fpu.visit_expr(&body.value);
+ lint_for_missing_headers(cx, item.def_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
+ }
+ }
+}
+
+fn lint_for_missing_headers<'tcx>(
+ cx: &LateContext<'tcx>,
+ def_id: LocalDefId,
+ span: impl Into<MultiSpan> + Copy,
+ sig: &hir::FnSig<'_>,
+ headers: DocHeaders,
+ body_id: Option<hir::BodyId>,
+ panic_span: Option<Span>,
+) {
+ if !cx.access_levels.is_exported(def_id) {
+ return; // Private functions do not require doc comments
+ }
+
+ // do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
+ if cx
+ .tcx
+ .hir()
+ .parent_iter(cx.tcx.hir().local_def_id_to_hir_id(def_id))
+ .any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id)))
+ {
+ return;
+ }
+
+ if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
+ span_lint(
+ cx,
+ MISSING_SAFETY_DOC,
+ span,
+ "unsafe function's docs miss `# Safety` section",
+ );
+ }
+ if !headers.panics && panic_span.is_some() {
+ span_lint_and_note(
+ cx,
+ MISSING_PANICS_DOC,
+ span,
+ "docs for function which may panic missing `# Panics` section",
+ panic_span,
+ "first possible panic found here",
+ );
+ }
+ if !headers.errors {
+ let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
+ if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) {
+ span_lint(
+ cx,
+ MISSING_ERRORS_DOC,
+ span,
+ "docs for function returning `Result` missing `# Errors` section",
+ );
+ } else {
+ if_chain! {
+ if let Some(body_id) = body_id;
+ if let Some(future) = cx.tcx.lang_items().future_trait();
+ let typeck = cx.tcx.typeck_body(body_id);
+ let body = cx.tcx.hir().body(body_id);
+ let ret_ty = typeck.expr_ty(&body.value);
+ if implements_trait(cx, ret_ty, future, &[]);
+ if let ty::Opaque(_, subs) = ret_ty.kind();
+ if let Some(gen) = subs.types().next();
+ if let ty::Generator(_, subs, _) = gen.kind();
+ if is_type_diagnostic_item(cx, subs.as_generator().return_ty(), sym::Result);
+ then {
+ span_lint(
+ cx,
+ MISSING_ERRORS_DOC,
+ span,
+ "docs for function returning `Result` missing `# Errors` section",
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Cleanup documentation decoration.
+///
+/// We can't use `rustc_ast::attr::AttributeMethods::with_desugared_doc` or
+/// `rustc_ast::parse::lexer::comments::strip_doc_comment_decoration` because we
+/// need to keep track of
+/// the spans but this function is inspired from the later.
+#[allow(clippy::cast_possible_truncation)]
+#[must_use]
+pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span: Span) -> (String, Vec<(usize, Span)>) {
+ // one-line comments lose their prefix
+ if comment_kind == CommentKind::Line {
+ let mut doc = doc.to_owned();
+ doc.push('\n');
+ let len = doc.len();
+ // +3 skips the opening delimiter
+ return (doc, vec![(len, span.with_lo(span.lo() + BytePos(3)))]);
+ }
+
+ let mut sizes = vec![];
+ let mut contains_initial_stars = false;
+ for line in doc.lines() {
+ let offset = line.as_ptr() as usize - doc.as_ptr() as usize;
+ debug_assert_eq!(offset as u32 as usize, offset);
+ contains_initial_stars |= line.trim_start().starts_with('*');
+ // +1 adds the newline, +3 skips the opening delimiter
+ sizes.push((line.len() + 1, span.with_lo(span.lo() + BytePos(3 + offset as u32))));
+ }
+ if !contains_initial_stars {
+ return (doc.to_string(), sizes);
+ }
+ // remove the initial '*'s if any
+ let mut no_stars = String::with_capacity(doc.len());
+ for line in doc.lines() {
+ let mut chars = line.chars();
+ for c in &mut chars {
+ if c.is_whitespace() {
+ no_stars.push(c);
+ } else {
+ no_stars.push(if c == '*' { ' ' } else { c });
+ break;
+ }
+ }
+ no_stars.push_str(chars.as_str());
+ no_stars.push('\n');
+ }
+
+ (no_stars, sizes)
+}
+
+#[derive(Copy, Clone)]
+struct DocHeaders {
+ safety: bool,
+ errors: bool,
+ panics: bool,
+}
+
+fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> DocHeaders {
+ use pulldown_cmark::{BrokenLink, CowStr, Options};
+ /// We don't want the parser to choke on intra doc links. Since we don't
+ /// actually care about rendering them, just pretend that all broken links are
+ /// point to a fake address.
+ #[allow(clippy::unnecessary_wraps)] // we're following a type signature
+ fn fake_broken_link_callback<'a>(_: BrokenLink<'_>) -> Option<(CowStr<'a>, CowStr<'a>)> {
+ Some(("fake".into(), "fake".into()))
+ }
+
+ let mut doc = String::new();
+ let mut spans = vec![];
+
+ for attr in attrs {
+ if let AttrKind::DocComment(comment_kind, comment) = attr.kind {
+ let (comment, current_spans) = strip_doc_comment_decoration(&comment.as_str(), comment_kind, attr.span);
+ spans.extend_from_slice(¤t_spans);
+ doc.push_str(&comment);
+ } else if attr.has_name(sym::doc) {
+ // ignore mix of sugared and non-sugared doc
+ // don't trigger the safety or errors check
+ return DocHeaders {
+ safety: true,
+ errors: true,
+ panics: true,
+ };
+ }
+ }
+
+ let mut current = 0;
+ for &mut (ref mut offset, _) in &mut spans {
+ let offset_copy = *offset;
+ *offset = current;
+ current += offset_copy;
+ }
+
+ if doc.is_empty() {
+ return DocHeaders {
+ safety: false,
+ errors: false,
+ panics: false,
+ };
+ }
+
+ let mut cb = fake_broken_link_callback;
+
+ let parser =
+ pulldown_cmark::Parser::new_with_broken_link_callback(&doc, Options::empty(), Some(&mut cb)).into_offset_iter();
+ // Iterate over all `Events` and combine consecutive events into one
+ let events = parser.coalesce(|previous, current| {
+ use pulldown_cmark::Event::Text;
+
+ let previous_range = previous.1;
+ let current_range = current.1;
+
+ match (previous.0, current.0) {
+ (Text(previous), Text(current)) => {
+ let mut previous = previous.to_string();
+ previous.push_str(¤t);
+ Ok((Text(previous.into()), previous_range))
+ },
+ (previous, current) => Err(((previous, previous_range), (current, current_range))),
+ }
+ });
+ check_doc(cx, valid_idents, events, &spans)
+}
+
+const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
+
+fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
+ cx: &LateContext<'_>,
+ valid_idents: &FxHashSet<String>,
+ events: Events,
+ spans: &[(usize, Span)],
+) -> DocHeaders {
+ // true if a safety header was found
+ use pulldown_cmark::Event::{
+ Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
+ };
+ use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph};
+ use pulldown_cmark::{CodeBlockKind, CowStr};
+
+ let mut headers = DocHeaders {
+ safety: false,
+ errors: false,
+ panics: false,
+ };
+ let mut in_code = false;
+ let mut in_link = None;
+ let mut in_heading = false;
+ let mut is_rust = false;
+ let mut edition = None;
+ let mut ticks_unbalanced = false;
+ let mut text_to_check: Vec<(CowStr<'_>, Span)> = Vec::new();
+ let mut paragraph_span = spans.get(0).expect("function isn't called if doc comment is empty").1;
+ for (event, range) in events {
+ match event {
+ Start(CodeBlock(ref kind)) => {
+ in_code = true;
+ if let CodeBlockKind::Fenced(lang) = kind {
+ for item in lang.split(',') {
+ if item == "ignore" {
+ is_rust = false;
+ break;
+ }
+ if let Some(stripped) = item.strip_prefix("edition") {
+ is_rust = true;
+ edition = stripped.parse::<Edition>().ok();
+ } else if item.is_empty() || RUST_CODE.contains(&item) {
+ is_rust = true;
+ }
+ }
+ }
+ },
+ End(CodeBlock(_)) => {
+ in_code = false;
+ is_rust = false;
+ },
+ Start(Link(_, url, _)) => in_link = Some(url),
+ End(Link(..)) => in_link = None,
+ Start(Heading(_) | Paragraph | Item) => {
+ if let Start(Heading(_)) = event {
+ in_heading = true;
+ }
+ ticks_unbalanced = false;
+ let (_, span) = get_current_span(spans, range.start);
+ paragraph_span = first_line_of_span(cx, span);
+ },
+ End(Heading(_) | Paragraph | Item) => {
+ if let End(Heading(_)) = event {
+ in_heading = false;
+ }
+ if ticks_unbalanced {
+ span_lint_and_help(
+ cx,
+ DOC_MARKDOWN,
+ paragraph_span,
+ "backticks are unbalanced",
+ None,
+ "a backtick may be missing a pair",
+ );
+ } else {
+ for (text, span) in text_to_check {
+ check_text(cx, valid_idents, &text, span);
+ }
+ }
+ text_to_check = Vec::new();
+ },
+ Start(_tag) | End(_tag) => (), // We don't care about other tags
+ Html(_html) => (), // HTML is weird, just ignore it
+ SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
+ FootnoteReference(text) | Text(text) => {
+ let (begin, span) = get_current_span(spans, range.start);
+ paragraph_span = paragraph_span.with_hi(span.hi());
+ ticks_unbalanced |= text.contains('`') && !in_code;
+ if Some(&text) == in_link.as_ref() || ticks_unbalanced {
+ // Probably a link of the form `<http://example.com>`
+ // Which are represented as a link to "http://example.com" with
+ // text "http://example.com" by pulldown-cmark
+ continue;
+ }
+ let trimmed_text = text.trim();
+ headers.safety |= in_heading && trimmed_text == "Safety";
+ headers.safety |= in_heading && trimmed_text == "Implementation safety";
+ headers.safety |= in_heading && trimmed_text == "Implementation Safety";
+ headers.errors |= in_heading && trimmed_text == "Errors";
+ headers.panics |= in_heading && trimmed_text == "Panics";
+ if in_code {
+ if is_rust {
+ let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
+ check_code(cx, &text, edition, span);
+ }
+ } else {
+ // Adjust for the beginning of the current `Event`
+ let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin));
+ text_to_check.push((text, span));
+ }
+ },
+ }
+ }
+ headers
+}
+
+fn get_current_span(spans: &[(usize, Span)], idx: usize) -> (usize, Span) {
+ let index = match spans.binary_search_by(|c| c.0.cmp(&idx)) {
+ Ok(o) => o,
+ Err(e) => e - 1,
+ };
+ spans[index]
+}
+
+fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
+ fn has_needless_main(code: String, edition: Edition) -> bool {
+ rustc_driver::catch_fatal_errors(|| {
+ rustc_span::create_session_globals_then(edition, || {
+ let filename = FileName::anon_source_code(&code);
+
+ let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let emitter = EmitterWriter::new(Box::new(io::sink()), None, false, false, false, None, false);
+ let handler = Handler::with_emitter(false, None, Box::new(emitter));
+ let sess = ParseSess::with_span_handler(handler, sm);
+
+ let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
+ Ok(p) => p,
+ Err(errs) => {
+ for mut err in errs {
+ err.cancel();
+ }
+ return false;
+ },
+ };
+
+ let mut relevant_main_found = false;
+ loop {
+ match parser.parse_item(ForceCollect::No) {
+ Ok(Some(item)) => match &item.kind {
+ // Tests with one of these items are ignored
+ ItemKind::Static(..)
+ | ItemKind::Const(..)
+ | ItemKind::ExternCrate(..)
+ | ItemKind::ForeignMod(..) => return false,
+ // We found a main function ...
- span_lint_and_sugg(
++ ItemKind::Fn(box Fn {
++ sig, body: Some(block), ..
++ }) if item.ident.name == sym::main => {
+ let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
+ let returns_nothing = match &sig.decl.output {
+ FnRetTy::Default(..) => true,
+ FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
+ FnRetTy::Ty(_) => false,
+ };
+
+ if returns_nothing && !is_async && !block.stmts.is_empty() {
+ // This main function should be linted, but only if there are no other functions
+ relevant_main_found = true;
+ } else {
+ // This main function should not be linted, we're done
+ return false;
+ }
+ },
+ // Another function was found; this case is ignored too
+ ItemKind::Fn(..) => return false,
+ _ => {},
+ },
+ Ok(None) => break,
+ Err(mut e) => {
+ e.cancel();
+ return false;
+ },
+ }
+ }
+
+ relevant_main_found
+ })
+ })
+ .ok()
+ .unwrap_or_default()
+ }
+
+ // Because of the global session, we need to create a new session in a different thread with
+ // the edition we need.
+ let text = text.to_owned();
+ if thread::spawn(move || has_needless_main(text, edition))
+ .join()
+ .expect("thread::spawn failed")
+ {
+ span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
+ }
+}
+
+fn check_text(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span) {
+ for word in text.split(|c: char| c.is_whitespace() || c == '\'') {
+ // Trim punctuation as in `some comment (see foo::bar).`
+ // ^^
+ // Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
+ let mut word = word.trim_matches(|c: char| !c.is_alphanumeric() && c != ':');
+
+ // Remove leading or trailing single `:` which may be part of a sentence.
+ if word.starts_with(':') && !word.starts_with("::") {
+ word = word.trim_start_matches(':');
+ }
+ if word.ends_with(':') && !word.ends_with("::") {
+ word = word.trim_end_matches(':');
+ }
+
+ if valid_idents.contains(word) || word.chars().all(|c| c == ':') {
+ continue;
+ }
+
+ // Adjust for the current word
+ let offset = word.as_ptr() as usize - text.as_ptr() as usize;
+ let span = Span::new(
+ span.lo() + BytePos::from_usize(offset),
+ span.lo() + BytePos::from_usize(offset + word.len()),
+ span.ctxt(),
+ span.parent(),
+ );
+
+ check_word(cx, word, span);
+ }
+}
+
+fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
+ /// Checks if a string is camel-case, i.e., contains at least two uppercase
+ /// letters (`Clippy` is ok) and one lower-case letter (`NASA` is ok).
+ /// Plurals are also excluded (`IDs` is ok).
+ fn is_camel_case(s: &str) -> bool {
+ if s.starts_with(|c: char| c.is_digit(10)) {
+ return false;
+ }
+
+ let s = s.strip_suffix('s').unwrap_or(s);
+
+ s.chars().all(char::is_alphanumeric)
+ && s.chars().filter(|&c| c.is_uppercase()).take(2).count() > 1
+ && s.chars().filter(|&c| c.is_lowercase()).take(1).count() > 0
+ }
+
+ fn has_underscore(s: &str) -> bool {
+ s != "_" && !s.contains("\\_") && s.contains('_')
+ }
+
+ fn has_hyphen(s: &str) -> bool {
+ s != "-" && s.contains('-')
+ }
+
+ if let Ok(url) = Url::parse(word) {
+ // try to get around the fact that `foo::bar` parses as a valid URL
+ if !url.cannot_be_a_base() {
+ span_lint(
+ cx,
+ DOC_MARKDOWN,
+ span,
+ "you should put bare URLs between `<`/`>` or make a proper Markdown link",
+ );
+
+ return;
+ }
+ }
+
+ // We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
+ if has_underscore(word) && has_hyphen(word) {
+ return;
+ }
+
+ if has_underscore(word) || word.contains("::") || is_camel_case(word) {
+ let mut applicability = Applicability::MachineApplicable;
+
- "try",
- format!("`{}`", snippet_with_applicability(cx, span, "..", &mut applicability)),
- applicability,
++ span_lint_and_then(
+ cx,
+ DOC_MARKDOWN,
+ span,
+ "item in documentation is missing backticks",
++ |diag| {
++ let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
++ diag.span_suggestion_with_style(
++ span,
++ "try",
++ format!("`{}`", snippet),
++ applicability,
++ // always show the suggestion in a separate line, since the
++ // inline presentation adds another pair of backticks
++ SuggestionStyle::ShowAlways,
++ );
++ },
+ );
+ }
+}
+
+struct FindPanicUnwrap<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ panic_span: Option<Span>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.panic_span.is_some() {
+ return;
+ }
+
+ // check for `begin_panic`
+ if_chain! {
+ if let ExprKind::Call(func_expr, _) = expr.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
+ if let Some(path_def_id) = path.res.opt_def_id();
+ if match_panic_def_id(self.cx, path_def_id);
+ if is_expn_of(expr.span, "unreachable").is_none();
+ if !is_expn_of_debug_assertions(expr.span);
+ then {
+ self.panic_span = Some(expr.span);
+ }
+ }
+
+ // check for `assert_eq` or `assert_ne`
+ if is_expn_of(expr.span, "assert_eq").is_some() || is_expn_of(expr.span, "assert_ne").is_some() {
+ self.panic_span = Some(expr.span);
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option)
+ || is_type_diagnostic_item(self.cx, receiver_ty, sym::Result)
+ {
+ self.panic_span = Some(expr.span);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ // Panics in const blocks will cause compilation to fail.
+ fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+fn is_expn_of_debug_assertions(span: Span) -> bool {
+ const MACRO_NAMES: &[&str] = &["debug_assert", "debug_assert_eq", "debug_assert_ne"];
+ MACRO_NAMES.iter().any(|name| is_expn_of(span, name).is_some())
+}
--- /dev/null
+//! Lint on unnecessary double comparisons. Some examples:
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::eq_expr_value;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for double comparisons that could be simplified to a single expression.
+ ///
+ ///
+ /// ### Why is this bad?
+ /// Readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 2;
+ /// if x == y || x < y {}
+ /// ```
+ ///
+ /// Could be written as:
+ ///
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 2;
+ /// if x <= y {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DOUBLE_COMPARISONS,
+ complexity,
+ "unnecessary double comparisons that can be simplified"
+}
+
+declare_lint_pass!(DoubleComparisons => [DOUBLE_COMPARISONS]);
+
+impl<'tcx> DoubleComparisons {
+ #[allow(clippy::similar_names)]
+ fn check_binop(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
+ let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
+ (ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {
+ (lb.node, llhs, lrhs, rb.node, rlhs, rrhs)
+ },
+ _ => return,
+ };
+ if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) {
+ return;
+ }
+ macro_rules! lint_double_comparison {
+ ($op:tt) => {{
+ let mut applicability = Applicability::MachineApplicable;
+ let lhs_str = snippet_with_applicability(cx, llhs.span, "", &mut applicability);
+ let rhs_str = snippet_with_applicability(cx, lrhs.span, "", &mut applicability);
+ let sugg = format!("{} {} {}", lhs_str, stringify!($op), rhs_str);
+ span_lint_and_sugg(
+ cx,
+ DOUBLE_COMPARISONS,
+ span,
+ "this binary expression can be simplified",
+ "try",
+ sugg,
+ applicability,
+ );
+ }};
+ }
+ #[rustfmt::skip]
+ match (op, lkind, rkind) {
+ (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Lt) | (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Eq) => {
+ lint_double_comparison!(<=);
+ },
+ (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Eq) => {
+ lint_double_comparison!(>=);
+ },
+ (BinOpKind::Or, BinOpKind::Lt, BinOpKind::Gt) | (BinOpKind::Or, BinOpKind::Gt, BinOpKind::Lt) => {
+ lint_double_comparison!(!=);
+ },
+ (BinOpKind::And, BinOpKind::Le, BinOpKind::Ge) | (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => {
+ lint_double_comparison!(==);
+ },
+ _ => (),
+ };
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for DoubleComparisons {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref kind, lhs, rhs) = expr.kind {
+ Self::check_binop(cx, kind.node, lhs, rhs, expr.span);
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast::{Expr, ExprKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary double parentheses.
+ ///
+ /// ### Why is this bad?
+ /// This makes code harder to read and might indicate a
+ /// mistake.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// fn simple_double_parens() -> i32 {
+ /// ((0))
+ /// }
+ ///
+ /// // Good
+ /// fn simple_no_parens() -> i32 {
+ /// 0
+ /// }
+ ///
+ /// // or
+ ///
+ /// # fn foo(bar: usize) {}
+ /// // Bad
+ /// foo((0));
+ ///
+ /// // Good
+ /// foo(0);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DOUBLE_PARENS,
+ complexity,
+ "Warn on unnecessary double parentheses"
+}
+
+declare_lint_pass!(DoubleParens => [DOUBLE_PARENS]);
+
+impl EarlyLintPass for DoubleParens {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ let msg: &str = "consider removing unnecessary double parentheses";
+
+ match expr.kind {
+ ExprKind::Paren(ref in_paren) => match in_paren.kind {
+ ExprKind::Paren(_) | ExprKind::Tup(_) => {
+ span_lint(cx, DOUBLE_PARENS, expr.span, msg);
+ },
+ _ => {},
+ },
+ ExprKind::Call(_, ref params) => {
+ if params.len() == 1 {
+ let param = ¶ms[0];
+ if let ExprKind::Paren(_) = param.kind {
+ span_lint(cx, DOUBLE_PARENS, param.span, msg);
+ }
+ }
+ },
+ ExprKind::MethodCall(_, ref params, _) => {
+ if params.len() == 2 {
+ let param = ¶ms[1];
+ if let ExprKind::Paren(_) = param.kind {
+ span_lint(cx, DOUBLE_PARENS, param.span, msg);
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_note;
+use clippy_utils::ty::is_copy;
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::drop` with a reference
+ /// instead of an owned value.
+ ///
+ /// ### Why is this bad?
+ /// Calling `drop` on a reference will only drop the
+ /// reference itself, which is a no-op. It will not call the `drop` method (from
+ /// the `Drop` trait implementation) on the underlying referenced value, which
+ /// is likely what was intended.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let mut lock_guard = mutex.lock();
+ /// std::mem::drop(&lock_guard) // Should have been drop(lock_guard), mutex
+ /// // still locked
+ /// operation_that_requires_mutex_to_be_unlocked();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DROP_REF,
+ correctness,
+ "calls to `std::mem::drop` with a reference instead of an owned value"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::forget` with a reference
+ /// instead of an owned value.
+ ///
+ /// ### Why is this bad?
+ /// Calling `forget` on a reference will only forget the
+ /// reference itself, which is a no-op. It will not forget the underlying
+ /// referenced
+ /// value, which is likely what was intended.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Box::new(1);
+ /// std::mem::forget(&x) // Should have been forget(x), x will still be dropped
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub FORGET_REF,
+ correctness,
+ "calls to `std::mem::forget` with a reference instead of an owned value"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::drop` with a value
+ /// that derives the Copy trait
+ ///
+ /// ### Why is this bad?
+ /// Calling `std::mem::drop` [does nothing for types that
+ /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html), since the
+ /// value will be copied and moved into the function on invocation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: i32 = 42; // i32 implements Copy
+ /// std::mem::drop(x) // A copy of x is passed to the function, leaving the
+ /// // original unaffected
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DROP_COPY,
+ correctness,
+ "calls to `std::mem::drop` with a value that implements Copy"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `std::mem::forget` with a value that
+ /// derives the Copy trait
+ ///
+ /// ### Why is this bad?
+ /// Calling `std::mem::forget` [does nothing for types that
+ /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html) since the
+ /// value will be copied and moved into the function on invocation.
+ ///
+ /// An alternative, but also valid, explanation is that Copy types do not
+ /// implement
+ /// the Drop trait, which means they have no destructors. Without a destructor,
+ /// there
+ /// is nothing for `std::mem::forget` to ignore.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: i32 = 42; // i32 implements Copy
+ /// std::mem::forget(x) // A copy of x is passed to the function, leaving the
+ /// // original unaffected
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub FORGET_COPY,
+ correctness,
+ "calls to `std::mem::forget` with a value that implements Copy"
+}
+
+const DROP_REF_SUMMARY: &str = "calls to `std::mem::drop` with a reference instead of an owned value. \
+ Dropping a reference does nothing";
+const FORGET_REF_SUMMARY: &str = "calls to `std::mem::forget` with a reference instead of an owned value. \
+ Forgetting a reference does nothing";
+const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that implements `Copy`. \
+ Dropping a copy leaves the original intact";
+const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \
+ Forgetting a copy leaves the original intact";
+
+declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COPY]);
+
+impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(path, args) = expr.kind;
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if args.len() == 1;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ then {
+ let lint;
+ let msg;
+ let arg = &args[0];
+ let arg_ty = cx.typeck_results().expr_ty(arg);
+
+ if let ty::Ref(..) = arg_ty.kind() {
+ if match_def_path(cx, def_id, &paths::DROP) {
+ lint = DROP_REF;
+ msg = DROP_REF_SUMMARY.to_string();
+ } else if match_def_path(cx, def_id, &paths::MEM_FORGET) {
+ lint = FORGET_REF;
+ msg = FORGET_REF_SUMMARY.to_string();
+ } else {
+ return;
+ }
+ span_lint_and_note(cx,
+ lint,
+ expr.span,
+ &msg,
+ Some(arg.span),
+ &format!("argument has type `{}`", arg_ty));
+ } else if is_copy(cx, arg_ty) {
+ if match_def_path(cx, def_id, &paths::DROP) {
+ lint = DROP_COPY;
+ msg = DROP_COPY_SUMMARY.to_string();
+ } else if match_def_path(cx, def_id, &paths::MEM_FORGET) {
+ lint = FORGET_COPY;
+ msg = FORGET_COPY_SUMMARY.to_string();
+ } else {
+ return;
+ }
+ span_lint_and_note(cx,
+ lint,
+ expr.span,
+ &msg,
+ Some(arg.span),
+ &format!("argument has type {}", arg_ty));
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::match_type;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::paths;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calculation of subsecond microseconds or milliseconds
+ /// from other `Duration` methods.
+ ///
+ /// ### Why is this bad?
+ /// It's more concise to call `Duration::subsec_micros()` or
+ /// `Duration::subsec_millis()` than to calculate them.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::time::Duration;
+ /// let dur = Duration::new(5, 0);
+ ///
+ /// // Bad
+ /// let _micros = dur.subsec_nanos() / 1_000;
+ /// let _millis = dur.subsec_nanos() / 1_000_000;
+ ///
+ /// // Good
+ /// let _micros = dur.subsec_micros();
+ /// let _millis = dur.subsec_millis();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DURATION_SUBSEC,
+ complexity,
+ "checks for calculation of subsecond microseconds or milliseconds"
+}
+
+declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]);
+
+impl<'tcx> LateLintPass<'tcx> for DurationSubsec {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind;
+ if let ExprKind::MethodCall(method_path, _ , args, _) = left.kind;
+ if match_type(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::DURATION);
+ if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right);
+ then {
+ let suggested_fn = match (method_path.ident.as_str().as_ref(), divisor) {
+ ("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis",
+ ("subsec_nanos", 1_000) => "subsec_micros",
+ _ => return,
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ DURATION_SUBSEC,
+ expr.span,
+ &format!("calling `{}()` is more concise than this calculation", suggested_fn),
+ "try",
+ format!(
+ "{}.{}()",
+ snippet_with_applicability(cx, args[0].span, "_", &mut applicability),
+ suggested_fn
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+//! Lint on if expressions with an else if, but without a final else branch.
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_ast::ast::{Expr, ExprKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of if expressions with an `else if` branch,
+ /// but without a final `else` branch.
+ ///
+ /// ### Why is this bad?
+ /// Some coding guidelines require this (e.g., MISRA-C:2004 Rule 14.10).
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # let x: i32 = 1;
+ /// if x.is_positive() {
+ /// a();
+ /// } else if x.is_negative() {
+ /// b();
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # fn a() {}
+ /// # fn b() {}
+ /// # let x: i32 = 1;
+ /// if x.is_positive() {
+ /// a();
+ /// } else if x.is_negative() {
+ /// b();
+ /// } else {
+ /// // We don't care about zero.
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ELSE_IF_WITHOUT_ELSE,
+ restriction,
+ "`if` expression with an `else if`, but without a final `else` branch"
+}
+
+declare_lint_pass!(ElseIfWithoutElse => [ELSE_IF_WITHOUT_ELSE]);
+
+impl EarlyLintPass for ElseIfWithoutElse {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, mut item: &Expr) {
+ if in_external_macro(cx.sess, item.span) {
+ return;
+ }
+
+ while let ExprKind::If(_, _, Some(ref els)) = item.kind {
+ if let ExprKind::If(_, _, None) = els.kind {
+ span_lint_and_help(
+ cx,
+ ELSE_IF_WITHOUT_ELSE,
+ els.span,
+ "`if` expression with an `else if`, but without a final `else`",
+ None,
+ "add an `else` block here",
+ );
+ }
+
+ item = els;
+ }
+ }
+}
--- /dev/null
+//! lint when there is an enum with no variants
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `enum`s with no variants.
+ ///
+ /// As of this writing, the `never_type` is still a
+ /// nightly-only experimental API. Therefore, this lint is only triggered
+ /// if the `never_type` is enabled.
+ ///
+ /// ### Why is this bad?
+ /// If you want to introduce a type which
+ /// can't be instantiated, you should use `!` (the primitive type "never"),
+ /// or a wrapper around it, because `!` has more extensive
+ /// compiler support (type inference, etc...) and wrappers
+ /// around it are the conventional way to define an uninhabited type.
+ /// For further information visit [never type documentation](https://doc.rust-lang.org/std/primitive.never.html)
+ ///
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// enum Test {}
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// #![feature(never_type)]
+ ///
+ /// struct Test(!);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EMPTY_ENUM,
+ pedantic,
+ "enum with no variants"
+}
+
+declare_lint_pass!(EmptyEnum => [EMPTY_ENUM]);
+
+impl<'tcx> LateLintPass<'tcx> for EmptyEnum {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ // Only suggest the `never_type` if the feature is enabled
+ if !cx.tcx.features().never_type {
+ return;
+ }
+
+ if let ItemKind::Enum(..) = item.kind {
+ let ty = cx.tcx.type_of(item.def_id);
+ let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
+ if adt.variants.is_empty() {
+ span_lint_and_help(
+ cx,
+ EMPTY_ENUM,
+ item.span,
+ "enum with no variants",
+ None,
+ "consider using the uninhabited type `!` (never type) or a wrapper \
+ around it to introduce a type which can't be instantiated",
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::higher;
+use clippy_utils::{
+ can_move_expr_to_closure_no_visit,
+ diagnostics::span_lint_and_sugg,
+ is_expr_final_block_expr, is_expr_used_or_unified, match_def_path, paths, peel_hir_expr_while,
+ source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context},
+ SpanlessEq,
+};
+use core::fmt::Write;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ hir_id::HirIdSet,
+ intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
+ Block, Expr, ExprKind, Guard, HirId, Pat, Stmt, StmtKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{Span, SyntaxContext, DUMMY_SP};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of `contains_key` + `insert` on `HashMap`
+ /// or `BTreeMap`.
+ ///
+ /// ### Why is this bad?
+ /// Using `entry` is more efficient.
+ ///
+ /// ### Known problems
+ /// The suggestion may have type inference errors in some cases. e.g.
+ /// ```rust
+ /// let mut map = std::collections::HashMap::new();
+ /// let _ = if !map.contains_key(&0) {
+ /// map.insert(0, 0)
+ /// } else {
+ /// None
+ /// };
+ /// ```
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # let mut map = HashMap::new();
+ /// # let k = 1;
+ /// # let v = 1;
+ /// if !map.contains_key(&k) {
+ /// map.insert(k, v);
+ /// }
+ /// ```
+ /// can both be rewritten as:
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// # let mut map = HashMap::new();
+ /// # let k = 1;
+ /// # let v = 1;
+ /// map.entry(k).or_insert(v);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MAP_ENTRY,
+ perf,
+ "use of `contains_key` followed by `insert` on a `HashMap` or `BTreeMap`"
+}
+
+declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
+
+impl<'tcx> LateLintPass<'tcx> for HashMapPass {
+ #[allow(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (cond_expr, then_expr, else_expr) = match higher::If::hir(expr) {
+ Some(higher::If { cond, then, r#else }) => (cond, then, r#else),
+ _ => return,
+ };
+
+ let (map_ty, contains_expr) = match try_parse_contains(cx, cond_expr) {
+ Some(x) => x,
+ None => return,
+ };
+
+ let then_search = match find_insert_calls(cx, &contains_expr, then_expr) {
+ Some(x) => x,
+ None => return,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+ let map_str = snippet_with_context(cx, contains_expr.map.span, contains_expr.call_ctxt, "..", &mut app).0;
+ let key_str = snippet_with_context(cx, contains_expr.key.span, contains_expr.call_ctxt, "..", &mut app).0;
+ let sugg = if let Some(else_expr) = else_expr {
+ let else_search = match find_insert_calls(cx, &contains_expr, else_expr) {
+ Some(search) => search,
+ None => return,
+ };
+
+ if then_search.edits.is_empty() && else_search.edits.is_empty() {
+ // No insertions
+ return;
+ } else if then_search.edits.is_empty() || else_search.edits.is_empty() {
+ // if .. { insert } else { .. } or if .. { .. } else { insert }
+ let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) {
+ (true, true) => (
+ then_search.snippet_vacant(cx, then_expr.span, &mut app),
+ snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
+ ),
+ (true, false) => (
+ then_search.snippet_occupied(cx, then_expr.span, &mut app),
+ snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
+ ),
+ (false, true) => (
+ else_search.snippet_occupied(cx, else_expr.span, &mut app),
+ snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
+ ),
+ (false, false) => (
+ else_search.snippet_vacant(cx, else_expr.span, &mut app),
+ snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
+ ),
+ };
+ format!(
+ "if let {}::{} = {}.entry({}) {} else {}",
+ map_ty.entry_path(),
+ entry_kind,
+ map_str,
+ key_str,
+ then_str,
+ else_str,
+ )
+ } else {
+ // if .. { insert } else { insert }
+ let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated {
+ (
+ then_search.snippet_vacant(cx, then_expr.span, &mut app),
+ else_search.snippet_occupied(cx, else_expr.span, &mut app),
+ )
+ } else {
+ (
+ then_search.snippet_occupied(cx, then_expr.span, &mut app),
+ else_search.snippet_vacant(cx, else_expr.span, &mut app),
+ )
+ };
+ let indent_str = snippet_indent(cx, expr.span);
+ let indent_str = indent_str.as_deref().unwrap_or("");
+ format!(
+ "match {}.entry({}) {{\n{indent} {entry}::{} => {}\n\
+ {indent} {entry}::{} => {}\n{indent}}}",
+ map_str,
+ key_str,
+ then_entry,
+ reindent_multiline(then_str.into(), true, Some(4 + indent_str.len())),
+ else_entry,
+ reindent_multiline(else_str.into(), true, Some(4 + indent_str.len())),
+ entry = map_ty.entry_path(),
+ indent = indent_str,
+ )
+ }
+ } else {
+ if then_search.edits.is_empty() {
+ // no insertions
+ return;
+ }
+
+ // if .. { insert }
+ if !then_search.allow_insert_closure {
+ let (body_str, entry_kind) = if contains_expr.negated {
+ then_search.snippet_vacant(cx, then_expr.span, &mut app)
+ } else {
+ then_search.snippet_occupied(cx, then_expr.span, &mut app)
+ };
+ format!(
+ "if let {}::{} = {}.entry({}) {}",
+ map_ty.entry_path(),
+ entry_kind,
+ map_str,
+ key_str,
+ body_str,
+ )
+ } else if let Some(insertion) = then_search.as_single_insertion() {
+ let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
+ if contains_expr.negated {
+ if insertion.value.can_have_side_effects() {
+ format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, value_str)
+ } else {
+ format!("{}.entry({}).or_insert({});", map_str, key_str, value_str)
+ }
+ } else {
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
+ // This would need to be a different lint.
+ return;
+ }
+ } else {
+ let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
+ if contains_expr.negated {
+ format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, block_str)
+ } else {
+ // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
+ // This would need to be a different lint.
+ return;
+ }
+ }
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MAP_ENTRY,
+ expr.span,
+ &format!("usage of `contains_key` followed by `insert` on a `{}`", map_ty.name()),
+ "try this",
+ sugg,
+ app,
+ );
+ }
+}
+
+#[derive(Clone, Copy)]
+enum MapType {
+ Hash,
+ BTree,
+}
+impl MapType {
+ fn name(self) -> &'static str {
+ match self {
+ Self::Hash => "HashMap",
+ Self::BTree => "BTreeMap",
+ }
+ }
+ fn entry_path(self) -> &'static str {
+ match self {
+ Self::Hash => "std::collections::hash_map::Entry",
+ Self::BTree => "std::collections::btree_map::Entry",
+ }
+ }
+}
+
+struct ContainsExpr<'tcx> {
+ negated: bool,
+ map: &'tcx Expr<'tcx>,
+ key: &'tcx Expr<'tcx>,
+ call_ctxt: SyntaxContext,
+}
+fn try_parse_contains(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(MapType, ContainsExpr<'tcx>)> {
+ let mut negated = false;
+ let expr = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::Unary(UnOp::Not, e) => {
+ negated = !negated;
+ Some(e)
+ },
+ _ => None,
+ });
+ match expr.kind {
+ ExprKind::MethodCall(
+ _,
+ _,
+ [
+ map,
+ Expr {
+ kind: ExprKind::AddrOf(_, _, key),
+ span: key_span,
+ ..
+ },
+ ],
+ _,
+ ) if key_span.ctxt() == expr.span.ctxt() => {
+ let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+ let expr = ContainsExpr {
+ negated,
+ map,
+ key,
+ call_ctxt: expr.span.ctxt(),
+ };
+ if match_def_path(cx, id, &paths::BTREEMAP_CONTAINS_KEY) {
+ Some((MapType::BTree, expr))
+ } else if match_def_path(cx, id, &paths::HASHMAP_CONTAINS_KEY) {
+ Some((MapType::Hash, expr))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+struct InsertExpr<'tcx> {
+ map: &'tcx Expr<'tcx>,
+ key: &'tcx Expr<'tcx>,
+ value: &'tcx Expr<'tcx>,
+}
+fn try_parse_insert(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> {
+ if let ExprKind::MethodCall(_, _, [map, key, value], _) = expr.kind {
+ let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+ if match_def_path(cx, id, &paths::BTREEMAP_INSERT) || match_def_path(cx, id, &paths::HASHMAP_INSERT) {
+ Some(InsertExpr { map, key, value })
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+}
+
+/// An edit that will need to be made to move the expression to use the entry api
+#[derive(Clone, Copy)]
+enum Edit<'tcx> {
+ /// A semicolon that needs to be removed. Used to create a closure for `insert_with`.
+ RemoveSemi(Span),
+ /// An insertion into the map.
+ Insertion(Insertion<'tcx>),
+}
+impl Edit<'tcx> {
+ fn as_insertion(self) -> Option<Insertion<'tcx>> {
+ if let Self::Insertion(i) = self { Some(i) } else { None }
+ }
+}
+#[derive(Clone, Copy)]
+struct Insertion<'tcx> {
+ call: &'tcx Expr<'tcx>,
+ value: &'tcx Expr<'tcx>,
+}
+
+/// This visitor needs to do a multiple things:
+/// * Find all usages of the map. An insertion can only be made before any other usages of the map.
+/// * Determine if there's an insertion using the same key. There's no need for the entry api
+/// otherwise.
+/// * Determine if the final statement executed is an insertion. This is needed to use
+/// `or_insert_with`.
+/// * Determine if there's any sub-expression that can't be placed in a closure.
+/// * Determine if there's only a single insert statement. `or_insert` can be used in this case.
+#[allow(clippy::struct_excessive_bools)]
+struct InsertSearcher<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ /// The map expression used in the contains call.
+ map: &'tcx Expr<'tcx>,
+ /// The key expression used in the contains call.
+ key: &'tcx Expr<'tcx>,
+ /// The context of the top level block. All insert calls must be in the same context.
+ ctxt: SyntaxContext,
+ /// Whether this expression can be safely moved into a closure.
+ allow_insert_closure: bool,
+ /// Whether this expression can use the entry api.
+ can_use_entry: bool,
+ /// Whether this expression is the final expression in this code path. This may be a statement.
+ in_tail_pos: bool,
+ // Is this expression a single insert. A slightly better suggestion can be made in this case.
+ is_single_insert: bool,
+ /// If the visitor has seen the map being used.
+ is_map_used: bool,
+ /// The locations where changes need to be made for the suggestion.
+ edits: Vec<Edit<'tcx>>,
+ /// A stack of loops the visitor is currently in.
+ loops: Vec<HirId>,
+ /// Local variables created in the expression. These don't need to be captured.
+ locals: HirIdSet,
+}
+impl<'tcx> InsertSearcher<'_, 'tcx> {
+ /// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
+ /// only if they are on separate code paths. This will return whether the map was used in the
+ /// given expression.
+ fn visit_cond_arm(&mut self, e: &'tcx Expr<'_>) -> bool {
+ let is_map_used = self.is_map_used;
+ let in_tail_pos = self.in_tail_pos;
+ self.visit_expr(e);
+ let res = self.is_map_used;
+ self.is_map_used = is_map_used;
+ self.in_tail_pos = in_tail_pos;
+ res
+ }
+
+ /// Visits an expression which is not itself in a tail position, but other sibling expressions
+ /// may be. e.g. if conditions
+ fn visit_non_tail_expr(&mut self, e: &'tcx Expr<'_>) {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ self.visit_expr(e);
+ self.in_tail_pos = in_tail_pos;
+ }
+}
+impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
+ type Map = ErasedMap<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Semi(e) => {
+ self.visit_expr(e);
+
+ if self.in_tail_pos && self.allow_insert_closure {
+ // The spans are used to slice the top level expression into multiple parts. This requires that
+ // they all come from the same part of the source code.
+ if stmt.span.ctxt() == self.ctxt && e.span.ctxt() == self.ctxt {
+ self.edits
+ .push(Edit::RemoveSemi(stmt.span.trim_start(e.span).unwrap_or(DUMMY_SP)));
+ } else {
+ self.allow_insert_closure = false;
+ }
+ }
+ },
+ StmtKind::Expr(e) => self.visit_expr(e),
+ StmtKind::Local(l) => {
+ self.visit_pat(l.pat);
+ if let Some(e) = l.init {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.in_tail_pos = false;
+ self.is_single_insert = false;
+ self.visit_expr(e);
+ }
+ },
+ StmtKind::Item(_) => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.is_single_insert = false;
+ },
+ }
+ }
+
+ fn visit_block(&mut self, block: &'tcx Block<'_>) {
+ // If the block is in a tail position, then the last expression (possibly a statement) is in the
+ // tail position. The rest, however, are not.
+ match (block.stmts, block.expr) {
+ ([], None) => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ },
+ ([], Some(expr)) => self.visit_expr(expr),
+ (stmts, Some(expr)) => {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ for stmt in stmts {
+ self.visit_stmt(stmt);
+ }
+ self.in_tail_pos = in_tail_pos;
+ self.visit_expr(expr);
+ },
+ ([stmts @ .., stmt], None) => {
+ let in_tail_pos = self.in_tail_pos;
+ self.in_tail_pos = false;
+ for stmt in stmts {
+ self.visit_stmt(stmt);
+ }
+ self.in_tail_pos = in_tail_pos;
+ self.visit_stmt(stmt);
+ },
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if !self.can_use_entry {
+ return;
+ }
+
+ match try_parse_insert(self.cx, expr) {
+ Some(insert_expr) if SpanlessEq::new(self.cx).eq_expr(self.map, insert_expr.map) => {
+ // Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
+ if self.is_map_used
+ || !SpanlessEq::new(self.cx).eq_expr(self.key, insert_expr.key)
+ || expr.span.ctxt() != self.ctxt
+ {
+ self.can_use_entry = false;
+ return;
+ }
+
+ self.edits.push(Edit::Insertion(Insertion {
+ call: expr,
+ value: insert_expr.value,
+ }));
+ self.is_map_used = true;
+ self.allow_insert_closure &= self.in_tail_pos;
+
+ // The value doesn't affect whether there is only a single insert expression.
+ let is_single_insert = self.is_single_insert;
+ self.visit_non_tail_expr(insert_expr.value);
+ self.is_single_insert = is_single_insert;
+ },
+ _ if SpanlessEq::new(self.cx).eq_expr(self.map, expr) => {
+ self.is_map_used = true;
+ },
+ _ => match expr.kind {
+ ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
+ self.is_single_insert = false;
+ self.visit_non_tail_expr(cond_expr);
+ // Each branch may contain it's own insert expression.
+ let mut is_map_used = self.visit_cond_arm(then_expr);
+ is_map_used |= self.visit_cond_arm(else_expr);
+ self.is_map_used = is_map_used;
+ },
+ ExprKind::Match(scrutinee_expr, arms, _) => {
+ self.is_single_insert = false;
+ self.visit_non_tail_expr(scrutinee_expr);
+ // Each branch may contain it's own insert expression.
+ let mut is_map_used = self.is_map_used;
+ for arm in arms {
+ self.visit_pat(arm.pat);
+ if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard {
+ self.visit_non_tail_expr(guard);
+ }
+ is_map_used |= self.visit_cond_arm(arm.body);
+ }
+ self.is_map_used = is_map_used;
+ },
+ ExprKind::Loop(block, ..) => {
+ self.loops.push(expr.hir_id);
+ self.is_single_insert = false;
+ self.allow_insert_closure &= !self.in_tail_pos;
+ // Don't allow insertions inside of a loop.
+ let edit_len = self.edits.len();
+ self.visit_block(block);
+ if self.edits.len() != edit_len {
+ self.can_use_entry = false;
+ }
+ self.loops.pop();
+ },
+ ExprKind::Block(block, _) => self.visit_block(block),
+ ExprKind::InlineAsm(_) | ExprKind::LlvmInlineAsm(_) => {
+ self.can_use_entry = false;
+ },
+ _ => {
+ self.allow_insert_closure &= !self.in_tail_pos;
+ self.allow_insert_closure &=
+ can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops, &self.locals);
+ // Sub expressions are no longer in the tail position.
+ self.is_single_insert = false;
+ self.in_tail_pos = false;
+ walk_expr(self, expr);
+ },
+ },
+ }
+ }
+
+ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
+ p.each_binding_or_first(&mut |_, id, _, _| {
+ self.locals.insert(id);
+ });
+ }
+}
+
+struct InsertSearchResults<'tcx> {
+ edits: Vec<Edit<'tcx>>,
+ allow_insert_closure: bool,
+ is_single_insert: bool,
+}
+impl InsertSearchResults<'tcx> {
+ fn as_single_insertion(&self) -> Option<Insertion<'tcx>> {
+ self.is_single_insert.then(|| self.edits[0].as_insertion().unwrap())
+ }
+
+ fn snippet(
+ &self,
+ cx: &LateContext<'_>,
+ mut span: Span,
+ app: &mut Applicability,
+ write_wrapped: impl Fn(&mut String, Insertion<'_>, SyntaxContext, &mut Applicability),
+ ) -> String {
+ let ctxt = span.ctxt();
+ let mut res = String::new();
+ for insertion in self.edits.iter().filter_map(|e| e.as_insertion()) {
+ res.push_str(&snippet_with_applicability(
+ cx,
+ span.until(insertion.call.span),
+ "..",
+ app,
+ ));
+ if is_expr_used_or_unified(cx.tcx, insertion.call) {
+ write_wrapped(&mut res, insertion, ctxt, app);
+ } else {
+ let _ = write!(
+ res,
+ "e.insert({})",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
+ );
+ }
+ span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
+ }
+ res.push_str(&snippet_with_applicability(cx, span, "..", app));
+ res
+ }
+
+ fn snippet_occupied(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
+ (
+ self.snippet(cx, span, app, |res, insertion, ctxt, app| {
+ // Insertion into a map would return `Some(&mut value)`, but the entry returns `&mut value`
+ let _ = write!(
+ res,
+ "Some(e.insert({}))",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
+ );
+ }),
+ "Occupied(mut e)",
+ )
+ }
+
+ fn snippet_vacant(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
+ (
+ self.snippet(cx, span, app, |res, insertion, ctxt, app| {
+ // Insertion into a map would return `None`, but the entry returns a mutable reference.
+ let _ = if is_expr_final_block_expr(cx.tcx, insertion.call) {
+ write!(
+ res,
+ "e.insert({});\n{}None",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
+ snippet_indent(cx, insertion.call.span).as_deref().unwrap_or(""),
+ )
+ } else {
+ write!(
+ res,
+ "{{ e.insert({}); None }}",
+ snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
+ )
+ };
+ }),
+ "Vacant(e)",
+ )
+ }
+
+ fn snippet_closure(&self, cx: &LateContext<'_>, mut span: Span, app: &mut Applicability) -> String {
+ let ctxt = span.ctxt();
+ let mut res = String::new();
+ for edit in &self.edits {
+ match *edit {
+ Edit::Insertion(insertion) => {
+ // Cut out the value from `map.insert(key, value)`
+ res.push_str(&snippet_with_applicability(
+ cx,
+ span.until(insertion.call.span),
+ "..",
+ app,
+ ));
+ res.push_str(&snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0);
+ span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
+ },
+ Edit::RemoveSemi(semi_span) => {
+ // Cut out the semicolon. This allows the value to be returned from the closure.
+ res.push_str(&snippet_with_applicability(cx, span.until(semi_span), "..", app));
+ span = span.trim_start(semi_span).unwrap_or(DUMMY_SP);
+ },
+ }
+ }
+ res.push_str(&snippet_with_applicability(cx, span, "..", app));
+ res
+ }
+}
+
+fn find_insert_calls(
+ cx: &LateContext<'tcx>,
+ contains_expr: &ContainsExpr<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<InsertSearchResults<'tcx>> {
+ let mut s = InsertSearcher {
+ cx,
+ map: contains_expr.map,
+ key: contains_expr.key,
+ ctxt: expr.span.ctxt(),
+ edits: Vec::new(),
+ is_map_used: false,
+ allow_insert_closure: true,
+ can_use_entry: true,
+ in_tail_pos: true,
+ is_single_insert: true,
+ loops: Vec::new(),
+ locals: HirIdSet::default(),
+ };
+ s.visit_expr(expr);
+ let allow_insert_closure = s.allow_insert_closure;
+ let is_single_insert = s.is_single_insert;
+ let edits = s.edits;
+ s.can_use_entry.then(|| InsertSearchResults {
+ edits,
+ allow_insert_closure,
+ is_single_insert,
+ })
+}
--- /dev/null
+//! lint on C-like enums that are `repr(isize/usize)` and have values that
+//! don't fit into an `i32`
+
+use clippy_utils::consts::{miri_to_const, Constant};
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::util::IntTypeExt;
+use rustc_middle::ty::{self, IntTy, UintTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::convert::TryFrom;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for C-like enumerations that are
+ /// `repr(isize/usize)` and have values that don't fit into an `i32`.
+ ///
+ /// ### Why is this bad?
+ /// This will truncate the variant value on 32 bit
+ /// architectures, but works fine on 64 bit.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # #[cfg(target_pointer_width = "64")]
+ /// #[repr(usize)]
+ /// enum NonPortable {
+ /// X = 0x1_0000_0000,
+ /// Y = 0,
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ENUM_CLIKE_UNPORTABLE_VARIANT,
+ correctness,
+ "C-like enums that are `repr(isize/usize)` and have values that don't fit into an `i32`"
+}
+
+declare_lint_pass!(UnportableVariant => [ENUM_CLIKE_UNPORTABLE_VARIANT]);
+
+impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
+ #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss)]
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if cx.tcx.data_layout.pointer_size.bits() != 64 {
+ return;
+ }
+ if let ItemKind::Enum(def, _) = &item.kind {
+ for var in def.variants {
+ if let Some(anon_const) = &var.disr_expr {
+ let def_id = cx.tcx.hir().body_owner_def_id(anon_const.body);
+ let mut ty = cx.tcx.type_of(def_id.to_def_id());
+ let constant = cx
+ .tcx
+ .const_eval_poly(def_id.to_def_id())
+ .ok()
+ .map(|val| rustc_middle::ty::Const::from_value(cx.tcx, val, ty));
+ if let Some(Constant::Int(val)) = constant.and_then(miri_to_const) {
+ if let ty::Adt(adt, _) = ty.kind() {
+ if adt.is_enum() {
+ ty = adt.repr.discr_type().to_ty(cx.tcx);
+ }
+ }
+ match ty.kind() {
+ ty::Int(IntTy::Isize) => {
+ let val = ((val as i128) << 64) >> 64;
+ if i32::try_from(val).is_ok() {
+ continue;
+ }
+ },
+ ty::Uint(UintTy::Usize) if val > u128::from(u32::MAX) => {},
+ _ => continue,
+ }
+ span_lint(
+ cx,
+ ENUM_CLIKE_UNPORTABLE_VARIANT,
+ var.span,
+ "C-like enum variant discriminant is not portable to 32-bit targets",
+ );
+ };
+ }
+ }
+ }
+ }
+}
--- /dev/null
+//! lint on enum variants that are prefixed or suffixed by the same characters
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::source::is_present_in_source;
+use clippy_utils::str_utils::{self, count_match_end, count_match_start};
+use rustc_hir::{EnumDef, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Symbol;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects enumeration variants that are prefixed or suffixed
+ /// by the same characters.
+ ///
+ /// ### Why is this bad?
+ /// Enumeration variant names should specify their variant,
+ /// not repeat the enumeration name.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum Cake {
+ /// BlackForestCake,
+ /// HummingbirdCake,
+ /// BattenbergCake,
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// enum Cake {
+ /// BlackForest,
+ /// Hummingbird,
+ /// Battenberg,
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ENUM_VARIANT_NAMES,
+ style,
+ "enums where all variants share a prefix/postfix"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects type names that are prefixed or suffixed by the
+ /// containing module's name.
+ ///
+ /// ### Why is this bad?
+ /// It requires the user to type the module name twice.
+ ///
+ /// ### Example
+ /// ```rust
+ /// mod cake {
+ /// struct BlackForestCake;
+ /// }
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// mod cake {
+ /// struct BlackForest;
+ /// }
+ /// ```
++ #[clippy::version = "1.33.0"]
+ pub MODULE_NAME_REPETITIONS,
+ pedantic,
+ "type names prefixed/postfixed with their containing module's name"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for modules that have the same name as their
+ /// parent module
+ ///
+ /// ### Why is this bad?
+ /// A typical beginner mistake is to have `mod foo;` and
+ /// again `mod foo { ..
+ /// }` in `foo.rs`.
+ /// The expectation is that items inside the inner `mod foo { .. }` are then
+ /// available
+ /// through `foo::x`, but they are only available through
+ /// `foo::foo::x`.
+ /// If this is done on purpose, it would be better to choose a more
+ /// representative module name.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // lib.rs
+ /// mod foo;
+ /// // foo.rs
+ /// mod foo {
+ /// ...
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MODULE_INCEPTION,
+ style,
+ "modules that have the same name as their parent module"
+}
+
+pub struct EnumVariantNames {
+ modules: Vec<(Symbol, String)>,
+ threshold: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl EnumVariantNames {
+ #[must_use]
+ pub fn new(threshold: u64, avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ modules: Vec::new(),
+ threshold,
+ avoid_breaking_exported_api,
+ }
+ }
+}
+
+impl_lint_pass!(EnumVariantNames => [
+ ENUM_VARIANT_NAMES,
+ MODULE_NAME_REPETITIONS,
+ MODULE_INCEPTION
+]);
+
+fn check_variant(
+ cx: &LateContext<'_>,
+ threshold: u64,
+ def: &EnumDef<'_>,
+ item_name: &str,
+ item_name_chars: usize,
+ span: Span,
+) {
+ if (def.variants.len() as u64) < threshold {
+ return;
+ }
+ for var in def.variants {
+ let name = var.ident.name.as_str();
+ if count_match_start(item_name, &name).char_count == item_name_chars
+ && name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase())
+ && name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric())
+ {
+ span_lint(
+ cx,
+ ENUM_VARIANT_NAMES,
+ var.span,
+ "variant name starts with the enum's name",
+ );
+ }
+ if count_match_end(item_name, &name).char_count == item_name_chars {
+ span_lint(
+ cx,
+ ENUM_VARIANT_NAMES,
+ var.span,
+ "variant name ends with the enum's name",
+ );
+ }
+ }
+ let first = &def.variants[0].ident.name.as_str();
+ let mut pre = &first[..str_utils::camel_case_until(&*first).byte_index];
+ let mut post = &first[str_utils::camel_case_start(&*first).byte_index..];
+ for var in def.variants {
+ let name = var.ident.name.as_str();
+
+ let pre_match = count_match_start(pre, &name).byte_count;
+ pre = &pre[..pre_match];
+ let pre_camel = str_utils::camel_case_until(pre).byte_index;
+ pre = &pre[..pre_camel];
+ while let Some((next, last)) = name[pre.len()..].chars().zip(pre.chars().rev()).next() {
+ if next.is_numeric() {
+ return;
+ }
+ if next.is_lowercase() {
+ let last = pre.len() - last.len_utf8();
+ let last_camel = str_utils::camel_case_until(&pre[..last]);
+ pre = &pre[..last_camel.byte_index];
+ } else {
+ break;
+ }
+ }
+
+ let post_match = count_match_end(post, &name);
+ let post_end = post.len() - post_match.byte_count;
+ post = &post[post_end..];
+ let post_camel = str_utils::camel_case_start(post);
+ post = &post[post_camel.byte_index..];
+ }
+ let (what, value) = match (pre.is_empty(), post.is_empty()) {
+ (true, true) => return,
+ (false, _) => ("pre", pre),
+ (true, false) => ("post", post),
+ };
+ span_lint_and_help(
+ cx,
+ ENUM_VARIANT_NAMES,
+ span,
+ &format!("all variants have the same {}fix: `{}`", what, value),
+ None,
+ &format!(
+ "remove the {}fixes and use full paths to \
+ the variants instead of glob imports",
+ what
+ ),
+ );
+}
+
+#[must_use]
+fn to_camel_case(item_name: &str) -> String {
+ let mut s = String::new();
+ let mut up = true;
+ for c in item_name.chars() {
+ if c.is_uppercase() {
+ // we only turn snake case text into CamelCase
+ return item_name.to_string();
+ }
+ if c == '_' {
+ up = true;
+ continue;
+ }
+ if up {
+ up = false;
+ s.extend(c.to_uppercase());
+ } else {
+ s.push(c);
+ }
+ }
+ s
+}
+
+impl LateLintPass<'_> for EnumVariantNames {
+ fn check_item_post(&mut self, _cx: &LateContext<'_>, _item: &Item<'_>) {
+ let last = self.modules.pop();
+ assert!(last.is_some());
+ }
+
+ #[allow(clippy::similar_names)]
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ let item_name = item.ident.name.as_str();
+ let item_name_chars = item_name.chars().count();
+ let item_camel = to_camel_case(&item_name);
+ if !item.span.from_expansion() && is_present_in_source(cx, item.span) {
+ if let Some(&(ref mod_name, ref mod_camel)) = self.modules.last() {
+ // constants don't have surrounding modules
+ if !mod_camel.is_empty() {
+ if mod_name == &item.ident.name {
+ if let ItemKind::Mod(..) = item.kind {
+ span_lint(
+ cx,
+ MODULE_INCEPTION,
+ item.span,
+ "module has the same name as its containing module",
+ );
+ }
+ }
+ // The `module_name_repetitions` lint should only trigger if the item has the module in its
+ // name. Having the same name is accepted.
+ if item.vis.node.is_pub() && item_camel.len() > mod_camel.len() {
+ let matching = count_match_start(mod_camel, &item_camel);
+ let rmatching = count_match_end(mod_camel, &item_camel);
+ let nchars = mod_camel.chars().count();
+
+ let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
+
+ if matching.char_count == nchars {
+ match item_camel.chars().nth(nchars) {
+ Some(c) if is_word_beginning(c) => span_lint(
+ cx,
+ MODULE_NAME_REPETITIONS,
+ item.span,
+ "item name starts with its containing module's name",
+ ),
+ _ => (),
+ }
+ }
+ if rmatching.char_count == nchars {
+ span_lint(
+ cx,
+ MODULE_NAME_REPETITIONS,
+ item.span,
+ "item name ends with its containing module's name",
+ );
+ }
+ }
+ }
+ }
+ }
+ if let ItemKind::Enum(ref def, _) = item.kind {
+ if !(self.avoid_breaking_exported_api && cx.access_levels.is_exported(item.def_id)) {
+ check_variant(cx, self.threshold, def, &item_name, item_name_chars, item.span);
+ }
+ }
+ self.modules.push((item.ident.name, item_camel));
+ }
+}
--- /dev/null
- use clippy_utils::{
- ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, in_macro, is_expn_of, is_in_test_function,
- };
+use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{implements_trait, is_copy};
- in_macro(expr.span)
++use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, is_expn_of, is_in_test_function};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for equal operands to comparison, logical and
+ /// bitwise, difference and division binary operators (`==`, `>`, etc., `&&`,
+ /// `||`, `&`, `|`, `^`, `-` and `/`).
+ ///
+ /// ### Why is this bad?
+ /// This is usually just a typo or a copy and paste error.
+ ///
+ /// ### Known problems
+ /// False negatives: We had some false positives regarding
+ /// calls (notably [racer](https://github.com/phildawes/racer) had one instance
+ /// of `x.pop() && x.pop()`), so we removed matching any function or method
+ /// calls. We may introduce a list of known pure functions in the future.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// if x + 1 == x + 1 {}
+ /// ```
+ /// or
+ /// ```rust
+ /// # let a = 3;
+ /// # let b = 4;
+ /// assert_eq!(a, a);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub OP_REF,
+ style,
+ "taking a reference to satisfy the type constraints on `==`"
+}
+
+declare_lint_pass!(EqOp => [EQ_OP, OP_REF]);
+
+const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_eq", "debug_assert_ne"];
+
+impl<'tcx> LateLintPass<'tcx> for EqOp {
+ #[allow(clippy::similar_names, clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Block(block, _) = e.kind {
+ for stmt in block.stmts {
+ for amn in &ASSERT_MACRO_NAMES {
+ if_chain! {
+ if is_expn_of(stmt.span, amn).is_some();
+ if let StmtKind::Semi(matchexpr) = stmt.kind;
+ if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr);
+ if macro_args.len() == 2;
+ let (lhs, rhs) = (macro_args[0], macro_args[1]);
+ if eq_expr_value(cx, lhs, rhs);
+ if !is_in_test_function(cx.tcx, e.hir_id);
+ then {
+ span_lint(
+ cx,
+ EQ_OP,
+ lhs.span.to(rhs.span),
+ &format!("identical args used in this `{}!` macro call", amn),
+ );
+ }
+ }
+ }
+ }
+ }
+ if let ExprKind::Binary(op, left, right) = e.kind {
+ if e.span.from_expansion() {
+ return;
+ }
+ let macro_with_not_op = |expr_kind: &ExprKind<'_>| {
+ if let ExprKind::Unary(_, expr) = *expr_kind {
++ expr.span.from_expansion()
+ } else {
+ false
+ }
+ };
+ if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
+ return;
+ }
+ if is_useless_with_eq_exprs(op.node.into())
+ && eq_expr_value(cx, left, right)
+ && !is_in_test_function(cx.tcx, e.hir_id)
+ {
+ 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, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
+ let lty = cx.typeck_results().expr_ty(l);
+ let rty = cx.typeck_results().expr_ty(r);
+ let lcpy = is_copy(cx, lty);
+ let rcpy = is_copy(cx, rty);
+ // either operator autorefs or both args are copyable
+ if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of both operands",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ multispan_sugg(
+ diag,
+ "use the values directly",
+ vec![(left.span, lsnip), (right.span, rsnip)],
+ );
+ },
+ );
+ } else if lcpy
+ && !rcpy
+ && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ } else if !lcpy
+ && rcpy
+ && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of right operand",
+ |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ }
+ },
+ // &foo == bar
+ (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
+ let lty = cx.typeck_results().expr_ty(l);
+ let lcpy = is_copy(cx, lty);
+ if (requires_ref || lcpy)
+ && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()])
+ {
+ span_lint_and_then(
+ cx,
+ OP_REF,
+ e.span,
+ "needlessly taken reference of left operand",
+ |diag| {
+ let lsnip = snippet(cx, l.span, "...").to_string();
+ diag.span_suggestion(
+ left.span,
+ "use the left value directly",
+ lsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ },
+ );
+ }
+ },
+ // foo == &bar
+ (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
+ let rty = cx.typeck_results().expr_ty(r);
+ let rcpy = is_copy(cx, rty);
+ if (requires_ref || rcpy)
+ && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
+ {
+ span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| {
+ let rsnip = snippet(cx, r.span, "...").to_string();
+ diag.span_suggestion(
+ right.span,
+ "use the right value directly",
+ rsnip,
+ Applicability::MaybeIncorrect, // FIXME #2597
+ );
+ });
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::implements_trait;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Pat, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for pattern matchings that can be expressed using equality.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// * It reads better and has less cognitive load because equality won't cause binding.
+ /// * It is a [Yoda condition](https://en.wikipedia.org/wiki/Yoda_conditions). Yoda conditions are widely
+ /// criticized for increasing the cognitive load of reading the code.
+ /// * Equality is a simple bool expression and can be merged with `&&` and `||` and
+ /// reuse if blocks
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// if let Some(2) = x {
+ /// do_thing();
+ /// }
+ /// ```
+ /// Should be written
+ /// ```rust,ignore
+ /// if x == Some(2) {
+ /// do_thing();
+ /// }
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub EQUATABLE_IF_LET,
+ nursery,
+ "using pattern matching instead of equality"
+}
+
+declare_lint_pass!(PatternEquality => [EQUATABLE_IF_LET]);
+
+/// detects if pattern matches just one thing
+fn unary_pattern(pat: &Pat<'_>) -> bool {
+ fn array_rec(pats: &[Pat<'_>]) -> bool {
+ pats.iter().all(unary_pattern)
+ }
+ match &pat.kind {
+ PatKind::Slice(_, _, _) | PatKind::Range(_, _, _) | PatKind::Binding(..) | PatKind::Wild | PatKind::Or(_) => {
+ false
+ },
+ PatKind::Struct(_, a, etc) => !etc && a.iter().all(|x| unary_pattern(x.pat)),
+ PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => !etc.is_some() && array_rec(a),
+ PatKind::Ref(x, _) | PatKind::Box(x) => unary_pattern(x),
+ PatKind::Path(_) | PatKind::Lit(_) => true,
+ }
+}
+
+fn is_structural_partial_eq(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> bool {
+ if let Some(def_id) = cx.tcx.lang_items().eq_trait() {
+ implements_trait(cx, ty, def_id, &[other.into()])
+ } else {
+ false
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for PatternEquality {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if_chain! {
+ if let ExprKind::Let(pat, exp, _) = expr.kind;
+ if unary_pattern(pat);
+ let exp_ty = cx.typeck_results().expr_ty(exp);
+ let pat_ty = cx.typeck_results().pat_ty(pat);
+ if is_structural_partial_eq(cx, exp_ty, pat_ty);
+ then {
+
+ let mut applicability = Applicability::MachineApplicable;
+ let pat_str = match pat.kind {
+ PatKind::Struct(..) => format!(
+ "({})",
+ snippet_with_context(cx, pat.span, expr.span.ctxt(), "..", &mut applicability).0,
+ ),
+ _ => snippet_with_context(cx, pat.span, expr.span.ctxt(), "..", &mut applicability).0.to_string(),
+ };
+ span_lint_and_sugg(
+ cx,
+ EQUATABLE_IF_LET,
+ expr.span,
+ "this pattern matching can be expressed using equality",
+ "try",
+ format!(
+ "{} == {}",
+ snippet_with_context(cx, exp.span, expr.span.ctxt(), "..", &mut applicability).0,
+ pat_str,
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for erasing operations, e.g., `x * 0`.
+ ///
+ /// ### Why is this bad?
+ /// The whole expression can be replaced by zero.
+ /// This is most likely not the intended outcome and should probably be
+ /// corrected
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1;
+ /// 0 / x;
+ /// 0 * x;
+ /// x & 0;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ERASING_OP,
+ correctness,
+ "using erasing operations, e.g., `x * 0` or `y & 0`"
+}
+
+declare_lint_pass!(ErasingOp => [ERASING_OP]);
+
+impl<'tcx> LateLintPass<'tcx> for ErasingOp {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+ if let ExprKind::Binary(ref cmp, left, right) = e.kind {
+ match cmp.node {
+ BinOpKind::Mul | BinOpKind::BitAnd => {
+ check(cx, left, e.span);
+ check(cx, right, e.span);
+ },
+ BinOpKind::Div => check(cx, left, e.span),
+ _ => (),
+ }
+ }
+ }
+}
+
+fn check(cx: &LateContext<'_>, e: &Expr<'_>, span: Span) {
+ if constant_simple(cx, cx.typeck_results(), e) == Some(Constant::Int(0)) {
+ span_lint(
+ cx,
+ ERASING_OP,
+ span,
+ "this operation will always return zero. This is likely not the intended outcome",
+ );
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::contains_ty;
+use rustc_hir::intravisit;
+use rustc_hir::{self, AssocItemKind, Body, FnDecl, HirId, HirIdSet, Impl, ItemKind, Node};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, TraitRef, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::kw;
+use rustc_target::spec::abi::Abi;
+use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+
+#[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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn foo(bar: usize) {}
+ /// // Bad
+ /// let x = Box::new(1);
+ /// foo(*x);
+ /// println!("{}", *x);
+ ///
+ /// // Good
+ /// let x = 1;
+ /// foo(x);
+ /// println!("{}", x);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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,
+ trait_self_ty: Option<Ty<'tcx>>,
+ too_large_for_stack: u64,
+}
+
+impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]);
+
+impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ fn_kind: intravisit::FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ hir_id: HirId,
+ ) {
+ if let Some(header) = fn_kind.header() {
+ if header.abi != Abi::Rust {
+ return;
+ }
+ }
+
+ let parent_id = cx.tcx.hir().get_parent_item(hir_id);
+ let parent_node = cx.tcx.hir().find(parent_id);
+
+ let mut trait_self_ty = None;
+ if let Some(Node::Item(item)) = parent_node {
+ // If the method is an impl for a trait, don't warn.
+ if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = item.kind {
+ return;
+ }
+
+ // find `self` ty for this trait if relevant
+ if let ItemKind::Trait(_, _, _, _, items) = item.kind {
+ for trait_item in items {
+ if trait_item.id.hir_id() == hir_id {
+ // be sure we have `self` parameter in this function
+ if trait_item.kind == (AssocItemKind::Fn { has_self: true }) {
+ trait_self_ty = Some(
+ TraitRef::identity(cx.tcx, trait_item.id.def_id.to_def_id())
+ .self_ty()
+ .skip_binder(),
+ );
+ }
+ }
+ }
+ }
+ }
+
+ let mut v = EscapeDelegate {
+ cx,
+ set: HirIdSet::default(),
+ trait_self_ty,
+ too_large_for_stack: self.too_large_for_stack,
+ };
+
+ let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
+ });
+
+ for node in v.set {
+ span_lint(
+ cx,
+ BOXED_LOCAL,
+ cx.tcx.hir().span(node),
+ "local variable doesn't need to be boxed here",
+ );
+ }
+ }
+}
+
+// TODO: Replace with Map::is_argument(..) when it's fixed
+fn is_argument(map: rustc_middle::hir::map::Map<'_>, id: HirId) -> bool {
+ match map.find(id) {
+ Some(Node::Binding(_)) => (),
+ _ => return false,
+ }
+
+ matches!(map.find(map.get_parent_node(id)), Some(Node::Param(_)))
+}
+
+impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> {
+ fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ if cmt.place.projections.is_empty() {
+ if let PlaceBase::Local(lid) = cmt.place.base {
+ self.set.remove(&lid);
+ let map = &self.cx.tcx.hir();
+ if let Some(Node::Binding(_)) = map.find(cmt.hir_id) {
+ if self.set.contains(&lid) {
+ // let y = x where x is known
+ // remove x, insert y
+ self.set.insert(cmt.hir_id);
+ self.set.remove(&lid);
+ }
+ }
+ }
+ }
+ }
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
+ if cmt.place.projections.is_empty() {
+ if let PlaceBase::Local(lid) = cmt.place.base {
+ self.set.remove(&lid);
+ }
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ if cmt.place.projections.is_empty() {
+ let map = &self.cx.tcx.hir();
+ if is_argument(*map, cmt.hir_id) {
+ // Skip closure arguments
+ let parent_id = map.get_parent_node(cmt.hir_id);
+ if let Some(Node::Expr(..)) = map.find(map.get_parent_node(parent_id)) {
+ return;
+ }
+
+ // skip if there is a `self` parameter binding to a type
+ // that contains `Self` (i.e.: `self: Box<Self>`), see #4804
+ if let Some(trait_self_ty) = self.trait_self_ty {
+ if map.name(cmt.hir_id) == kw::SelfLower && contains_ty(self.cx.tcx, cmt.place.ty(), trait_self_ty)
+ {
+ return;
+ }
+ }
+
+ if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) {
+ self.set.insert(cmt.hir_id);
+ }
+ }
+ }
+ }
+
+ fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> {
+ fn is_large_box(&self, ty: Ty<'tcx>) -> bool {
+ // Large types need to be boxed to avoid stack overflows.
+ if ty.is_box() {
+ self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
- use clippy_utils::usage::UsedAfterExprVisitor;
- use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local_id};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::higher::VecArgs;
+use clippy_utils::source::snippet_opt;
- || UsedAfterExprVisitor::is_found(cx, callee);
++use clippy_utils::usage::local_used_after_expr;
++use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Expr, ExprKind, Param, PatKind, Unsafety};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
+use rustc_middle::ty::subst::Subst;
+use rustc_middle::ty::{self, ClosureKind, Ty, TypeFoldable};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for closures which just call another function where
+ /// the function can be called directly. `unsafe` functions or calls where types
+ /// get adjusted are ignored.
+ ///
+ /// ### Why is this bad?
+ /// Needlessly creating a closure adds code for no benefit
+ /// and gives the optimizer more work.
+ ///
+ /// ### Known problems
+ /// If creating the closure inside the closure has a side-
+ /// effect then moving the closure creation out will change when that side-
+ /// effect runs.
+ /// See [#1439](https://github.com/rust-lang/rust-clippy/issues/1439) for more details.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// xs.map(|x| foo(x))
+ ///
+ /// // Good
+ /// xs.map(foo)
+ /// ```
+ /// where `foo(_)` is a plain function that takes the exact argument type of
+ /// `x`.
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// Some('a').map(|s| s.to_uppercase());
+ /// ```
+ /// may be rewritten as
+ /// ```rust,ignore
+ /// Some('a').map(char::to_uppercase);
+ /// ```
++ #[clippy::version = "1.35.0"]
+ 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 expr.span.from_expansion() {
+ return;
+ }
+ let body = match expr.kind {
+ ExprKind::Closure(_, _, id, _, _) => cx.tcx.hir().body(id),
+ _ => return,
+ };
+ if body.value.span.from_expansion() {
+ if body.params.is_empty() {
+ if let Some(VecArgs::Vec(&[])) = higher::VecArgs::hir(cx, &body.value) {
+ // replace `|| vec![]` with `Vec::new`
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_CLOSURE,
+ expr.span,
+ "redundant closure",
+ "replace the closure with `Vec::new`",
+ "std::vec::Vec::new".into(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ // skip `foo(|| macro!())`
+ return;
+ }
+
+ let closure_ty = cx.typeck_results().expr_ty(expr);
+
+ if_chain!(
+ if let ExprKind::Call(callee, args) = body.value.kind;
+ if let ExprKind::Path(_) = callee.kind;
+ if check_inputs(cx, body.params, args);
+ let callee_ty = cx.typeck_results().expr_ty_adjusted(callee);
+ let call_ty = cx.typeck_results().type_dependent_def_id(body.value.hir_id)
+ .map_or(callee_ty, |id| cx.tcx.type_of(id));
+ if check_sig(cx, closure_ty, call_ty);
+ let substs = cx.typeck_results().node_substs(callee.hir_id);
+ // This fixes some false positives that I don't entirely understand
+ if substs.is_empty() || !cx.typeck_results().expr_ty(expr).has_late_bound_regions();
+ // A type param function ref like `T::f` is not 'static, however
+ // it is if cast like `T::f as fn()`. This seems like a rustc bug.
+ if !substs.types().any(|t| matches!(t.kind(), ty::Param(_)));
+ then {
+ span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
+ if let Some(mut snippet) = snippet_opt(cx, callee.span) {
+ if_chain! {
+ if let ty::Closure(_, substs) = callee_ty.peel_refs().kind();
+ if substs.as_closure().kind() == ClosureKind::FnMut;
+ if get_enclosing_loop_or_closure(cx.tcx, expr).is_some()
++ || path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, callee));
+
+ then {
+ // Mutable closure is used after current expr; we cannot consume it.
+ snippet = format!("&mut {}", snippet);
+ }
+ }
+ diag.span_suggestion(
+ expr.span,
+ "replace the closure with the function itself",
+ snippet,
+ Applicability::MachineApplicable,
+ );
+ }
+ });
+ }
+ );
+
+ if_chain!(
+ if let ExprKind::MethodCall(path, _, args, _) = body.value.kind;
+ if check_inputs(cx, body.params, args);
+ let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap();
+ let substs = cx.typeck_results().node_substs(body.value.hir_id);
+ let call_ty = cx.tcx.type_of(method_def_id).subst(cx.tcx, substs);
+ if check_sig(cx, closure_ty, call_ty);
+ then {
+ span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| {
+ let name = get_ufcs_type_name(cx, method_def_id);
+ diag.span_suggestion(
+ expr.span,
+ "replace the closure with the method itself",
+ format!("{}::{}", name, path.ident.name),
+ Applicability::MachineApplicable,
+ );
+ })
+ }
+ );
+ }
+}
+
+fn check_inputs(cx: &LateContext<'_>, params: &[Param<'_>], call_args: &[Expr<'_>]) -> bool {
+ if params.len() != call_args.len() {
+ return false;
+ }
+ std::iter::zip(params, call_args).all(|(param, arg)| {
+ match param.pat.kind {
+ PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {},
+ _ => return false,
+ }
+ match *cx.typeck_results().expr_adjustments(arg) {
+ [] => true,
+ [
+ Adjustment {
+ kind: Adjust::Deref(None),
+ ..
+ },
+ Adjustment {
+ kind: Adjust::Borrow(AutoBorrow::Ref(_, mu2)),
+ ..
+ },
+ ] => {
+ // re-borrow with the same mutability is allowed
+ let ty = cx.typeck_results().expr_ty(arg);
+ matches!(*ty.kind(), ty::Ref(.., mu1) if mu1 == mu2.into())
+ },
+ _ => false,
+ }
+ })
+}
+
+fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tcx>) -> bool {
+ let call_sig = call_ty.fn_sig(cx.tcx);
+ if call_sig.unsafety() == Unsafety::Unsafe {
+ return false;
+ }
+ if !closure_ty.has_late_bound_regions() {
+ return true;
+ }
+ let substs = match closure_ty.kind() {
+ ty::Closure(_, substs) => substs,
+ _ => return false,
+ };
+ let closure_sig = cx.tcx.signature_unclosure(substs.as_closure().sig(), Unsafety::Normal);
+ cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig)
+}
+
+fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: DefId) -> String {
+ match cx.tcx.associated_item(method_def_id).container {
+ ty::TraitContainer(def_id) => cx.tcx.def_path_str(def_id),
+ ty::ImplContainer(def_id) => {
+ let ty = cx.tcx.type_of(def_id);
+ match ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did),
+ _ => ty.to_string(),
+ }
+ },
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
+use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id};
+use if_chain::if_chain;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a read and a write to the same variable where
+ /// whether the read occurs before or after the write depends on the evaluation
+ /// order of sub-expressions.
+ ///
+ /// ### Why is this bad?
+ /// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands),
+ /// the operands of these expressions are evaluated before applying the effects of the expression.
+ ///
+ /// ### Known problems
+ /// Code which intentionally depends on the evaluation
+ /// order, or which is correct for any evaluation order.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut x = 0;
+ ///
+ /// // Bad
+ /// let a = {
+ /// x = 1;
+ /// 1
+ /// } + x;
+ /// // Unclear whether a is 1 or 2.
+ ///
+ /// // Good
+ /// let tmp = {
+ /// x = 1;
+ /// 1
+ /// };
+ /// let a = tmp + x;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EVAL_ORDER_DEPENDENCE,
+ suspicious,
+ "whether a variable read occurs before a write depends on sub-expression evaluation order"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for diverging calls that are not match arms or
+ /// statements.
+ ///
+ /// ### Why is this bad?
+ /// It is often confusing to read. In addition, the
+ /// sub-expression evaluation order for Rust is not well documented.
+ ///
+ /// ### Known problems
+ /// Someone might want to use `some_bool || panic!()` as a
+ /// shorthand.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # fn b() -> bool { true }
+ /// # fn c() -> bool { true }
+ /// let a = b() || panic!() || c();
+ /// // `c()` is dead, `panic!()` is only called if `b()` returns `false`
+ /// let x = (a, b, c, panic!());
+ /// // can simply be replaced by `panic!()`
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DIVERGING_SUB_EXPRESSION,
+ complexity,
+ "whether an expression contains a diverging sub expression"
+}
+
+declare_lint_pass!(EvalOrderDependence => [EVAL_ORDER_DEPENDENCE, DIVERGING_SUB_EXPRESSION]);
+
+impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Find a write to a local variable.
+ let var = if_chain! {
+ if let ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) = expr.kind;
+ if let Some(var) = path_to_local(lhs);
+ if expr.span.desugaring_kind().is_none();
+ then { var } else { return; }
+ };
+ let mut visitor = ReadVisitor {
+ cx,
+ var,
+ write_expr: expr,
+ last_expr: expr,
+ };
+ check_for_unsequenced_reads(&mut visitor);
+ }
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if let Local { init: Some(e), .. } = local {
+ DivergenceVisitor { cx }.visit_expr(e);
+ }
+ },
+ StmtKind::Expr(e) | StmtKind::Semi(e) => DivergenceVisitor { cx }.maybe_walk_expr(e),
+ StmtKind::Item(..) => {},
+ }
+ }
+}
+
+struct DivergenceVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
+ fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Closure(..) => {},
+ ExprKind::Match(e, arms, _) => {
+ self.visit_expr(e);
+ for arm in arms {
+ if let Some(Guard::If(if_expr)) = arm.guard {
+ self.visit_expr(if_expr);
+ }
+ // make sure top level arm expressions aren't linted
+ self.maybe_walk_expr(&*arm.body);
+ }
+ },
+ _ => walk_expr(self, e),
+ }
+ }
+ fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) {
+ span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges");
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
+ ExprKind::Call(func, _) => {
+ let typ = self.cx.typeck_results().expr_ty(func);
+ match typ.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let sig = typ.fn_sig(self.cx.tcx);
+ if self.cx.tcx.erase_late_bound_regions(sig).output().kind() == &ty::Never {
+ self.report_diverging_sub_expr(e);
+ }
+ },
+ _ => {},
+ }
+ },
+ ExprKind::MethodCall(..) => {
+ let borrowed_table = self.cx.typeck_results();
+ if borrowed_table.expr_ty(e).is_never() {
+ self.report_diverging_sub_expr(e);
+ }
+ },
+ _ => {
+ // do not lint expressions referencing objects of type `!`, as that required a
+ // diverging expression
+ // to begin with
+ },
+ }
+ self.maybe_walk_expr(e);
+ }
+ fn visit_block(&mut self, _: &'tcx Block<'_>) {
+ // don't continue over blocks, LateLintPass already does that
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Walks up the AST from the given write expression (`vis.write_expr`) looking
+/// for reads to the same variable that are unsequenced relative to the write.
+///
+/// This means reads for which there is a common ancestor between the read and
+/// the write such that
+///
+/// * evaluating the ancestor necessarily evaluates both the read and the write (for example, `&x`
+/// and `|| x = 1` don't necessarily evaluate `x`), and
+///
+/// * which one is evaluated first depends on the order of sub-expression evaluation. Blocks, `if`s,
+/// loops, `match`es, and the short-circuiting logical operators are considered to have a defined
+/// evaluation order.
+///
+/// When such a read is found, the lint is triggered.
+fn check_for_unsequenced_reads(vis: &mut ReadVisitor<'_, '_>) {
+ let map = &vis.cx.tcx.hir();
+ let mut cur_id = vis.write_expr.hir_id;
+ loop {
+ let parent_id = map.get_parent_node(cur_id);
+ if parent_id == cur_id {
+ break;
+ }
+ let parent_node = match map.find(parent_id) {
+ Some(parent) => parent,
+ None => break,
+ };
+
+ let stop_early = match parent_node {
+ Node::Expr(expr) => check_expr(vis, expr),
+ Node::Stmt(stmt) => check_stmt(vis, stmt),
+ Node::Item(_) => {
+ // We reached the top of the function, stop.
+ break;
+ },
+ _ => StopEarly::KeepGoing,
+ };
+ match stop_early {
+ StopEarly::Stop => break,
+ StopEarly::KeepGoing => {},
+ }
+
+ cur_id = parent_id;
+ }
+}
+
+/// Whether to stop early for the loop in `check_for_unsequenced_reads`. (If
+/// `check_expr` weren't an independent function, this would be unnecessary and
+/// we could just use `break`).
+enum StopEarly {
+ KeepGoing,
+ Stop,
+}
+
+fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -> StopEarly {
+ if expr.hir_id == vis.last_expr.hir_id {
+ return StopEarly::KeepGoing;
+ }
+
+ match expr.kind {
+ ExprKind::Array(_)
+ | ExprKind::Tup(_)
+ | ExprKind::MethodCall(..)
+ | ExprKind::Call(_, _)
+ | ExprKind::Assign(..)
+ | ExprKind::Index(_, _)
+ | ExprKind::Repeat(_, _)
+ | ExprKind::Struct(_, _, _) => {
+ walk_expr(vis, expr);
+ },
+ ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => {
+ if op.node == BinOpKind::And || op.node == BinOpKind::Or {
+ // x && y and x || y always evaluate x first, so these are
+ // strictly sequenced.
+ } else {
+ walk_expr(vis, expr);
+ }
+ },
+ ExprKind::Closure(_, _, _, _, _) => {
+ // Either
+ //
+ // * `var` is defined in the closure body, in which case we've reached the top of the enclosing
+ // function and can stop, or
+ //
+ // * `var` is captured by the closure, in which case, because evaluating a closure does not evaluate
+ // its body, we don't necessarily have a write, so we need to stop to avoid generating false
+ // positives.
+ //
+ // This is also the only place we need to stop early (grrr).
+ return StopEarly::Stop;
+ },
+ // All other expressions either have only one child or strictly
+ // sequence the evaluation order of their sub-expressions.
+ _ => {},
+ }
+
+ vis.last_expr = expr;
+
+ StopEarly::KeepGoing
+}
+
+fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
+ match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr),
+ // If the declaration is of a local variable, check its initializer
+ // expression if it has one. Otherwise, keep going.
+ StmtKind::Local(local) => local
+ .init
+ .as_ref()
+ .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)),
+ StmtKind::Item(..) => StopEarly::KeepGoing,
+ }
+}
+
+/// A visitor that looks for reads from a variable.
+struct ReadVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ /// The ID of the variable we're looking for.
+ var: HirId,
+ /// The expressions where the write to the variable occurred (for reporting
+ /// in the lint).
+ write_expr: &'tcx Expr<'tcx>,
+ /// The last (highest in the AST) expression we've checked, so we know not
+ /// to recheck it.
+ last_expr: &'tcx Expr<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if expr.hir_id == self.last_expr.hir_id {
+ return;
+ }
+
+ if path_to_local_id(expr, self.var) {
+ // Check that this is a read, not a write.
+ if !is_in_assignment_position(self.cx, expr) {
+ span_lint_and_note(
+ self.cx,
+ EVAL_ORDER_DEPENDENCE,
+ expr.span,
+ &format!("unsequenced read of `{}`", self.cx.tcx.hir().name(self.var)),
+ Some(self.write_expr.span),
+ "whether read occurs before this write depends on evaluation order",
+ );
+ }
+ }
+ match expr.kind {
+ // We're about to descend a closure. Since we don't know when (or
+ // if) the closure will be evaluated, any reads in it might not
+ // occur here (or ever). Like above, bail to avoid false positives.
+ ExprKind::Closure(_, _, _, _, _) |
+
+ // We want to avoid a false positive when a variable name occurs
+ // only to have its address taken, so we stop here. Technically,
+ // this misses some weird cases, eg.
+ //
+ // ```rust
+ // let mut x = 0;
+ // let a = foo(&{x = 1; x}, x);
+ // ```
+ //
+ // TODO: fix this
+ ExprKind::AddrOf(_, _, _) => {
+ return;
+ }
+ _ => {}
+ }
+
+ walk_expr(self, expr);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`.
+fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::Assign(lhs, ..) = parent.kind {
+ return lhs.hir_id == expr.hir_id;
+ }
+ }
+ false
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_help;
- if in_macro(item.span) {
+use rustc_ast::ast::{AssocItemKind, Extern, Fn, FnSig, Impl, Item, ItemKind, Trait, Ty, TyKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Span};
+
+use std::convert::TryInto;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for excessive
+ /// use of bools in structs.
+ ///
+ /// ### Why is this bad?
+ /// Excessive bools in a struct
+ /// is often a sign that it's used as a state machine,
+ /// which is much better implemented as an enum.
+ /// If it's not the case, excessive bools usually benefit
+ /// from refactoring into two-variant enums for better
+ /// readability and API.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// struct S {
+ /// is_pending: bool,
+ /// is_processing: bool,
+ /// is_finished: bool,
+ /// }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// enum S {
+ /// Pending,
+ /// Processing,
+ /// Finished,
+ /// }
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub STRUCT_EXCESSIVE_BOOLS,
+ pedantic,
+ "using too many bools in a struct"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for excessive use of
+ /// bools in function definitions.
+ ///
+ /// ### Why is this bad?
+ /// Calls to such functions
+ /// are confusing and error prone, because it's
+ /// hard to remember argument order and you have
+ /// no type system support to back you up. Using
+ /// two-variant enums instead of bools often makes
+ /// API easier to use.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// fn f(is_round: bool, is_hot: bool) { ... }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// enum Shape {
+ /// Round,
+ /// Spiky,
+ /// }
+ ///
+ /// enum Temperature {
+ /// Hot,
+ /// IceCold,
+ /// }
+ ///
+ /// fn f(shape: Shape, temperature: Temperature) { ... }
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub FN_PARAMS_EXCESSIVE_BOOLS,
+ pedantic,
+ "using too many bools in function parameters"
+}
+
+pub struct ExcessiveBools {
+ max_struct_bools: u64,
+ max_fn_params_bools: u64,
+}
+
+impl ExcessiveBools {
+ #[must_use]
+ pub fn new(max_struct_bools: u64, max_fn_params_bools: u64) -> Self {
+ Self {
+ max_struct_bools,
+ max_fn_params_bools,
+ }
+ }
+
+ fn check_fn_sig(&self, cx: &EarlyContext<'_>, fn_sig: &FnSig, span: Span) {
+ match fn_sig.header.ext {
+ Extern::Implicit | Extern::Explicit(_) => return,
+ Extern::None => (),
+ }
+
+ let fn_sig_bools = fn_sig
+ .decl
+ .inputs
+ .iter()
+ .filter(|param| is_bool_ty(¶m.ty))
+ .count()
+ .try_into()
+ .unwrap();
+ if self.max_fn_params_bools < fn_sig_bools {
+ span_lint_and_help(
+ cx,
+ FN_PARAMS_EXCESSIVE_BOOLS,
+ span,
+ &format!("more than {} bools in function parameters", self.max_fn_params_bools),
+ None,
+ "consider refactoring bools into two-variant enums",
+ );
+ }
+ }
+}
+
+impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_BOOLS]);
+
+fn is_bool_ty(ty: &Ty) -> bool {
+ if let TyKind::Path(None, path) = &ty.kind {
+ if let [name] = path.segments.as_slice() {
+ return name.ident.name == sym::bool;
+ }
+ }
+ false
+}
+
+impl EarlyLintPass for ExcessiveBools {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
++ if item.span.from_expansion() {
+ return;
+ }
+ match &item.kind {
+ ItemKind::Struct(variant_data, _) => {
+ if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
+ return;
+ }
+
+ let struct_bools = variant_data
+ .fields()
+ .iter()
+ .filter(|field| is_bool_ty(&field.ty))
+ .count()
+ .try_into()
+ .unwrap();
+ if self.max_struct_bools < struct_bools {
+ span_lint_and_help(
+ cx,
+ STRUCT_EXCESSIVE_BOOLS,
+ item.span,
+ &format!("more than {} bools in a struct", self.max_struct_bools),
+ None,
+ "consider using a state machine or refactoring bools into two-variant enums",
+ );
+ }
+ },
+ ItemKind::Impl(box Impl {
+ of_trait: None, items, ..
+ })
+ | ItemKind::Trait(box Trait { items, .. }) => {
+ for item in items {
+ if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind {
+ self.check_fn_sig(cx, sig, item.span);
+ }
+ }
+ },
+ ItemKind::Fn(box Fn { sig, .. }) => self.check_fn_sig(cx, sig, item.span),
+ _ => (),
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::indent_of;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns on any exported `enum`s that are not tagged `#[non_exhaustive]`
+ ///
+ /// ### Why is this bad?
+ /// Exhaustive enums are typically fine, but a project which does
+ /// not wish to make a stability commitment around exported enums may wish to
+ /// disable them by default.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum Foo {
+ /// Bar,
+ /// Baz
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[non_exhaustive]
+ /// enum Foo {
+ /// Bar,
+ /// Baz
+ /// }
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub EXHAUSTIVE_ENUMS,
+ restriction,
+ "detects exported enums that have not been marked #[non_exhaustive]"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns on any exported `structs`s that are not tagged `#[non_exhaustive]`
+ ///
+ /// ### Why is this bad?
+ /// Exhaustive structs are typically fine, but a project which does
+ /// not wish to make a stability commitment around exported structs may wish to
+ /// disable them by default.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo {
+ /// bar: u8,
+ /// baz: String,
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[non_exhaustive]
+ /// struct Foo {
+ /// bar: u8,
+ /// baz: String,
+ /// }
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub EXHAUSTIVE_STRUCTS,
+ restriction,
+ "detects exported structs that have not been marked #[non_exhaustive]"
+}
+
+declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]);
+
+impl LateLintPass<'_> for ExhaustiveItems {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if_chain! {
+ if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind;
+ if cx.access_levels.is_exported(item.def_id);
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if !attrs.iter().any(|a| a.has_name(sym::non_exhaustive));
+ then {
+ let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind {
+ if v.fields().iter().any(|f| !f.vis.node.is_pub()) {
+ // skip structs with private fields
+ return;
+ }
+ (EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive")
+ } else {
+ (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive")
+ };
+ let suggestion_span = item.span.shrink_to_lo();
+ let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
+ span_lint_and_then(
+ cx,
+ lint,
+ item.span,
+ msg,
+ |diag| {
+ let sugg = format!("#[non_exhaustive]\n{}", indent);
+ diag.span_suggestion(suggestion_span,
+ "try adding #[non_exhaustive]",
+ sugg,
+ Applicability::MaybeIncorrect);
+ }
+ );
+
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{is_entrypoint_fn, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// `exit()` terminates the program and doesn't provide a
+ /// stack trace.
+ ///
+ /// ### Why is this bad?
+ /// Ideally a program is terminated by finishing
+ /// the main function.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// std::process::exit(0)
+ /// ```
++ #[clippy::version = "1.41.0"]
+ pub EXIT,
+ restriction,
+ "`std::process::exit` is called, terminating the program"
+}
+
+declare_lint_pass!(Exit => [EXIT]);
+
+impl<'tcx> LateLintPass<'tcx> for Exit {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(path_expr, _args) = e.kind;
+ if let ExprKind::Path(ref path) = path_expr.kind;
+ if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::EXIT);
+ let parent = cx.tcx.hir().get_parent_item(e.hir_id);
+ if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent);
+ // If the next item up is a function we check if it is an entry point
+ // and only then emit a linter warning
+ let def_id = cx.tcx.hir().local_def_id(parent);
+ if !is_entrypoint_fn(cx, def_id.to_def_id());
+ then {
+ span_lint(cx, EXIT, e.span, "usage of `process::exit`");
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::higher::FormatArgsExpn;
+use clippy_utils::{is_expn_of, match_function_call, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `write!()` / `writeln()!` which can be
+ /// replaced with `(e)print!()` / `(e)println!()`
+ ///
+ /// ### Why is this bad?
+ /// Using `(e)println! is clearer and more concise
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::io::Write;
+ /// # let bar = "furchtbar";
+ /// // this would be clearer as `eprintln!("foo: {:?}", bar);`
+ /// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_WRITE,
+ complexity,
+ "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work"
+}
+
+declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
+
+impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // match call to unwrap
+ if let ExprKind::MethodCall(unwrap_fun, _, [write_call], _) = expr.kind;
+ if unwrap_fun.ident.name == sym::unwrap;
+ // match call to write_fmt
+ if let ExprKind::MethodCall(write_fun, _, [write_recv, write_arg], _) = write_call.kind;
+ if write_fun.ident.name == sym!(write_fmt);
+ // match calls to std::io::stdout() / std::io::stderr ()
+ if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
+ Some("stdout")
+ } else if match_function_call(cx, write_recv, &paths::STDERR).is_some() {
+ Some("stderr")
+ } else {
+ None
+ };
+ if let Some(format_args) = FormatArgsExpn::parse(write_arg);
+ then {
+ let calling_macro =
+ // ordering is important here, since `writeln!` uses `write!` internally
+ if is_expn_of(write_call.span, "writeln").is_some() {
+ Some("writeln")
+ } else if is_expn_of(write_call.span, "write").is_some() {
+ Some("write")
+ } else {
+ None
+ };
+ let prefix = if dest_name == "stderr" {
+ "e"
+ } else {
+ ""
+ };
+
+ // We need to remove the last trailing newline from the string because the
+ // underlying `fmt::write` function doesn't know whether `println!` or `print!` was
+ // used.
+ let (used, sugg_mac) = if let Some(macro_name) = calling_macro {
+ (
+ format!("{}!({}(), ...)", macro_name, dest_name),
+ macro_name.replace("write", "print"),
+ )
+ } else {
+ (
+ format!("{}().write_fmt(...)", dest_name),
+ "print".into(),
+ )
+ };
+ let msg = format!("use of `{}.unwrap()`", used);
+ if let [write_output] = *format_args.format_string_symbols {
+ let mut write_output = write_output.to_string();
+ if write_output.ends_with('\n') {
+ write_output.pop();
+ }
+
+ let sugg = format!("{}{}!(\"{}\")", prefix, sugg_mac, write_output.escape_default());
+ span_lint_and_sugg(
+ cx,
+ EXPLICIT_WRITE,
+ expr.span,
+ &msg,
+ "try this",
+ sugg,
+ Applicability::MachineApplicable
+ );
+ } else {
+ // We don't have a proper suggestion
+ let help = format!("consider using `{}{}!` instead", prefix, sugg_mac);
+ span_lint_and_help(cx, EXPLICIT_WRITE, expr.span, &msg, None, &help);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_expn_of, match_panic_def_id, method_chain_args};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
+ ///
+ /// ### Why is this bad?
+ /// `TryFrom` should be used if there's a possibility of failure.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo(i32);
+ ///
+ /// // Bad
+ /// impl From<String> for Foo {
+ /// fn from(s: String) -> Self {
+ /// Foo(s.parse().unwrap())
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// // Good
+ /// struct Foo(i32);
+ ///
+ /// use std::convert::TryFrom;
+ /// impl TryFrom<String> for Foo {
+ /// type Error = ();
+ /// fn try_from(s: String) -> Result<Self, Self::Error> {
+ /// if let Ok(parsed) = s.parse() {
+ /// Ok(Foo(parsed))
+ /// } else {
+ /// Err(())
+ /// }
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub FALLIBLE_IMPL_FROM,
+ nursery,
+ "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
+}
+
+declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
+
+impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ // check for `impl From<???> for ..`
+ if_chain! {
+ if let hir::ItemKind::Impl(impl_) = &item.kind;
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id);
+ if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.def_id);
+ then {
+ lint_impl_body(cx, item.span, impl_.items);
+ }
+ }
+ }
+}
+
+fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef]) {
+ use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
+ use rustc_hir::{Expr, ExprKind, ImplItemKind, QPath};
+
+ struct FindPanicUnwrap<'a, 'tcx> {
+ lcx: &'a LateContext<'tcx>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+ result: Vec<Span>,
+ }
+
+ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // check for `begin_panic`
+ if_chain! {
+ if let ExprKind::Call(func_expr, _) = expr.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
+ if let Some(path_def_id) = path.res.opt_def_id();
+ if match_panic_def_id(self.lcx, path_def_id);
+ if is_expn_of(expr.span, "unreachable").is_none();
+ then {
+ self.result.push(expr.span);
+ }
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
+ || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+ {
+ self.result.push(expr.span);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+ }
+
+ for impl_item in impl_items {
+ if_chain! {
+ if impl_item.ident.name == sym::from;
+ if let ImplItemKind::Fn(_, body_id) =
+ cx.tcx.hir().impl_item(impl_item.id).kind;
+ then {
+ // check the body for `begin_panic` or `unwrap`
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindPanicUnwrap {
+ lcx: cx,
+ typeck_results: cx.tcx.typeck(impl_item.id.def_id),
+ result: Vec::new(),
+ };
+ fpu.visit_expr(&body.value);
+
+ // if we've found one, lint
+ if !fpu.result.is_empty() {
+ span_lint_and_then(
+ cx,
+ FALLIBLE_IMPL_FROM,
+ impl_span,
+ "consider implementing `TryFrom` instead",
+ move |diag| {
+ diag.help(
+ "`From` is intended for infallible conversions only. \
+ Use `TryFrom` if there's a possibility for the conversion to fail");
+ diag.span_note(fpu.result, "potential failure(s)");
+ });
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
+use rustc_hir::CRATE_HIR_ID;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::DUMMY_SP;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
+ ///
+ /// ### Why is this bad?
+ /// These prefixes and suffixes have no significant meaning.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # The `Cargo.toml` with feature name redundancy
+ /// [features]
+ /// default = ["use-abc", "with-def", "ghi-support"]
+ /// use-abc = [] // redundant
+ /// with-def = [] // redundant
+ /// ghi-support = [] // redundant
+ /// ```
+ ///
+ /// Use instead:
+ /// ```toml
+ /// [features]
+ /// default = ["abc", "def", "ghi"]
+ /// abc = []
+ /// def = []
+ /// ghi = []
+ /// ```
+ ///
++ #[clippy::version = "1.57.0"]
+ pub REDUNDANT_FEATURE_NAMES,
+ cargo,
+ "usage of a redundant feature name"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for negative feature names with prefix `no-` or `not-`
+ ///
+ /// ### Why is this bad?
+ /// Features are supposed to be additive, and negatively-named features violate it.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # The `Cargo.toml` with negative feature names
+ /// [features]
+ /// default = []
+ /// no-abc = []
+ /// not-def = []
+ ///
+ /// ```
+ /// Use instead:
+ /// ```toml
+ /// [features]
+ /// default = ["abc", "def"]
+ /// abc = []
+ /// def = []
+ ///
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub NEGATIVE_FEATURE_NAMES,
+ cargo,
+ "usage of a negative feature name"
+}
+
+declare_lint_pass!(FeatureName => [REDUNDANT_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES]);
+
+static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
+static SUFFIXES: [&str; 2] = ["-support", "_support"];
+
+fn is_negative_prefix(s: &str) -> bool {
+ s.starts_with("no")
+}
+
+fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
+ let is_negative = is_prefix && is_negative_prefix(substring);
+ span_lint_and_help(
+ cx,
+ if is_negative {
+ NEGATIVE_FEATURE_NAMES
+ } else {
+ REDUNDANT_FEATURE_NAMES
+ },
+ DUMMY_SP,
+ &format!(
+ "the \"{}\" {} in the feature name \"{}\" is {}",
+ substring,
+ if is_prefix { "prefix" } else { "suffix" },
+ feature,
+ if is_negative { "negative" } else { "redundant" }
+ ),
+ None,
+ &format!(
+ "consider renaming the feature to \"{}\"{}",
+ if is_prefix {
+ feature.strip_prefix(substring)
+ } else {
+ feature.strip_suffix(substring)
+ }
+ .unwrap(),
+ if is_negative {
+ ", but make sure the feature adds functionality"
+ } else {
+ ""
+ }
+ ),
+ );
+}
+
+impl LateLintPass<'_> for FeatureName {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ if is_lint_allowed(cx, REDUNDANT_FEATURE_NAMES, CRATE_HIR_ID)
+ && is_lint_allowed(cx, NEGATIVE_FEATURE_NAMES, CRATE_HIR_ID)
+ {
+ return;
+ }
+
+ let metadata = unwrap_cargo_metadata!(cx, REDUNDANT_FEATURE_NAMES, false);
+
+ for package in metadata.packages {
+ let mut features: Vec<&String> = package.features.keys().collect();
+ features.sort();
+ for feature in features {
+ let prefix_opt = {
+ let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
+ if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
+ Some(PREFIXES[i - 1])
+ } else {
+ None
+ }
+ };
+ if let Some(prefix) = prefix_opt {
+ lint(cx, feature, prefix, true);
+ }
+
+ let suffix_opt: Option<&str> = {
+ let i = SUFFIXES.partition_point(|suffix| {
+ suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
+ });
+ if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
+ Some(SUFFIXES[i - 1])
+ } else {
+ None
+ }
+ };
+ if let Some(suffix) = suffix_opt {
+ lint(cx, feature, suffix, false);
+ }
+ }
+ }
+ }
+}
+
+#[test]
+fn test_prefixes_sorted() {
+ let mut sorted_prefixes = PREFIXES;
+ sorted_prefixes.sort_unstable();
+ assert_eq!(PREFIXES, sorted_prefixes);
+ let mut sorted_suffixes = SUFFIXES;
+ sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
+ assert_eq!(SUFFIXES, sorted_suffixes);
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{match_def_path, paths, sugg};
+use if_chain::if_chain;
+use rustc_ast::util::parser::AssocOp;
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for statements of the form `(a - b) < f32::EPSILON` or
+ /// `(a - b) < f64::EPSILON`. Notes the missing `.abs()`.
+ ///
+ /// ### Why is this bad?
+ /// The code without `.abs()` is more likely to have a bug.
+ ///
+ /// ### Known problems
+ /// If the user can ensure that b is larger than a, the `.abs()` is
+ /// technically unneccessary. However, it will make the code more robust and doesn't have any
+ /// large performance implications. If the abs call was deliberately left out for performance
+ /// reasons, it is probably better to state this explicitly in the code, which then can be done
+ /// with an allow.
+ ///
+ /// ### Example
+ /// ```rust
+ /// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
+ /// (a - b) < f32::EPSILON
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// pub fn is_roughly_equal(a: f32, b: f32) -> bool {
+ /// (a - b).abs() < f32::EPSILON
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub FLOAT_EQUALITY_WITHOUT_ABS,
+ suspicious,
+ "float equality check without `.abs()`"
+}
+
+declare_lint_pass!(FloatEqualityWithoutAbs => [FLOAT_EQUALITY_WITHOUT_ABS]);
+
+impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let lhs;
+ let rhs;
+
+ // check if expr is a binary expression with a lt or gt operator
+ if let ExprKind::Binary(op, left, right) = expr.kind {
+ match op.node {
+ BinOpKind::Lt => {
+ lhs = left;
+ rhs = right;
+ },
+ BinOpKind::Gt => {
+ lhs = right;
+ rhs = left;
+ },
+ _ => return,
+ };
+ } else {
+ return;
+ }
+
+ if_chain! {
+
+ // left hand side is a substraction
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Sub,
+ ..
+ },
+ val_l,
+ val_r,
+ ) = lhs.kind;
+
+ // right hand side matches either f32::EPSILON or f64::EPSILON
+ if let ExprKind::Path(ref epsilon_path) = rhs.kind;
+ if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id);
+ if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON);
+
+ // values of the substractions on the left hand side are of the type float
+ let t_val_l = cx.typeck_results().expr_ty(val_l);
+ let t_val_r = cx.typeck_results().expr_ty(val_r);
+ if let ty::Float(_) = t_val_l.kind();
+ if let ty::Float(_) = t_val_r.kind();
+
+ then {
+ let sug_l = sugg::Sugg::hir(cx, val_l, "..");
+ let sug_r = sugg::Sugg::hir(cx, val_r, "..");
+ // format the suggestion
+ let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par());
+ // spans the lint
+ span_lint_and_then(
+ cx,
+ FLOAT_EQUALITY_WITHOUT_ABS,
+ expr.span,
+ "float equality check without `.abs()`",
+ | diag | {
+ diag.span_suggestion(
+ lhs.span,
+ "add `.abs()`",
+ suggestion,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::numeric_literal;
+use if_chain::if_chain;
+use rustc_ast::ast::{self, LitFloatType, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, FloatTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::fmt;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for float literals with a precision greater
+ /// than that supported by the underlying type.
+ ///
+ /// ### Why is this bad?
+ /// Rust will truncate the literal silently.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let v: f32 = 0.123_456_789_9;
+ /// println!("{}", v); // 0.123_456_789
+ ///
+ /// // Good
+ /// let v: f64 = 0.123_456_789_9;
+ /// println!("{}", v); // 0.123_456_789_9
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EXCESSIVE_PRECISION,
+ style,
+ "excessive precision for float literal"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for whole number float literals that
+ /// cannot be represented as the underlying type without loss.
+ ///
+ /// ### Why is this bad?
+ /// Rust will silently lose precision during
+ /// conversion to a float.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let _: f32 = 16_777_217.0; // 16_777_216.0
+ ///
+ /// // Good
+ /// let _: f32 = 16_777_216.0;
+ /// let _: f64 = 16_777_217.0;
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub LOSSY_FLOAT_LITERAL,
+ restriction,
+ "lossy whole number float literals"
+}
+
+declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
+
+impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if_chain! {
+ if let ty::Float(fty) = *ty.kind();
+ if let hir::ExprKind::Lit(ref lit) = expr.kind;
+ if let LitKind::Float(sym, lit_float_ty) = lit.node;
+ then {
+ let sym_str = sym.as_str();
+ let formatter = FloatFormat::new(&sym_str);
+ // Try to bail out if the float is for sure fine.
+ // If its within the 2 decimal digits of being out of precision we
+ // check if the parsed representation is the same as the string
+ // since we'll need the truncated string anyway.
+ let digits = count_digits(&sym_str);
+ let max = max_digits(fty);
+ let type_suffix = match lit_float_ty {
+ LitFloatType::Suffixed(ast::FloatTy::F32) => Some("f32"),
+ LitFloatType::Suffixed(ast::FloatTy::F64) => Some("f64"),
+ LitFloatType::Unsuffixed => None
+ };
+ let (is_whole, mut float_str) = match fty {
+ FloatTy::F32 => {
+ let value = sym_str.parse::<f32>().unwrap();
+
+ (value.fract() == 0.0, formatter.format(value))
+ },
+ FloatTy::F64 => {
+ let value = sym_str.parse::<f64>().unwrap();
+
+ (value.fract() == 0.0, formatter.format(value))
+ },
+ };
+
+ if is_whole && !sym_str.contains(|c| c == 'e' || c == 'E') {
+ // Normalize the literal by stripping the fractional portion
+ if sym_str.split('.').next().unwrap() != float_str {
+ // If the type suffix is missing the suggestion would be
+ // incorrectly interpreted as an integer so adding a `.0`
+ // suffix to prevent that.
+ if type_suffix.is_none() {
+ float_str.push_str(".0");
+ }
+
+ span_lint_and_sugg(
+ cx,
+ LOSSY_FLOAT_LITERAL,
+ expr.span,
+ "literal cannot be represented as the underlying type without loss of precision",
+ "consider changing the type or replacing it with",
+ numeric_literal::format(&float_str, type_suffix, true),
+ Applicability::MachineApplicable,
+ );
+ }
+ } else if digits > max as usize && float_str.len() < sym_str.len() {
+ span_lint_and_sugg(
+ cx,
+ EXCESSIVE_PRECISION,
+ expr.span,
+ "float has excessive precision",
+ "consider changing the type or truncating it to",
+ numeric_literal::format(&float_str, type_suffix, true),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+#[must_use]
+fn max_digits(fty: FloatTy) -> u32 {
+ match fty {
+ FloatTy::F32 => f32::DIGITS,
+ FloatTy::F64 => f64::DIGITS,
+ }
+}
+
+/// Counts the digits excluding leading zeros
+#[must_use]
+fn count_digits(s: &str) -> usize {
+ // Note that s does not contain the f32/64 suffix, and underscores have been stripped
+ s.chars()
+ .filter(|c| *c != '-' && *c != '.')
+ .take_while(|c| *c != 'e' && *c != 'E')
+ .fold(0, |count, c| {
+ // leading zeros
+ if c == '0' && count == 0 { count } else { count + 1 }
+ })
+}
+
+enum FloatFormat {
+ LowerExp,
+ UpperExp,
+ Normal,
+}
+impl FloatFormat {
+ #[must_use]
+ fn new(s: &str) -> Self {
+ s.chars()
+ .find_map(|x| match x {
+ 'e' => Some(Self::LowerExp),
+ 'E' => Some(Self::UpperExp),
+ _ => None,
+ })
+ .unwrap_or(Self::Normal)
+ }
+ fn format<T>(&self, f: T) -> String
+ where
+ T: fmt::UpperExp + fmt::LowerExp + fmt::Display,
+ {
+ match self {
+ Self::LowerExp => format!("{:e}", f),
+ Self::UpperExp => format!("{:E}", f),
+ Self::Normal => format!("{}", f),
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{eq_expr_value, get_parent_expr, numeric_literal, sugg};
+use clippy_utils::consts::{
+ constant, constant_simple, Constant,
+ Constant::{Int, F32, F64},
+};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
++use clippy_utils::{eq_expr_value, get_parent_expr, in_constant, numeric_literal, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+use rustc_ast::ast;
+use std::f32::consts as f32_consts;
+use std::f64::consts as f64_consts;
+use sugg::Sugg;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for floating-point expressions that
+ /// can be expressed using built-in methods to improve accuracy
+ /// at the cost of performance.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts accuracy.
+ ///
+ /// ### 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();
+ /// ```
++ #[clippy::version = "1.43.0"]
+ 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.
+ ///
+ /// ### 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();
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub SUBOPTIMAL_FLOPS,
+ nursery,
+ "usage of sub-optimal floating point operations"
+}
+
+declare_lint_pass!(FloatingPointArithmetic => [
+ IMPRECISE_FLOPS,
+ SUBOPTIMAL_FLOPS
+]);
+
+// Returns the specialized log method for a given base if base is constant
+// and is one of 2, 10 and e
+fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> {
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), base) {
+ if F32(2.0) == value || F64(2.0) == value {
+ return Some("log2");
+ } else if F32(10.0) == value || F64(10.0) == value {
+ return Some("log10");
+ } else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ return Some("ln");
+ }
+ }
+
+ None
+}
+
+// Adds type suffixes and parenthesis to method receivers if necessary
+fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> {
+ let mut suggestion = Sugg::hir(cx, expr, "..");
+
+ if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
+ expr = inner_expr;
+ }
+
+ if_chain! {
+ // if the expression is a float literal and it is unsuffixed then
+ // add a suffix so the suggestion is valid and unambiguous
+ if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind();
+ if let ExprKind::Lit(lit) = &expr.kind;
+ if let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node;
+ then {
+ let op = format!(
+ "{}{}{}",
+ suggestion,
+ // Check for float literals without numbers following the decimal
+ // separator such as `2.` and adds a trailing zero
+ if sym.as_str().ends_with('.') {
+ "0"
+ } else {
+ ""
+ },
+ float_ty.name_str()
+ ).into();
+
+ suggestion = match suggestion {
+ Sugg::MaybeParen(_) => Sugg::MaybeParen(op),
+ _ => Sugg::NonParen(op)
+ };
+ }
+ }
+
+ suggestion.maybe_par()
+}
+
+fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some(method) = get_specialized_log_method(cx, &args[1]) {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "logarithm for bases 2, 10 and e can be computed more accurately",
+ "consider using",
+ format!("{}.{}()", Sugg::hir(cx, &args[0], ".."), method),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and
+// suggest usage of `(x + (y - 1)).ln_1p()` instead
+fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = &args[0].kind
+ {
+ let recv = match (
+ constant(cx, cx.typeck_results(), lhs),
+ constant(cx, cx.typeck_results(), rhs),
+ ) {
+ (Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs,
+ (_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs,
+ _ => return,
+ };
+
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "ln(1 + x) can be computed more accurately",
+ "consider using",
+ format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// Returns an integer if the float constant is a whole number and it can be
+// converted to an integer without loss of precision. For now we only check
+// ranges [-16777215, 16777216) for type f32 as whole number floats outside
+// this range are lossy and ambiguous.
+#[allow(clippy::cast_possible_truncation)]
+fn get_integer_from_float_constant(value: &Constant) -> Option<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.typeck_results(), &args[0]) {
+ let method = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
+ "exp"
+ } else if F32(2.0) == value || F64(2.0) == value {
+ "exp2"
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "exponent for bases 2 and e can be computed more accurately",
+ "consider using",
+ format!("{}.{}()", prepare_receiver_sugg(cx, &args[1]), method),
+ Applicability::MachineApplicable,
+ );
+ }
+
+ // Check argument
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[1]) {
+ let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
+ (
+ SUBOPTIMAL_FLOPS,
+ "square-root of a number can be computed more efficiently and accurately",
+ format!("{}.sqrt()", Sugg::hir(cx, &args[0], "..")),
+ )
+ } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
+ (
+ IMPRECISE_FLOPS,
+ "cube-root of a number can be computed more accurately",
+ format!("{}.cbrt()", Sugg::hir(cx, &args[0], "..")),
+ )
+ } else if let Some(exponent) = get_integer_from_float_constant(&value) {
+ (
+ SUBOPTIMAL_FLOPS,
+ "exponentiation with integer powers can be computed more efficiently",
+ format!(
+ "{}.powi({})",
+ Sugg::hir(cx, &args[0], ".."),
+ numeric_literal::format(&exponent.to_string(), None, false)
+ ),
+ )
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ help,
+ "consider using",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[1]) {
+ if value == Int(2) {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let Some(grandparent) = get_parent_expr(cx, parent) {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = grandparent.kind {
+ if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
+ return;
+ }
+ }
+ }
+
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ 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,
+ "multiply and add expressions can be calculated more efficiently and accurately",
+ "consider using",
+ format!(
+ "{}.mul_add({}, {})",
+ Sugg::hir(cx, &args[0], ".."),
+ Sugg::hir(cx, &args[0], ".."),
+ Sugg::hir(cx, other_addend, ".."),
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ add_lhs,
+ 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, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
+ if eq_expr_value(cx, lmul_lhs, lmul_rhs);
+ if eq_expr_value(cx, rmul_lhs, rmul_rhs);
+ then {
+ return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, ".."), Sugg::hir(cx, rmul_lhs, "..")));
+ }
+ }
+
+ // check if expression of the form x.powi(2) + y.powi(2)
+ if_chain! {
+ if let ExprKind::MethodCall(
+ PathSegment { ident: lmethod_name, .. },
+ _lspan,
+ [largs_0, largs_1, ..],
+ _
+ ) = &add_lhs.kind;
+ if let ExprKind::MethodCall(
+ PathSegment { ident: rmethod_name, .. },
+ _rspan,
+ [rargs_0, rargs_1, ..],
+ _
+ ) = &add_rhs.kind;
+ if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
+ if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), largs_1);
+ if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), rargs_1);
+ if Int(2) == lvalue && Int(2) == rvalue;
+ then {
+ return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, ".."), Sugg::hir(cx, rargs_0, "..")));
+ }
+ }
+ }
+
+ None
+}
+
+fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
+ if let Some(message) = detect_hypot(cx, args) {
+ span_lint_and_sugg(
+ cx,
+ IMPRECISE_FLOPS,
+ expr.span,
+ "hypotenuse can be computed more accurately",
+ "consider using",
+ message,
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+// TODO: Lint expressions of the form `x.exp() - y` where y > 1
+// and suggest usage of `x.exp_m1() - (y - 1)` instead
+fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
+ if cx.typeck_results().expr_ty(lhs).is_floating_point();
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
+ if F32(1.0) == value || F64(1.0) == value;
+ if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &lhs.kind;
+ if cx.typeck_results().expr_ty(self_arg).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, self_arg, "..")
+ ),
+ 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, .. }, lhs, rhs) = &expr.kind;
+ if cx.typeck_results().expr_ty(lhs).is_floating_point();
+ if cx.typeck_results().expr_ty(rhs).is_floating_point();
+ then {
+ return Some((lhs, rhs));
+ }
+ }
+
+ None
+}
+
+// TODO: Fix rust-lang/rust-clippy#4735
+fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ rhs,
+ ) = &expr.kind
+ {
+ if let Some(parent) = get_parent_expr(cx, expr) {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, _, args, _) = parent.kind {
+ if method_name.as_str() == "sqrt" && detect_hypot(cx, args).is_some() {
+ return;
+ }
+ }
+ }
+
+ let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs) {
+ (inner_lhs, inner_rhs, rhs)
+ } else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs) {
+ (inner_lhs, inner_rhs, lhs)
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "multiply and add expressions can be calculated more efficiently and accurately",
+ "consider using",
+ format!(
+ "{}.mul_add({}, {})",
+ prepare_receiver_sugg(cx, recv),
+ Sugg::hir(cx, arg1, ".."),
+ Sugg::hir(cx, arg2, ".."),
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+}
+
+/// Returns true iff expr is an expression which tests whether or not
+/// test is positive or an expression which tests whether or not test
+/// is nonnegative.
+/// Used for check-custom-abs function below
+fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
+ if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
+ match op {
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// See [`is_testing_positive`]
+fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
+ if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
+ match op {
+ BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test),
+ BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test),
+ _ => false,
+ }
+ } else {
+ false
+ }
+}
+
+/// Returns true iff expr is some zero literal
+fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match constant_simple(cx, cx.typeck_results(), expr) {
+ Some(Constant::Int(i)) => i == 0,
+ Some(Constant::F32(f)) => f == 0.0,
+ Some(Constant::F64(f)) => f == 0.0,
+ _ => false,
+ }
+}
+
+/// If the two expressions are negations of each other, then it returns
+/// a tuple, in which the first element is true iff expr1 is the
+/// positive expressions, and the second element is the positive
+/// one of the two expressions
+/// If the two expressions are not negations of each other, then it
+/// returns None.
+fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
+ if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind {
+ if eq_expr_value(cx, expr1_negated, expr2) {
+ return Some((false, expr2));
+ }
+ }
+ if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind {
+ if eq_expr_value(cx, expr1, expr2_negated) {
+ return Some((true, expr1));
+ }
+ }
+ None
+}
+
+fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
+ if let ExprKind::Block(block, _) = then.kind;
+ if block.stmts.is_empty();
+ if let Some(if_body_expr) = block.expr;
+ if let Some(ExprKind::Block(else_block, _)) = r#else.map(|el| &el.kind);
+ if else_block.stmts.is_empty();
+ if let Some(else_body_expr) = else_block.expr;
+ if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr);
+ then {
+ let positive_abs_sugg = (
+ "manual implementation of `abs` method",
+ format!("{}.abs()", Sugg::hir(cx, body, "..")),
+ );
+ let negative_abs_sugg = (
+ "manual implementation of negation of `abs` method",
+ format!("-{}.abs()", Sugg::hir(cx, body, "..")),
+ );
+ let sugg = if is_testing_positive(cx, cond, body) {
+ if if_expr_positive {
+ positive_abs_sugg
+ } else {
+ negative_abs_sugg
+ }
+ } else if is_testing_negative(cx, cond, body) {
+ if if_expr_positive {
+ negative_abs_sugg
+ } else {
+ positive_abs_sugg
+ }
+ } else {
+ return;
+ };
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ sugg.0,
+ "try",
+ sugg.1,
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind;
+ if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind;
+ then {
+ return method_name_a.as_str() == method_name_b.as_str() &&
+ args_a.len() == args_b.len() &&
+ (
+ ["ln", "log2", "log10"].contains(&&*method_name_a.as_str()) ||
+ method_name_a.as_str() == "log" && args_a.len() == 2 && eq_expr_value(cx, &args_a[1], &args_b[1])
+ );
+ }
+ }
+
+ false
+}
+
+fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // check if expression of the form x.logN() / y.logN()
+ if_chain! {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Div, ..
+ },
+ lhs,
+ rhs,
+ ) = &expr.kind;
+ if are_same_base_logs(cx, lhs, rhs);
+ if let ExprKind::MethodCall(_, _, [largs_self, ..], _) = &lhs.kind;
+ if let ExprKind::MethodCall(_, _, [rargs_self, ..], _) = &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_self, ".."), Sugg::hir(cx, rargs_self, ".."),),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Div, ..
+ },
+ div_lhs,
+ div_rhs,
+ ) = &expr.kind;
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Mul, ..
+ },
+ mul_lhs,
+ mul_rhs,
+ ) = &div_lhs.kind;
+ if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), div_rhs);
+ if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), mul_rhs);
+ then {
+ // TODO: also check for constant values near PI/180 or 180/PI
+ if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
+ (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
+ {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "conversion to degrees can be done more accurately",
+ "consider using",
+ format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..")),
+ Applicability::MachineApplicable,
+ );
+ } else if
+ (F32(180_f32) == rvalue || F64(180_f64) == rvalue) &&
+ (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
+ {
+ span_lint_and_sugg(
+ cx,
+ SUBOPTIMAL_FLOPS,
+ expr.span,
+ "conversion to radians can be done more accurately",
+ "consider using",
+ format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..")),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ // All of these operations are currently not const.
++ if in_constant(cx, expr.hir_id) {
++ return;
++ }
++
+ if let ExprKind::MethodCall(path, _, args, _) = &expr.kind {
+ let recv_ty = cx.typeck_results().expr_ty(&args[0]);
+
+ if recv_ty.is_floating_point() {
+ match &*path.ident.name.as_str() {
+ "ln" => check_ln1p(cx, expr, args),
+ "log" => check_log_base(cx, expr, args),
+ "powf" => check_powf(cx, expr, args),
+ "powi" => check_powi(cx, expr, args),
+ "sqrt" => check_hypot(cx, expr, args),
+ _ => {},
+ }
+ }
+ } else {
+ check_expm1(cx, expr);
+ check_mul_add(cx, expr);
+ check_custom_abs(cx, expr);
+ check_log_division(cx, expr);
+ check_radians(cx, expr);
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::FormatExpn;
+use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `format!("string literal with no
+ /// argument")` and `format!("{}", foo)` where `foo` is a string.
+ ///
+ /// ### Why is this bad?
+ /// There is no point of doing that. `format!("foo")` can
+ /// be replaced by `"foo".to_owned()` if you really need a `String`. The even
+ /// worse `&format!("foo")` is often encountered in the wild. `format!("{}",
+ /// foo)` can be replaced by `foo.clone()` if `foo: String` or `foo.to_owned()`
+ /// if `foo: &str`.
+ ///
+ /// ### Examples
+ /// ```rust
+ ///
+ /// // Bad
+ /// let foo = "foo";
+ /// format!("{}", foo);
+ ///
+ /// // Good
+ /// foo.to_owned();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_FORMAT,
+ complexity,
+ "useless use of `format!`"
+}
+
+declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
+
+impl<'tcx> LateLintPass<'tcx> for UselessFormat {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let FormatExpn { call_site, format_args } = match FormatExpn::parse(expr) {
+ Some(e) if !e.call_site.from_expansion() => e,
+ _ => return,
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ if format_args.value_args.is_empty() {
+ if format_args.format_string_parts.is_empty() {
+ span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability);
+ } else {
+ if_chain! {
+ if let [e] = &*format_args.format_string_parts;
+ if let ExprKind::Lit(lit) = &e.kind;
+ if let Some(s_src) = snippet_opt(cx, lit.span);
+ then {
+ // Simulate macro expansion, converting {{ and }} to { and }.
+ let s_expand = s_src.replace("{{", "{").replace("}}", "}");
+ let sugg = format!("{}.to_string()", s_expand);
+ span_useless_format(cx, call_site, sugg, applicability);
+ }
+ }
+ }
+ } else if let [value] = *format_args.value_args {
+ if_chain! {
+ if format_args.format_string_symbols == [kw::Empty];
+ if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did),
+ ty::Str => true,
+ _ => false,
+ };
+ if let Some(args) = format_args.args();
+ if args.iter().all(|arg| arg.is_display() && !arg.has_string_formatting());
+ then {
+ let is_new_string = match value.kind {
+ ExprKind::Binary(..) => true,
+ ExprKind::MethodCall(path, ..) => path.ident.name.as_str() == "to_string",
+ _ => false,
+ };
+ let sugg = if is_new_string {
+ snippet_with_applicability(cx, value.span, "..", &mut applicability).into_owned()
+ } else {
+ let sugg = Sugg::hir_with_applicability(cx, value, "<arg>", &mut applicability);
+ format!("{}.to_string()", sugg.maybe_par())
+ };
+ span_useless_format(cx, call_site, sugg, applicability);
+ }
+ }
+ };
+ }
+}
+
+fn span_useless_format_empty(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) {
+ span_lint_and_sugg(
+ cx,
+ USELESS_FORMAT,
+ span,
+ "useless use of `format!`",
+ "consider using `String::new()`",
+ sugg,
+ applicability,
+ );
+}
+
+fn span_useless_format(cx: &LateContext<'_>, span: Span, sugg: String, applicability: Applicability) {
+ span_lint_and_sugg(
+ cx,
+ USELESS_FORMAT,
+ span,
+ "useless use of `format!`",
+ "consider using `.to_string()`",
+ sugg,
+ applicability,
+ );
+}
--- /dev/null
- use rustc_span::{sym, BytePos, ExpnData, ExpnKind, Span, Symbol};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::higher::{FormatArgsArg, FormatArgsExpn, FormatExpn};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{is_diag_trait_item, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::adjustment::{Adjust, Adjustment};
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
- trim_semicolon(cx, call_site),
++use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `format!` within the arguments of another macro that does
+ /// formatting such as `format!` itself, `write!` or `println!`. Suggests
+ /// inlining the `format!` call.
+ ///
+ /// ### Why is this bad?
+ /// The recommended code is both shorter and avoids a temporary allocation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: {}", format!("something failed at {}", Location::caller()));
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller());
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub FORMAT_IN_FORMAT_ARGS,
+ perf,
+ "`format!` used in a macro that does formatting"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
+ /// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
+ /// in a macro that does formatting.
+ ///
+ /// ### Why is this bad?
+ /// Since the type implements `Display`, the use of `to_string` is
+ /// unnecessary.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller().to_string());
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::panic::Location;
+ /// println!("error: something failed at {}", Location::caller());
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub TO_STRING_IN_FORMAT_ARGS,
+ perf,
+ "`to_string` applied to a type that implements `Display` in format args"
+}
+
+declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
+
+const FORMAT_MACRO_PATHS: &[&[&str]] = &[
+ &paths::FORMAT_ARGS_MACRO,
+ &paths::ASSERT_EQ_MACRO,
+ &paths::ASSERT_MACRO,
+ &paths::ASSERT_NE_MACRO,
+ &paths::EPRINT_MACRO,
+ &paths::EPRINTLN_MACRO,
+ &paths::PRINT_MACRO,
+ &paths::PRINTLN_MACRO,
+ &paths::WRITE_MACRO,
+ &paths::WRITELN_MACRO,
+];
+
+const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro];
+
+impl<'tcx> LateLintPass<'tcx> for FormatArgs {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if_chain! {
+ if let Some(format_args) = FormatArgsExpn::parse(expr);
+ let expr_expn_data = expr.span.ctxt().outer_expn_data();
+ let outermost_expn_data = outermost_expn_data(expr_expn_data);
+ if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
+ if FORMAT_MACRO_PATHS
+ .iter()
+ .any(|path| match_def_path(cx, macro_def_id, path))
+ || FORMAT_MACRO_DIAG_ITEMS
+ .iter()
+ .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id));
+ if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
+ if let Some(args) = format_args.args();
+ then {
+ for (i, arg) in args.iter().enumerate() {
+ if !arg.is_display() {
+ continue;
+ }
+ if arg.has_string_formatting() {
+ continue;
+ }
+ if is_aliased(&args, i) {
+ continue;
+ }
+ check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg);
+ check_to_string_in_format_args(cx, name, arg);
+ }
+ }
+ }
+ }
+}
+
+fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
+ if expn_data.call_site.from_expansion() {
+ outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
+ } else {
+ expn_data
+ }
+}
+
+fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_>) {
+ if_chain! {
+ if FormatExpn::parse(arg.value).is_some();
+ if !arg.value.span.ctxt().outer_expn_data().call_site.from_expansion();
+ then {
+ span_lint_and_then(
+ cx,
+ FORMAT_IN_FORMAT_ARGS,
- fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
- snippet_opt(cx, span).map_or(span, |snippet| {
- let snippet = snippet.trim_end_matches(';');
- span.with_hi(span.lo() + BytePos(u32::try_from(snippet.len()).unwrap()))
- })
- }
-
++ call_site,
+ &format!("`format!` in `{}!` args", name),
+ |diag| {
+ diag.help(&format!(
+ "combine the `format!(..)` arguments with the outer `{}!(..)` call",
+ name
+ ));
+ diag.help("or consider changing `format!` to `format_args!`");
+ },
+ );
+ }
+ }
+}
+
+fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, arg: &FormatArgsArg<'tcx>) {
+ let value = arg.value;
+ if_chain! {
+ if !value.span.from_expansion();
+ if let ExprKind::MethodCall(_, _, [receiver], _) = value.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
+ if is_diag_trait_item(cx, method_def_id, sym::ToString);
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
+ if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
+ then {
+ let (n_needed_derefs, target) = count_needed_derefs(
+ receiver_ty,
+ cx.typeck_results().expr_adjustments(receiver).iter(),
+ );
+ if implements_trait(cx, target, display_trait_id, &[]) {
+ if n_needed_derefs == 0 {
+ span_lint_and_sugg(
+ cx,
+ TO_STRING_IN_FORMAT_ARGS,
+ value.span.with_lo(receiver.span.hi()),
+ &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
+ "remove this",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ TO_STRING_IN_FORMAT_ARGS,
+ value.span,
+ &format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
+ "use this",
+ format!("{:*>width$}{}", "", receiver_snippet, width = n_needed_derefs),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
+fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool {
+ let value = args[i].value;
+ args.iter()
+ .enumerate()
+ .any(|(j, arg)| i != j && std::ptr::eq(value, arg.value))
+}
+
+fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
+where
+ I: Iterator<Item = &'tcx Adjustment<'tcx>>,
+{
+ let mut n_total = 0;
+ let mut n_needed = 0;
+ loop {
+ if let Some(Adjustment {
+ kind: Adjust::Deref(overloaded_deref),
+ target,
+ }) = iter.next()
+ {
+ n_total += 1;
+ if overloaded_deref.is_some() {
+ n_needed = n_total;
+ }
+ ty = target;
+ } else {
+ return (n_needed, ty);
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
+use clippy_utils::differing_macro_contexts;
+use clippy_utils::source::snippet_opt;
+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.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// a =- 42; // confusing, should it be `a -= 42` or `a = -42`?
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ suspicious,
+ "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.
+ ///
+ /// ### 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
+ /// }
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub SUSPICIOUS_UNARY_OP_FORMATTING,
+ suspicious,
+ "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.
+ ///
+ /// ### 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?
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_ELSE_FORMATTING,
+ suspicious,
+ "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.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let a = &[
+ /// -1, -2, -3 // <= no comma here
+ /// -4, -5, -6
+ /// ];
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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 an 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((pre_else, post_else)) = else_snippet.split_once("else");
+ if let Some((_, post_else_post_eol)) = post_else.split_once('\n');
+
+ then {
+ // Allow allman style braces `} \n else \n {`
+ if_chain! {
+ if is_block(else_);
+ if let Some((_, pre_else_post_eol)) = pre_else.split_once('\n');
+ // Exactly one eol before and after the else
+ if !pre_else_post_eol.contains('\n');
+ if !post_else_post_eol.contains('\n');
+ then {
+ return;
+ }
+ }
+
+ let else_desc = if is_if(else_) { "if" } else { "{..}" };
+ 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_chain! {
+ if !differing_macro_contexts(first.span, second.span);
+ if !first.span.from_expansion();
+ if let ExprKind::If(cond_expr, ..) = &first.kind;
+ if is_block(second) || is_if(second);
+
+ // Proc-macros can give weird spans. Make sure this is actually an `if`.
+ if let Some(if_snip) = snippet_opt(cx, first.span.until(cond_expr.span));
+ if if_snip.starts_with("if");
+
+ // If there is a line break between the two expressions, don't lint.
+ // If there is a non-whitespace character, this span came from a proc-macro.
+ let else_span = first.span.between(second.span);
+ if let Some(else_snippet) = snippet_opt(cx, else_span);
+ if !else_snippet.chars().any(|c| c == '\n' || !c.is_whitespace());
+ then {
+ let (looks_like, next_thing) = if is_if(second) {
+ ("an `else if`", "the second `if`")
+ } else {
+ ("an `else {..}`", "the next block")
+ };
+
+ span_lint_and_note(
+ cx,
+ SUSPICIOUS_ELSE_FORMATTING,
+ else_span,
+ &format!("this looks like {} but the `else` is missing", looks_like),
+ None,
+ &format!(
+ "to remove this lint, add the missing `else` or add a new line before {}",
+ next_thing,
+ ),
+ );
+ }
+ }
+}
+
+fn is_block(expr: &Expr) -> bool {
+ matches!(expr.kind, ExprKind::Block(..))
+}
+
+/// Check if the expression is an `if` or `if let`
+fn is_if(expr: &Expr) -> bool {
+ matches!(expr.kind, ExprKind::If(..))
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Searches for implementations of the `Into<..>` trait and suggests to implement `From<..>` instead.
+ ///
+ /// ### Why is this bad?
+ /// According the std docs implementing `From<..>` is preferred since it gives you `Into<..>` for free where the reverse isn't true.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct StringWrapper(String);
+ ///
+ /// impl Into<StringWrapper> for String {
+ /// fn into(self) -> StringWrapper {
+ /// StringWrapper(self)
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct StringWrapper(String);
+ ///
+ /// impl From<String> for StringWrapper {
+ /// fn from(s: String) -> StringWrapper {
+ /// StringWrapper(s)
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub FROM_OVER_INTO,
+ style,
+ "Warns on implementations of `Into<..>` to use `From<..>`"
+}
+
+pub struct FromOverInto {
+ msrv: Option<RustcVersion>,
+}
+
+impl FromOverInto {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ FromOverInto { msrv }
+ }
+}
+
+impl_lint_pass!(FromOverInto => [FROM_OVER_INTO]);
+
+impl LateLintPass<'_> for FromOverInto {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::RE_REBALANCING_COHERENCE) {
+ return;
+ }
+
+ if_chain! {
+ if let hir::ItemKind::Impl{ .. } = &item.kind;
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id);
+ if cx.tcx.is_diagnostic_item(sym::Into, impl_trait_ref.def_id);
+
+ then {
+ span_lint_and_help(
+ cx,
+ FROM_OVER_INTO,
+ cx.tcx.sess.source_map().guess_head_span(item.span),
+ "an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true",
+ None,
+ &format!("consider to implement `From<{}>` instead", impl_trait_ref.self_ty()),
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for function invocations of the form `primitive::from_str_radix(s, 10)`
+ ///
+ /// ### Why is this bad?
+ ///
+ /// This specific common use case can be rewritten as `s.parse::<primitive>()`
+ /// (and in most cases, the turbofish can be removed), which reduces code length
+ /// and complexity.
+ ///
+ /// ### Known problems
+ ///
+ /// This lint may suggest using (&<expression>).parse() instead of <expression>.parse() directly
+ /// in some cases, which is correct but adds unnecessary complexity to the code.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let input: &str = get_input();
+ /// let num = u16::from_str_radix(input, 10)?;
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// let input: &str = get_input();
+ /// let num: u16 = input.parse()?;
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub FROM_STR_RADIX_10,
+ style,
+ "from_str_radix with radix 10"
+}
+
+declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
+
+impl LateLintPass<'tcx> for FromStrRadix10 {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
+ if_chain! {
+ if let ExprKind::Call(maybe_path, arguments) = &exp.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
+
+ // check if the first part of the path is some integer primitive
+ if let TyKind::Path(ty_qpath) = &ty.kind;
+ let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
+ if let def::Res::PrimTy(prim_ty) = ty_res;
+ if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
+
+ // check if the second part of the path indeed calls the associated
+ // function `from_str_radix`
+ if pathseg.ident.name.as_str() == "from_str_radix";
+
+ // check if the second argument is a primitive `10`
+ if arguments.len() == 2;
+ if let ExprKind::Lit(lit) = &arguments[1].kind;
+ if let rustc_ast::ast::LitKind::Int(10, _) = lit.node;
+
+ then {
+ let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if is_ty_stringish(cx, ty) {
+ expr
+ } else {
+ &arguments[0]
+ }
+ } else {
+ &arguments[0]
+ };
+
+ let sugg = Sugg::hir_with_applicability(
+ cx,
+ expr,
+ "<string>",
+ &mut Applicability::MachineApplicable
+ ).maybe_par();
+
+ span_lint_and_sugg(
+ cx,
+ FROM_STR_RADIX_10,
+ exp.span,
+ "this call to `from_str_radix` can be replaced with a call to `str::parse`",
+ "try",
+ format!("{}.parse::<{}>()", sugg, prim_ty.name_str()),
+ Applicability::MaybeIncorrect
+ );
+ }
+ }
+ }
+}
+
+/// Checks if a Ty is `String` or `&str`
+fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ is_type_diagnostic_item(cx, ty, sym::String) || is_type_diagnostic_item(cx, ty, sym::str)
+}
--- /dev/null
+mod must_use;
+mod not_unsafe_ptr_arg_deref;
+mod result_unit_err;
+mod too_many_arguments;
+mod too_many_lines;
+
+use rustc_hir as hir;
+use rustc_hir::intravisit;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Color;
+ /// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
+ /// // ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn im_too_long() {
+ /// println!("");
+ /// // ... 100 more LoC
+ /// println!("");
+ /// }
+ /// ```
++ #[clippy::version = "1.34.0"]
+ 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 });
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Why is this bad?
+ /// Unit values are useless. The attribute is likely
+ /// a remnant of a refactoring that removed the return type.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// #[must_use]
+ /// fn useless() { }
+ /// ```
++ #[clippy::version = "1.40.0"]
+ 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]`.
+ ///
+ /// ### 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.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// #[must_use]
+ /// fn double_must_use() -> Result<(), ()> {
+ /// unimplemented!();
+ /// }
+ /// ```
++ #[clippy::version = "1.40.0"]
+ 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.
+ ///
+ /// ### 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 }
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub MUST_USE_CANDIDATE,
+ pedantic,
+ "function or method that could take a `#[must_use]` attribute"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for public functions that return a `Result`
+ /// with an `Err` type of `()`. It suggests using a custom type that
+ /// implements `std::error::Error`.
+ ///
+ /// ### Why is this bad?
+ /// Unit does not implement `Error` and carries no
+ /// further information about what went wrong.
+ ///
+ /// ### Known problems
+ /// Of course, this lint assumes that `Result` is used
+ /// for a fallible operation (which is after all the intended use). However
+ /// code may opt to (mis)use it as a basic two-variant-enum. In that case,
+ /// the suggestion is misguided, and the code should use a custom enum
+ /// instead.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// pub fn read_u8() -> Result<u8, ()> { Err(()) }
+ /// ```
+ /// should become
+ /// ```rust,should_panic
+ /// use std::fmt;
+ ///
+ /// #[derive(Debug)]
+ /// pub struct EndOfStream;
+ ///
+ /// impl fmt::Display for EndOfStream {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// write!(f, "End of Stream")
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for EndOfStream { }
+ ///
+ /// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
+ ///# fn main() {
+ ///# read_u8().unwrap();
+ ///# }
+ /// ```
+ ///
+ /// Note that there are crates that simplify creating the error type, e.g.
+ /// [`thiserror`](https://docs.rs/thiserror).
++ #[clippy::version = "1.49.0"]
+ pub RESULT_UNIT_ERR,
+ style,
+ "public function returning `Result` with an `Err` type of `()`"
+}
+
+#[derive(Copy, Clone)]
+pub struct Functions {
+ too_many_arguments_threshold: u64,
+ too_many_lines_threshold: u64,
+}
+
+impl Functions {
+ pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64) -> Self {
+ Self {
+ too_many_arguments_threshold,
+ too_many_lines_threshold,
+ }
+ }
+}
+
+impl_lint_pass!(Functions => [
+ TOO_MANY_ARGUMENTS,
+ TOO_MANY_LINES,
+ NOT_UNSAFE_PTR_ARG_DEREF,
+ MUST_USE_UNIT,
+ DOUBLE_MUST_USE,
+ MUST_USE_CANDIDATE,
+ RESULT_UNIT_ERR,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Functions {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: intravisit::FnKind<'tcx>,
+ decl: &'tcx hir::FnDecl<'_>,
+ body: &'tcx hir::Body<'_>,
+ span: Span,
+ hir_id: hir::HirId,
+ ) {
+ too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
+ too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold);
+ not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ must_use::check_item(cx, item);
+ result_unit_err::check_item(cx, item);
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
+ must_use::check_impl_item(cx, item);
+ result_unit_err::check_impl_item(cx, item);
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
+ not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
+ must_use::check_trait_item(cx, item);
+ result_unit_err::check_trait_item(cx, item);
+ }
+}
--- /dev/null
- let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
- let single_idx = line.find("//").unwrap_or_else(|| line.len());
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_lint::{LateContext, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_span::Span;
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::source::snippet_opt;
+
+use super::TOO_MANY_LINES;
+
+pub(super) fn check_fn(
+ cx: &LateContext<'_>,
+ kind: FnKind<'tcx>,
+ span: Span,
+ body: &'tcx hir::Body<'_>,
+ too_many_lines_threshold: u64,
+) {
+ // Closures must be contained in a parent body, which will be checked for `too_many_lines`.
+ // Don't check closures for `too_many_lines` to avoid duplicated lints.
+ if matches!(kind, FnKind::Closure) || in_external_macro(cx.sess(), span) {
+ return;
+ }
+
+ let code_snippet = match snippet_opt(cx, body.value.span) {
+ Some(s) => s,
+ _ => return,
+ };
+ let mut line_count: u64 = 0;
+ let mut in_comment = false;
+ let mut code_in_line;
+
+ let function_lines = if matches!(body.value.kind, hir::ExprKind::Block(..))
+ && code_snippet.as_bytes().first().copied() == Some(b'{')
+ && code_snippet.as_bytes().last().copied() == Some(b'}')
+ {
+ // Removing the braces from the enclosing block
+ &code_snippet[1..code_snippet.len() - 1]
+ } else {
+ &code_snippet
+ }
+ .trim() // Remove leading and trailing blank lines
+ .lines();
+
+ for mut line in function_lines {
+ code_in_line = false;
+ loop {
+ line = line.trim_start();
+ if line.is_empty() {
+ break;
+ }
+ if in_comment {
+ if let Some(i) = line.find("*/") {
+ line = &line[i + 2..];
+ in_comment = false;
+ continue;
+ }
+ } else {
++ let multi_idx = line.find("/*").unwrap_or(line.len());
++ let single_idx = line.find("//").unwrap_or(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 > too_many_lines_threshold {
+ span_lint(
+ cx,
+ TOO_MANY_LINES,
+ span,
+ &format!(
+ "this function has too many lines ({}/{})",
+ line_count, too_many_lines_threshold
+ ),
+ );
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::return_ty;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{Body, FnDecl, HirId};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::subst::Subst;
+use rustc_middle::ty::{Opaque, PredicateKind::Trait};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt;
+use rustc_trait_selection::traits::{self, FulfillmentError, TraitEngine};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint requires Future implementations returned from
+ /// functions and methods to implement the `Send` marker trait. It is mostly
+ /// used by library authors (public and internal) that target an audience where
+ /// multithreaded executors are likely to be used for running these Futures.
+ ///
+ /// ### Why is this bad?
+ /// A Future implementation captures some state that it
+ /// needs to eventually produce its final value. When targeting a multithreaded
+ /// executor (which is the norm on non-embedded devices) this means that this
+ /// state may need to be transported to other threads, in other words the
+ /// whole Future needs to implement the `Send` marker trait. If it does not,
+ /// then the resulting Future cannot be submitted to a thread pool in the
+ /// end user’s code.
+ ///
+ /// Especially for generic functions it can be confusing to leave the
+ /// discovery of this problem to the end user: the reported error location
+ /// will be far from its cause and can in many cases not even be fixed without
+ /// modifying the library where the offending Future implementation is
+ /// produced.
+ ///
+ /// ### Example
+ /// ```rust
+ /// async fn not_send(bytes: std::rc::Rc<[u8]>) {}
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// async fn is_send(bytes: std::sync::Arc<[u8]>) {}
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub FUTURE_NOT_SEND,
+ nursery,
+ "public Futures must be Send"
+}
+
+declare_lint_pass!(FutureNotSend => [FUTURE_NOT_SEND]);
+
+impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'tcx>,
+ _: &'tcx Body<'tcx>,
+ _: Span,
+ hir_id: HirId,
+ ) {
+ if let FnKind::Closure = kind {
+ return;
+ }
+ let ret_ty = return_ty(cx, hir_id);
+ if let Opaque(id, subst) = *ret_ty.kind() {
+ let preds = cx.tcx.explicit_item_bounds(id);
+ let mut is_future = false;
+ for &(p, _span) in preds {
+ let p = p.subst(cx.tcx, subst);
+ if let Some(trait_ref) = p.to_opt_poly_trait_ref() {
+ if Some(trait_ref.value.def_id()) == cx.tcx.lang_items().future_trait() {
+ is_future = true;
+ break;
+ }
+ }
+ }
+ if is_future {
+ let send_trait = cx.tcx.get_diagnostic_item(sym::Send).unwrap();
+ let span = decl.output.span();
+ let send_errors = cx.tcx.infer_ctxt().enter(|infcx| {
+ let cause = traits::ObligationCause::misc(span, hir_id);
+ let mut fulfillment_cx = traits::FulfillmentContext::new();
+ fulfillment_cx.register_bound(&infcx, cx.param_env, ret_ty, send_trait, cause);
+ fulfillment_cx.select_all_or_error(&infcx)
+ });
+ if !send_errors.is_empty() {
+ span_lint_and_then(
+ cx,
+ FUTURE_NOT_SEND,
+ span,
+ "future cannot be sent between threads safely",
+ |db| {
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ for FulfillmentError { obligation, .. } in send_errors {
+ infcx.maybe_note_obligation_cause_for_async_await(db, &obligation);
+ if let Trait(trait_pred) = obligation.predicate.kind().skip_binder() {
+ db.note(&format!(
+ "`{}` doesn't implement `{}`",
+ trait_pred.self_ty(),
+ trait_pred.trait_ref.print_only_trait_path(),
+ ));
+ }
+ }
+ });
+ },
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
+//! lint on using `x.get(x.len() - 1)` instead of `x.last()`
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::SpanlessEq;
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for using `x.get(x.len() - 1)` instead of
+ /// `x.last()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `x.last()` is easier to read and has the same
+ /// result.
+ ///
+ /// Note that using `x[x.len() - 1]` is semantically different from
+ /// `x.last()`. Indexing into the array will panic on out-of-bounds
+ /// accesses, while `x.get()` and `x.last()` will return `None`.
+ ///
+ /// There is another lint (get_unwrap) that covers the case of using
+ /// `x.get(index).unwrap()` instead of `x[index]`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x = vec![2, 3, 5];
+ /// let last_element = x.get(x.len() - 1);
+ ///
+ /// // Good
+ /// let x = vec![2, 3, 5];
+ /// let last_element = x.last();
+ /// ```
++ #[clippy::version = "1.37.0"]
+ pub GET_LAST_WITH_LEN,
+ complexity,
+ "Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler"
+}
+
+declare_lint_pass!(GetLastWithLen => [GET_LAST_WITH_LEN]);
+
+impl<'tcx> LateLintPass<'tcx> for GetLastWithLen {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // Is a method call
+ if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
+
+ // Method name is "get"
+ if path.ident.name == sym!(get);
+
+ // Argument 0 (the struct we're calling the method on) is a vector
+ if let Some(struct_calling_on) = args.get(0);
+ let struct_ty = cx.typeck_results().expr_ty(struct_calling_on);
+ if is_type_diagnostic_item(cx, struct_ty, sym::Vec);
+
+ // Argument to "get" is a subtraction
+ if let Some(get_index_arg) = args.get(1);
+ if let ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Sub,
+ ..
+ },
+ lhs,
+ rhs,
+ ) = &get_index_arg.kind;
+
+ // LHS of subtraction is "x.len()"
+ if let ExprKind::MethodCall(arg_lhs_path, _, lhs_args, _) = &lhs.kind;
+ if arg_lhs_path.ident.name == sym::len;
+ if let Some(arg_lhs_struct) = lhs_args.get(0);
+
+ // The two vectors referenced (x in x.get(...) and in x.len())
+ if SpanlessEq::new(cx).eq_expr(struct_calling_on, arg_lhs_struct);
+
+ // RHS of subtraction is 1
+ if let ExprKind::Lit(rhs_lit) = &rhs.kind;
+ if let LitKind::Int(1, ..) = rhs_lit.node;
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let vec_name = snippet_with_applicability(
+ cx,
+ struct_calling_on.span, "vec",
+ &mut applicability,
+ );
+
+ span_lint_and_sugg(
+ cx,
+ GET_LAST_WITH_LEN,
+ expr.span,
+ &format!("accessing last element with `{0}.get({0}.len() - 1)`", vec_name),
+ "try",
+ format!("{}.last()", vec_name),
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::source::snippet;
+use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{clip, unsext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for identity operations, e.g., `x + 0`.
+ ///
+ /// ### Why is this bad?
+ /// This code can be removed without changing the
+ /// meaning. So it just obscures what's going on. Delete it mercilessly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// x / 1 + 0 * 1 - 0 | 0;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub IDENTITY_OP,
+ complexity,
+ "using identity operations, e.g., `x + 0` or `y / 1`"
+}
+
+declare_lint_pass!(IdentityOp => [IDENTITY_OP]);
+
+impl<'tcx> LateLintPass<'tcx> for IdentityOp {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+ if let ExprKind::Binary(cmp, left, right) = e.kind {
+ if is_allowed(cx, cmp, left, right) {
+ return;
+ }
+ match cmp.node {
+ BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
+ check(cx, left, 0, e.span, right.span);
+ check(cx, right, 0, e.span, left.span);
+ },
+ BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => check(cx, right, 0, e.span, left.span),
+ BinOpKind::Mul => {
+ check(cx, left, 1, e.span, right.span);
+ check(cx, right, 1, e.span, left.span);
+ },
+ BinOpKind::Div => check(cx, right, 1, e.span, left.span),
+ BinOpKind::BitAnd => {
+ check(cx, left, -1, e.span, right.span);
+ check(cx, right, -1, e.span, left.span);
+ },
+ _ => (),
+ }
+ }
+ }
+}
+
+fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool {
+ // `1 << 0` is a common pattern in bit manipulation code
+ cmp.node == BinOpKind::Shl
+ && constant_simple(cx, cx.typeck_results(), right) == Some(Constant::Int(0))
+ && constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1))
+}
+
+fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
+ if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e) {
+ let check = match *cx.typeck_results().expr_ty(e).kind() {
+ ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),
+ ty::Uint(uty) => clip(cx.tcx, !0, uty),
+ _ => return,
+ };
+ if match m {
+ 0 => v == 0,
+ -1 => v == check,
+ 1 => v == 1,
+ _ => unreachable!(),
+ } {
+ span_lint(
+ cx,
+ IDENTITY_OP,
+ span,
+ &format!(
+ "the operation is ineffective. Consider reducing it to `{}`",
+ snippet(cx, arg, "..")
+ ),
+ );
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::higher;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::SpanlessEq;
+use if_chain::if_chain;
+use rustc_hir::intravisit::{self as visit, NestedVisitorMap, Visitor};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+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.
+ ///
+ /// ### 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);
+ /// }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ 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>, expr: &'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 Some(higher::IfLet {
+ let_expr,
+ if_then,
+ if_else: Some(if_else),
+ ..
+ }) = higher::IfLet::hir(cx, expr)
+ {
+ op_visit.visit_expr(let_expr);
+ if op_visit.mutex_lock_called {
+ arm_visit.visit_expr(if_then);
+ arm_visit.visit_expr(if_else);
+
+ if arm_visit.mutex_lock_called && arm_visit.same_mutex(cx, op_visit.found_mutex.unwrap()) {
+ span_lint_and_help(
+ cx,
+ IF_LET_MUTEX,
+ expr.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 let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
+ 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 let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
+ self.found_mutex = Some(mutex);
+ self.mutex_lock_called = true;
+ return;
+ }
+ visit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
+ fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool {
+ 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, [self_arg, ..], _) = &expr.kind;
+ if path.ident.as_str() == "lock";
+ let ty = cx.typeck_results().expr_ty(self_arg);
+ if is_type_diagnostic_item(cx, ty, sym::Mutex);
+ then {
+ Some(self_arg)
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
+//! lint on if branches that could be swapped so no `!` operation is necessary
+//! on the condition
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::is_else_clause;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `!` or `!=` in an if condition with an
+ /// else branch.
+ ///
+ /// ### Why is this bad?
+ /// Negations reduce the readability of statements.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v: Vec<usize> = vec![];
+ /// # fn a() {}
+ /// # fn b() {}
+ /// if !v.is_empty() {
+ /// a()
+ /// } else {
+ /// b()
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # let v: Vec<usize> = vec![];
+ /// # fn a() {}
+ /// # fn b() {}
+ /// if v.is_empty() {
+ /// b()
+ /// } else {
+ /// a()
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub IF_NOT_ELSE,
+ pedantic,
+ "`if` branches that could be swapped so no negation operation is necessary on the condition"
+}
+
+declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]);
+
+impl LateLintPass<'_> for IfNotElse {
+ fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
+ // While loops will be desugared to ExprKind::If. This will cause the lint to fire.
+ // To fix this, return early if this span comes from a macro or desugaring.
+ if item.span.from_expansion() {
+ return;
+ }
+ if let ExprKind::If(cond, _, Some(els)) = item.kind {
+ if let ExprKind::Block(..) = els.kind {
+ // Disable firing the lint in "else if" expressions.
+ if is_else_clause(cx.tcx, item) {
+ return;
+ }
+
+ match cond.peel_drop_temps().kind {
+ ExprKind::Unary(UnOp::Not, _) => {
+ span_lint_and_help(
+ cx,
+ IF_NOT_ELSE,
+ item.span,
+ "unnecessary boolean `not` operation",
+ None,
+ "remove the `!` and swap the blocks of the `if`/`else`",
+ );
+ },
+ ExprKind::Binary(ref kind, _, _) if kind.node == BinOpKind::Ne => {
+ span_lint_and_help(
+ cx,
+ IF_NOT_ELSE,
+ item.span,
+ "unnecessary `!=` operation",
+ None,
+ "change to `==` and swap the blocks of the `if`/`else`",
+ );
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet_with_macro_callsite;
- use rustc_hir::{Expr, ExprKind};
++use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
++use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for if-else that could be written to `bool::then`.
+ ///
+ /// ### Why is this bad?
+ /// Looks a little redundant. Using `bool::then` helps it have less lines of code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v = vec![0];
+ /// let a = if v.is_empty() {
+ /// println!("true!");
+ /// Some(42)
+ /// } else {
+ /// None
+ /// };
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # let v = vec![0];
+ /// let a = v.is_empty().then(|| {
+ /// println!("true!");
+ /// 42
+ /// });
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub IF_THEN_SOME_ELSE_NONE,
+ restriction,
+ "Finds if-else that could be written using `bool::then`"
+}
+
+pub struct IfThenSomeElseNone {
+ msrv: Option<RustcVersion>,
+}
+
+impl IfThenSomeElseNone {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]);
+
+impl LateLintPass<'_> for IfThenSomeElseNone {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::BOOL_THEN) {
+ return;
+ }
+
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ // We only care about the top-most `if` in the chain
+ if is_else_clause(cx.tcx, expr) {
+ return;
+ }
+
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr);
+ if let ExprKind::Block(then_block, _) = then.kind;
+ if let Some(then_expr) = then_block.expr;
+ if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind;
+ if let ExprKind::Path(ref then_call_qpath) = then_call.kind;
+ if is_lang_ctor(cx, then_call_qpath, OptionSome);
+ if let ExprKind::Block(els_block, _) = els.kind;
+ if els_block.stmts.is_empty();
+ if let Some(els_expr) = els_block.expr;
+ if let ExprKind::Path(ref qpath) = els_expr.kind;
+ if is_lang_ctor(cx, qpath, OptionNone);
++ if !stmts_contains_early_return(then_block.stmts);
+ then {
+ let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]");
+ let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) {
+ format!("({})", cond_snip)
+ } else {
+ cond_snip.into_owned()
+ };
+ let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, "");
+ let closure_body = if then_block.stmts.is_empty() {
+ arg_snip.into_owned()
+ } else {
+ format!("{{ /* snippet */ {} }}", arg_snip)
+ };
+ let help = format!(
+ "consider using `bool::then` like: `{}.then(|| {})`",
+ cond_snip,
+ closure_body,
+ );
+ span_lint_and_help(
+ cx,
+ IF_THEN_SOME_ELSE_NONE,
+ expr.span,
+ "this could be simplified with `bool::then`",
+ None,
+ &help,
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
++
++fn stmts_contains_early_return(stmts: &[Stmt<'_>]) -> bool {
++ stmts.iter().any(|stmt| {
++ let Stmt { kind: StmtKind::Semi(e), .. } = stmt else { return false };
++
++ contains_return(e)
++ })
++}
--- /dev/null
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+
+use rustc_errors::DiagnosticBuilder;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, NestedVisitorMap, Visitor};
+use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, 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::{Ty, TyS, TypeckResults};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use rustc_typeck::hir_ty_to_ty;
+
+use if_chain::if_chain;
+
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::differing_macro_contexts;
+use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::ty::is_type_diagnostic_item;
+
+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>) { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.def_id) {
+ return;
+ }
+
+ match item.kind {
+ ItemKind::Impl(ref impl_) => {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(impl_.self_ty);
+
+ for target in &vis.found {
+ if differing_macro_contexts(item.span, target.span()) {
+ return;
+ }
+
+ let generics_suggestion_span = impl_.generics.span.substitute_dummy({
+ let pos = snippet_opt(cx, item.span.until(target.span()))
+ .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
+ if let Some(pos) = pos {
+ Span::new(pos, pos, item.span.ctxt(), item.span.parent())
+ } else {
+ return;
+ }
+ });
+
+ let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
+ for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
+ ctr_vis.visit_impl_item(item);
+ }
+
+ span_lint_and_then(
+ cx,
+ IMPLICIT_HASHER,
+ target.span(),
+ &format!(
+ "impl for `{}` should be generalized over different hashers",
+ target.type_name()
+ ),
+ move |diag| {
+ suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
+ },
+ );
+ }
+ },
+ ItemKind::Fn(ref sig, ref generics, body_id) => {
+ let body = cx.tcx.hir().body(body_id);
+
+ for ty in sig.decl.inputs {
+ let mut vis = ImplicitHasherTypeVisitor::new(cx);
+ vis.visit_ty(ty);
+
+ for target in &vis.found {
+ if in_external_macro(cx.sess(), generics.span) {
+ continue;
+ }
+ let generics_suggestion_span = generics.span.substitute_dummy({
+ let pos = snippet_opt(
+ cx,
+ Span::new(
+ item.span.lo(),
+ body.params[0].pat.span.lo(),
+ item.span.ctxt(),
+ item.span.parent(),
+ ),
+ )
+ .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.ctxt(), item.span.parent())
+ });
+
+ 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, 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) && 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) && 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 visit_infer(&mut self, inf: &'tcx hir::InferArg) {
+ if let Some(target) = ImplicitHasherType::new(self.cx, &inf.to_ty()) {
+ self.found.push(target);
+ }
+
+ walk_inf(self, inf);
+ }
+
+ 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_results: Option<&'tcx TypeckResults<'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_results: cx.maybe_typeck_results(),
+ target,
+ suggestions: BTreeMap::new(),
+ }
+ }
+}
+
+impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_body(&mut self, body: &'tcx Body<'_>) {
+ let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
+ walk_body(self, body);
+ self.maybe_typeck_results = old_maybe_typeck_results;
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(fun, args) = e.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
+ if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
+ if let Some(ty_did) = ty_path.res.opt_def_id();
+ then {
+ if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
+ return;
+ }
+
+ if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) {
+ 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 self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) {
+ 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())
+ }
+}
--- /dev/null
- visitors::visit_break_exprs,
+use clippy_utils::{
+ diagnostics::span_lint_and_sugg,
+ get_async_fn_body, is_async_fn,
+ source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
- use rustc_hir::intravisit::FnKind;
++ visitors::expr_visitor_no_bodies,
+};
+use rustc_errors::Applicability;
- visit_break_exprs(block, |break_expr, dest, sub_expr| {
- if dest.target_id.ok() == Some(expr.hir_id) {
- if call_site_span.is_none() && break_expr.span.ctxt() == ctxt {
- // At this point sub_expr can be `None` in async functions which either diverge, or return the
- // unit type.
- if let Some(sub_expr) = sub_expr {
- lint_break(cx, break_expr.span, sub_expr.span);
++use rustc_hir::intravisit::{FnKind, Visitor};
+use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for missing return statements at the end of a block.
+ ///
+ /// ### Why is this bad?
+ /// Actually omitting the return keyword is idiomatic Rust code. Programmers
+ /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss
+ /// the last returning statement because the only difference is a missing `;`. Especially in bigger
+ /// code with multiple return paths having a `return` keyword makes it easier to find the
+ /// corresponding statements.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
+ /// add return
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// return x;
+ /// }
+ /// ```
++ #[clippy::version = "1.33.0"]
+ pub IMPLICIT_RETURN,
+ restriction,
+ "use a return statement like `return expr` instead of an expression"
+}
+
+declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
+
+fn lint_return(cx: &LateContext<'_>, span: Span) {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_applicability(cx, span, "..", &mut app);
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_RETURN,
+ span,
+ "missing `return` statement",
+ "add `return` as shown",
+ format!("return {}", snip),
+ app,
+ );
+}
+
+fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_RETURN,
+ break_span,
+ "missing `return` statement",
+ "change `break` to `return` as shown",
+ format!("return {}", snip),
+ app,
+ );
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum LintLocation {
+ /// The lint was applied to a parent expression.
+ Parent,
+ /// The lint was applied to this expression, a child, or not applied.
+ Inner,
+}
+impl LintLocation {
+ fn still_parent(self, b: bool) -> Self {
+ if b { self } else { Self::Inner }
+ }
+
+ fn is_parent(self) -> bool {
+ self == Self::Parent
+ }
+}
+
+// Gets the call site if the span is in a child context. Otherwise returns `None`.
+fn get_call_site(span: Span, ctxt: SyntaxContext) -> Option<Span> {
+ (span.ctxt() != ctxt).then(|| walk_span_to_context(span, ctxt).unwrap_or(span))
+}
+
+fn lint_implicit_returns(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ // The context of the function body.
+ ctxt: SyntaxContext,
+ // Whether the expression is from a macro expansion.
+ call_site_span: Option<Span>,
+) -> LintLocation {
+ match expr.kind {
+ ExprKind::Block(
+ Block {
+ expr: Some(block_expr), ..
+ },
+ _,
+ ) => lint_implicit_returns(
+ cx,
+ block_expr,
+ ctxt,
+ call_site_span.or_else(|| get_call_site(block_expr.span, ctxt)),
+ )
+ .still_parent(call_site_span.is_some()),
+
+ ExprKind::If(_, then_expr, Some(else_expr)) => {
+ // Both `then_expr` or `else_expr` are required to be blocks in the same context as the `if`. Don't
+ // bother checking.
+ let res = lint_implicit_returns(cx, then_expr, ctxt, call_site_span).still_parent(call_site_span.is_some());
+ if res.is_parent() {
+ // The return was added as a parent of this if expression.
+ return res;
+ }
+ lint_implicit_returns(cx, else_expr, ctxt, call_site_span).still_parent(call_site_span.is_some())
+ },
+
+ ExprKind::Match(_, arms, _) => {
+ for arm in arms {
+ let res = lint_implicit_returns(
+ cx,
+ arm.body,
+ ctxt,
+ call_site_span.or_else(|| get_call_site(arm.body.span, ctxt)),
+ )
+ .still_parent(call_site_span.is_some());
+ if res.is_parent() {
+ // The return was added as a parent of this match expression.
+ return res;
+ }
+ }
+ LintLocation::Inner
+ },
+
+ ExprKind::Loop(block, ..) => {
+ let mut add_return = false;
- } else {
- // the break expression is from a macro call, add a return to the loop
- add_return = true;
++ expr_visitor_no_bodies(|e| {
++ if let ExprKind::Break(dest, sub_expr) = e.kind {
++ if dest.target_id.ok() == Some(expr.hir_id) {
++ if call_site_span.is_none() && e.span.ctxt() == ctxt {
++ // At this point sub_expr can be `None` in async functions which either diverge, or return
++ // the unit type.
++ if let Some(sub_expr) = sub_expr {
++ lint_break(cx, e.span, sub_expr.span);
++ }
++ } else {
++ // the break expression is from a macro call, add a return to the loop
++ add_return = true;
+ }
- });
+ }
+ }
++ true
++ })
++ .visit_block(block);
+ if add_return {
+ #[allow(clippy::option_if_let_else)]
+ if let Some(span) = call_site_span {
+ lint_return(cx, span);
+ LintLocation::Parent
+ } else {
+ lint_return(cx, expr.span);
+ LintLocation::Inner
+ }
+ } else {
+ LintLocation::Inner
+ }
+ },
+
+ // If expressions without an else clause, and blocks without a final expression can only be the final expression
+ // if they are divergent, or return the unit type.
+ ExprKind::If(_, _, None) | ExprKind::Block(Block { expr: None, .. }, _) | ExprKind::Ret(_) => {
+ LintLocation::Inner
+ },
+
+ // Any divergent expression doesn't need a return statement.
+ ExprKind::MethodCall(..)
+ | ExprKind::Call(..)
+ | ExprKind::Binary(..)
+ | ExprKind::Unary(..)
+ | ExprKind::Index(..)
+ if cx.typeck_results().expr_ty(expr).is_never() =>
+ {
+ LintLocation::Inner
+ },
+
+ _ =>
+ {
+ #[allow(clippy::option_if_let_else)]
+ if let Some(span) = call_site_span {
+ lint_return(cx, span);
+ LintLocation::Parent
+ } else {
+ lint_return(cx, expr.span);
+ LintLocation::Inner
+ }
+ },
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_)))
+ || span.ctxt() != body.value.span.ctxt()
+ || in_external_macro(cx.sess(), span)
+ {
+ return;
+ }
+
+ let res_ty = cx.typeck_results().expr_ty(&body.value);
+ if res_ty.is_unit() || res_ty.is_never() {
+ return;
+ }
+
+ let expr = if is_async_fn(kind) {
+ match get_async_fn_body(cx.tcx, body) {
+ Some(e) => e,
+ None => return,
+ }
+ } else {
+ &body.value
+ };
+ lint_implicit_returns(cx, expr, expr.span.ctxt(), None);
+ }
+}
--- /dev/null
- use clippy_utils::{in_macro, SpanlessEq};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
- if in_macro(expr.span) {
++use clippy_utils::SpanlessEq;
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{lang_items::LangItem, BinOpKind, Expr, ExprKind, QPath, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for implicit saturating subtraction.
+ ///
+ /// ### Why is this bad?
+ /// Simplicity and readability. Instead we can easily use an builtin function.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let end: u32 = 10;
+ /// let start: u32 = 5;
+ ///
+ /// let mut i: u32 = end - start;
+ ///
+ /// // Bad
+ /// if i != 0 {
+ /// i -= 1;
+ /// }
+ ///
+ /// // Good
+ /// i = i.saturating_sub(1);
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub IMPLICIT_SATURATING_SUB,
+ pedantic,
+ "Perform saturating subtraction instead of implicitly checking lower bound of data type"
+}
+
+declare_lint_pass!(ImplicitSaturatingSub => [IMPLICIT_SATURATING_SUB]);
+
+impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
++ if expr.span.from_expansion() {
+ return;
+ }
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr);
+
+ // Check if the conditional expression is a binary operation
+ if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
+
+ // Ensure that the binary operator is >, != and <
+ if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node;
+
+ // Check if the true condition block has only one statement
+ if let ExprKind::Block(block, _) = then.kind;
+ if block.stmts.len() == 1 && block.expr.is_none();
+
+ // Check if assign operation is done
+ if let StmtKind::Semi(e) = block.stmts[0].kind;
+ if let Some(target) = subtracts_one(cx, e);
+
+ // Extracting out the variable name
+ if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind;
+
+ then {
+ // Handle symmetric conditions in the if statement
+ let (cond_var, cond_num_val) = if SpanlessEq::new(cx).eq_expr(cond_left, target) {
+ if BinOpKind::Gt == cond_op.node || BinOpKind::Ne == cond_op.node {
+ (cond_left, cond_right)
+ } else {
+ return;
+ }
+ } else if SpanlessEq::new(cx).eq_expr(cond_right, target) {
+ if BinOpKind::Lt == cond_op.node || BinOpKind::Ne == cond_op.node {
+ (cond_right, cond_left)
+ } else {
+ return;
+ }
+ } else {
+ return;
+ };
+
+ // Check if the variable in the condition statement is an integer
+ if !cx.typeck_results().expr_ty(cond_var).is_integral() {
+ return;
+ }
+
+ // Get the variable name
+ let var_name = ares_path.segments[0].ident.name.as_str();
+ const INT_TYPES: [LangItem; 5] = [
+ LangItem::I8,
+ LangItem::I16,
+ LangItem::I32,
+ LangItem::I64,
+ LangItem::Isize
+ ];
+
+ match cond_num_val.kind {
+ ExprKind::Lit(ref cond_lit) => {
+ // Check if the constant is zero
+ if let LitKind::Int(0, _) = cond_lit.node {
+ if cx.typeck_results().expr_ty(cond_left).is_signed() {
+ } else {
+ print_lint_and_sugg(cx, &var_name, expr);
+ };
+ }
+ },
+ ExprKind::Path(QPath::TypeRelative(_, name)) => {
+ if_chain! {
+ if name.ident.as_str() == "MIN";
+ if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(const_id);
+ let mut int_ids = INT_TYPES.iter().filter_map(|&ty| cx.tcx.lang_items().require(ty).ok());
+ if int_ids.any(|int_id| int_id == impl_id);
+ then {
+ print_lint_and_sugg(cx, &var_name, expr)
+ }
+ }
+ },
+ ExprKind::Call(func, []) => {
+ if_chain! {
+ if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind;
+ if name.ident.as_str() == "min_value";
+ if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id);
+ if let Some(impl_id) = cx.tcx.impl_of_method(func_id);
+ let mut int_ids = INT_TYPES.iter().filter_map(|&ty| cx.tcx.lang_items().require(ty).ok());
+ if int_ids.any(|int_id| int_id == impl_id);
+ then {
+ print_lint_and_sugg(cx, &var_name, expr)
+ }
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+}
+
+fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'a>> {
+ match expr.kind {
+ ExprKind::AssignOp(ref op1, target, value) => {
+ if_chain! {
+ if BinOpKind::Sub == op1.node;
+ // Check if literal being subtracted is one
+ if let ExprKind::Lit(ref lit1) = value.kind;
+ if let LitKind::Int(1, _) = lit1.node;
+ then {
+ Some(target)
+ } else {
+ None
+ }
+ }
+ },
+ ExprKind::Assign(target, value, _) => {
+ if_chain! {
+ if let ExprKind::Binary(ref op1, left1, right1) = value.kind;
+ if BinOpKind::Sub == op1.node;
+
+ if SpanlessEq::new(cx).eq_expr(left1, target);
+
+ if let ExprKind::Lit(ref lit1) = right1.kind;
+ if let LitKind::Int(1, _) = lit1.node;
+ then {
+ Some(target)
+ } else {
+ None
+ }
+ }
+ },
+ _ => None,
+ }
+}
+
+fn print_lint_and_sugg(cx: &LateContext<'_>, var_name: &str, expr: &Expr<'_>) {
+ span_lint_and_sugg(
+ cx,
+ IMPLICIT_SATURATING_SUB,
+ expr.span,
+ "implicitly performing saturating subtraction",
+ "try",
+ format!("{} = {}.saturating_sub({});", var_name, var_name, '1'),
+ Applicability::MachineApplicable,
+ );
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if !in_macro(expr.span);
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::{self as hir, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Symbol;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for struct constructors where all fields are shorthand and
+ /// the order of the field init shorthand in the constructor is inconsistent
+ /// with the order in the struct definition.
+ ///
+ /// ### Why is this bad?
+ /// Since the order of fields in a constructor doesn't affect the
+ /// resulted instance as the below example indicates,
+ ///
+ /// ```rust
+ /// #[derive(Debug, PartialEq, Eq)]
+ /// struct Foo {
+ /// x: i32,
+ /// y: i32,
+ /// }
+ /// let x = 1;
+ /// let y = 2;
+ ///
+ /// // This assertion never fails:
+ /// assert_eq!(Foo { x, y }, Foo { y, x });
+ /// ```
+ ///
+ /// inconsistent order can be confusing and decreases readability and consistency.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo {
+ /// x: i32,
+ /// y: i32,
+ /// }
+ /// let x = 1;
+ /// let y = 2;
+ ///
+ /// Foo { y, x };
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// # struct Foo {
+ /// # x: i32,
+ /// # y: i32,
+ /// # }
+ /// # let x = 1;
+ /// # let y = 2;
+ /// Foo { x, y };
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub INCONSISTENT_STRUCT_CONSTRUCTOR,
+ pedantic,
+ "the order of the field init shorthand is inconsistent with the order in the struct definition"
+}
+
+declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
+
+impl LateLintPass<'_> for InconsistentStructConstructor {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
++ if !expr.span.from_expansion();
+ if let ExprKind::Struct(qpath, fields, base) = expr.kind;
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let Some(adt_def) = ty.ty_adt_def();
+ if adt_def.is_struct();
+ if let Some(variant) = adt_def.variants.iter().next();
+ if fields.iter().all(|f| f.is_shorthand);
+ then {
+ let mut def_order_map = FxHashMap::default();
+ for (idx, field) in variant.fields.iter().enumerate() {
+ def_order_map.insert(field.ident.name, idx);
+ }
+
+ if is_consistent_order(fields, &def_order_map) {
+ return;
+ }
+
+ let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
+ ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
+
+ let mut fields_snippet = String::new();
+ let (last_ident, idents) = ordered_fields.split_last().unwrap();
+ for ident in idents {
+ fields_snippet.push_str(&format!("{}, ", ident));
+ }
+ fields_snippet.push_str(&last_ident.to_string());
+
+ let base_snippet = if let Some(base) = base {
+ format!(", ..{}", snippet(cx, base.span, ".."))
+ } else {
+ String::new()
+ };
+
+ let sugg = format!("{} {{ {}{} }}",
+ snippet(cx, qpath.span(), ".."),
+ fields_snippet,
+ base_snippet,
+ );
+
+ span_lint_and_sugg(
+ cx,
+ INCONSISTENT_STRUCT_CONSTRUCTOR,
+ expr.span,
+ "struct constructor field order is inconsistent with struct definition field order",
+ "try",
+ sugg,
+ Applicability::MachineApplicable,
+ )
+ }
+ }
+ }
+}
+
+// Check whether the order of the fields in the constructor is consistent with the order in the
+// definition.
+fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map: &FxHashMap<Symbol, usize>) -> bool {
+ let mut cur_idx = usize::MIN;
+ for f in fields {
+ let next_idx = def_order_map[&f.ident.name];
+ if cur_idx > next_idx {
+ return false;
+ }
+ cur_idx = next_idx;
+ }
+
+ true
+}
--- /dev/null
--- /dev/null
++use clippy_utils::consts::{constant, Constant};
++use clippy_utils::diagnostics::span_lint_and_then;
++use clippy_utils::higher::IfLet;
++use clippy_utils::ty::is_copy;
++use clippy_utils::{is_expn_of, is_lint_allowed, meets_msrv, msrvs, path_to_local};
++use if_chain::if_chain;
++use rustc_data_structures::fx::{FxHashMap, FxHashSet};
++use rustc_errors::Applicability;
++use rustc_hir as hir;
++use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
++use rustc_lint::{LateContext, LateLintPass, LintContext};
++use rustc_middle::hir::map::Map;
++use rustc_middle::ty;
++use rustc_semver::RustcVersion;
++use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::{symbol::Ident, Span};
++use std::convert::TryInto;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// The lint checks for slice bindings in patterns that are only used to
++ /// access individual slice values.
++ ///
++ /// ### Why is this bad?
++ /// Accessing slice values using indices can lead to panics. Using refutable
++ /// patterns can avoid these. Binding to individual values also improves the
++ /// readability as they can be named.
++ ///
++ /// ### Limitations
++ /// This lint currently only checks for immutable access inside `if let`
++ /// patterns.
++ ///
++ /// ### Example
++ /// ```rust
++ /// let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ ///
++ /// if let Some(slice) = slice {
++ /// println!("{}", slice[0]);
++ /// }
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ ///
++ /// if let Some(&[first, ..]) = slice {
++ /// println!("{}", first);
++ /// }
++ /// ```
++ #[clippy::version = "1.58.0"]
++ pub INDEX_REFUTABLE_SLICE,
++ nursery,
++ "avoid indexing on slices which could be destructed"
++}
++
++#[derive(Copy, Clone)]
++pub struct IndexRefutableSlice {
++ max_suggested_slice: u64,
++ msrv: Option<RustcVersion>,
++}
++
++impl IndexRefutableSlice {
++ pub fn new(max_suggested_slice_pattern_length: u64, msrv: Option<RustcVersion>) -> Self {
++ Self {
++ max_suggested_slice: max_suggested_slice_pattern_length,
++ msrv,
++ }
++ }
++}
++
++impl_lint_pass!(IndexRefutableSlice => [INDEX_REFUTABLE_SLICE]);
++
++impl LateLintPass<'_> for IndexRefutableSlice {
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
++ if_chain! {
++ if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some();
++ if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr);
++ if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id);
++ if meets_msrv(self.msrv.as_ref(), &msrvs::SLICE_PATTERNS);
++
++ let found_slices = find_slice_values(cx, let_pat);
++ if !found_slices.is_empty();
++ let filtered_slices = filter_lintable_slices(cx, found_slices, self.max_suggested_slice, if_then);
++ if !filtered_slices.is_empty();
++ then {
++ for slice in filtered_slices.values() {
++ lint_slice(cx, slice);
++ }
++ }
++ }
++ }
++
++ extract_msrv_attr!(LateContext);
++}
++
++fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxHashMap<hir::HirId, SliceLintInformation> {
++ let mut removed_pat: FxHashSet<hir::HirId> = FxHashSet::default();
++ let mut slices: FxHashMap<hir::HirId, SliceLintInformation> = FxHashMap::default();
++ pat.walk_always(|pat| {
++ if let hir::PatKind::Binding(binding, value_hir_id, ident, sub_pat) = pat.kind {
++ // We'll just ignore mut and ref mut for simplicity sake right now
++ if let hir::BindingAnnotation::Mutable | hir::BindingAnnotation::RefMut = binding {
++ return;
++ }
++
++ // This block catches bindings with sub patterns. It would be hard to build a correct suggestion
++ // for them and it's likely that the user knows what they are doing in such a case.
++ if removed_pat.contains(&value_hir_id) {
++ return;
++ }
++ if sub_pat.is_some() {
++ removed_pat.insert(value_hir_id);
++ slices.remove(&value_hir_id);
++ return;
++ }
++
++ let bound_ty = cx.typeck_results().node_type(pat.hir_id);
++ if let ty::Slice(inner_ty) | ty::Array(inner_ty, _) = bound_ty.peel_refs().kind() {
++ // The values need to use the `ref` keyword if they can't be copied.
++ // This will need to be adjusted if the lint want to support multable access in the future
++ let src_is_ref = bound_ty.is_ref() && binding != hir::BindingAnnotation::Ref;
++ let needs_ref = !(src_is_ref || is_copy(cx, inner_ty));
++
++ let slice_info = slices
++ .entry(value_hir_id)
++ .or_insert_with(|| SliceLintInformation::new(ident, needs_ref));
++ slice_info.pattern_spans.push(pat.span);
++ }
++ }
++ });
++
++ slices
++}
++
++fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
++ let used_indices = slice
++ .index_use
++ .iter()
++ .map(|(index, _)| *index)
++ .collect::<FxHashSet<_>>();
++
++ let value_name = |index| format!("{}_{}", slice.ident.name, index);
++
++ if let Some(max_index) = used_indices.iter().max() {
++ let opt_ref = if slice.needs_ref { "ref " } else { "" };
++ let pat_sugg_idents = (0..=*max_index)
++ .map(|index| {
++ if used_indices.contains(&index) {
++ format!("{}{}", opt_ref, value_name(index))
++ } else {
++ "_".to_string()
++ }
++ })
++ .collect::<Vec<_>>();
++ let pat_sugg = format!("[{}, ..]", pat_sugg_idents.join(", "));
++
++ span_lint_and_then(
++ cx,
++ INDEX_REFUTABLE_SLICE,
++ slice.ident.span,
++ "this binding can be a slice pattern to avoid indexing",
++ |diag| {
++ diag.multipart_suggestion(
++ "try using a slice pattern here",
++ slice
++ .pattern_spans
++ .iter()
++ .map(|span| (*span, pat_sugg.clone()))
++ .collect(),
++ Applicability::MaybeIncorrect,
++ );
++
++ diag.multipart_suggestion(
++ "and replace the index expressions here",
++ slice
++ .index_use
++ .iter()
++ .map(|(index, span)| (*span, value_name(*index)))
++ .collect(),
++ Applicability::MaybeIncorrect,
++ );
++
++ // The lint message doesn't contain a warning about the removed index expression,
++ // since `filter_lintable_slices` will only return slices where all access indices
++ // are known at compile time. Therefore, they can be removed without side effects.
++ },
++ );
++ }
++}
++
++#[derive(Debug)]
++struct SliceLintInformation {
++ ident: Ident,
++ needs_ref: bool,
++ pattern_spans: Vec<Span>,
++ index_use: Vec<(u64, Span)>,
++}
++
++impl SliceLintInformation {
++ fn new(ident: Ident, needs_ref: bool) -> Self {
++ Self {
++ ident,
++ needs_ref,
++ pattern_spans: Vec::new(),
++ index_use: Vec::new(),
++ }
++ }
++}
++
++fn filter_lintable_slices<'a, 'tcx>(
++ cx: &'a LateContext<'tcx>,
++ slice_lint_info: FxHashMap<hir::HirId, SliceLintInformation>,
++ max_suggested_slice: u64,
++ scope: &'tcx hir::Expr<'tcx>,
++) -> FxHashMap<hir::HirId, SliceLintInformation> {
++ let mut visitor = SliceIndexLintingVisitor {
++ cx,
++ slice_lint_info,
++ max_suggested_slice,
++ };
++
++ intravisit::walk_expr(&mut visitor, scope);
++
++ visitor.slice_lint_info
++}
++
++struct SliceIndexLintingVisitor<'a, 'tcx> {
++ cx: &'a LateContext<'tcx>,
++ slice_lint_info: FxHashMap<hir::HirId, SliceLintInformation>,
++ max_suggested_slice: u64,
++}
++
++impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
++ type Map = Map<'tcx>;
++
++ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
++ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
++ }
++
++ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
++ if let Some(local_id) = path_to_local(expr) {
++ let Self {
++ cx,
++ ref mut slice_lint_info,
++ max_suggested_slice,
++ } = *self;
++
++ if_chain! {
++ // Check if this is even a local we're interested in
++ if let Some(use_info) = slice_lint_info.get_mut(&local_id);
++
++ let map = cx.tcx.hir();
++
++ // Checking for slice indexing
++ let parent_id = map.get_parent_node(expr.hir_id);
++ if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
++ if let hir::ExprKind::Index(_, index_expr) = parent_expr.kind;
++ if let Some((Constant::Int(index_value), _)) = constant(cx, cx.typeck_results(), index_expr);
++ if let Ok(index_value) = index_value.try_into();
++ if index_value < max_suggested_slice;
++
++ // Make sure that this slice index is read only
++ let maybe_addrof_id = map.get_parent_node(parent_id);
++ if let Some(hir::Node::Expr(maybe_addrof_expr)) = map.find(maybe_addrof_id);
++ if let hir::ExprKind::AddrOf(_kind, hir::Mutability::Not, _inner_expr) = maybe_addrof_expr.kind;
++ then {
++ use_info.index_use.push((index_value, map.span(parent_expr.hir_id)));
++ return;
++ }
++ }
++
++ // The slice was used for something other than indexing
++ self.slice_lint_info.remove(&local_id);
++ }
++ intravisit::walk_expr(self, expr);
++ }
++}
--- /dev/null
+//! lint on indexing and slicing operations
+
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::higher;
+use rustc_ast::ast::RangeLimits;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for out of bounds array indexing with a constant
+ /// index.
+ ///
+ /// ### Why is this bad?
+ /// This will always panic at runtime.
+ ///
+ /// ### Known problems
+ /// Hopefully none.
+ ///
+ /// ### Example
+ /// ```no_run
+ /// # #![allow(const_err)]
+ /// let x = [1, 2, 3, 4];
+ ///
+ /// // Bad
+ /// x[9];
+ /// &x[2..9];
+ ///
+ /// // Good
+ /// x[0];
+ /// x[3];
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub OUT_OF_BOUNDS_INDEXING,
+ correctness,
+ "out of bounds constant indexing"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of indexing or slicing. Arrays are special cases, this lint
+ /// does report on arrays if we can tell that slicing operations are in bounds and does not
+ /// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint.
+ ///
+ /// ### Why is this bad?
+ /// Indexing and slicing can panic at runtime and there are
+ /// safe alternatives.
+ ///
+ /// ### Known problems
+ /// Hopefully none.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// // Vector
+ /// let x = vec![0; 5];
+ ///
+ /// // Bad
+ /// x[2];
+ /// &x[2..100];
+ /// &x[2..];
+ /// &x[..100];
+ ///
+ /// // Good
+ /// x.get(2);
+ /// x.get(2..100);
+ /// x.get(2..);
+ /// x.get(..100);
+ ///
+ /// // Array
+ /// let y = [0, 1, 2, 3];
+ ///
+ /// // Bad
+ /// &y[10..100];
+ /// &y[10..];
+ /// &y[..100];
+ ///
+ /// // Good
+ /// &y[2..];
+ /// &y[..2];
+ /// &y[0..3];
+ /// y.get(10);
+ /// y.get(10..100);
+ /// y.get(10..);
+ /// y.get(..100);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INDEXING_SLICING,
+ restriction,
+ "indexing/slicing usage"
+}
+
+declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]);
+
+impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Index(array, index) = &expr.kind {
+ let ty = cx.typeck_results().expr_ty(array).peel_refs();
+ if let Some(range) = higher::Range::hir(index) {
+ // Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..]
+ if let ty::Array(_, s) = ty.kind() {
+ let size: u128 = if let Some(size) = s.try_eval_usize(cx.tcx, cx.param_env) {
+ size.into()
+ } else {
+ return;
+ };
+
+ let const_range = to_const_range(cx, range, size);
+
+ if let (Some(start), _) = const_range {
+ if start > size {
+ span_lint(
+ cx,
+ OUT_OF_BOUNDS_INDEXING,
+ range.start.map_or(expr.span, |start| start.span),
+ "range is out of bounds",
+ );
+ return;
+ }
+ }
+
+ if let (_, Some(end)) = const_range {
+ if end > size {
+ span_lint(
+ cx,
+ OUT_OF_BOUNDS_INDEXING,
+ range.end.map_or(expr.span, |end| end.span),
+ "range is out of bounds",
+ );
+ return;
+ }
+ }
+
+ if let (Some(_), Some(_)) = const_range {
+ // early return because both start and end are constants
+ // and we have proven above that they are in bounds
+ return;
+ }
+ }
+
+ let help_msg = match (range.start, range.end) {
+ (None, Some(_)) => "consider using `.get(..n)`or `.get_mut(..n)` instead",
+ (Some(_), None) => "consider using `.get(n..)` or .get_mut(n..)` instead",
+ (Some(_), Some(_)) => "consider using `.get(n..m)` or `.get_mut(n..m)` instead",
+ (None, None) => return, // [..] is ok.
+ };
+
+ span_lint_and_help(cx, INDEXING_SLICING, expr.span, "slicing may panic", None, help_msg);
+ } else {
+ // Catchall non-range index, i.e., [n] or [n << m]
+ if let ty::Array(..) = ty.kind() {
+ // Index is a constant uint.
+ if let Some(..) = constant(cx, cx.typeck_results(), index) {
+ // Let rustc's `const_err` lint handle constant `usize` indexing on arrays.
+ return;
+ }
+ }
+
+ span_lint_and_help(
+ cx,
+ INDEXING_SLICING,
+ expr.span,
+ "indexing may panic",
+ None,
+ "consider using `.get(n)` or `.get_mut(n)` instead",
+ );
+ }
+ }
+ }
+}
+
+/// Returns a tuple of options with the start and end (exclusive) values of
+/// the range. If the start or end is not constant, None is returned.
+fn to_const_range<'tcx>(
+ cx: &LateContext<'tcx>,
+ range: higher::Range<'_>,
+ array_size: u128,
+) -> (Option<u128>, Option<u128>) {
+ let s = range
+ .start
+ .map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
+ let start = match s {
+ Some(Some(Constant::Int(x))) => Some(x),
+ Some(_) => None,
+ None => Some(0),
+ };
+
+ let e = range
+ .end
+ .map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
+ let end = match e {
+ Some(Some(Constant::Int(x))) => {
+ if range.limits == RangeLimits::Closed {
+ Some(x + 1)
+ } else {
+ Some(x)
+ }
+ },
+ Some(_) => None,
+ None => Some(array_size),
+ };
+
+ (start, end)
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{get_trait_def_id, higher, is_qpath_def_path, paths};
+use rustc_hir::{BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::{sym, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for iteration that is guaranteed to be infinite.
+ ///
+ /// ### Why is this bad?
+ /// While there may be places where this is acceptable
+ /// (e.g., in event streams), in most cases this is simply an error.
+ ///
+ /// ### Example
+ /// ```no_run
+ /// use std::iter;
+ ///
+ /// iter::repeat(1_u8).collect::<Vec<_>>();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INFINITE_ITER,
+ correctness,
+ "infinite iteration"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for iteration that may be infinite.
+ ///
+ /// ### Why is this bad?
+ /// While there may be places where this is acceptable
+ /// (e.g., in event streams), in most cases this is simply an error.
+ ///
+ /// ### Known problems
+ /// The code may have a condition to stop iteration, but
+ /// this lint is not clever enough to analyze it.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let infinite_iter = 0..;
+ /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5));
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MAYBE_INFINITE_ITER,
+ pedantic,
+ "possible infinite iteration"
+}
+
+declare_lint_pass!(InfiniteIter => [INFINITE_ITER, MAYBE_INFINITE_ITER]);
+
+impl<'tcx> LateLintPass<'tcx> for InfiniteIter {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (lint, msg) = match complete_infinite_iter(cx, expr) {
+ Infinite => (INFINITE_ITER, "infinite iteration detected"),
+ MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"),
+ Finite => {
+ return;
+ },
+ };
+ span_lint(cx, lint, expr.span, msg);
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum Finiteness {
+ Infinite,
+ MaybeInfinite,
+ Finite,
+}
+
+use self::Finiteness::{Finite, Infinite, MaybeInfinite};
+
+impl Finiteness {
+ #[must_use]
+ fn and(self, b: Self) -> Self {
+ match (self, b) {
+ (Finite, _) | (_, Finite) => Finite,
+ (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
+ _ => Infinite,
+ }
+ }
+
+ #[must_use]
+ fn or(self, b: Self) -> Self {
+ match (self, b) {
+ (Infinite, _) | (_, Infinite) => Infinite,
+ (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
+ _ => Finite,
+ }
+ }
+}
+
+impl From<bool> for Finiteness {
+ #[must_use]
+ fn from(b: bool) -> Self {
+ if b { Infinite } else { Finite }
+ }
+}
+
+/// This tells us what to look for to know if the iterator returned by
+/// this method is infinite
+#[derive(Copy, Clone)]
+enum Heuristic {
+ /// infinite no matter what
+ Always,
+ /// infinite if the first argument is
+ First,
+ /// infinite if any of the supplied arguments is
+ Any,
+ /// infinite if all of the supplied arguments are
+ All,
+}
+
+use self::Heuristic::{All, Always, Any, First};
+
+/// a slice of (method name, number of args, heuristic, bounds) tuples
+/// that will be used to determine whether the method in question
+/// returns an infinite or possibly infinite iterator. The finiteness
+/// is an upper bound, e.g., some methods can return a possibly
+/// infinite iterator at worst, e.g., `take_while`.
+const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [
+ ("zip", 2, All, Infinite),
+ ("chain", 2, Any, Infinite),
+ ("cycle", 1, Always, Infinite),
+ ("map", 2, First, Infinite),
+ ("by_ref", 1, First, Infinite),
+ ("cloned", 1, First, Infinite),
+ ("rev", 1, First, Infinite),
+ ("inspect", 1, First, Infinite),
+ ("enumerate", 1, First, Infinite),
+ ("peekable", 2, First, Infinite),
+ ("fuse", 1, First, Infinite),
+ ("skip", 2, First, Infinite),
+ ("skip_while", 1, First, Infinite),
+ ("filter", 2, First, Infinite),
+ ("filter_map", 2, First, Infinite),
+ ("flat_map", 2, First, Infinite),
+ ("unzip", 1, First, Infinite),
+ ("take_while", 2, First, MaybeInfinite),
+ ("scan", 3, First, MaybeInfinite),
+];
+
+fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
+ match expr.kind {
+ ExprKind::MethodCall(method, _, args, _) => {
+ for &(name, len, heuristic, cap) in &HEURISTICS {
+ if method.ident.name.as_str() == name && args.len() == len {
+ return (match heuristic {
+ Always => Infinite,
+ First => is_infinite(cx, &args[0]),
+ Any => is_infinite(cx, &args[0]).or(is_infinite(cx, &args[1])),
+ All => is_infinite(cx, &args[0]).and(is_infinite(cx, &args[1])),
+ })
+ .and(cap);
+ }
+ }
+ if method.ident.name == sym!(flat_map) && args.len() == 2 {
+ if let ExprKind::Closure(_, _, body_id, _, _) = args[1].kind {
+ let body = cx.tcx.hir().body(body_id);
+ return is_infinite(cx, &body.value);
+ }
+ }
+ Finite
+ },
+ ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
+ ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
+ ExprKind::Call(path, _) => {
+ if let ExprKind::Path(ref qpath) = path.kind {
+ is_qpath_def_path(cx, qpath, path.hir_id, &paths::ITER_REPEAT).into()
+ } else {
+ Finite
+ }
+ },
+ ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(),
+ _ => Finite,
+ }
+}
+
+/// the names and argument lengths of methods that *may* exhaust their
+/// iterators
+const POSSIBLY_COMPLETING_METHODS: [(&str, usize); 6] = [
+ ("find", 2),
+ ("rfind", 2),
+ ("position", 2),
+ ("rposition", 2),
+ ("any", 2),
+ ("all", 2),
+];
+
+/// the names and argument lengths of methods that *always* exhaust
+/// their iterators
+const COMPLETING_METHODS: [(&str, usize); 12] = [
+ ("count", 1),
+ ("fold", 3),
+ ("for_each", 2),
+ ("partition", 2),
+ ("max", 1),
+ ("max_by", 2),
+ ("max_by_key", 2),
+ ("min", 1),
+ ("min_by", 2),
+ ("min_by_key", 2),
+ ("sum", 1),
+ ("product", 1),
+];
+
+/// the paths of types that are known to be infinitely allocating
+const INFINITE_COLLECTORS: &[Symbol] = &[
+ sym::BinaryHeap,
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::HashMap,
+ sym::HashSet,
+ sym::LinkedList,
+ sym::Vec,
+ sym::VecDeque,
+];
+
+fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
+ match expr.kind {
+ ExprKind::MethodCall(method, _, args, _) => {
+ for &(name, len) in &COMPLETING_METHODS {
+ if method.ident.name.as_str() == name && args.len() == len {
+ return is_infinite(cx, &args[0]);
+ }
+ }
+ for &(name, len) in &POSSIBLY_COMPLETING_METHODS {
+ if method.ident.name.as_str() == name && args.len() == len {
+ return MaybeInfinite.and(is_infinite(cx, &args[0]));
+ }
+ }
+ if method.ident.name == sym!(last) && args.len() == 1 {
+ let not_double_ended = get_trait_def_id(cx, &paths::DOUBLE_ENDED_ITERATOR).map_or(false, |id| {
+ !implements_trait(cx, cx.typeck_results().expr_ty(&args[0]), id, &[])
+ });
+ if not_double_ended {
+ return is_infinite(cx, &args[0]);
+ }
+ } else if method.ident.name == sym!(collect) {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if INFINITE_COLLECTORS
+ .iter()
+ .any(|diag_item| is_type_diagnostic_item(cx, ty, *diag_item))
+ {
+ return is_infinite(cx, &args[0]);
+ }
+ }
+ },
+ ExprKind::Binary(op, l, r) => {
+ if op.node.is_comparison() {
+ return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
+ }
+ }, // TODO: ExprKind::Loop + Match
+ _ => (),
+ }
+ Finite
+}
--- /dev/null
- use clippy_utils::{in_macro, is_lint_allowed};
+//! lint on inherent implementations
+
+use clippy_utils::diagnostics::span_lint_and_note;
- (!in_macro(span) && impl_item.generics.params.is_empty() && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id))
- .then(|| span)
++use clippy_utils::is_lint_allowed;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::{def_id::LocalDefId, Item, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+use std::collections::hash_map::Entry;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for multiple inherent implementations of a struct
+ ///
+ /// ### Why is this bad?
+ /// Splitting the implementation of a type makes the code harder to navigate.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct X;
+ /// impl X {
+ /// fn one() {}
+ /// }
+ /// impl X {
+ /// fn other() {}
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// struct X;
+ /// impl X {
+ /// fn one() {}
+ /// fn other() {}
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MULTIPLE_INHERENT_IMPL,
+ restriction,
+ "Multiple inherent impl that could be grouped"
+}
+
+declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]);
+
+impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ // Map from a type to it's first impl block. Needed to distinguish generic arguments.
+ // e.g. `Foo<Bar>` and `Foo<Baz>`
+ let mut type_map = FxHashMap::default();
+ // List of spans to lint. (lint_span, first_span)
+ let mut lint_spans = Vec::new();
+
+ for (_, impl_ids) in cx
+ .tcx
+ .crate_inherent_impls(())
+ .inherent_impls
+ .iter()
+ .filter(|(&id, impls)| {
+ impls.len() > 1
+ // Check for `#[allow]` on the type definition
+ && !is_lint_allowed(
+ cx,
+ MULTIPLE_INHERENT_IMPL,
+ cx.tcx.hir().local_def_id_to_hir_id(id),
+ )
+ })
+ {
+ for impl_id in impl_ids.iter().map(|id| id.expect_local()) {
+ match type_map.entry(cx.tcx.type_of(impl_id)) {
+ Entry::Vacant(e) => {
+ // Store the id for the first impl block of this type. The span is retrieved lazily.
+ e.insert(IdOrSpan::Id(impl_id));
+ },
+ Entry::Occupied(mut e) => {
+ if let Some(span) = get_impl_span(cx, impl_id) {
+ let first_span = match *e.get() {
+ IdOrSpan::Span(s) => s,
+ IdOrSpan::Id(id) => {
+ if let Some(s) = get_impl_span(cx, id) {
+ // Remember the span of the first block.
+ *e.get_mut() = IdOrSpan::Span(s);
+ s
+ } else {
+ // The first impl block isn't considered by the lint. Replace it with the
+ // current one.
+ *e.get_mut() = IdOrSpan::Span(span);
+ continue;
+ }
+ },
+ };
+ lint_spans.push((span, first_span));
+ }
+ },
+ }
+ }
+
+ // Switching to the next type definition, no need to keep the current entries around.
+ type_map.clear();
+ }
+
+ // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first.
+ lint_spans.sort_by_key(|x| x.0.lo());
+ for (span, first_span) in lint_spans {
+ span_lint_and_note(
+ cx,
+ MULTIPLE_INHERENT_IMPL,
+ span,
+ "multiple implementations of this structure",
+ Some(first_span),
+ "first implementation here",
+ );
+ }
+ }
+}
+
+/// Gets the span for the given impl block unless it's not being considered by the lint.
+fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option<Span> {
+ let id = cx.tcx.hir().local_def_id_to_hir_id(id);
+ if let Node::Item(&Item {
+ kind: ItemKind::Impl(ref impl_item),
+ span,
+ ..
+ }) = cx.tcx.hir().get(id)
+ {
++ (!span.from_expansion()
++ && impl_item.generics.params.is_empty()
++ && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id))
++ .then(|| span)
+ } else {
+ None
+ }
+}
+
+enum IdOrSpan {
+ Id(LocalDefId),
+ Span(Span),
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use clippy_utils::{get_trait_def_id, paths, return_ty, trait_ref_of_method};
+use if_chain::if_chain;
+use rustc_hir::{ImplItem, ImplItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the definition of inherent methods with a signature of `to_string(&self) -> String`.
+ ///
+ /// ### Why is this bad?
+ /// This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred.
+ ///
+ /// ### Known problems
+ /// None
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// pub struct A;
+ ///
+ /// impl A {
+ /// pub fn to_string(&self) -> String {
+ /// "I am A".to_string()
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// // Good
+ /// use std::fmt;
+ ///
+ /// pub struct A;
+ ///
+ /// impl fmt::Display for A {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "I am A")
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.38.0"]
+ pub INHERENT_TO_STRING,
+ style,
+ "type implements inherent method `to_string()`, but should instead implement the `Display` trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the definition of inherent methods with a signature of `to_string(&self) -> String` and if the type implementing this method also implements the `Display` trait.
+ ///
+ /// ### Why is this bad?
+ /// This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`.
+ ///
+ /// ### Known problems
+ /// None
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// use std::fmt;
+ ///
+ /// pub struct A;
+ ///
+ /// impl A {
+ /// pub fn to_string(&self) -> String {
+ /// "I am A".to_string()
+ /// }
+ /// }
+ ///
+ /// impl fmt::Display for A {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "I am A, too")
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// // Good
+ /// use std::fmt;
+ ///
+ /// pub struct A;
+ ///
+ /// impl fmt::Display for A {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "I am A")
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.38.0"]
+ pub INHERENT_TO_STRING_SHADOW_DISPLAY,
+ correctness,
+ "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait"
+}
+
+declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_SHADOW_DISPLAY]);
+
+impl<'tcx> LateLintPass<'tcx> for InherentToString {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
+ if impl_item.span.from_expansion() {
+ return;
+ }
+
+ if_chain! {
+ // Check if item is a method, called to_string and has a parameter 'self'
+ if let ImplItemKind::Fn(ref signature, _) = impl_item.kind;
+ if impl_item.ident.name.as_str() == "to_string";
+ let decl = &signature.decl;
+ if decl.implicit_self.has_implicit_self();
+ if decl.inputs.len() == 1;
+ if impl_item.generics.params.is_empty();
+
+ // Check if return type is String
+ if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::String);
+
+ // Filters instances of to_string which are required by a trait
+ if trait_ref_of_method(cx, impl_item.hir_id()).is_none();
+
+ then {
+ show_lint(cx, impl_item);
+ }
+ }
+ }
+}
+
+fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
+ let display_trait_id = get_trait_def_id(cx, &paths::DISPLAY_TRAIT).expect("Failed to get trait ID of `Display`!");
+
+ // Get the real type of 'self'
+ let self_type = cx.tcx.fn_sig(item.def_id).input(0);
+ let self_type = self_type.skip_binder().peel_refs();
+
+ // Emit either a warning or an error
+ if implements_trait(cx, self_type, display_trait_id, &[]) {
+ span_lint_and_help(
+ cx,
+ INHERENT_TO_STRING_SHADOW_DISPLAY,
+ item.span,
+ &format!(
+ "type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
+ self_type
+ ),
+ None,
+ &format!("remove the inherent method from type `{}`", self_type),
+ );
+ } else {
+ span_lint_and_help(
+ cx,
+ INHERENT_TO_STRING,
+ item.span,
+ &format!(
+ "implementation of inherent method `to_string(&self) -> String` for type `{}`",
+ self_type
+ ),
+ None,
+ &format!("implement trait `Display` for type `{}` instead", self_type),
+ );
+ }
+}
--- /dev/null
+//! checks for `#[inline]` on trait methods without bodies
+
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::DiagnosticBuilderExt;
+use rustc_ast::ast::Attribute;
+use rustc_errors::Applicability;
+use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[inline]` on trait methods without bodies
+ ///
+ /// ### Why is this bad?
+ /// Only implementations of trait methods may be inlined.
+ /// The inline attribute is ignored for trait methods without bodies.
+ ///
+ /// ### Example
+ /// ```rust
+ /// trait Animal {
+ /// #[inline]
+ /// fn name(&self) -> &'static str;
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INLINE_FN_WITHOUT_BODY,
+ correctness,
+ "use of `#[inline]` on trait methods without bodies"
+}
+
+declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]);
+
+impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ check_attrs(cx, item.ident.name, attrs);
+ }
+ }
+}
+
+fn check_attrs(cx: &LateContext<'_>, name: Symbol, attrs: &[Attribute]) {
+ for attr in attrs {
+ if !attr.has_name(sym::inline) {
+ continue;
+ }
+
+ span_lint_and_then(
+ cx,
+ INLINE_FN_WITHOUT_BODY,
+ attr.span,
+ &format!("use of `#[inline]` on trait method `{}` which has no body", name),
+ |diag| {
+ diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable);
+ },
+ );
+ }
+}
--- /dev/null
+//! lint on blocks unnecessarily using >= with a + 1 or - 1
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use rustc_ast::ast::{BinOpKind, Expr, ExprKind, Lit, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `x >= y + 1` or `x - 1 >= y` (and `<=`) in a block
+ ///
+ /// ### Why is this bad?
+ /// Readability -- better to use `> y` instead of `>= y + 1`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 1;
+ /// if x >= y + 1 {}
+ /// ```
+ ///
+ /// Could be written as:
+ ///
+ /// ```rust
+ /// # let x = 1;
+ /// # let y = 1;
+ /// if x > y {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INT_PLUS_ONE,
+ complexity,
+ "instead of using `x >= y + 1`, use `x > y`"
+}
+
+declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]);
+
+// cases:
+// BinOpKind::Ge
+// x >= y + 1
+// x - 1 >= y
+//
+// BinOpKind::Le
+// x + 1 <= y
+// x <= y - 1
+
+#[derive(Copy, Clone)]
+enum Side {
+ Lhs,
+ Rhs,
+}
+
+impl IntPlusOne {
+ #[allow(clippy::cast_sign_loss)]
+ fn check_lit(lit: &Lit, target_value: i128) -> bool {
+ if let LitKind::Int(value, ..) = lit.kind {
+ return value == (target_value as u128);
+ }
+ false
+ }
+
+ fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option<String> {
+ match (binop, &lhs.kind, &rhs.kind) {
+ // case where `x - 1 >= ...` or `-1 + x >= ...`
+ (BinOpKind::Ge, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _) => {
+ match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) {
+ // `-1 + x`
+ (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
+ // `x - 1`
+ (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `... >= y + 1` or `... >= 1 + y`
+ (BinOpKind::Ge, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs))
+ if rhskind.node == BinOpKind::Add =>
+ {
+ match (&rhslhs.kind, &rhsrhs.kind) {
+ // `y + 1` and `1 + y`
+ (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
+ (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `x + 1 <= ...` or `1 + x <= ...`
+ (BinOpKind::Le, &ExprKind::Binary(ref lhskind, ref lhslhs, ref lhsrhs), _)
+ if lhskind.node == BinOpKind::Add =>
+ {
+ match (&lhslhs.kind, &lhsrhs.kind) {
+ // `1 + x` and `x + 1`
+ (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
+ },
+ (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
+ },
+ _ => None,
+ }
+ },
+ // case where `... >= y - 1` or `... >= -1 + y`
+ (BinOpKind::Le, _, &ExprKind::Binary(ref rhskind, ref rhslhs, ref rhsrhs)) => {
+ match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) {
+ // `-1 + y`
+ (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => {
+ Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
+ },
+ // `y - 1`
+ (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => {
+ Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
+ },
+ _ => None,
+ }
+ },
+ _ => None,
+ }
+ }
+
+ fn generate_recommendation(
+ cx: &EarlyContext<'_>,
+ binop: BinOpKind,
+ node: &Expr,
+ other_side: &Expr,
+ side: Side,
+ ) -> Option<String> {
+ let binop_string = match binop {
+ BinOpKind::Ge => ">",
+ BinOpKind::Le => "<",
+ _ => return None,
+ };
+ if let Some(snippet) = snippet_opt(cx, node.span) {
+ if let Some(other_side_snippet) = snippet_opt(cx, other_side.span) {
+ let rec = match side {
+ Side::Lhs => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)),
+ Side::Rhs => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)),
+ };
+ return rec;
+ }
+ }
+ None
+ }
+
+ fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) {
+ span_lint_and_sugg(
+ cx,
+ INT_PLUS_ONE,
+ block.span,
+ "unnecessary `>= y + 1` or `x - 1 >=`",
+ "change it to",
+ recommendation,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+}
+
+impl EarlyLintPass for IntPlusOne {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
+ if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
+ if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
+ Self::emit_warning(cx, item, rec);
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for division of integers
+ ///
+ /// ### Why is this bad?
+ /// When outside of some very specific algorithms,
+ /// integer division is very often a mistake because it discards the
+ /// remainder.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x = 3 / 2;
+ /// println!("{}", x);
+ ///
+ /// // Good
+ /// let x = 3f32 / 2f32;
+ /// println!("{}", x);
+ /// ```
++ #[clippy::version = "1.37.0"]
+ pub INTEGER_DIVISION,
+ restriction,
+ "integer division may cause loss of precision"
+}
+
+declare_lint_pass!(IntegerDivision => [INTEGER_DIVISION]);
+
+impl<'tcx> LateLintPass<'tcx> for IntegerDivision {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_integer_division(cx, expr) {
+ span_lint_and_help(
+ cx,
+ INTEGER_DIVISION,
+ expr.span,
+ "integer division",
+ None,
+ "division of integers may cause loss of precision. consider using floats",
+ );
+ }
+ }
+}
+
+fn is_integer_division<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) -> bool {
+ if_chain! {
+ if let hir::ExprKind::Binary(binop, left, right) = &expr.kind;
+ if binop.node == hir::BinOpKind::Div;
+ then {
+ let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right));
+ return left_ty.is_integral() && right_ty.is_integral();
+ }
+ }
+
+ false
+}
--- /dev/null
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, IntTy, UintTy};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+use clippy_utils::comparisons;
+use clippy_utils::comparisons::Rel;
+use clippy_utils::consts::{constant_full_int, FullInt};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::source::snippet;
+
+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;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INVALID_UPCAST_COMPARISONS,
+ pedantic,
+ "a comparison involving an upcast which is always true or false"
+}
+
+declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]);
+
+fn numeric_cast_precast_bounds<'a>(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<(FullInt, FullInt)> {
+ if let ExprKind::Cast(cast_exp, _) = expr.kind {
+ let pre_cast_ty = cx.typeck_results().expr_ty(cast_exp);
+ let cast_ty = cx.typeck_results().expr_ty(expr);
+ // if it's a cast from i32 to u32 wrapping will invalidate all these checks
+ if cx.layout_of(pre_cast_ty).ok().map(|l| l.size) == cx.layout_of(cast_ty).ok().map(|l| l.size) {
+ return None;
+ }
+ match pre_cast_ty.kind() {
+ ty::Int(int_ty) => Some(match int_ty {
+ IntTy::I8 => (FullInt::S(i128::from(i8::MIN)), FullInt::S(i128::from(i8::MAX))),
+ IntTy::I16 => (FullInt::S(i128::from(i16::MIN)), FullInt::S(i128::from(i16::MAX))),
+ IntTy::I32 => (FullInt::S(i128::from(i32::MIN)), FullInt::S(i128::from(i32::MAX))),
+ IntTy::I64 => (FullInt::S(i128::from(i64::MIN)), FullInt::S(i128::from(i64::MAX))),
+ IntTy::I128 => (FullInt::S(i128::MIN), FullInt::S(i128::MAX)),
+ IntTy::Isize => (FullInt::S(isize::MIN as i128), FullInt::S(isize::MAX as i128)),
+ }),
+ ty::Uint(uint_ty) => Some(match uint_ty {
+ UintTy::U8 => (FullInt::U(u128::from(u8::MIN)), FullInt::U(u128::from(u8::MAX))),
+ UintTy::U16 => (FullInt::U(u128::from(u16::MIN)), FullInt::U(u128::from(u16::MAX))),
+ UintTy::U32 => (FullInt::U(u128::from(u32::MIN)), FullInt::U(u128::from(u32::MAX))),
+ UintTy::U64 => (FullInt::U(u128::from(u64::MIN)), FullInt::U(u128::from(u64::MAX))),
+ UintTy::U128 => (FullInt::U(u128::MIN), FullInt::U(u128::MAX)),
+ UintTy::Usize => (FullInt::U(usize::MIN as u128), FullInt::U(usize::MAX as u128)),
+ }),
+ _ => None,
+ }
+ } else {
+ None
+ }
+}
+
+fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) {
+ if let ExprKind::Cast(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,
+) {
+ if let Some((lb, ub)) = lhs_bounds {
+ if let Some(norm_rhs_val) = constant_full_int(cx, cx.typeck_results(), 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, lhs, 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);
+ }
+ }
+}
--- /dev/null
+//! lint when items are used after statements
+
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast::{Block, ItemKind, StmtKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for items declared after some statement in a block.
+ ///
+ /// ### Why is this bad?
+ /// Items live for the entire scope they are declared
+ /// in. But statements are processed in order. This might cause confusion as
+ /// it's hard to figure out which item is meant in a statement.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// fn foo() {
+ /// println!("cake");
+ /// }
+ ///
+ /// fn main() {
+ /// foo(); // prints "foo"
+ /// fn foo() {
+ /// println!("foo");
+ /// }
+ /// foo(); // prints "foo"
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// // Good
+ /// fn foo() {
+ /// println!("cake");
+ /// }
+ ///
+ /// fn main() {
+ /// fn foo() {
+ /// println!("foo");
+ /// }
+ /// foo(); // prints "foo"
+ /// foo(); // prints "foo"
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ITEMS_AFTER_STATEMENTS,
+ pedantic,
+ "blocks where an item comes after a statement"
+}
+
+declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]);
+
+impl EarlyLintPass for ItemsAfterStatements {
+ fn check_block(&mut self, cx: &EarlyContext<'_>, item: &Block) {
+ if in_external_macro(cx.sess, item.span) {
+ return;
+ }
+
+ // skip initial items and trailing semicolons
+ let stmts = item
+ .stmts
+ .iter()
+ .map(|stmt| &stmt.kind)
+ .skip_while(|s| matches!(**s, StmtKind::Item(..) | StmtKind::Empty));
+
+ // lint on all further items
+ for stmt in stmts {
+ if let StmtKind::Item(ref it) = *stmt {
+ if in_external_macro(cx.sess, it.span) {
+ return;
+ }
+ if let ItemKind::MacroDef(..) = it.kind {
+ // do not lint `macro_rules`, but continue processing further statements
+ continue;
+ }
+ span_lint(
+ cx,
+ ITEMS_AFTER_STATEMENTS,
+ it.span,
+ "adding items after statements is confusing, since items exist from the \
+ start of the scope",
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::{diagnostics::span_lint, return_ty, ty::implements_trait};
+use rustc_hir::{ImplItem, ImplItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects methods named `iter` or `iter_mut` that do not have a return type that implements `Iterator`.
+ ///
+ /// ### Why is this bad?
+ /// Methods named `iter` or `iter_mut` conventionally return an `Iterator`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // `String` does not implement `Iterator`
+ /// struct Data {}
+ /// impl Data {
+ /// fn iter(&self) -> String {
+ /// todo!()
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::str::Chars;
+ /// struct Data {}
+ /// impl Data {
+ /// fn iter(&self) -> Chars<'static> {
+ /// todo!()
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub ITER_NOT_RETURNING_ITERATOR,
+ pedantic,
+ "methods named `iter` or `iter_mut` that do not return an `Iterator`"
+}
+
+declare_lint_pass!(IterNotReturningIterator => [ITER_NOT_RETURNING_ITERATOR]);
+
+impl LateLintPass<'_> for IterNotReturningIterator {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'tcx>) {
+ let name: &str = &impl_item.ident.name.as_str();
+ if_chain! {
+ if let ImplItemKind::Fn(fn_sig, _) = &impl_item.kind;
+ let ret_ty = return_ty(cx, impl_item.hir_id());
+ if matches!(name, "iter" | "iter_mut");
+ if let [param] = cx.tcx.fn_arg_names(impl_item.def_id);
+ if param.name == kw::SelfLower;
+ if let Some(iter_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+ if !implements_trait(cx, ret_ty, iter_trait_id, &[]);
+
+ then {
+ span_lint(
+ cx,
+ ITER_NOT_RETURNING_ITERATOR,
+ fn_sig.span,
+ &format!("this method is named `{}` but its return type does not implement `Iterator`", name),
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::interpret::ConstValue;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, ConstKind};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{BytePos, Pos, Span};
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for large `const` arrays that should
+ /// be defined as `static` instead.
+ ///
+ /// ### Why is this bad?
+ /// Performance: const variables are inlined upon use.
+ /// Static items result in only one instance and has a fixed location in memory.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// pub const a = [0u32; 1_000_000];
+ ///
+ /// // Good
+ /// pub static a = [0u32; 1_000_000];
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub LARGE_CONST_ARRAYS,
+ perf,
+ "large non-scalar const array may cause performance overhead"
+}
+
+pub struct LargeConstArrays {
+ maximum_allowed_size: u64,
+}
+
+impl LargeConstArrays {
+ #[must_use]
+ pub fn new(maximum_allowed_size: u64) -> Self {
+ Self { maximum_allowed_size }
+ }
+}
+
+impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]);
+
+impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if_chain! {
+ if !item.span.from_expansion();
+ if let ItemKind::Const(hir_ty, _) = &item.kind;
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ if let ty::Array(element_type, cst) = ty.kind();
+ if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val;
+ if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx);
+ if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes());
+ if self.maximum_allowed_size < element_count * element_size;
+
+ then {
+ let hi_pos = item.ident.span.lo() - BytePos::from_usize(1);
+ let sugg_span = Span::new(
+ hi_pos - BytePos::from_usize("const".len()),
+ hi_pos,
+ item.span.ctxt(),
+ item.span.parent(),
+ );
+ span_lint_and_then(
+ cx,
+ LARGE_CONST_ARRAYS,
+ item.span,
+ "large array defined as const",
+ |diag| {
+ diag.span_suggestion(
+ sugg_span,
+ "make this a static item",
+ "static".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ );
+ }
+ }
+ }
+}
--- /dev/null
+//! lint when there is a large size difference between variants on an enum
+
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for large size differences between variants on
+ /// `enum`s.
+ ///
+ /// ### Why is this bad?
+ /// Enum size is bounded by the largest variant. Having a
+ /// large variant can penalize the memory layout of that enum.
+ ///
+ /// ### Known problems
+ /// This lint obviously cannot take the distribution of
+ /// variants in your running program into account. It is possible that the
+ /// smaller variants make up less than 1% of all instances, in which case
+ /// the overhead is negligible and the boxing is counter-productive. Always
+ /// measure the change this lint suggests.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// enum Test {
+ /// A(i32),
+ /// B([i32; 8000]),
+ /// }
+ ///
+ /// // Possibly better
+ /// enum Test2 {
+ /// A(i32),
+ /// B(Box<[i32; 8000]>),
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub LARGE_ENUM_VARIANT,
+ perf,
+ "large size difference between variants on an enum"
+}
+
+#[derive(Copy, Clone)]
+pub struct LargeEnumVariant {
+ maximum_size_difference_allowed: u64,
+}
+
+impl LargeEnumVariant {
+ #[must_use]
+ pub fn new(maximum_size_difference_allowed: u64) -> Self {
+ Self {
+ maximum_size_difference_allowed,
+ }
+ }
+}
+
+struct FieldInfo {
+ ind: usize,
+ size: u64,
+}
+
+struct VariantInfo {
+ ind: usize,
+ size: u64,
+ fields_size: Vec<FieldInfo>,
+}
+
+impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
+
+impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+ if let ItemKind::Enum(ref def, _) = item.kind {
+ let ty = cx.tcx.type_of(item.def_id);
+ let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
+ if adt.variants.len() <= 1 {
+ return;
+ }
+ let mut variants_size: Vec<VariantInfo> = adt
+ .variants
+ .iter()
+ .enumerate()
+ .map(|(i, variant)| {
+ let mut fields_size = Vec::new();
+ let size: u64 = variant
+ .fields
+ .iter()
+ .enumerate()
+ .filter_map(|(i, f)| {
+ let ty = cx.tcx.type_of(f.did);
+ // don't count generics by filtering out everything
+ // that does not have a layout
+ cx.layout_of(ty).ok().map(|l| {
+ let size = l.size.bytes();
+ fields_size.push(FieldInfo { ind: i, size });
+ size
+ })
+ })
+ .sum();
+ VariantInfo {
+ ind: i,
+ size,
+ fields_size,
+ }
+ })
+ .collect();
+
+ variants_size.sort_by(|a, b| (b.size.cmp(&a.size)));
+
+ let mut difference = variants_size[0].size - variants_size[1].size;
+ if difference > self.maximum_size_difference_allowed {
+ let help_text = "consider boxing the large fields to reduce the total size of the enum";
+ span_lint_and_then(
+ cx,
+ LARGE_ENUM_VARIANT,
+ def.variants[variants_size[0].ind].span,
+ "large size difference between variants",
+ |diag| {
+ diag.span_label(
+ def.variants[variants_size[0].ind].span,
+ &format!("this variant is {} bytes", variants_size[0].size),
+ );
+ diag.span_note(
+ def.variants[variants_size[1].ind].span,
+ &format!("and the second-largest variant is {} bytes:", variants_size[1].size),
+ );
+
+ let fields = def.variants[variants_size[0].ind].data.fields();
+ variants_size[0].fields_size.sort_by(|a, b| (a.size.cmp(&b.size)));
+ let mut applicability = Applicability::MaybeIncorrect;
+ let sugg: Vec<(Span, String)> = variants_size[0]
+ .fields_size
+ .iter()
+ .rev()
+ .map_while(|val| {
+ if difference > self.maximum_size_difference_allowed {
+ difference = difference.saturating_sub(val.size);
+ Some((
+ fields[val.ind].ty.span,
+ format!(
+ "Box<{}>",
+ snippet_with_applicability(
+ cx,
+ fields[val.ind].ty.span,
+ "..",
+ &mut applicability
+ )
+ .into_owned()
+ ),
+ ))
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ if !sugg.is_empty() {
+ diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect);
+ return;
+ }
+
+ diag.span_help(def.variants[variants_size[0].ind].span, help_text);
+ },
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::interpret::ConstValue;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, ConstKind};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for local arrays that may be too large.
+ ///
+ /// ### Why is this bad?
+ /// Large local arrays may cause stack overflow.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let a = [0u32; 1_000_000];
+ /// ```
++ #[clippy::version = "1.41.0"]
+ pub LARGE_STACK_ARRAYS,
+ pedantic,
+ "allocating large arrays on stack may cause stack overflow"
+}
+
+pub struct LargeStackArrays {
+ maximum_allowed_size: u64,
+}
+
+impl LargeStackArrays {
+ #[must_use]
+ pub fn new(maximum_allowed_size: u64) -> Self {
+ Self { maximum_allowed_size }
+ }
+}
+
+impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]);
+
+impl<'tcx> LateLintPass<'tcx> for LargeStackArrays {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Repeat(_, _) = expr.kind;
+ if let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind();
+ if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val;
+ if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx);
+ if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes());
+ if self.maximum_allowed_size < element_count * element_size;
+ then {
+ span_lint_and_help(
+ cx,
+ LARGE_STACK_ARRAYS,
+ expr.span,
+ &format!(
+ "allocating a local array larger than {} bytes",
+ self.maximum_allowed_size
+ ),
+ None,
+ &format!(
+ "consider allocating on the heap with `vec!{}.into_boxed_slice()`",
+ snippet(cx, expr.span, "[...]")
+ ),
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefIdSet;
+use rustc_hir::{
+ def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
+ ItemKind, Mutability, Node, TraitItemRef, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, AssocKind, FnSig, Ty, TyS};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{
+ source_map::{Span, Spanned, Symbol},
+ symbol::sym,
+};
+
+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.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if x.len() == 0 {
+ /// ..
+ /// }
+ /// if y.len() != 0 {
+ /// ..
+ /// }
+ /// ```
+ /// instead use
+ /// ```ignore
+ /// if x.is_empty() {
+ /// ..
+ /// }
+ /// if !y.is_empty() {
+ /// ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// impl X {
+ /// pub fn len(&self) -> usize {
+ /// ..
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub LEN_WITHOUT_IS_EMPTY,
+ style,
+ "traits or impls with a public `len` method but no corresponding `is_empty` method"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for comparing to an empty slice such as `""` or `[]`,
+ /// and suggests using `.is_empty()` where applicable.
+ ///
+ /// ### Why is this bad?
+ /// Some structures can answer `.is_empty()` much faster
+ /// than checking for equality. So it is good to get into the habit of using
+ /// `.is_empty()`, and having it is cheap.
+ /// Besides, it makes the intent clearer than a manual comparison in some contexts.
+ ///
+ /// ### Example
+ ///
+ /// ```ignore
+ /// if s == "" {
+ /// ..
+ /// }
+ ///
+ /// if arr == [] {
+ /// ..
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// if s.is_empty() {
+ /// ..
+ /// }
+ ///
+ /// if arr.is_empty() {
+ /// ..
+ /// }
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub COMPARISON_TO_EMPTY,
+ style,
+ "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead"
+}
+
+declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMPTY]);
+
+impl<'tcx> LateLintPass<'tcx> for LenZero {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if item.span.from_expansion() {
+ return;
+ }
+
+ if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind {
+ check_trait_items(cx, item, trait_items);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if_chain! {
+ if item.ident.name == sym::len;
+ if let ImplItemKind::Fn(sig, _) = &item.kind;
+ if sig.decl.implicit_self.has_implicit_self();
+ if cx.access_levels.is_exported(item.def_id);
+ if matches!(sig.decl.output, FnRetTy::Return(_));
+ if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id());
+ if imp.of_trait.is_none();
+ if let TyKind::Path(ty_path) = &imp.self_ty.kind;
+ if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id();
+ if let Some(local_id) = ty_id.as_local();
+ let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
+ if !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
+ if let Some(output) = parse_len_output(cx, cx.tcx.fn_sig(item.def_id).skip_binder());
+ then {
+ let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
+ Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
+ Some(Node::Item(x)) => match x.kind {
+ ItemKind::Struct(..) => (x.ident.name, "struct"),
+ ItemKind::Enum(..) => (x.ident.name, "enum"),
+ ItemKind::Union(..) => (x.ident.name, "union"),
+ _ => (x.ident.name, "type"),
+ }
+ _ => return,
+ };
+ check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind)
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Binary(Spanned { node: cmp, .. }, left, 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: Symbol) -> bool {
+ item.ident.name == name
+ && if let AssocItemKind::Fn { has_self } = item.kind {
+ has_self && { cx.tcx.fn_sig(item.id.def_id).inputs().skip_binder().len() == 1 }
+ } else {
+ false
+ }
+ }
+
+ // fill the set with current and super traits
+ fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, 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.def_id) && trait_items.iter().any(|i| is_named_self(cx, i, sym::len))
+ {
+ let mut current_and_super_traits = DefIdSet::default();
+ fill_trait_set(visited_trait.def_id.to_def_id(), &mut current_and_super_traits, cx);
+
+ let is_empty_method_found = current_and_super_traits
+ .iter()
+ .flat_map(|&i| cx.tcx.associated_items(i).in_definition_order())
+ .any(|i| {
+ i.kind == ty::AssocKind::Fn
+ && i.fn_has_self_parameter
+ && i.ident.name == sym!(is_empty)
+ && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1
+ });
+
+ if !is_empty_method_found {
+ span_lint(
+ cx,
+ LEN_WITHOUT_IS_EMPTY,
+ visited_trait.span,
+ &format!(
+ "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
+ visited_trait.ident.name
+ ),
+ );
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum LenOutput<'tcx> {
+ Integral,
+ Option(DefId),
+ Result(DefId, Ty<'tcx>),
+}
+fn parse_len_output(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
+ match *sig.output().kind() {
+ ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
+ ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) => {
+ subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did))
+ },
+ ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did) => subs
+ .type_at(0)
+ .is_integral()
+ .then(|| LenOutput::Result(adt.did, subs.type_at(1))),
+ _ => None,
+ }
+}
+
+impl LenOutput<'_> {
+ fn matches_is_empty_output(self, ty: Ty<'_>) -> bool {
+ match (self, ty.kind()) {
+ (_, &ty::Bool) => true,
+ (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did => subs.type_at(0).is_bool(),
+ (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did => {
+ subs.type_at(0).is_bool() && TyS::same_type(subs.type_at(1), err_ty)
+ },
+ _ => false,
+ }
+ }
+
+ fn expected_sig(self, self_kind: ImplicitSelfKind) -> String {
+ let self_ref = match self_kind {
+ ImplicitSelfKind::ImmRef => "&",
+ ImplicitSelfKind::MutRef => "&mut ",
+ _ => "",
+ };
+ match self {
+ Self::Integral => format!("expected signature: `({}self) -> bool`", self_ref),
+ Self::Option(_) => format!(
+ "expected signature: `({}self) -> bool` or `({}self) -> Option<bool>",
+ self_ref, self_ref
+ ),
+ Self::Result(..) => format!(
+ "expected signature: `({}self) -> bool` or `({}self) -> Result<bool>",
+ self_ref, self_ref
+ ),
+ }
+ }
+}
+
+/// Checks if the given signature matches the expectations for `is_empty`
+fn check_is_empty_sig(sig: FnSig<'_>, self_kind: ImplicitSelfKind, len_output: LenOutput<'_>) -> bool {
+ match &**sig.inputs_and_output {
+ [arg, res] if len_output.matches_is_empty_output(res) => {
+ matches!(
+ (arg.kind(), self_kind),
+ (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
+ | (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::MutRef)
+ ) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut))
+ },
+ _ => false,
+ }
+}
+
+/// Checks if the given type has an `is_empty` method with the appropriate signature.
+fn check_for_is_empty(
+ cx: &LateContext<'_>,
+ span: Span,
+ self_kind: ImplicitSelfKind,
+ output: LenOutput<'_>,
+ impl_ty: DefId,
+ item_name: Symbol,
+ item_kind: &str,
+) {
+ let is_empty = Symbol::intern("is_empty");
+ let is_empty = cx
+ .tcx
+ .inherent_impls(impl_ty)
+ .iter()
+ .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty))
+ .find(|item| item.kind == AssocKind::Fn);
+
+ let (msg, is_empty_span, self_kind) = match is_empty {
+ None => (
+ format!(
+ "{} `{}` has a public `len` method, but no `is_empty` method",
+ item_kind,
+ item_name.as_str(),
+ ),
+ None,
+ None,
+ ),
+ Some(is_empty) if !cx.access_levels.is_exported(is_empty.def_id.expect_local()) => (
+ format!(
+ "{} `{}` has a public `len` method, but a private `is_empty` method",
+ item_kind,
+ item_name.as_str(),
+ ),
+ Some(cx.tcx.def_span(is_empty.def_id)),
+ None,
+ ),
+ Some(is_empty)
+ if !(is_empty.fn_has_self_parameter
+ && check_is_empty_sig(cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind, output)) =>
+ {
+ (
+ format!(
+ "{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
+ item_kind,
+ item_name.as_str(),
+ ),
+ Some(cx.tcx.def_span(is_empty.def_id)),
+ Some(self_kind),
+ )
+ },
+ Some(_) => return,
+ };
+
+ span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, &msg, |db| {
+ if let Some(span) = is_empty_span {
+ db.span_note(span, "`is_empty` defined here");
+ }
+ if let Some(self_kind) = self_kind {
+ db.note(&output.expected_sig(self_kind));
+ }
+ });
+}
+
+fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
+ if let (&ExprKind::MethodCall(method_path, _, args, _), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind) {
+ // check if we are in an is_empty() method
+ if let Some(name) = get_item_name(cx, method) {
+ if name.as_str() == "is_empty" {
+ return;
+ }
+ }
+
+ check_len(cx, span, method_path.ident.name, args, &lit.node, op, compare_to);
+ } else {
+ check_empty_expr(cx, span, method, lit, op);
+ }
+}
+
+fn check_len(
+ cx: &LateContext<'_>,
+ span: Span,
+ method_name: Symbol,
+ args: &[Expr<'_>],
+ lit: &LitKind,
+ op: &str,
+ compare_to: u32,
+) {
+ if let LitKind::Int(lit, _) = *lit {
+ // check if length is compared to the specified number
+ if lit != u128::from(compare_to) {
+ return;
+ }
+
+ if method_name == sym::len && args.len() == 1 && has_is_empty(cx, &args[0]) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ LEN_ZERO,
+ span,
+ &format!("length comparison to {}", if compare_to == 0 { "zero" } else { "one" }),
+ &format!("using `{}is_empty` is clearer and more explicit", op),
+ format!(
+ "{}{}.is_empty()",
+ op,
+ snippet_with_applicability(cx, args[0].span, "_", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) {
+ if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ COMPARISON_TO_EMPTY,
+ span,
+ "comparison to empty slice",
+ &format!("using `{}is_empty` is clearer and more explicit", op),
+ format!(
+ "{}{}.is_empty()",
+ op,
+ snippet_with_applicability(cx, lit1.span, "_", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
+
+fn is_empty_string(expr: &Expr<'_>) -> bool {
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ if let LitKind::Str(lit, _) = lit.node {
+ let lit = lit.as_str();
+ return lit == "";
+ }
+ }
+ false
+}
+
+fn is_empty_array(expr: &Expr<'_>) -> bool {
+ if let ExprKind::Array(arr) = expr.kind {
+ return arr.is_empty();
+ }
+ false
+}
+
+/// Checks if this type has an `is_empty` method.
+fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ /// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
+ fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
+ if item.kind == ty::AssocKind::Fn && 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
+ }
+ }
+
+ /// Checks the inherent impl's items for an `is_empty(self)` method.
+ fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool {
+ cx.tcx.inherent_impls(id).iter().any(|imp| {
+ cx.tcx
+ .associated_items(*imp)
+ .in_definition_order()
+ .any(|item| is_is_empty(cx, item))
+ })
+ }
+
+ let ty = &cx.typeck_results().expr_ty(expr).peel_refs();
+ match ty.kind() {
+ ty::Dynamic(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 clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::{path_to_local_id, visitors::is_local_used};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::BindingAnnotation;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for variable declarations immediately followed by a
+ /// conditional affectation.
+ ///
+ /// ### Why is this bad?
+ /// This is not idiomatic Rust.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let foo;
+ ///
+ /// if bar() {
+ /// foo = 42;
+ /// } else {
+ /// foo = 0;
+ /// }
+ ///
+ /// let mut baz = None;
+ ///
+ /// if bar() {
+ /// baz = Some(42);
+ /// }
+ /// ```
+ ///
+ /// should be written
+ ///
+ /// ```rust,ignore
+ /// let foo = if bar() {
+ /// 42
+ /// } else {
+ /// 0
+ /// };
+ ///
+ /// let baz = if bar() {
+ /// Some(42)
+ /// } else {
+ /// None
+ /// };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_LET_IF_SEQ,
+ nursery,
+ "unidiomatic `let mut` declaration followed by initialization in `if`"
+}
+
+declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]);
+
+impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ let mut it = block.stmts.iter().peekable();
+ while let Some(stmt) = it.next() {
+ if_chain! {
+ if let Some(expr) = it.peek();
+ if let hir::StmtKind::Local(local) = stmt.kind;
+ if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
+ if let hir::StmtKind::Expr(if_) = expr.kind;
+ if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
+ if !is_local_used(cx, *cond, canonical_id);
+ if let hir::ExprKind::Block(then, _) = then.kind;
+ if let Some(value) = check_assign(cx, canonical_id, &*then);
+ if !is_local_used(cx, value, canonical_id);
+ then {
+ let span = stmt.span.to(if_.span);
+
+ let has_interior_mutability = !cx.typeck_results().node_type(canonical_id).is_freeze(
+ cx.tcx.at(span),
+ cx.param_env,
+ );
+ if has_interior_mutability { return; }
+
+ let (default_multi_stmts, default) = if let Some(else_) = else_ {
+ if let hir::ExprKind::Block(else_, _) = else_.kind {
+ if let Some(default) = check_assign(cx, canonical_id, else_) {
+ (else_.stmts.len() > 1, default)
+ } else if let Some(default) = local.init {
+ (true, default)
+ } else {
+ continue;
+ }
+ } else {
+ continue;
+ }
+ } else if let Some(default) = local.init {
+ (false, default)
+ } else {
+ continue;
+ };
+
+ let mutability = match mode {
+ BindingAnnotation::RefMut | BindingAnnotation::Mutable => "<mut> ",
+ _ => "",
+ };
+
+ // FIXME: this should not suggest `mut` if we can detect that the variable is not
+ // use mutably after the `if`
+
+ let sug = format!(
+ "let {mut}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};",
+ mut=mutability,
+ name=ident.name,
+ cond=snippet(cx, cond.span, "_"),
+ then=if then.stmts.len() > 1 { " ..;" } else { "" },
+ else=if default_multi_stmts { " ..;" } else { "" },
+ value=snippet(cx, value.span, "<value>"),
+ default=snippet(cx, default.span, "<default>"),
+ );
+ span_lint_and_then(cx,
+ USELESS_LET_IF_SEQ,
+ span,
+ "`if _ { .. } else { .. }` is an expression",
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "it is more idiomatic to write",
+ sug,
+ Applicability::HasPlaceholders,
+ );
+ if !mutability.is_empty() {
+ diag.note("you might not need `mut` at all");
+ }
+ });
+ }
+ }
+ }
+ }
+}
+
+fn check_assign<'tcx>(
+ cx: &LateContext<'tcx>,
+ decl: hir::HirId,
+ block: &'tcx hir::Block<'_>,
+) -> Option<&'tcx hir::Expr<'tcx>> {
+ if_chain! {
+ if block.expr.is_none();
+ if let Some(expr) = block.stmts.iter().last();
+ if let hir::StmtKind::Semi(expr) = expr.kind;
+ if let hir::ExprKind::Assign(var, value, _) = expr.kind;
+ if path_to_local_id(var, decl);
+ then {
+ if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
+ None
+ } else {
+ Some(value)
+ }
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
- /// Checks for `let _ = sync_lock`
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::{is_must_use_ty, match_type};
+use clippy_utils::{is_must_use_func_call, paths};
+use if_chain::if_chain;
+use rustc_hir::{Local, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `let _ = <expr>` where expr is `#[must_use]`
+ ///
+ /// ### Why is this bad?
+ /// It's better to explicitly handle the value of a `#[must_use]`
+ /// expr
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn f() -> Result<u32, u32> {
+ /// Ok(0)
+ /// }
+ ///
+ /// let _ = f();
+ /// // is_ok() is marked #[must_use]
+ /// let _ = f().is_ok();
+ /// ```
++ #[clippy::version = "1.42.0"]
+ pub LET_UNDERSCORE_MUST_USE,
+ restriction,
+ "non-binding let on a `#[must_use]` expression"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
- const SYNC_GUARD_PATHS: [&[&str]; 3] = [
++ /// Checks for `let _ = sync_lock`.
++ /// This supports `mutex` and `rwlock` in `std::sync` and `parking_lot`.
+ ///
+ /// ### Why is this bad?
+ /// This statement immediately drops the lock instead of
+ /// extending its lifetime to the end of the scope, which is often not intended.
+ /// To extend lock lifetime to the end of the scope, use an underscore-prefixed
+ /// name instead (i.e. _lock). If you want to explicitly drop the lock,
+ /// `std::mem::drop` conveys your intention better and is less error-prone.
+ ///
+ /// ### Example
+ ///
+ /// Bad:
+ /// ```rust,ignore
+ /// let _ = mutex.lock();
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// let _lock = mutex.lock();
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub LET_UNDERSCORE_LOCK,
+ correctness,
+ "non-binding let on a synchronization lock"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `let _ = <expr>`
+ /// where expr has a type that implements `Drop`
+ ///
+ /// ### Why is this bad?
+ /// This statement immediately drops the initializer
+ /// expression instead of extending its lifetime to the end of the scope, which
+ /// is often not intended. To extend the expression's lifetime to the end of the
+ /// scope, use an underscore-prefixed name instead (i.e. _var). If you want to
+ /// explicitly drop the expression, `std::mem::drop` conveys your intention
+ /// better and is less error-prone.
+ ///
+ /// ### Example
+ ///
+ /// Bad:
+ /// ```rust,ignore
+ /// struct Droppable;
+ /// impl Drop for Droppable {
+ /// fn drop(&mut self) {}
+ /// }
+ /// {
+ /// let _ = Droppable;
+ /// // ^ dropped here
+ /// /* more code */
+ /// }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// {
+ /// let _droppable = Droppable;
+ /// /* more code */
+ /// // dropped at end of scope
+ /// }
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub LET_UNDERSCORE_DROP,
+ pedantic,
+ "non-binding let on a type that implements `Drop`"
+}
+
+declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_DROP]);
+
++const SYNC_GUARD_PATHS: [&[&str]; 5] = [
+ &paths::MUTEX_GUARD,
+ &paths::RWLOCK_READ_GUARD,
+ &paths::RWLOCK_WRITE_GUARD,
++ &paths::PARKING_LOT_RAWMUTEX,
++ &paths::PARKING_LOT_RAWRWLOCK,
+];
+
+impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
+ if in_external_macro(cx.tcx.sess, local.span) {
+ return;
+ }
+
+ if_chain! {
+ if let PatKind::Wild = local.pat.kind;
+ if let Some(init) = local.init;
+ then {
+ let init_ty = cx.typeck_results().expr_ty(init);
+ let contains_sync_guard = init_ty.walk(cx.tcx).any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => {
+ SYNC_GUARD_PATHS.iter().any(|path| match_type(cx, inner_ty, path))
+ },
+
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ });
+ if contains_sync_guard {
+ span_lint_and_help(
+ cx,
+ LET_UNDERSCORE_LOCK,
+ local.span,
+ "non-binding let on a synchronization lock",
+ None,
+ "consider using an underscore-prefixed named \
+ binding or dropping explicitly with `std::mem::drop`"
+ );
+ } else if init_ty.needs_drop(cx.tcx, cx.param_env) {
+ span_lint_and_help(
+ cx,
+ LET_UNDERSCORE_DROP,
+ local.span,
+ "non-binding `let` on a type that implements `Drop`",
+ None,
+ "consider using an underscore-prefixed named \
+ binding or dropping explicitly with `std::mem::drop`"
+ );
+ } else if is_must_use_ty(cx, cx.typeck_results().expr_ty(init)) {
+ span_lint_and_help(
+ cx,
+ LET_UNDERSCORE_MUST_USE,
+ local.span,
+ "non-binding let on an expression with `#[must_use]` type",
+ None,
+ "consider explicitly using expression value"
+ );
+ } else if is_must_use_func_call(cx, init) {
+ span_lint_and_help(
+ cx,
+ LET_UNDERSCORE_MUST_USE,
+ local.span,
+ "non-binding let on a result of a `#[must_use]` function",
+ None,
+ "consider explicitly using function result"
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- LintId::of(needless_borrow::NEEDLESS_BORROW),
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::all", Some("clippy_all"), vec![
+ LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
+ LintId::of(approx_const::APPROX_CONSTANT),
+ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
+ LintId::of(assign_ops::ASSIGN_OP_PATTERN),
+ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC),
+ LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(attrs::DEPRECATED_SEMVER),
+ LintId::of(attrs::MISMATCHED_TARGET_OS),
+ LintId::of(attrs::USELESS_ATTRIBUTE),
+ LintId::of(bit_mask::BAD_BIT_MASK),
+ LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
+ LintId::of(blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(booleans::LOGIC_BUG),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(casts::CAST_REF_TO_MUT),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(copies::IFS_SAME_COND),
+ LintId::of(copies::IF_SAME_THEN_ELSE),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
++ LintId::of(dereference::NEEDLESS_BORROW),
+ LintId::of(derivable_impls::DERIVABLE_IMPLS),
+ LintId::of(derive::DERIVE_HASH_XOR_EQ),
+ LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
+ LintId::of(doc::MISSING_SAFETY_DOC),
+ LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
+ LintId::of(double_comparison::DOUBLE_COMPARISONS),
+ LintId::of(double_parens::DOUBLE_PARENS),
+ LintId::of(drop_forget_ref::DROP_COPY),
+ LintId::of(drop_forget_ref::DROP_REF),
+ LintId::of(drop_forget_ref::FORGET_COPY),
+ LintId::of(drop_forget_ref::FORGET_REF),
+ LintId::of(duration_subsec::DURATION_SUBSEC),
+ LintId::of(entry::MAP_ENTRY),
+ LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
+ LintId::of(enum_variants::ENUM_VARIANT_NAMES),
+ LintId::of(enum_variants::MODULE_INCEPTION),
+ LintId::of(eq_op::EQ_OP),
+ LintId::of(eq_op::OP_REF),
+ LintId::of(erasing_op::ERASING_OP),
+ LintId::of(escape::BOXED_LOCAL),
+ LintId::of(eta_reduction::REDUNDANT_CLOSURE),
+ LintId::of(eval_order_dependence::DIVERGING_SUB_EXPRESSION),
+ LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+ LintId::of(explicit_write::EXPLICIT_WRITE),
+ LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
+ LintId::of(float_literal::EXCESSIVE_PRECISION),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
+ LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
+ LintId::of(formatting::POSSIBLE_MISSING_COMMA),
+ LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(from_over_into::FROM_OVER_INTO),
+ LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
+ LintId::of(functions::DOUBLE_MUST_USE),
+ LintId::of(functions::MUST_USE_UNIT),
+ LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
+ LintId::of(functions::RESULT_UNIT_ERR),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(identity_op::IDENTITY_OP),
+ LintId::of(if_let_mutex::IF_LET_MUTEX),
+ LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
+ LintId::of(infinite_iter::INFINITE_ITER),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
+ LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
+ LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::EMPTY_LOOP),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(loops::ITER_NEXT_LOOP),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::MANUAL_MEMCPY),
+ LintId::of(loops::MUT_RANGE_BOUND),
+ LintId::of(loops::NEEDLESS_COLLECT),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::NEVER_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
+ LintId::of(loops::WHILE_LET_LOOP),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_map::MANUAL_MAP),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
+ LintId::of(map_clone::MAP_CLONE),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(match_result_ok::MATCH_RESULT_OK),
+ LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
+ 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_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::CLONE_DOUBLE_REF),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::EXPECT_FUN_CALL),
+ LintId::of(methods::EXTEND_WITH_DRAIN),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::ITERATOR_STEP_BY_ZERO),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MANUAL_SPLIT_ONCE),
+ LintId::of(methods::MANUAL_STR_REPEAT),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
++ LintId::of(methods::MAP_FLATTEN),
+ LintId::of(methods::MAP_IDENTITY),
++ LintId::of(methods::NEEDLESS_SPLITN),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::OR_FUN_CALL),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::SINGLE_CHAR_PATTERN),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(methods::SUSPICIOUS_SPLITN),
+ LintId::of(methods::UNINIT_ASSUMED_INIT),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
+ 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::MODULO_ONE),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
+ LintId::of(mutex_atomic::MUTEX_ATOMIC),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
++ LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
+ LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
+ LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
+ LintId::of(needless_update::NEEDLESS_UPDATE),
+ LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
+ LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY),
++ LintId::of(octal_escapes::OCTAL_ESCAPES),
+ LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
+ LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::INVALID_NULL_PTR_USAGE),
+ LintId::of(ptr::MUT_FROM_REF),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(ptr_eq::PTR_EQ),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(ranges::REVERSED_EMPTY_RANGES),
+ LintId::of(redundant_clone::REDUNDANT_CLONE),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(reference::REF_IN_DEREF),
+ LintId::of(regex::INVALID_REGEX),
+ LintId::of(repeat_once::REPEAT_ONCE),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(self_assignment::SELF_ASSIGNMENT),
+ LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
+ LintId::of(serde_api::SERDE_API_MISUSE),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
+ LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
+ LintId::of(stable_sort_primitive::STABLE_SORT_PRIMITIVE),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+ LintId::of(swap::ALMOST_SWAPPED),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
+ 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::BORROWED_BOX),
+ LintId::of(types::BOX_COLLECTION),
+ LintId::of(types::REDUNDANT_ALLOCATION),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
+ LintId::of(unicode::INVISIBLE_CHARACTERS),
+ LintId::of(uninit_vec::UNINIT_VEC),
+ LintId::of(unit_hash::UNIT_HASH),
+ LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
+ LintId::of(unit_types::UNIT_ARG),
+ LintId::of(unit_types::UNIT_CMP),
+ LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),
+ LintId::of(unnamed_address::VTABLE_ADDRESS_COMPARISONS),
+ LintId::of(unnecessary_sort_by::UNNECESSARY_SORT_BY),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_io_amount::UNUSED_IO_AMOUNT),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(unwrap::PANICKING_UNWRAP),
+ LintId::of(unwrap::UNNECESSARY_UNWRAP),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(useless_conversion::USELESS_CONVERSION),
+ LintId::of(vec::USELESS_VEC),
+ LintId::of(vec_init_then_push::VEC_INIT_THEN_PUSH),
+ LintId::of(vec_resize_to_zero::VEC_RESIZE_TO_ZERO),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+ LintId::of(zero_div_zero::ZERO_DIVIDED_BY_ZERO),
+])
--- /dev/null
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![
+ LintId::of(attrs::DEPRECATED_CFG_ATTR),
+ LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(casts::CHAR_LIT_AS_U8),
+ LintId::of(casts::UNNECESSARY_CAST),
+ LintId::of(derivable_impls::DERIVABLE_IMPLS),
+ 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(explicit_write::EXPLICIT_WRITE),
+ LintId::of(format::USELESS_FORMAT),
+ LintId::of(functions::TOO_MANY_ARGUMENTS),
+ LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
+ LintId::of(identity_op::IDENTITY_OP),
+ LintId::of(int_plus_one::INT_PLUS_ONE),
+ LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
+ LintId::of(lifetimes::NEEDLESS_LIFETIMES),
+ LintId::of(loops::EXPLICIT_COUNTER_LOOP),
+ LintId::of(loops::MANUAL_FLATTEN),
+ LintId::of(loops::SINGLE_ELEMENT_LOOP),
+ LintId::of(loops::WHILE_LET_LOOP),
+ LintId::of(manual_strip::MANUAL_STRIP),
+ LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
+ LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
+ LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
+ LintId::of(matches::MATCH_AS_REF),
+ LintId::of(matches::MATCH_SINGLE_BINDING),
+ LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
+ LintId::of(methods::BIND_INSTEAD_OF_MAP),
+ LintId::of(methods::CLONE_ON_COPY),
+ LintId::of(methods::FILTER_MAP_IDENTITY),
+ LintId::of(methods::FILTER_NEXT),
+ LintId::of(methods::FLAT_MAP_IDENTITY),
+ LintId::of(methods::INSPECT_FOR_EACH),
+ LintId::of(methods::ITER_COUNT),
+ LintId::of(methods::MANUAL_FILTER_MAP),
+ LintId::of(methods::MANUAL_FIND_MAP),
+ LintId::of(methods::MANUAL_SPLIT_ONCE),
++ LintId::of(methods::MAP_FLATTEN),
+ LintId::of(methods::MAP_IDENTITY),
++ LintId::of(methods::NEEDLESS_SPLITN),
+ LintId::of(methods::OPTION_AS_REF_DEREF),
+ LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::SEARCH_IS_SOME),
+ LintId::of(methods::SKIP_WHILE_NEXT),
+ LintId::of(methods::UNNECESSARY_FILTER_MAP),
+ LintId::of(methods::USELESS_ASREF),
+ LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
+ LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
+ LintId::of(misc_early::ZERO_PREFIXED_LITERAL),
+ LintId::of(needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE),
+ LintId::of(needless_bool::BOOL_COMPARISON),
+ LintId::of(needless_bool::NEEDLESS_BOOL),
+ LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
+ LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
+ LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
+ LintId::of(needless_update::NEEDLESS_UPDATE),
+ LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
+ LintId::of(no_effect::NO_EFFECT),
+ LintId::of(no_effect::UNNECESSARY_OPERATION),
+ LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
+ LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
+ LintId::of(precedence::PRECEDENCE),
+ LintId::of(ptr_offset_with_cast::PTR_OFFSET_WITH_CAST),
+ LintId::of(ranges::RANGE_ZIP_WITH_LEN),
+ LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
+ LintId::of(redundant_slicing::REDUNDANT_SLICING),
+ LintId::of(reference::DEREF_ADDROF),
+ LintId::of(reference::REF_IN_DEREF),
+ LintId::of(repeat_once::REPEAT_ONCE),
+ LintId::of(strings::STRING_FROM_UTF8_AS_BYTES),
+ LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS),
+ LintId::of(swap::MANUAL_SWAP),
+ LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
+ LintId::of(transmute::CROSSPOINTER_TRANSMUTE),
+ LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
+ LintId::of(transmute::TRANSMUTE_BYTES_TO_STR),
+ LintId::of(transmute::TRANSMUTE_FLOAT_TO_INT),
+ LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
+ LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
+ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
+ LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
+ LintId::of(types::BORROWED_BOX),
+ LintId::of(types::TYPE_COMPLEXITY),
+ LintId::of(types::VEC_BOX),
+ LintId::of(unit_types::UNIT_ARG),
+ 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),
+])
--- /dev/null
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+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::IF_CHAIN_STYLE),
+ LintId::of(utils::internal_lints::INTERNING_DEFINED_SYMBOL),
++ LintId::of(utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE),
+ LintId::of(utils::internal_lints::INVALID_PATHS),
+ LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
+ LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
++ LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE),
+ LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
+ LintId::of(utils::internal_lints::PRODUCE_ICE),
+ LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),
+])
--- /dev/null
- disallowed_method::DISALLOWED_METHOD,
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_lints(&[
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::CLIPPY_LINTS_INTERNAL,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::COMPILER_LINT_FUNCTIONS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::DEFAULT_LINT,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::IF_CHAIN_STYLE,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::INTERNING_DEFINED_SYMBOL,
+ #[cfg(feature = "internal-lints")]
++ utils::internal_lints::INVALID_CLIPPY_VERSION_ATTRIBUTE,
++ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::INVALID_PATHS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::LINT_WITHOUT_LINT_PASS,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
+ #[cfg(feature = "internal-lints")]
++ utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
++ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::OUTER_EXPN_EXPN_DATA,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::PRODUCE_ICE,
+ #[cfg(feature = "internal-lints")]
+ utils::internal_lints::UNNECESSARY_SYMBOL_STR,
+ absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS,
+ approx_const::APPROX_CONSTANT,
+ arithmetic::FLOAT_ARITHMETIC,
+ arithmetic::INTEGER_ARITHMETIC,
+ as_conversions::AS_CONVERSIONS,
+ asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,
+ asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX,
+ assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
+ assign_ops::ASSIGN_OP_PATTERN,
+ assign_ops::MISREFACTORED_ASSIGN_OP,
+ async_yields_async::ASYNC_YIELDS_ASYNC,
+ attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
+ attrs::DEPRECATED_CFG_ATTR,
+ attrs::DEPRECATED_SEMVER,
+ attrs::EMPTY_LINE_AFTER_OUTER_ATTR,
+ attrs::INLINE_ALWAYS,
+ attrs::MISMATCHED_TARGET_OS,
+ attrs::USELESS_ATTRIBUTE,
+ await_holding_invalid::AWAIT_HOLDING_LOCK,
+ await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
+ bit_mask::BAD_BIT_MASK,
+ bit_mask::INEFFECTIVE_BIT_MASK,
+ bit_mask::VERBOSE_BIT_MASK,
+ blacklisted_name::BLACKLISTED_NAME,
+ blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
+ bool_assert_comparison::BOOL_ASSERT_COMPARISON,
+ booleans::LOGIC_BUG,
+ booleans::NONMINIMAL_BOOL,
+ bytecount::NAIVE_BYTECOUNT,
+ cargo_common_metadata::CARGO_COMMON_METADATA,
+ case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ casts::CAST_LOSSLESS,
+ casts::CAST_POSSIBLE_TRUNCATION,
+ casts::CAST_POSSIBLE_WRAP,
+ casts::CAST_PRECISION_LOSS,
+ casts::CAST_PTR_ALIGNMENT,
+ casts::CAST_REF_TO_MUT,
+ casts::CAST_SIGN_LOSS,
+ casts::CHAR_LIT_AS_U8,
+ casts::FN_TO_NUMERIC_CAST,
+ casts::FN_TO_NUMERIC_CAST_ANY,
+ casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
+ casts::PTR_AS_PTR,
+ casts::UNNECESSARY_CAST,
+ checked_conversions::CHECKED_CONVERSIONS,
+ cognitive_complexity::COGNITIVE_COMPLEXITY,
+ collapsible_if::COLLAPSIBLE_ELSE_IF,
+ collapsible_if::COLLAPSIBLE_IF,
+ collapsible_match::COLLAPSIBLE_MATCH,
+ comparison_chain::COMPARISON_CHAIN,
+ copies::BRANCHES_SHARING_CODE,
+ copies::IFS_SAME_COND,
+ copies::IF_SAME_THEN_ELSE,
+ copies::SAME_FUNCTIONS_IN_IF_CONDITION,
+ copy_iterator::COPY_ITERATOR,
+ create_dir::CREATE_DIR,
+ dbg_macro::DBG_MACRO,
+ default::DEFAULT_TRAIT_ACCESS,
+ default::FIELD_REASSIGN_WITH_DEFAULT,
+ default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
+ dereference::EXPLICIT_DEREF_METHODS,
++ dereference::NEEDLESS_BORROW,
++ dereference::REF_BINDING_TO_REFERENCE,
+ derivable_impls::DERIVABLE_IMPLS,
+ derive::DERIVE_HASH_XOR_EQ,
+ derive::DERIVE_ORD_XOR_PARTIAL_ORD,
+ derive::EXPL_IMPL_CLONE_ON_COPY,
+ derive::UNSAFE_DERIVE_DESERIALIZE,
- disallowed_type::DISALLOWED_TYPE,
++ disallowed_methods::DISALLOWED_METHODS,
+ disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS,
- needless_borrow::NEEDLESS_BORROW,
- needless_borrow::REF_BINDING_TO_REFERENCE,
++ disallowed_types::DISALLOWED_TYPES,
+ doc::DOC_MARKDOWN,
+ doc::MISSING_ERRORS_DOC,
+ doc::MISSING_PANICS_DOC,
+ doc::MISSING_SAFETY_DOC,
+ doc::NEEDLESS_DOCTEST_MAIN,
+ double_comparison::DOUBLE_COMPARISONS,
+ double_parens::DOUBLE_PARENS,
+ drop_forget_ref::DROP_COPY,
+ drop_forget_ref::DROP_REF,
+ drop_forget_ref::FORGET_COPY,
+ drop_forget_ref::FORGET_REF,
+ duration_subsec::DURATION_SUBSEC,
+ else_if_without_else::ELSE_IF_WITHOUT_ELSE,
+ empty_enum::EMPTY_ENUM,
+ entry::MAP_ENTRY,
+ enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
+ enum_variants::ENUM_VARIANT_NAMES,
+ enum_variants::MODULE_INCEPTION,
+ enum_variants::MODULE_NAME_REPETITIONS,
+ eq_op::EQ_OP,
+ eq_op::OP_REF,
+ equatable_if_let::EQUATABLE_IF_LET,
+ erasing_op::ERASING_OP,
+ escape::BOXED_LOCAL,
+ eta_reduction::REDUNDANT_CLOSURE,
+ eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
+ eval_order_dependence::DIVERGING_SUB_EXPRESSION,
+ eval_order_dependence::EVAL_ORDER_DEPENDENCE,
+ excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS,
+ excessive_bools::STRUCT_EXCESSIVE_BOOLS,
+ exhaustive_items::EXHAUSTIVE_ENUMS,
+ exhaustive_items::EXHAUSTIVE_STRUCTS,
+ exit::EXIT,
+ explicit_write::EXPLICIT_WRITE,
+ fallible_impl_from::FALLIBLE_IMPL_FROM,
+ feature_name::NEGATIVE_FEATURE_NAMES,
+ feature_name::REDUNDANT_FEATURE_NAMES,
+ float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
+ float_literal::EXCESSIVE_PRECISION,
+ float_literal::LOSSY_FLOAT_LITERAL,
+ floating_point_arithmetic::IMPRECISE_FLOPS,
+ floating_point_arithmetic::SUBOPTIMAL_FLOPS,
+ format::USELESS_FORMAT,
+ format_args::FORMAT_IN_FORMAT_ARGS,
+ format_args::TO_STRING_IN_FORMAT_ARGS,
+ formatting::POSSIBLE_MISSING_COMMA,
+ formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
+ formatting::SUSPICIOUS_ELSE_FORMATTING,
+ formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
+ from_over_into::FROM_OVER_INTO,
+ from_str_radix_10::FROM_STR_RADIX_10,
+ functions::DOUBLE_MUST_USE,
+ functions::MUST_USE_CANDIDATE,
+ functions::MUST_USE_UNIT,
+ functions::NOT_UNSAFE_PTR_ARG_DEREF,
+ functions::RESULT_UNIT_ERR,
+ functions::TOO_MANY_ARGUMENTS,
+ functions::TOO_MANY_LINES,
+ future_not_send::FUTURE_NOT_SEND,
+ get_last_with_len::GET_LAST_WITH_LEN,
+ identity_op::IDENTITY_OP,
+ if_let_mutex::IF_LET_MUTEX,
+ if_not_else::IF_NOT_ELSE,
+ if_then_some_else_none::IF_THEN_SOME_ELSE_NONE,
+ implicit_hasher::IMPLICIT_HASHER,
+ implicit_return::IMPLICIT_RETURN,
+ implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
+ inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR,
++ index_refutable_slice::INDEX_REFUTABLE_SLICE,
+ 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,
+ invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
+ items_after_statements::ITEMS_AFTER_STATEMENTS,
+ iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
+ large_const_arrays::LARGE_CONST_ARRAYS,
+ large_enum_variant::LARGE_ENUM_VARIANT,
+ large_stack_arrays::LARGE_STACK_ARRAYS,
+ len_zero::COMPARISON_TO_EMPTY,
+ len_zero::LEN_WITHOUT_IS_EMPTY,
+ len_zero::LEN_ZERO,
+ let_if_seq::USELESS_LET_IF_SEQ,
+ let_underscore::LET_UNDERSCORE_DROP,
+ let_underscore::LET_UNDERSCORE_LOCK,
+ let_underscore::LET_UNDERSCORE_MUST_USE,
+ lifetimes::EXTRA_UNUSED_LIFETIMES,
+ lifetimes::NEEDLESS_LIFETIMES,
+ literal_representation::DECIMAL_LITERAL_REPRESENTATION,
+ literal_representation::INCONSISTENT_DIGIT_GROUPING,
+ literal_representation::LARGE_DIGIT_GROUPS,
+ literal_representation::MISTYPED_LITERAL_SUFFIXES,
+ literal_representation::UNREADABLE_LITERAL,
+ literal_representation::UNUSUAL_BYTE_GROUPINGS,
+ loops::EMPTY_LOOP,
+ loops::EXPLICIT_COUNTER_LOOP,
+ loops::EXPLICIT_INTO_ITER_LOOP,
+ loops::EXPLICIT_ITER_LOOP,
+ loops::FOR_KV_MAP,
+ loops::FOR_LOOPS_OVER_FALLIBLES,
+ loops::ITER_NEXT_LOOP,
+ loops::MANUAL_FLATTEN,
+ loops::MANUAL_MEMCPY,
+ loops::MUT_RANGE_BOUND,
+ loops::NEEDLESS_COLLECT,
+ loops::NEEDLESS_RANGE_LOOP,
+ loops::NEVER_LOOP,
+ loops::SAME_ITEM_PUSH,
+ loops::SINGLE_ELEMENT_LOOP,
+ loops::WHILE_IMMUTABLE_CONDITION,
+ loops::WHILE_LET_LOOP,
+ loops::WHILE_LET_ON_ITERATOR,
+ macro_use::MACRO_USE_IMPORTS,
+ main_recursion::MAIN_RECURSION,
+ manual_assert::MANUAL_ASSERT,
+ manual_async_fn::MANUAL_ASYNC_FN,
+ manual_map::MANUAL_MAP,
+ manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
+ manual_ok_or::MANUAL_OK_OR,
+ manual_strip::MANUAL_STRIP,
+ manual_unwrap_or::MANUAL_UNWRAP_OR,
+ map_clone::MAP_CLONE,
+ map_err_ignore::MAP_ERR_IGNORE,
+ map_unit_fn::OPTION_MAP_UNIT_FN,
+ map_unit_fn::RESULT_MAP_UNIT_FN,
+ match_on_vec_items::MATCH_ON_VEC_ITEMS,
+ match_result_ok::MATCH_RESULT_OK,
+ match_str_case_mismatch::MATCH_STR_CASE_MISMATCH,
+ matches::INFALLIBLE_DESTRUCTURING_MATCH,
+ matches::MATCH_AS_REF,
+ matches::MATCH_BOOL,
+ matches::MATCH_LIKE_MATCHES_MACRO,
+ matches::MATCH_OVERLAPPING_ARM,
+ matches::MATCH_REF_PATS,
+ matches::MATCH_SAME_ARMS,
+ matches::MATCH_SINGLE_BINDING,
+ matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ matches::MATCH_WILD_ERR_ARM,
+ matches::REDUNDANT_PATTERN_MATCHING,
+ matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ matches::SINGLE_MATCH,
+ matches::SINGLE_MATCH_ELSE,
+ matches::WILDCARD_ENUM_MATCH_ARM,
+ matches::WILDCARD_IN_OR_PATTERNS,
+ mem_forget::MEM_FORGET,
+ mem_replace::MEM_REPLACE_OPTION_WITH_NONE,
+ mem_replace::MEM_REPLACE_WITH_DEFAULT,
+ mem_replace::MEM_REPLACE_WITH_UNINIT,
+ methods::BIND_INSTEAD_OF_MAP,
+ methods::BYTES_NTH,
+ methods::CHARS_LAST_CMP,
+ methods::CHARS_NEXT_CMP,
+ methods::CLONED_INSTEAD_OF_COPIED,
+ methods::CLONE_DOUBLE_REF,
+ methods::CLONE_ON_COPY,
+ methods::CLONE_ON_REF_PTR,
+ methods::EXPECT_FUN_CALL,
+ methods::EXPECT_USED,
+ methods::EXTEND_WITH_DRAIN,
+ methods::FILETYPE_IS_FILE,
+ methods::FILTER_MAP_IDENTITY,
+ methods::FILTER_MAP_NEXT,
+ methods::FILTER_NEXT,
+ methods::FLAT_MAP_IDENTITY,
+ methods::FLAT_MAP_OPTION,
+ methods::FROM_ITER_INSTEAD_OF_COLLECT,
+ methods::GET_UNWRAP,
+ methods::IMPLICIT_CLONE,
+ methods::INEFFICIENT_TO_STRING,
+ methods::INSPECT_FOR_EACH,
+ methods::INTO_ITER_ON_REF,
+ methods::ITERATOR_STEP_BY_ZERO,
+ methods::ITER_CLONED_COLLECT,
+ methods::ITER_COUNT,
+ methods::ITER_NEXT_SLICE,
+ methods::ITER_NTH,
+ methods::ITER_NTH_ZERO,
+ methods::ITER_SKIP_NEXT,
+ methods::MANUAL_FILTER_MAP,
+ methods::MANUAL_FIND_MAP,
+ methods::MANUAL_SATURATING_ARITHMETIC,
+ methods::MANUAL_SPLIT_ONCE,
+ methods::MANUAL_STR_REPEAT,
+ methods::MAP_COLLECT_RESULT_UNIT,
+ methods::MAP_FLATTEN,
+ methods::MAP_IDENTITY,
+ methods::MAP_UNWRAP_OR,
++ methods::NEEDLESS_SPLITN,
+ methods::NEW_RET_NO_SELF,
+ methods::OK_EXPECT,
+ methods::OPTION_AS_REF_DEREF,
+ methods::OPTION_FILTER_MAP,
+ methods::OPTION_MAP_OR_NONE,
+ methods::OR_FUN_CALL,
+ methods::RESULT_MAP_OR_INTO_OPTION,
+ methods::SEARCH_IS_SOME,
+ methods::SHOULD_IMPLEMENT_TRAIT,
+ methods::SINGLE_CHAR_ADD_STR,
+ methods::SINGLE_CHAR_PATTERN,
+ methods::SKIP_WHILE_NEXT,
+ methods::STRING_EXTEND_CHARS,
+ methods::SUSPICIOUS_MAP,
+ methods::SUSPICIOUS_SPLITN,
+ methods::UNINIT_ASSUMED_INIT,
+ methods::UNNECESSARY_FILTER_MAP,
+ methods::UNNECESSARY_FOLD,
+ methods::UNNECESSARY_LAZY_EVALUATIONS,
+ methods::UNWRAP_OR_ELSE_DEFAULT,
+ methods::UNWRAP_USED,
+ methods::USELESS_ASREF,
+ methods::WRONG_SELF_CONVENTION,
+ methods::ZST_OFFSET,
+ minmax::MIN_MAX,
+ misc::CMP_NAN,
+ misc::CMP_OWNED,
+ misc::FLOAT_CMP,
+ misc::FLOAT_CMP_CONST,
+ misc::MODULO_ONE,
+ misc::SHORT_CIRCUIT_STATEMENT,
+ misc::TOPLEVEL_REF_ARG,
+ misc::USED_UNDERSCORE_BINDING,
+ misc::ZERO_PTR,
+ misc_early::BUILTIN_TYPE_SHADOW,
+ misc_early::DOUBLE_NEG,
+ misc_early::DUPLICATE_UNDERSCORE_ARGUMENT,
+ misc_early::MIXED_CASE_HEX_LITERALS,
+ misc_early::REDUNDANT_PATTERN,
+ misc_early::SEPARATED_LITERAL_SUFFIX,
+ 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_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
+ missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
+ module_style::MOD_MODULE_FILES,
+ module_style::SELF_NAMED_MODULE_FILES,
+ modulo_arithmetic::MODULO_ARITHMETIC,
+ multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
+ mut_key::MUTABLE_KEY_TYPE,
+ mut_mut::MUT_MUT,
+ mut_mutex_lock::MUT_MUTEX_LOCK,
+ mut_reference::UNNECESSARY_MUT_PASSED,
+ mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL,
+ mutex_atomic::MUTEX_ATOMIC,
+ mutex_atomic::MUTEX_INTEGER,
+ needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE,
+ needless_bitwise_bool::NEEDLESS_BITWISE_BOOL,
+ needless_bool::BOOL_COMPARISON,
+ needless_bool::NEEDLESS_BOOL,
+ needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
+ needless_continue::NEEDLESS_CONTINUE,
+ needless_for_each::NEEDLESS_FOR_EACH,
++ needless_late_init::NEEDLESS_LATE_INIT,
+ needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF,
+ needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
+ needless_question_mark::NEEDLESS_QUESTION_MARK,
+ needless_update::NEEDLESS_UPDATE,
+ neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD,
+ neg_multiply::NEG_MULTIPLY,
+ new_without_default::NEW_WITHOUT_DEFAULT,
+ no_effect::NO_EFFECT,
+ no_effect::NO_EFFECT_UNDERSCORE_BINDING,
+ 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,
+ non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
+ non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
+ nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
++ octal_escapes::OCTAL_ESCAPES,
+ open_options::NONSENSICAL_OPEN_OPTIONS,
+ option_env_unwrap::OPTION_ENV_UNWRAP,
+ option_if_let_else::OPTION_IF_LET_ELSE,
+ overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL,
+ panic_in_result_fn::PANIC_IN_RESULT_FN,
+ panic_unimplemented::PANIC,
+ panic_unimplemented::TODO,
+ panic_unimplemented::UNIMPLEMENTED,
+ panic_unimplemented::UNREACHABLE,
+ partialeq_ne_impl::PARTIALEQ_NE_IMPL,
+ pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE,
+ pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF,
+ path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE,
+ pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
+ precedence::PRECEDENCE,
+ ptr::CMP_NULL,
+ ptr::INVALID_NULL_PTR_USAGE,
+ ptr::MUT_FROM_REF,
+ ptr::PTR_ARG,
+ ptr_eq::PTR_EQ,
+ ptr_offset_with_cast::PTR_OFFSET_WITH_CAST,
+ question_mark::QUESTION_MARK,
+ ranges::MANUAL_RANGE_CONTAINS,
+ ranges::RANGE_MINUS_ONE,
+ ranges::RANGE_PLUS_ONE,
+ ranges::RANGE_ZIP_WITH_LEN,
+ ranges::REVERSED_EMPTY_RANGES,
+ redundant_clone::REDUNDANT_CLONE,
+ redundant_closure_call::REDUNDANT_CLOSURE_CALL,
+ redundant_else::REDUNDANT_ELSE,
+ redundant_field_names::REDUNDANT_FIELD_NAMES,
+ redundant_pub_crate::REDUNDANT_PUB_CRATE,
+ redundant_slicing::REDUNDANT_SLICING,
+ redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES,
+ ref_option_ref::REF_OPTION_REF,
+ reference::DEREF_ADDROF,
+ reference::REF_IN_DEREF,
+ regex::INVALID_REGEX,
+ regex::TRIVIAL_REGEX,
+ repeat_once::REPEAT_ONCE,
+ returns::LET_AND_RETURN,
+ returns::NEEDLESS_RETURN,
+ same_name_method::SAME_NAME_METHOD,
+ self_assignment::SELF_ASSIGNMENT,
+ self_named_constructors::SELF_NAMED_CONSTRUCTORS,
+ semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED,
+ serde_api::SERDE_API_MISUSE,
+ shadow::SHADOW_REUSE,
+ shadow::SHADOW_SAME,
+ shadow::SHADOW_UNRELATED,
+ single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
+ size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
+ slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
+ stable_sort_primitive::STABLE_SORT_PRIMITIVE,
+ strings::STRING_ADD,
+ strings::STRING_ADD_ASSIGN,
+ strings::STRING_FROM_UTF8_AS_BYTES,
+ strings::STRING_LIT_AS_BYTES,
+ strings::STRING_SLICE,
+ strings::STRING_TO_STRING,
+ strings::STR_TO_STRING,
+ strlen_on_c_strings::STRLEN_ON_C_STRINGS,
+ suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS,
+ suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
+ suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
+ swap::ALMOST_SWAPPED,
+ swap::MANUAL_SWAP,
+ tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
+ temporary_assignment::TEMPORARY_ASSIGNMENT,
+ to_digit_is_some::TO_DIGIT_IS_SOME,
+ to_string_in_display::TO_STRING_IN_DISPLAY,
+ trailing_empty_array::TRAILING_EMPTY_ARRAY,
+ trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
+ trait_bounds::TYPE_REPETITION_IN_BOUNDS,
+ transmute::CROSSPOINTER_TRANSMUTE,
+ transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ transmute::TRANSMUTE_BYTES_TO_STR,
+ transmute::TRANSMUTE_FLOAT_TO_INT,
+ transmute::TRANSMUTE_INT_TO_BOOL,
+ transmute::TRANSMUTE_INT_TO_CHAR,
+ transmute::TRANSMUTE_INT_TO_FLOAT,
+ transmute::TRANSMUTE_NUM_TO_BYTES,
+ transmute::TRANSMUTE_PTR_TO_PTR,
+ transmute::TRANSMUTE_PTR_TO_REF,
+ transmute::UNSOUND_COLLECTION_TRANSMUTE,
+ transmute::USELESS_TRANSMUTE,
+ transmute::WRONG_TRANSMUTE,
+ transmuting_null::TRANSMUTING_NULL,
+ try_err::TRY_ERR,
+ types::BORROWED_BOX,
+ types::BOX_COLLECTION,
+ types::LINKEDLIST,
+ types::OPTION_OPTION,
+ types::RC_BUFFER,
+ types::RC_MUTEX,
+ types::REDUNDANT_ALLOCATION,
+ types::TYPE_COMPLEXITY,
+ types::VEC_BOX,
+ undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
+ undropped_manually_drops::UNDROPPED_MANUALLY_DROPS,
+ unicode::INVISIBLE_CHARACTERS,
+ unicode::NON_ASCII_LITERAL,
+ unicode::UNICODE_NOT_NFC,
+ uninit_vec::UNINIT_VEC,
+ unit_hash::UNIT_HASH,
+ unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD,
+ unit_types::LET_UNIT_VALUE,
+ unit_types::UNIT_ARG,
+ unit_types::UNIT_CMP,
+ unnamed_address::FN_ADDRESS_COMPARISONS,
+ unnamed_address::VTABLE_ADDRESS_COMPARISONS,
+ unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS,
+ unnecessary_sort_by::UNNECESSARY_SORT_BY,
+ unnecessary_wraps::UNNECESSARY_WRAPS,
+ unnested_or_patterns::UNNESTED_OR_PATTERNS,
+ unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
+ unused_async::UNUSED_ASYNC,
+ unused_io_amount::UNUSED_IO_AMOUNT,
+ unused_self::UNUSED_SELF,
+ unused_unit::UNUSED_UNIT,
+ unwrap::PANICKING_UNWRAP,
+ unwrap::UNNECESSARY_UNWRAP,
+ unwrap_in_result::UNWRAP_IN_RESULT,
+ upper_case_acronyms::UPPER_CASE_ACRONYMS,
+ use_self::USE_SELF,
+ useless_conversion::USELESS_CONVERSION,
+ vec::USELESS_VEC,
+ vec_init_then_push::VEC_INIT_THEN_PUSH,
+ vec_resize_to_zero::VEC_RESIZE_TO_ZERO,
+ verbose_file_reads::VERBOSE_FILE_READS,
+ wildcard_dependencies::WILDCARD_DEPENDENCIES,
+ wildcard_imports::ENUM_GLOB_USE,
+ wildcard_imports::WILDCARD_IMPORTS,
+ write::PRINTLN_EMPTY_STRING,
+ write::PRINT_LITERAL,
+ write::PRINT_STDERR,
+ write::PRINT_STDOUT,
+ write::PRINT_WITH_NEWLINE,
+ write::USE_DEBUG,
+ write::WRITELN_EMPTY_STRING,
+ write::WRITE_LITERAL,
+ write::WRITE_WITH_NEWLINE,
+ zero_div_zero::ZERO_DIVIDED_BY_ZERO,
+ zero_sized_map_values::ZERO_SIZED_MAP_VALUES,
+])
--- /dev/null
- LintId::of(disallowed_method::DISALLOWED_METHOD),
- LintId::of(disallowed_type::DISALLOWED_TYPE),
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+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(copies::BRANCHES_SHARING_CODE),
++ LintId::of(disallowed_methods::DISALLOWED_METHODS),
++ LintId::of(disallowed_types::DISALLOWED_TYPES),
+ LintId::of(equatable_if_let::EQUATABLE_IF_LET),
+ 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(index_refutable_slice::INDEX_REFUTABLE_SLICE),
+ 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(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES),
+ LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
+ LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
+ LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
+ LintId::of(regex::TRIVIAL_REGEX),
+ LintId::of(strings::STRING_LIT_AS_BYTES),
+ LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
+ LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
+ LintId::of(transmute::USELESS_TRANSMUTE),
+ LintId::of(use_self::USE_SELF),
+])
--- /dev/null
- LintId::of(methods::MAP_FLATTEN),
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
+ LintId::of(attrs::INLINE_ALWAYS),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
+ LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
+ LintId::of(bit_mask::VERBOSE_BIT_MASK),
+ LintId::of(bytecount::NAIVE_BYTECOUNT),
+ LintId::of(case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS),
+ LintId::of(casts::CAST_LOSSLESS),
+ LintId::of(casts::CAST_POSSIBLE_TRUNCATION),
+ LintId::of(casts::CAST_POSSIBLE_WRAP),
+ LintId::of(casts::CAST_PRECISION_LOSS),
+ LintId::of(casts::CAST_PTR_ALIGNMENT),
+ LintId::of(casts::CAST_SIGN_LOSS),
+ LintId::of(casts::PTR_AS_PTR),
+ LintId::of(checked_conversions::CHECKED_CONVERSIONS),
+ LintId::of(copies::SAME_FUNCTIONS_IN_IF_CONDITION),
+ LintId::of(copy_iterator::COPY_ITERATOR),
+ LintId::of(default::DEFAULT_TRAIT_ACCESS),
+ LintId::of(dereference::EXPLICIT_DEREF_METHODS),
++ LintId::of(dereference::REF_BINDING_TO_REFERENCE),
+ LintId::of(derive::EXPL_IMPL_CLONE_ON_COPY),
+ LintId::of(derive::UNSAFE_DERIVE_DESERIALIZE),
+ LintId::of(doc::DOC_MARKDOWN),
+ LintId::of(doc::MISSING_ERRORS_DOC),
+ LintId::of(doc::MISSING_PANICS_DOC),
+ LintId::of(empty_enum::EMPTY_ENUM),
+ LintId::of(enum_variants::MODULE_NAME_REPETITIONS),
+ LintId::of(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_hasher::IMPLICIT_HASHER),
+ LintId::of(implicit_saturating_sub::IMPLICIT_SATURATING_SUB),
+ LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
+ LintId::of(infinite_iter::MAYBE_INFINITE_ITER),
+ LintId::of(invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS),
+ LintId::of(items_after_statements::ITEMS_AFTER_STATEMENTS),
+ LintId::of(iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR),
+ LintId::of(large_stack_arrays::LARGE_STACK_ARRAYS),
+ LintId::of(let_underscore::LET_UNDERSCORE_DROP),
+ LintId::of(literal_representation::LARGE_DIGIT_GROUPS),
+ LintId::of(literal_representation::UNREADABLE_LITERAL),
+ LintId::of(loops::EXPLICIT_INTO_ITER_LOOP),
+ LintId::of(loops::EXPLICIT_ITER_LOOP),
+ LintId::of(macro_use::MACRO_USE_IMPORTS),
+ LintId::of(manual_assert::MANUAL_ASSERT),
+ LintId::of(manual_ok_or::MANUAL_OK_OR),
+ LintId::of(match_on_vec_items::MATCH_ON_VEC_ITEMS),
+ LintId::of(matches::MATCH_BOOL),
+ LintId::of(matches::MATCH_SAME_ARMS),
+ LintId::of(matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS),
+ LintId::of(matches::MATCH_WILD_ERR_ARM),
+ LintId::of(matches::SINGLE_MATCH_ELSE),
+ LintId::of(methods::CLONED_INSTEAD_OF_COPIED),
+ LintId::of(methods::FILTER_MAP_NEXT),
+ LintId::of(methods::FLAT_MAP_OPTION),
+ LintId::of(methods::FROM_ITER_INSTEAD_OF_COLLECT),
+ LintId::of(methods::IMPLICIT_CLONE),
+ LintId::of(methods::INEFFICIENT_TO_STRING),
- LintId::of(needless_borrow::REF_BINDING_TO_REFERENCE),
+ LintId::of(methods::MAP_UNWRAP_OR),
+ LintId::of(misc::FLOAT_CMP),
+ LintId::of(misc::USED_UNDERSCORE_BINDING),
+ LintId::of(mut_mut::MUT_MUT),
+ LintId::of(needless_bitwise_bool::NEEDLESS_BITWISE_BOOL),
+ LintId::of(needless_continue::NEEDLESS_CONTINUE),
+ LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
+ LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
+ LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING),
+ LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
+ LintId::of(non_expressive_names::SIMILAR_NAMES),
+ LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
+ LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
+ LintId::of(ranges::RANGE_MINUS_ONE),
+ LintId::of(ranges::RANGE_PLUS_ONE),
+ LintId::of(redundant_else::REDUNDANT_ELSE),
+ LintId::of(ref_option_ref::REF_OPTION_REF),
+ LintId::of(semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED),
+ LintId::of(strings::STRING_ADD_ASSIGN),
+ LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
+ LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
+ LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
+ LintId::of(types::LINKEDLIST),
+ LintId::of(types::OPTION_OPTION),
+ LintId::of(unicode::UNICODE_NOT_NFC),
+ LintId::of(unit_types::LET_UNIT_VALUE),
+ LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS),
+ LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS),
+ LintId::of(unused_async::UNUSED_ASYNC),
+ LintId::of(unused_self::UNUSED_SELF),
+ LintId::of(wildcard_imports::ENUM_GLOB_USE),
+ LintId::of(wildcard_imports::WILDCARD_IMPORTS),
+ LintId::of(zero_sized_map_values::ZERO_SIZED_MAP_VALUES),
+])
--- /dev/null
- LintId::of(needless_borrow::NEEDLESS_BORROW),
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+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(blacklisted_name::BLACKLISTED_NAME),
+ LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
+ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
+ LintId::of(casts::FN_TO_NUMERIC_CAST),
+ LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
+ LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
+ LintId::of(collapsible_if::COLLAPSIBLE_IF),
+ LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
+ LintId::of(comparison_chain::COMPARISON_CHAIN),
+ LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
++ LintId::of(dereference::NEEDLESS_BORROW),
+ 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(from_over_into::FROM_OVER_INTO),
+ LintId::of(from_str_radix_10::FROM_STR_RADIX_10),
+ LintId::of(functions::DOUBLE_MUST_USE),
+ LintId::of(functions::MUST_USE_UNIT),
+ LintId::of(functions::RESULT_UNIT_ERR),
+ LintId::of(inherent_to_string::INHERENT_TO_STRING),
+ LintId::of(len_zero::COMPARISON_TO_EMPTY),
+ LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY),
+ LintId::of(len_zero::LEN_ZERO),
+ LintId::of(literal_representation::INCONSISTENT_DIGIT_GROUPING),
+ LintId::of(literal_representation::UNUSUAL_BYTE_GROUPINGS),
+ LintId::of(loops::FOR_KV_MAP),
+ LintId::of(loops::NEEDLESS_RANGE_LOOP),
+ LintId::of(loops::SAME_ITEM_PUSH),
+ LintId::of(loops::WHILE_LET_ON_ITERATOR),
+ LintId::of(main_recursion::MAIN_RECURSION),
+ LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
+ LintId::of(manual_map::MANUAL_MAP),
+ LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
+ LintId::of(map_clone::MAP_CLONE),
+ LintId::of(match_result_ok::MATCH_RESULT_OK),
+ LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
+ LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
+ LintId::of(matches::MATCH_OVERLAPPING_ARM),
+ LintId::of(matches::MATCH_REF_PATS),
+ LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
+ LintId::of(matches::SINGLE_MATCH),
+ LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
+ LintId::of(mem_replace::MEM_REPLACE_WITH_DEFAULT),
+ LintId::of(methods::BYTES_NTH),
+ LintId::of(methods::CHARS_LAST_CMP),
+ LintId::of(methods::CHARS_NEXT_CMP),
+ LintId::of(methods::INTO_ITER_ON_REF),
+ LintId::of(methods::ITER_CLONED_COLLECT),
+ LintId::of(methods::ITER_NEXT_SLICE),
+ LintId::of(methods::ITER_NTH_ZERO),
+ LintId::of(methods::ITER_SKIP_NEXT),
+ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
+ LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
+ LintId::of(methods::NEW_RET_NO_SELF),
+ LintId::of(methods::OK_EXPECT),
+ LintId::of(methods::OPTION_MAP_OR_NONE),
+ LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
+ LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
+ LintId::of(methods::SINGLE_CHAR_ADD_STR),
+ LintId::of(methods::STRING_EXTEND_CHARS),
+ LintId::of(methods::UNNECESSARY_FOLD),
+ LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
+ LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT),
+ LintId::of(methods::WRONG_SELF_CONVENTION),
+ LintId::of(misc::TOPLEVEL_REF_ARG),
+ LintId::of(misc::ZERO_PTR),
+ LintId::of(misc_early::BUILTIN_TYPE_SHADOW),
+ LintId::of(misc_early::DOUBLE_NEG),
+ LintId::of(misc_early::DUPLICATE_UNDERSCORE_ARGUMENT),
+ LintId::of(misc_early::MIXED_CASE_HEX_LITERALS),
+ LintId::of(misc_early::REDUNDANT_PATTERN),
+ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK),
+ LintId::of(mut_reference::UNNECESSARY_MUT_PASSED),
++ LintId::of(needless_late_init::NEEDLESS_LATE_INIT),
+ LintId::of(neg_multiply::NEG_MULTIPLY),
+ LintId::of(new_without_default::NEW_WITHOUT_DEFAULT),
+ LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST),
+ LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
+ LintId::of(ptr::CMP_NULL),
+ LintId::of(ptr::PTR_ARG),
+ LintId::of(ptr_eq::PTR_EQ),
+ LintId::of(question_mark::QUESTION_MARK),
+ LintId::of(ranges::MANUAL_RANGE_CONTAINS),
+ LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
+ LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
+ LintId::of(returns::LET_AND_RETURN),
+ LintId::of(returns::NEEDLESS_RETURN),
+ LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
+ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
+ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
+ LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
+ LintId::of(try_err::TRY_ERR),
+ LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
+ LintId::of(unused_unit::UNUSED_UNIT),
+ LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
+ LintId::of(write::PRINTLN_EMPTY_STRING),
+ LintId::of(write::PRINT_LITERAL),
+ LintId::of(write::PRINT_WITH_NEWLINE),
+ LintId::of(write::WRITELN_EMPTY_STRING),
+ LintId::of(write::WRITE_LITERAL),
+ LintId::of(write::WRITE_WITH_NEWLINE),
+])
--- /dev/null
+// This file was generated by `cargo dev update_lints`.
+// Use that command to update this file and do not edit by hand.
+// Manual edits will be overwritten.
+
+store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
+ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
+ LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
+ LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
+ LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
+ LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
+ LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
+ LintId::of(loops::EMPTY_LOOP),
+ LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
+ LintId::of(loops::MUT_RANGE_BOUND),
+ LintId::of(methods::SUSPICIOUS_MAP),
+ LintId::of(mut_key::MUTABLE_KEY_TYPE),
+ LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY),
++ LintId::of(octal_escapes::OCTAL_ESCAPES),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
+ LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
+])
--- /dev/null
- mod disallowed_method;
+// error-pattern:cargo-clippy
+
+#![feature(box_patterns)]
+#![feature(drain_filter)]
+#![feature(in_band_lifetimes)]
+#![feature(iter_zip)]
+#![feature(once_cell)]
+#![feature(rustc_private)]
+#![feature(stmt_expr_attributes)]
+#![feature(control_flow_enum)]
++#![feature(let_else)]
+#![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
+#![warn(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_data_structures;
+extern crate rustc_driver;
+extern crate rustc_errors;
+extern crate rustc_hir;
+extern crate rustc_hir_pretty;
+extern crate rustc_index;
+extern crate rustc_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_mir_dataflow;
+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;
+
+#[macro_use]
+extern crate clippy_utils;
+
+use clippy_utils::parse_msrv;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_lint::LintId;
+use rustc_session::Session;
+
+/// Macro used to declare a Clippy lint.
+///
+/// Every lint declaration consists of 4 parts:
+///
+/// 1. The documentation, which is used for the website
+/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions.
+/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or
+/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of.
+/// 4. The `description` that contains a short explanation on what's wrong with code where the
+/// lint is triggered.
+///
+/// Currently the categories `style`, `correctness`, `suspicious`, `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.
+/// ///
+/// /// ### 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, suspicious, $description:tt } => {
+ declare_tool_lint! {
+ $(#[$attr])* pub clippy::$name, Warn, $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
+ }
+ };
+}
+
+#[cfg(feature = "metadata-collector-lint")]
+mod deprecated_lints;
++#[cfg_attr(
++ any(feature = "internal-lints", feature = "metadata-collector-lint"),
++ allow(clippy::missing_clippy_version_attribute)
++)]
+mod utils;
+
+// begin lints modules, do not remove this comment, it’s used in `update_lints`
+mod absurd_extreme_comparisons;
+mod approx_const;
+mod arithmetic;
+mod as_conversions;
+mod asm_syntax;
+mod assertions_on_constants;
+mod assign_ops;
+mod async_yields_async;
+mod attrs;
+mod await_holding_invalid;
+mod bit_mask;
+mod blacklisted_name;
+mod blocks_in_if_conditions;
+mod bool_assert_comparison;
+mod booleans;
+mod bytecount;
+mod cargo_common_metadata;
+mod case_sensitive_file_extension_comparisons;
+mod casts;
+mod checked_conversions;
+mod cognitive_complexity;
+mod collapsible_if;
+mod collapsible_match;
+mod comparison_chain;
+mod copies;
+mod copy_iterator;
+mod create_dir;
+mod dbg_macro;
+mod default;
+mod default_numeric_fallback;
+mod dereference;
+mod derivable_impls;
+mod derive;
- mod disallowed_type;
++mod disallowed_methods;
+mod disallowed_script_idents;
- mod needless_borrow;
++mod disallowed_types;
+mod doc;
+mod double_comparison;
+mod double_parens;
+mod drop_forget_ref;
+mod duration_subsec;
+mod else_if_without_else;
+mod empty_enum;
+mod entry;
+mod enum_clike;
+mod enum_variants;
+mod eq_op;
+mod equatable_if_let;
+mod erasing_op;
+mod escape;
+mod eta_reduction;
+mod eval_order_dependence;
+mod excessive_bools;
+mod exhaustive_items;
+mod exit;
+mod explicit_write;
+mod fallible_impl_from;
+mod feature_name;
+mod float_equality_without_abs;
+mod float_literal;
+mod floating_point_arithmetic;
+mod format;
+mod format_args;
+mod formatting;
+mod from_over_into;
+mod from_str_radix_10;
+mod functions;
+mod future_not_send;
+mod get_last_with_len;
+mod identity_op;
+mod if_let_mutex;
+mod if_not_else;
+mod if_then_some_else_none;
+mod implicit_hasher;
+mod implicit_return;
+mod implicit_saturating_sub;
+mod inconsistent_struct_constructor;
++mod index_refutable_slice;
+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 invalid_upcast_comparisons;
+mod items_after_statements;
+mod iter_not_returning_iterator;
+mod large_const_arrays;
+mod large_enum_variant;
+mod large_stack_arrays;
+mod len_zero;
+mod let_if_seq;
+mod let_underscore;
+mod lifetimes;
+mod literal_representation;
+mod loops;
+mod macro_use;
+mod main_recursion;
+mod manual_assert;
+mod manual_async_fn;
+mod manual_map;
+mod manual_non_exhaustive;
+mod manual_ok_or;
+mod manual_strip;
+mod manual_unwrap_or;
+mod map_clone;
+mod map_err_ignore;
+mod map_unit_fn;
+mod match_on_vec_items;
+mod match_result_ok;
+mod match_str_case_mismatch;
+mod matches;
+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_enforced_import_rename;
+mod missing_inline;
+mod module_style;
+mod modulo_arithmetic;
+mod multiple_crate_versions;
+mod mut_key;
+mod mut_mut;
+mod mut_mutex_lock;
+mod mut_reference;
+mod mutable_debug_assertion;
+mod mutex_atomic;
+mod needless_arbitrary_self_type;
+mod needless_bitwise_bool;
+mod needless_bool;
- pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) {
+mod needless_borrowed_ref;
+mod needless_continue;
+mod needless_for_each;
++mod needless_late_init;
+mod needless_option_as_deref;
+mod needless_pass_by_value;
+mod needless_question_mark;
+mod needless_update;
+mod neg_cmp_op_on_partial_ord;
+mod neg_multiply;
+mod new_without_default;
+mod no_effect;
+mod non_copy_const;
+mod non_expressive_names;
+mod non_octal_unix_permissions;
+mod non_send_fields_in_send_ty;
+mod nonstandard_macro_braces;
++mod octal_escapes;
+mod open_options;
+mod option_env_unwrap;
+mod option_if_let_else;
+mod overflow_check_conditional;
+mod panic_in_result_fn;
+mod panic_unimplemented;
+mod partialeq_ne_impl;
+mod pass_by_ref_or_value;
+mod path_buf_push_overwrite;
+mod pattern_type_mismatch;
+mod precedence;
+mod ptr;
+mod ptr_eq;
+mod ptr_offset_with_cast;
+mod question_mark;
+mod ranges;
+mod redundant_clone;
+mod redundant_closure_call;
+mod redundant_else;
+mod redundant_field_names;
+mod redundant_pub_crate;
+mod redundant_slicing;
+mod redundant_static_lifetimes;
+mod ref_option_ref;
+mod reference;
+mod regex;
+mod repeat_once;
+mod returns;
+mod same_name_method;
+mod self_assignment;
+mod self_named_constructors;
+mod semicolon_if_nothing_returned;
+mod serde_api;
+mod shadow;
+mod single_component_path_imports;
+mod size_of_in_element_count;
+mod slow_vector_initialization;
+mod stable_sort_primitive;
+mod strings;
+mod strlen_on_c_strings;
+mod suspicious_operation_groupings;
+mod suspicious_trait_impl;
+mod swap;
+mod tabs_in_doc_comments;
+mod temporary_assignment;
+mod to_digit_is_some;
+mod to_string_in_display;
+mod trailing_empty_array;
+mod trait_bounds;
+mod transmute;
+mod transmuting_null;
+mod try_err;
+mod types;
+mod undocumented_unsafe_blocks;
+mod undropped_manually_drops;
+mod unicode;
+mod uninit_vec;
+mod unit_hash;
+mod unit_return_expecting_ord;
+mod unit_types;
+mod unnamed_address;
+mod unnecessary_self_imports;
+mod unnecessary_sort_by;
+mod unnecessary_wraps;
+mod unnested_or_patterns;
+mod unsafe_removed_from_name;
+mod unused_async;
+mod unused_io_amount;
+mod unused_self;
+mod unused_unit;
+mod unwrap;
+mod unwrap_in_result;
+mod upper_case_acronyms;
+mod use_self;
+mod useless_conversion;
+mod vec;
+mod vec_init_then_push;
+mod vec_resize_to_zero;
+mod verbose_file_reads;
+mod wildcard_dependencies;
+mod wildcard_imports;
+mod write;
+mod zero_div_zero;
+mod zero_sized_map_values;
+// end lints modules, do not remove this comment, it’s used in `update_lints`
+
+pub use crate::utils::conf::Conf;
+use crate::utils::conf::TryConf;
+
+/// 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`.
- store.register_pre_expansion_pass(|| Box::new(attrs::EarlyAttributes));
++pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ // NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
++
++ let msrv = conf.msrv.as_ref().and_then(|s| {
++ parse_msrv(s, None, None).or_else(|| {
++ sess.err(&format!(
++ "error reading Clippy's configuration file. `{}` is not a valid Rust version",
++ s
++ ));
++ None
++ })
++ });
++
+ store.register_pre_expansion_pass(|| Box::new(write::Write::default()));
- #[rustfmt::skip]
++ store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv }));
+ store.register_pre_expansion_pass(|| Box::new(dbg_macro::DbgMacro));
+}
+
+#[doc(hidden)]
+pub fn read_conf(sess: &Session) -> Conf {
+ let file_name = 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 TryConf { 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
+}
+
+/// Register all lints and lint groups with the rustc plugin registry
+///
+/// Used in `./src/driver.rs`.
+#[allow(clippy::too_many_lines)]
- store.register_late_pass(move || Box::new(types::Types::new(
- vec_box_size_threshold,
- type_complexity_threshold,
- avoid_breaking_exported_api,
- )));
+pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
+ register_removed_non_tool_lints(store);
+
+ include!("lib.deprecated.rs");
+
+ include!("lib.register_lints.rs");
+ include!("lib.register_restriction.rs");
+ include!("lib.register_pedantic.rs");
+
+ #[cfg(feature = "internal-lints")]
+ include!("lib.register_internal.rs");
+
+ include!("lib.register_all.rs");
+ include!("lib.register_style.rs");
+ include!("lib.register_complexity.rs");
+ include!("lib.register_correctness.rs");
+ include!("lib.register_suspicious.rs");
+ include!("lib.register_perf.rs");
+ include!("lib.register_cargo.rs");
+ include!("lib.register_nursery.rs");
+
+ #[cfg(feature = "metadata-collector-lint")]
+ {
+ if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) {
+ store.register_late_pass(|| Box::new(utils::internal_lints::metadata_collector::MetadataCollector::new()));
+ return;
+ }
+ }
+
+ // all the internal lints
+ #[cfg(feature = "internal-lints")]
+ {
+ store.register_early_pass(|| Box::new(utils::internal_lints::ClippyLintsInternal));
+ store.register_early_pass(|| Box::new(utils::internal_lints::ProduceIce));
+ store.register_late_pass(|| Box::new(utils::inspector::DeepCodeInspector));
+ store.register_late_pass(|| Box::new(utils::internal_lints::CollapsibleCalls));
+ store.register_late_pass(|| Box::new(utils::internal_lints::CompilerLintFunctions::new()));
+ store.register_late_pass(|| Box::new(utils::internal_lints::IfChainStyle));
+ store.register_late_pass(|| Box::new(utils::internal_lints::InvalidPaths));
+ store.register_late_pass(|| Box::new(utils::internal_lints::InterningDefinedSymbol::default()));
+ store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default()));
+ store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem));
+ store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass));
+ }
+
+ store.register_late_pass(|| Box::new(utils::author::Author));
+ store.register_late_pass(|| Box::new(await_holding_invalid::AwaitHolding));
+ store.register_late_pass(|| Box::new(serde_api::SerdeApi));
+ let vec_box_size_threshold = conf.vec_box_size_threshold;
+ let type_complexity_threshold = conf.type_complexity_threshold;
+ let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
- sess.err(&format!("error reading Clippy's configuration file. `{}` is not a valid Rust version", s));
++ store.register_late_pass(move || {
++ Box::new(types::Types::new(
++ vec_box_size_threshold,
++ type_complexity_threshold,
++ avoid_breaking_exported_api,
++ ))
++ });
+ store.register_late_pass(|| Box::new(booleans::NonminimalBool));
+ store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool));
+ store.register_late_pass(|| Box::new(eq_op::EqOp));
+ store.register_late_pass(|| Box::new(enum_clike::UnportableVariant));
+ store.register_late_pass(|| Box::new(float_literal::FloatLiteral));
+ let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
+ store.register_late_pass(move || Box::new(bit_mask::BitMask::new(verbose_bit_mask_threshold)));
+ store.register_late_pass(|| Box::new(ptr::Ptr));
+ store.register_late_pass(|| Box::new(ptr_eq::PtrEq));
+ store.register_late_pass(|| Box::new(needless_bool::NeedlessBool));
+ store.register_late_pass(|| Box::new(needless_option_as_deref::OptionNeedlessDeref));
+ store.register_late_pass(|| Box::new(needless_bool::BoolComparison));
+ store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach));
+ store.register_late_pass(|| Box::new(misc::MiscLints));
+ store.register_late_pass(|| Box::new(eta_reduction::EtaReduction));
+ store.register_late_pass(|| Box::new(identity_op::IdentityOp));
+ store.register_late_pass(|| Box::new(erasing_op::ErasingOp));
+ store.register_late_pass(|| Box::new(mut_mut::MutMut));
+ store.register_late_pass(|| Box::new(mut_reference::UnnecessaryMutPassed));
+ store.register_late_pass(|| Box::new(len_zero::LenZero));
+ store.register_late_pass(|| Box::new(attrs::Attributes));
+ store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions));
+ store.register_late_pass(|| Box::new(collapsible_match::CollapsibleMatch));
+ store.register_late_pass(|| Box::new(unicode::Unicode));
+ store.register_late_pass(|| Box::new(uninit_vec::UninitVec));
+ store.register_late_pass(|| Box::new(unit_hash::UnitHash));
+ store.register_late_pass(|| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd));
+ store.register_late_pass(|| Box::new(strings::StringAdd));
+ store.register_late_pass(|| Box::new(implicit_return::ImplicitReturn));
+ store.register_late_pass(|| Box::new(implicit_saturating_sub::ImplicitSaturatingSub));
+ store.register_late_pass(|| Box::new(default_numeric_fallback::DefaultNumericFallback));
+ store.register_late_pass(|| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
+ store.register_late_pass(|| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
+ store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
+
+ let msrv = conf.msrv.as_ref().and_then(|s| {
+ parse_msrv(s, None, None).or_else(|| {
- store.register_late_pass(|| Box::new(needless_borrow::NeedlessBorrow::default()));
++ sess.err(&format!(
++ "error reading Clippy's configuration file. `{}` is not a valid Rust version",
++ s
++ ));
+ None
+ })
+ });
+
+ let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
+ store.register_late_pass(move || Box::new(approx_const::ApproxConstant::new(msrv)));
+ store.register_late_pass(move || Box::new(methods::Methods::new(avoid_breaking_exported_api, msrv)));
+ store.register_late_pass(move || Box::new(matches::Matches::new(msrv)));
+ store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustive::new(msrv)));
+ store.register_late_pass(move || Box::new(manual_strip::ManualStrip::new(msrv)));
+ store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)));
+ store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv)));
+ store.register_late_pass(move || Box::new(checked_conversions::CheckedConversions::new(msrv)));
+ store.register_late_pass(move || Box::new(mem_replace::MemReplace::new(msrv)));
+ store.register_late_pass(move || Box::new(ranges::Ranges::new(msrv)));
+ store.register_late_pass(move || Box::new(from_over_into::FromOverInto::new(msrv)));
+ store.register_late_pass(move || Box::new(use_self::UseSelf::new(msrv)));
+ store.register_late_pass(move || Box::new(missing_const_for_fn::MissingConstForFn::new(msrv)));
+ store.register_late_pass(move || Box::new(needless_question_mark::NeedlessQuestionMark));
+ store.register_late_pass(move || Box::new(casts::Casts::new(msrv)));
+ store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv)));
+
+ store.register_late_pass(|| Box::new(size_of_in_element_count::SizeOfInElementCount));
+ store.register_late_pass(|| Box::new(same_name_method::SameNameMethod));
++ let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length;
++ store.register_late_pass(move || {
++ Box::new(index_refutable_slice::IndexRefutableSlice::new(
++ max_suggested_slice_pattern_length,
++ msrv,
++ ))
++ });
+ store.register_late_pass(|| Box::new(map_clone::MapClone));
+ store.register_late_pass(|| Box::new(map_err_ignore::MapErrIgnore));
+ store.register_late_pass(|| Box::new(shadow::Shadow::default()));
+ store.register_late_pass(|| Box::new(unit_types::UnitTypes));
+ store.register_late_pass(|| Box::new(loops::Loops));
+ store.register_late_pass(|| Box::new(main_recursion::MainRecursion::default()));
+ store.register_late_pass(|| Box::new(lifetimes::Lifetimes));
+ store.register_late_pass(|| Box::new(entry::HashMapPass));
+ store.register_late_pass(|| Box::new(minmax::MinMaxPass));
+ store.register_late_pass(|| Box::new(open_options::OpenOptions));
+ store.register_late_pass(|| Box::new(zero_div_zero::ZeroDiv));
+ store.register_late_pass(|| Box::new(mutex_atomic::Mutex));
+ store.register_late_pass(|| Box::new(needless_update::NeedlessUpdate));
- store.register_late_pass(move || Box::new(cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold)));
+ store.register_late_pass(|| Box::new(needless_borrowed_ref::NeedlessBorrowedRef));
+ store.register_late_pass(|| Box::new(no_effect::NoEffect));
+ store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment));
+ store.register_late_pass(|| Box::new(transmute::Transmute));
+ let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
- store.register_late_pass(move || Box::new(escape::BoxedLocal{too_large_for_stack}));
- store.register_late_pass(move || Box::new(vec::UselessVec{too_large_for_stack}));
++ store.register_late_pass(move || {
++ Box::new(cognitive_complexity::CognitiveComplexity::new(
++ cognitive_complexity_threshold,
++ ))
++ });
+ let too_large_for_stack = conf.too_large_for_stack;
- store.register_late_pass(move || Box::new(functions::Functions::new(too_many_arguments_threshold, too_many_lines_threshold)));
++ store.register_late_pass(move || Box::new(escape::BoxedLocal { too_large_for_stack }));
++ store.register_late_pass(move || Box::new(vec::UselessVec { too_large_for_stack }));
+ store.register_late_pass(|| Box::new(panic_unimplemented::PanicUnimplemented));
+ store.register_late_pass(|| Box::new(strings::StringLitAsBytes));
+ store.register_late_pass(|| Box::new(derive::Derive));
+ store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
+ store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen));
+ store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
+ store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
+ store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons));
+ store.register_late_pass(|| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
+ store.register_late_pass(|| Box::new(regex::Regex));
+ store.register_late_pass(|| Box::new(copies::CopyAndPaste));
+ store.register_late_pass(|| Box::new(copy_iterator::CopyIterator));
+ store.register_late_pass(|| Box::new(format::UselessFormat));
+ store.register_late_pass(|| Box::new(swap::Swap));
+ store.register_late_pass(|| Box::new(overflow_check_conditional::OverflowCheckConditional));
+ store.register_late_pass(|| Box::new(new_without_default::NewWithoutDefault::default()));
+ let blacklisted_names = conf.blacklisted_names.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move || Box::new(blacklisted_name::BlacklistedName::new(blacklisted_names.clone())));
+ let too_many_arguments_threshold = conf.too_many_arguments_threshold;
+ let too_many_lines_threshold = conf.too_many_lines_threshold;
- store.register_early_pass(move || Box::new(literal_representation::LiteralDigitGrouping::new(literal_representation_lint_fraction_readability)));
++ store.register_late_pass(move || {
++ Box::new(functions::Functions::new(
++ too_many_arguments_threshold,
++ too_many_lines_threshold,
++ ))
++ });
+ let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
+ store.register_late_pass(|| Box::new(neg_multiply::NegMultiply));
+ store.register_late_pass(|| Box::new(mem_forget::MemForget));
+ store.register_late_pass(|| Box::new(arithmetic::Arithmetic::default()));
+ store.register_late_pass(|| Box::new(assign_ops::AssignOps));
+ store.register_late_pass(|| Box::new(let_if_seq::LetIfSeq));
+ store.register_late_pass(|| Box::new(eval_order_dependence::EvalOrderDependence));
+ store.register_late_pass(|| Box::new(missing_doc::MissingDoc::new()));
+ store.register_late_pass(|| Box::new(missing_inline::MissingInline));
+ store.register_late_pass(move || Box::new(exhaustive_items::ExhaustiveItems));
+ store.register_late_pass(|| Box::new(match_result_ok::MatchResultOk));
+ store.register_late_pass(|| Box::new(partialeq_ne_impl::PartialEqNeImpl));
+ store.register_late_pass(|| Box::new(unused_io_amount::UnusedIoAmount));
+ let enum_variant_size_threshold = conf.enum_variant_size_threshold;
+ store.register_late_pass(move || Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)));
+ store.register_late_pass(|| Box::new(explicit_write::ExplicitWrite));
+ store.register_late_pass(|| Box::new(needless_pass_by_value::NeedlessPassByValue));
+ let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new(
+ conf.trivial_copy_size_limit,
+ conf.pass_by_value_size_limit,
+ conf.avoid_breaking_exported_api,
+ &sess.target,
+ );
+ store.register_late_pass(move || Box::new(pass_by_ref_or_value));
+ store.register_late_pass(|| Box::new(ref_option_ref::RefOptionRef));
+ store.register_late_pass(|| Box::new(try_err::TryErr));
+ store.register_late_pass(|| Box::new(bytecount::ByteCount));
+ store.register_late_pass(|| Box::new(infinite_iter::InfiniteIter));
+ store.register_late_pass(|| Box::new(inline_fn_without_body::InlineFnWithoutBody));
+ store.register_late_pass(|| Box::new(useless_conversion::UselessConversion::default()));
+ store.register_late_pass(|| Box::new(implicit_hasher::ImplicitHasher));
+ store.register_late_pass(|| Box::new(fallible_impl_from::FallibleImplFrom));
+ store.register_late_pass(|| Box::new(double_comparison::DoubleComparisons));
+ store.register_late_pass(|| Box::new(question_mark::QuestionMark));
+ store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings));
+ store.register_late_pass(|| Box::new(suspicious_trait_impl::SuspiciousImpl));
+ store.register_late_pass(|| Box::new(map_unit_fn::MapUnit));
+ store.register_late_pass(|| Box::new(inherent_impl::MultipleInherentImpl));
+ store.register_late_pass(|| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd));
+ store.register_late_pass(|| Box::new(unwrap::Unwrap));
+ store.register_late_pass(|| Box::new(duration_subsec::DurationSubsec));
+ store.register_late_pass(|| Box::new(indexing_slicing::IndexingSlicing));
+ store.register_late_pass(|| Box::new(non_copy_const::NonCopyConst));
+ store.register_late_pass(|| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
+ store.register_late_pass(|| Box::new(redundant_clone::RedundantClone));
+ store.register_late_pass(|| Box::new(slow_vector_initialization::SlowVectorInit));
+ store.register_late_pass(|| Box::new(unnecessary_sort_by::UnnecessarySortBy));
+ store.register_late_pass(move || Box::new(unnecessary_wraps::UnnecessaryWraps::new(avoid_breaking_exported_api)));
+ store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants));
+ store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull));
+ store.register_late_pass(|| Box::new(path_buf_push_overwrite::PathBufPushOverwrite));
+ store.register_late_pass(|| Box::new(integer_division::IntegerDivision));
+ store.register_late_pass(|| Box::new(inherent_to_string::InherentToString));
+ let max_trait_bounds = conf.max_trait_bounds;
+ store.register_late_pass(move || Box::new(trait_bounds::TraitBounds::new(max_trait_bounds)));
+ store.register_late_pass(|| Box::new(comparison_chain::ComparisonChain));
+ store.register_late_pass(|| Box::new(mut_key::MutableKeyType));
+ store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic));
+ store.register_early_pass(|| Box::new(reference::DerefAddrOf));
+ store.register_early_pass(|| Box::new(reference::RefInDeref));
+ store.register_early_pass(|| Box::new(double_parens::DoubleParens));
+ store.register_late_pass(|| Box::new(to_string_in_display::ToStringInDisplay::new()));
+ store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval));
+ store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
+ store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));
+ store.register_early_pass(|| Box::new(formatting::Formatting));
+ store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints));
+ store.register_early_pass(|| Box::new(redundant_closure_call::RedundantClosureCall));
+ store.register_late_pass(|| Box::new(redundant_closure_call::RedundantClosureCall));
+ store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
+ store.register_late_pass(|| Box::new(returns::Return));
+ store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
+ store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements));
+ store.register_early_pass(|| Box::new(precedence::Precedence));
+ store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue));
+ store.register_early_pass(|| Box::new(redundant_else::RedundantElse));
+ store.register_late_pass(|| Box::new(create_dir::CreateDir));
+ store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType));
+ let cargo_ignore_publish = conf.cargo_ignore_publish;
+ store.register_late_pass(move || Box::new(cargo_common_metadata::CargoCommonMetadata::new(cargo_ignore_publish)));
+ store.register_late_pass(|| Box::new(multiple_crate_versions::MultipleCrateVersions));
+ store.register_late_pass(|| Box::new(wildcard_dependencies::WildcardDependencies));
+ let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions;
- store.register_early_pass(move || Box::new(literal_representation::DecimalLiteralRepresentation::new(literal_representation_threshold)));
++ store.register_early_pass(move || {
++ Box::new(literal_representation::LiteralDigitGrouping::new(
++ literal_representation_lint_fraction_readability,
++ ))
++ });
+ let literal_representation_threshold = conf.literal_representation_threshold;
- store.register_late_pass(move || Box::new(enum_variants::EnumVariantNames::new(enum_variant_name_threshold, avoid_breaking_exported_api)));
++ store.register_early_pass(move || {
++ Box::new(literal_representation::DecimalLiteralRepresentation::new(
++ literal_representation_threshold,
++ ))
++ });
+ let enum_variant_name_threshold = conf.enum_variant_name_threshold;
- store.register_late_pass(move || Box::new(upper_case_acronyms::UpperCaseAcronyms::new(avoid_breaking_exported_api, upper_case_acronyms_aggressive)));
++ store.register_late_pass(move || {
++ Box::new(enum_variants::EnumVariantNames::new(
++ enum_variant_name_threshold,
++ avoid_breaking_exported_api,
++ ))
++ });
+ store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments));
+ let upper_case_acronyms_aggressive = conf.upper_case_acronyms_aggressive;
- store.register_early_pass(move || Box::new(excessive_bools::ExcessiveBools::new(max_struct_bools, max_fn_params_bools)));
++ store.register_late_pass(move || {
++ Box::new(upper_case_acronyms::UpperCaseAcronyms::new(
++ avoid_breaking_exported_api,
++ upper_case_acronyms_aggressive,
++ ))
++ });
+ store.register_late_pass(|| Box::new(default::Default::default()));
+ store.register_late_pass(|| Box::new(unused_self::UnusedSelf));
+ store.register_late_pass(|| Box::new(mutable_debug_assertion::DebugAssertWithMutCall));
+ store.register_late_pass(|| Box::new(exit::Exit));
+ store.register_late_pass(|| Box::new(to_digit_is_some::ToDigitIsSome));
+ let array_size_threshold = conf.array_size_threshold;
+ store.register_late_pass(move || Box::new(large_stack_arrays::LargeStackArrays::new(array_size_threshold)));
+ store.register_late_pass(move || Box::new(large_const_arrays::LargeConstArrays::new(array_size_threshold)));
+ store.register_late_pass(|| Box::new(floating_point_arithmetic::FloatingPointArithmetic));
+ store.register_early_pass(|| Box::new(as_conversions::AsConversions));
+ store.register_late_pass(|| Box::new(let_underscore::LetUnderscore));
+ store.register_early_pass(|| Box::new(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::new(non_expressive_names::NonExpressiveNames {
- single_char_binding_names_threshold,
- }));
++ store.register_early_pass(move || {
++ Box::new(excessive_bools::ExcessiveBools::new(
++ max_struct_bools,
++ max_fn_params_bools,
++ ))
++ });
+ store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
+ let warn_on_all_wildcard_imports = conf.warn_on_all_wildcard_imports;
+ store.register_late_pass(move || Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
+ store.register_late_pass(|| Box::new(verbose_file_reads::VerboseFileReads));
+ store.register_late_pass(|| Box::new(redundant_pub_crate::RedundantPubCrate::default()));
+ store.register_late_pass(|| Box::new(unnamed_address::UnnamedAddress));
+ store.register_late_pass(|| Box::new(dereference::Dereferencing::default()));
+ store.register_late_pass(|| Box::new(option_if_let_else::OptionIfLetElse));
+ store.register_late_pass(|| Box::new(future_not_send::FutureNotSend));
+ store.register_late_pass(|| Box::new(if_let_mutex::IfLetMutex));
+ store.register_late_pass(|| Box::new(if_not_else::IfNotElse));
+ store.register_late_pass(|| Box::new(equatable_if_let::PatternEquality));
+ store.register_late_pass(|| Box::new(mut_mutex_lock::MutMutexLock));
+ store.register_late_pass(|| Box::new(match_on_vec_items::MatchOnVecItems));
+ store.register_late_pass(|| Box::new(manual_async_fn::ManualAsyncFn));
+ store.register_late_pass(|| Box::new(vec_resize_to_zero::VecResizeToZero));
+ store.register_late_pass(|| Box::new(panic_in_result_fn::PanicInResultFn));
+ let single_char_binding_names_threshold = conf.single_char_binding_names_threshold;
- store.register_late_pass(move || Box::new(disallowed_method::DisallowedMethod::new(disallowed_methods.clone())));
++ store.register_early_pass(move || {
++ Box::new(non_expressive_names::NonExpressiveNames {
++ single_char_binding_names_threshold,
++ })
++ });
+ let macro_matcher = conf.standard_macro_braces.iter().cloned().collect::<FxHashSet<_>>();
+ store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(¯o_matcher)));
+ store.register_late_pass(|| Box::new(macro_use::MacroUseImports::default()));
+ store.register_late_pass(|| Box::new(pattern_type_mismatch::PatternTypeMismatch));
+ store.register_late_pass(|| Box::new(stable_sort_primitive::StableSortPrimitive));
+ store.register_late_pass(|| Box::new(repeat_once::RepeatOnce));
+ store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult));
+ store.register_late_pass(|| Box::new(self_assignment::SelfAssignment));
+ store.register_late_pass(|| Box::new(manual_unwrap_or::ManualUnwrapOr));
+ store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr));
+ store.register_late_pass(|| Box::new(float_equality_without_abs::FloatEqualityWithoutAbs));
+ store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
+ store.register_late_pass(|| Box::new(async_yields_async::AsyncYieldsAsync));
+ let disallowed_methods = conf.disallowed_methods.clone();
- store.register_late_pass(|| Box::new(case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons));
++ store.register_late_pass(move || Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
+ store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
+ store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
+ store.register_late_pass(|| Box::new(undropped_manually_drops::UndroppedManuallyDrops));
+ store.register_late_pass(|| Box::new(strings::StrToString));
+ store.register_late_pass(|| Box::new(strings::StringToString));
+ store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues));
+ store.register_late_pass(|| Box::new(vec_init_then_push::VecInitThenPush::default()));
- store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(disallowed_types.clone())));
++ store.register_late_pass(|| {
++ Box::new(case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons)
++ });
+ store.register_late_pass(|| Box::new(redundant_slicing::RedundantSlicing));
+ store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10));
+ store.register_late_pass(|| Box::new(manual_map::ManualMap));
+ store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
+ store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
+ store.register_early_pass(move || Box::new(module_style::ModStyle));
+ store.register_late_pass(|| Box::new(unused_async::UnusedAsync));
+ let disallowed_types = conf.disallowed_types.clone();
- store.register_late_pass(move || Box::new(missing_enforced_import_rename::ImportRename::new(import_renames.clone())));
++ store.register_late_pass(move || Box::new(disallowed_types::DisallowedTypes::new(disallowed_types.clone())));
+ let import_renames = conf.enforced_import_renames.clone();
- store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send)));
++ store.register_late_pass(move || {
++ Box::new(missing_enforced_import_rename::ImportRename::new(
++ import_renames.clone(),
++ ))
++ });
+ let scripts = conf.allowed_scripts.clone();
+ store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
+ store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings));
+ store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors));
+ store.register_late_pass(move || Box::new(feature_name::FeatureName));
+ store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator));
+ store.register_late_pass(move || Box::new(manual_assert::ManualAssert));
+ let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
++ store.register_late_pass(move || {
++ Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(
++ enable_raw_pointer_heuristic_for_send,
++ ))
++ });
+ store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
+ store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
+ store.register_late_pass(move || Box::new(format_args::FormatArgs));
+ store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
++ store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
++ store.register_late_pass(|| Box::new(needless_late_init::NeedlessLateInit));
+ // add lints here, do not remove this comment, it's used in `new_lint`
+}
+
+#[rustfmt::skip]
+fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
+ store.register_removed(
+ "should_assert_eq",
+ "`assert!()` will be more flexible with RFC 2011",
+ );
+ store.register_removed(
+ "extend_from_slice",
+ "`.extend_from_slice(_)` is a faster way to extend a Vec by a slice",
+ );
+ store.register_removed(
+ "range_step_by_zero",
+ "`iterator.step_by(0)` panics nowadays",
+ );
+ store.register_removed(
+ "unstable_as_slice",
+ "`Vec::as_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "unstable_as_mut_slice",
+ "`Vec::as_mut_slice` has been stabilized in 1.7",
+ );
+ store.register_removed(
+ "misaligned_transmute",
+ "this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
+ );
+ store.register_removed(
+ "assign_ops",
+ "using compound assignment operators (e.g., `+=`) is harmless",
+ );
+ store.register_removed(
+ "if_let_redundant_pattern_matching",
+ "this lint has been changed to redundant_pattern_matching",
+ );
+ store.register_removed(
+ "unsafe_vector_initialization",
+ "the replacement suggested by this lint had substantially different behavior",
+ );
+ store.register_removed(
+ "reverse_range_loop",
+ "this lint is now included in reversed_empty_ranges",
+ );
+}
+
+/// Register renamed lints.
+///
+/// Used in `./src/driver.rs`.
+pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
+ // NOTE: when renaming a lint, add a corresponding test to tests/ui/rename.rs
+ 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::box_vec", "clippy::box_collection");
+ ls.register_renamed("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions");
+ ls.register_renamed("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions");
+ ls.register_renamed("clippy::option_map_unwrap_or", "clippy::map_unwrap_or");
+ ls.register_renamed("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or");
+ ls.register_renamed("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or");
+ ls.register_renamed("clippy::option_unwrap_used", "clippy::unwrap_used");
+ ls.register_renamed("clippy::result_unwrap_used", "clippy::unwrap_used");
+ ls.register_renamed("clippy::option_expect_used", "clippy::expect_used");
+ ls.register_renamed("clippy::result_expect_used", "clippy::expect_used");
+ ls.register_renamed("clippy::for_loop_over_option", "clippy::for_loops_over_fallibles");
+ ls.register_renamed("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles");
+ ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion");
+ ls.register_renamed("clippy::zero_width_space", "clippy::invisible_characters");
+ ls.register_renamed("clippy::single_char_push_str", "clippy::single_char_add_str");
+ ls.register_renamed("clippy::if_let_some_result", "clippy::match_result_ok");
++ ls.register_renamed("clippy::disallowed_type", "clippy::disallowed_types");
++ ls.register_renamed("clippy::disallowed_method", "clippy::disallowed_methods");
+
+ // uplifted lints
+ ls.register_renamed("clippy::invalid_ref", "invalid_value");
+ ls.register_renamed("clippy::into_iter_on_array", "array_into_iter");
+ ls.register_renamed("clippy::unused_label", "unused_labels");
+ ls.register_renamed("clippy::drop_bounds", "drop_bounds");
+ ls.register_renamed("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr");
+ ls.register_renamed("clippy::panic_params", "non_fmt_panics");
+ ls.register_renamed("clippy::unknown_clippy_lints", "unknown_lints");
+ ls.register_renamed("clippy::invalid_atomic_ordering", "invalid_atomic_ordering");
+ ls.register_renamed("clippy::mem_discriminant_non_enum", "enum_intrinsics_non_enums");
+}
+
+// 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
- use clippy_utils::{in_macro, trait_ref_of_method};
+use clippy_utils::diagnostics::span_lint;
- if in_macro(span) || has_where_lifetimes(cx, &generics.where_clause) {
++use clippy_utils::trait_ref_of_method;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::intravisit::{
+ walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty,
+ NestedVisitorMap, Visitor,
+};
+use rustc_hir::FnRetTy::Return;
+use rustc_hir::{
+ BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem,
+ ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier,
+ TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WhereClause, WherePredicate,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{kw, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for lifetime annotations which can be removed by
+ /// relying on lifetime elision.
+ ///
+ /// ### Why is this bad?
+ /// The additional lifetimes make the code look more
+ /// complicated, while there is nothing out of the ordinary going on. Removing
+ /// them leads to more readable code.
+ ///
+ /// ### Known problems
+ /// - We bail out if the function has a `where` clause where lifetimes
+ /// are mentioned due to potenial false positives.
+ /// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the
+ /// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad: unnecessary lifetime annotations
+ /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 {
+ /// x
+ /// }
+ ///
+ /// // Good
+ /// fn elided(x: &u8, y: u8) -> &u8 {
+ /// x
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad: unnecessary lifetimes
+ /// fn unused_lifetime<'a>(x: u8) {
+ /// // ..
+ /// }
+ ///
+ /// // Good
+ /// fn no_lifetime(x: u8) {
+ /// // ...
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EXTRA_UNUSED_LIFETIMES,
+ complexity,
+ "unused lifetimes in function definitions"
+}
+
+declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]);
+
+impl<'tcx> LateLintPass<'tcx> for Lifetimes {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if let ItemKind::Fn(ref sig, ref generics, id) = item.kind {
+ check_fn_inner(cx, sig.decl, Some(id), generics, item.span, true);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if let ImplItemKind::Fn(ref sig, id) = item.kind {
+ let report_extra_lifetimes = trait_ref_of_method(cx, item.hir_id()).is_none();
+ check_fn_inner(
+ cx,
+ sig.decl,
+ Some(id),
+ &item.generics,
+ item.span,
+ report_extra_lifetimes,
+ );
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Fn(ref sig, ref body) = item.kind {
+ let body = match *body {
+ TraitFn::Required(_) => None,
+ TraitFn::Provided(id) => Some(id),
+ };
+ check_fn_inner(cx, sig.decl, body, &item.generics, item.span, true);
+ }
+ }
+}
+
+/// The lifetime of a &-reference.
+#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+enum RefLt {
+ Unnamed,
+ Static,
+ Named(Symbol),
+}
+
+fn check_fn_inner<'tcx>(
+ cx: &LateContext<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: Option<BodyId>,
+ generics: &'tcx Generics<'_>,
+ span: Span,
+ report_extra_lifetimes: bool,
+) {
++ if span.from_expansion() || has_where_lifetimes(cx, &generics.where_clause) {
+ return;
+ }
+
+ let types = generics
+ .params
+ .iter()
+ .filter(|param| matches!(param.kind, GenericParamKind::Type { .. }));
+ for typ in types {
+ for bound in typ.bounds {
+ let mut visitor = RefVisitor::new(cx);
+ walk_param_bound(&mut visitor, bound);
+ if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) {
+ return;
+ }
+ if let GenericBound::Trait(ref trait_ref, _) = *bound {
+ let params = &trait_ref
+ .trait_ref
+ .path
+ .segments
+ .last()
+ .expect("a path must have at least one segment")
+ .args;
+ if let Some(params) = *params {
+ let lifetimes = params.args.iter().filter_map(|arg| match arg {
+ GenericArg::Lifetime(lt) => Some(lt),
+ _ => None,
+ });
+ for bound in lifetimes {
+ if bound.name != LifetimeName::Static && !bound.is_elided() {
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ if could_use_elision(cx, decl, body, generics.params) {
+ span_lint(
+ cx,
+ NEEDLESS_LIFETIMES,
+ span.with_hi(decl.output.span().hi()),
+ "explicit lifetimes given in parameter types where they could be elided \
+ (or replaced with `'_` if needed by type declaration)",
+ );
+ }
+ if report_extra_lifetimes {
+ self::report_extra_lifetimes(cx, decl, generics);
+ }
+}
+
+fn could_use_elision<'tcx>(
+ cx: &LateContext<'tcx>,
+ func: &'tcx FnDecl<'_>,
+ body: Option<BodyId>,
+ named_generics: &'tcx [GenericParam<'_>],
+) -> bool {
+ // There are two scenarios where elision works:
+ // * no output references, all input references have different LT
+ // * output references, exactly one input reference with same LT
+ // All lifetimes must be unnamed, 'static or defined without bounds on the
+ // level of the current item.
+
+ // check named LTs
+ let allowed_lts = allowed_lts_from(named_generics);
+
+ // these will collect all the lifetimes for references in arg/return types
+ let mut input_visitor = RefVisitor::new(cx);
+ let mut output_visitor = RefVisitor::new(cx);
+
+ // extract lifetimes in input argument types
+ for arg in func.inputs {
+ input_visitor.visit_ty(arg);
+ }
+ // extract lifetimes in output type
+ if let Return(ty) = func.output {
+ output_visitor.visit_ty(ty);
+ }
+ for lt in named_generics {
+ input_visitor.visit_generic_param(lt);
+ }
+
+ if input_visitor.abort() || output_visitor.abort() {
+ return false;
+ }
+
+ if allowed_lts
+ .intersection(
+ &input_visitor
+ .nested_elision_site_lts
+ .iter()
+ .chain(output_visitor.nested_elision_site_lts.iter())
+ .cloned()
+ .filter(|v| matches!(v, RefLt::Named(_)))
+ .collect(),
+ )
+ .next()
+ .is_some()
+ {
+ return false;
+ }
+
+ let input_lts = input_visitor.lts;
+ let output_lts = output_visitor.lts;
+
+ if let Some(body_id) = body {
+ let mut checker = BodyLifetimeChecker {
+ lifetimes_used_in_body: false,
+ };
+ checker.visit_expr(&cx.tcx.hir().body(body_id).value);
+ if checker.lifetimes_used_in_body {
+ return false;
+ }
+ }
+
+ // check for lifetimes from higher scopes
+ for lt in input_lts.iter().chain(output_lts.iter()) {
+ if !allowed_lts.contains(lt) {
+ return false;
+ }
+ }
+
+ // no input lifetimes? easy case!
+ if input_lts.is_empty() {
+ false
+ } else if output_lts.is_empty() {
+ // no output lifetimes, check distinctness of input lifetimes
+
+ // only unnamed and static, ok
+ let unnamed_and_static = input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
+ if unnamed_and_static {
+ return false;
+ }
+ // we have no output reference, so we only need all distinct lifetimes
+ input_lts.len() == unique_lifetimes(&input_lts)
+ } else {
+ // we have output references, so we need one input reference,
+ // and all output lifetimes must be the same
+ if unique_lifetimes(&output_lts) > 1 {
+ return false;
+ }
+ if input_lts.len() == 1 {
+ match (&input_lts[0], &output_lts[0]) {
+ (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
+ (&RefLt::Named(_), &RefLt::Unnamed) => true,
+ _ => false, /* already elided, different named lifetimes
+ * or something static going on */
+ }
+ } else {
+ false
+ }
+ }
+}
+
+fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<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
+}
+
+/// Number of unique lifetimes in the given vector.
+#[must_use]
+fn unique_lifetimes(lts: &[RefLt]) -> usize {
+ lts.iter().collect::<FxHashSet<_>>().len()
+}
+
+const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, LangItem::FnOnce];
+
+/// A visitor usable for `rustc_front::visit::walk_ty()`.
+struct RefVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ lts: Vec<RefLt>,
+ nested_elision_site_lts: Vec<RefLt>,
+ unelided_trait_object_lifetime: bool,
+}
+
+impl<'a, 'tcx> RefVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ lts: Vec::new(),
+ nested_elision_site_lts: Vec::new(),
+ unelided_trait_object_lifetime: false,
+ }
+ }
+
+ fn record(&mut self, lifetime: &Option<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 all_lts(&self) -> Vec<RefLt> {
+ self.lts
+ .iter()
+ .chain(self.nested_elision_site_lts.iter())
+ .cloned()
+ .collect::<Vec<_>>()
+ }
+
+ fn abort(&self) -> bool {
+ self.unelided_trait_object_lifetime
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ // for lifetimes as parameters of generics
+ fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
+ self.record(&Some(*lifetime));
+ }
+
+ fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>, tbm: TraitBoundModifier) {
+ let trait_ref = &poly_tref.trait_ref;
+ if CLOSURE_TRAIT_BOUNDS.iter().any(|&item| {
+ self.cx
+ .tcx
+ .lang_items()
+ .require(item)
+ .map_or(false, |id| Some(id) == trait_ref.trait_def_id())
+ }) {
+ let mut sub_visitor = RefVisitor::new(self.cx);
+ sub_visitor.visit_trait_ref(trait_ref);
+ self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
+ } else {
+ walk_poly_trait_ref(self, poly_tref, tbm);
+ }
+ }
+
+ fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
+ match ty.kind {
+ TyKind::OpaqueDef(item, bounds) => {
+ let map = self.cx.tcx.hir();
+ let item = map.item(item);
+ walk_item(self, item);
+ walk_ty(self, ty);
+ self.lts.extend(bounds.iter().filter_map(|bound| match bound {
+ GenericArg::Lifetime(l) => Some(RefLt::Named(l.name.ident().name)),
+ _ => None,
+ }));
+ },
+ TyKind::BareFn(&BareFnTy { decl, .. }) => {
+ let mut sub_visitor = RefVisitor::new(self.cx);
+ sub_visitor.visit_fn_decl(decl);
+ self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
+ return;
+ },
+ TyKind::TraitObject(bounds, ref lt, _) => {
+ if !lt.is_elided() {
+ self.unelided_trait_object_lifetime = true;
+ }
+ for bound in bounds {
+ self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
+ }
+ return;
+ },
+ _ => (),
+ }
+ walk_ty(self, ty);
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<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.all_lts().is_empty() {
+ return true;
+ }
+ // if the bounds define new lifetimes, they are fine to occur
+ let allowed_lts = allowed_lts_from(pred.bound_generic_params);
+ // now walk the bounds
+ for bound in pred.bounds.iter() {
+ walk_param_bound(&mut visitor, bound);
+ }
+ // and check that all lifetimes are allowed
+ if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) {
+ return true;
+ }
+ },
+ WherePredicate::EqPredicate(ref pred) => {
+ let mut visitor = RefVisitor::new(cx);
+ walk_ty(&mut visitor, pred.lhs_ty);
+ walk_ty(&mut visitor, pred.rhs_ty);
+ if !visitor.lts.is_empty() {
+ return true;
+ }
+ },
+ }
+ }
+ false
+}
+
+struct LifetimeChecker {
+ map: FxHashMap<Symbol, 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::Empty && 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
- use clippy_utils::{
- in_macro,
- numeric_literal::{NumericLiteral, Radix},
- };
+//! Lints concerned with the grouping of digits with underscores in integral or
+//! floating-point literal expressions.
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
++use clippy_utils::numeric_literal::{NumericLiteral, Radix};
+use clippy_utils::source::snippet_opt;
- !in_macro(lit.span)
+use if_chain::if_chain;
+use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use std::iter;
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x: u64 = 61864918973511;
+ ///
+ /// // Good
+ /// let x: u64 = 61_864_918_973_511;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNREADABLE_LITERAL,
+ pedantic,
+ "long literal without underscores"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns for mistyped suffix in literals
+ ///
+ /// ### Why is this bad?
+ /// This is most probably a typo
+ ///
+ /// ### Known problems
+ /// - Recommends a signed suffix, even though the number might be too big and an unsigned
+ /// suffix is required
+ /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Probably mistyped
+ /// 2_32;
+ ///
+ /// // Good
+ /// 2_i32;
+ /// ```
++ #[clippy::version = "1.30.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x: u64 = 618_64_9189_73_511;
+ ///
+ /// // Good
+ /// let x: u64 = 61_864_918_973_511;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INCONSISTENT_DIGIT_GROUPING,
+ style,
+ "integer literals with digits grouped inconsistently"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if hexadecimal or binary literals are not grouped
+ /// by nibble or byte.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: u32 = 0xFFF_FFF;
+ /// let y: u8 = 0b01_011_101;
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub UNUSUAL_BYTE_GROUPINGS,
+ style,
+ "binary or hex literals that aren't grouped by four"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if the digits of an integral or floating-point
+ /// constant are grouped into groups that
+ /// are too large.
+ ///
+ /// ### Why is this bad?
+ /// Negatively impacts readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: u64 = 6186491_8973511;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// `255` => `0xFF`
+ /// `65_535` => `0xFFFF`
+ /// `4_042_322_160` => `0xF0F0_F0F0`
++ #[clippy::version = "pre 1.29.0"]
+ pub DECIMAL_LITERAL_REPRESENTATION,
+ restriction,
+ "using decimal representation when hexadecimal would be better"
+}
+
+enum WarningType {
+ UnreadableLiteral,
+ InconsistentDigitGrouping,
+ LargeDigitGroups,
+ DecimalRepresentation,
+ MistypedLiteralSuffix,
+ UnusualByteGroupings,
+}
+
+impl WarningType {
+ fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: rustc_span::Span) {
+ match self {
+ Self::MistypedLiteralSuffix => span_lint_and_sugg(
+ cx,
+ MISTYPED_LITERAL_SUFFIXES,
+ span,
+ "mistyped literal suffix",
+ "did you mean to write",
+ suggested_format,
+ Applicability::MaybeIncorrect,
+ ),
+ Self::UnreadableLiteral => span_lint_and_sugg(
+ cx,
+ UNREADABLE_LITERAL,
+ span,
+ "long literal lacking separators",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::LargeDigitGroups => span_lint_and_sugg(
+ cx,
+ LARGE_DIGIT_GROUPS,
+ span,
+ "digit groups should be smaller",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::InconsistentDigitGrouping => span_lint_and_sugg(
+ cx,
+ INCONSISTENT_DIGIT_GROUPING,
+ span,
+ "digits grouped inconsistently by underscores",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::DecimalRepresentation => span_lint_and_sugg(
+ cx,
+ DECIMAL_LITERAL_REPRESENTATION,
+ span,
+ "integer literal has a better hexadecimal representation",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ Self::UnusualByteGroupings => span_lint_and_sugg(
+ cx,
+ UNUSUAL_BYTE_GROUPINGS,
+ span,
+ "digits of hex or binary literal not grouped by four",
+ "consider",
+ suggested_format,
+ Applicability::MachineApplicable,
+ ),
+ };
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct LiteralDigitGrouping {
+ lint_fraction_readability: bool,
+}
+
+impl_lint_pass!(LiteralDigitGrouping => [
+ UNREADABLE_LITERAL,
+ INCONSISTENT_DIGIT_GROUPING,
+ LARGE_DIGIT_GROUPS,
+ MISTYPED_LITERAL_SUFFIXES,
+ UNUSUAL_BYTE_GROUPINGS,
+]);
+
+impl EarlyLintPass for LiteralDigitGrouping {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess, expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ self.check_lit(cx, lit);
+ }
+ }
+}
+
+// Length of each UUID hyphenated group in hex digits.
+const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
+
+impl LiteralDigitGrouping {
+ pub fn new(lint_fraction_readability: bool) -> Self {
+ Self {
+ lint_fraction_readability,
+ }
+ }
+
+ fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
+ if_chain! {
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(mut num_lit) = NumericLiteral::from_lit(&src, lit);
+ then {
+ if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
+ return;
+ }
+
+ if Self::is_literal_uuid_formatted(&mut num_lit) {
+ return;
+ }
+
+ let result = (|| {
+
+ let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
+ if let Some(fraction) = num_lit.fraction {
+ let fractional_group_size = Self::get_group_size(
+ fraction.rsplit('_'),
+ num_lit.radix,
+ self.lint_fraction_readability)?;
+
+ let consistent = Self::parts_consistent(integral_group_size,
+ fractional_group_size,
+ num_lit.integer.len(),
+ fraction.len());
+ if !consistent {
+ return Err(WarningType::InconsistentDigitGrouping);
+ };
+ }
+
+ Ok(())
+ })();
+
+
+ if let Err(warning_type) = result {
+ let should_warn = match warning_type {
+ | WarningType::UnreadableLiteral
+ | WarningType::InconsistentDigitGrouping
+ | WarningType::UnusualByteGroupings
+ | WarningType::LargeDigitGroups => {
++ !lit.span.from_expansion()
+ }
+ WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => {
+ true
+ }
+ };
+ if should_warn {
+ warning_type.display(num_lit.format(), cx, lit.span);
+ }
+ }
+ }
+ }
+ }
+
+ // Returns `false` if the check fails
+ fn check_for_mistyped_suffix(
+ cx: &EarlyContext<'_>,
+ span: rustc_span::Span,
+ num_lit: &mut NumericLiteral<'_>,
+ ) -> bool {
+ if num_lit.suffix.is_some() {
+ return true;
+ }
+
+ let (part, mistyped_suffixes, missing_char) = if let Some((_, exponent)) = &mut num_lit.exponent {
+ (exponent, &["32", "64"][..], 'f')
+ } else if num_lit.fraction.is_some() {
+ (&mut num_lit.integer, &["32", "64"][..], 'f')
+ } else {
+ (&mut num_lit.integer, &["8", "16", "32", "64"][..], 'i')
+ };
+
+ let mut split = part.rsplit('_');
+ let last_group = split.next().expect("At least one group");
+ if split.next().is_some() && mistyped_suffixes.contains(&last_group) {
+ *part = &part[..part.len() - last_group.len()];
+ let mut sugg = num_lit.format();
+ sugg.push('_');
+ sugg.push(missing_char);
+ sugg.push_str(last_group);
+ WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
+ false
+ } else {
+ true
+ }
+ }
+
+ /// Checks whether the numeric literal matches the formatting of a UUID.
+ ///
+ /// Returns `true` if the radix is hexadecimal, and the groups match the
+ /// UUID format of 8-4-4-4-12.
+ fn is_literal_uuid_formatted(num_lit: &mut NumericLiteral<'_>) -> bool {
+ if num_lit.radix != Radix::Hexadecimal {
+ return false;
+ }
+
+ // UUIDs should not have a fraction
+ if num_lit.fraction.is_some() {
+ return false;
+ }
+
+ let group_sizes: Vec<usize> = num_lit.integer.split('_').map(str::len).collect();
+ if UUID_GROUP_LENS.len() == group_sizes.len() {
+ iter::zip(&UUID_GROUP_LENS, &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>,
+ radix: Radix,
+ lint_unreadable: bool,
+ ) -> Result<Option<usize>, WarningType> {
+ let mut groups = groups.map(str::len);
+
+ let first = groups.next().expect("At least one group");
+
+ if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) {
+ return Err(WarningType::UnusualByteGroupings);
+ }
+
+ if let Some(second) = groups.next() {
+ if !groups.all(|x| x == second) || first > second {
+ Err(WarningType::InconsistentDigitGrouping)
+ } else if second > 4 {
+ Err(WarningType::LargeDigitGroups)
+ } else {
+ Ok(Some(second))
+ }
+ } else if first > 5 && lint_unreadable {
+ Err(WarningType::UnreadableLiteral)
+ } else {
+ Ok(None)
+ }
+ }
+}
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct DecimalLiteralRepresentation {
+ threshold: u64,
+}
+
+impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION]);
+
+impl EarlyLintPass for DecimalLiteralRepresentation {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess, expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ self.check_lit(cx, lit);
+ }
+ }
+}
+
+impl DecimalLiteralRepresentation {
+ #[must_use]
+ pub fn new(threshold: u64) -> Self {
+ Self { threshold }
+ }
+ fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
+ // Lint integral literals.
+ if_chain! {
+ if let LitKind::Int(val, _) = lit.kind;
+ if let Some(src) = snippet_opt(cx, lit.span);
+ if let Some(num_lit) = NumericLiteral::from_lit(&src, lit);
+ if num_lit.radix == Radix::Decimal;
+ if val >= u128::from(self.threshold);
+ then {
+ let hex = format!("{:#X}", val);
+ let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
+ let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| {
+ warning_type.display(num_lit.format(), cx, lit.span);
+ });
+ }
+ }
+ }
+
+ fn do_lint(digits: &str) -> Result<(), WarningType> {
+ if digits.len() == 1 {
+ // Lint for 1 digit literals, if someone really sets the threshold that low
+ if digits == "1"
+ || digits == "2"
+ || digits == "4"
+ || digits == "8"
+ || digits == "3"
+ || digits == "7"
+ || digits == "F"
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else if digits.len() < 4 {
+ // Lint for Literals with a hex-representation of 2 or 3 digits
+ let f = &digits[0..1]; // first digit
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && s.chars().all(|c| c == '0'))
+ // Powers of 2 minus 1
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && s.chars().all(|c| c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ } else {
+ // Lint for Literals with a hex-representation of 4 digits or more
+ let f = &digits[0..1]; // first digit
+ let m = &digits[1..digits.len() - 1]; // middle digits, except last
+ let s = &digits[1..]; // suffix
+
+ // Powers of 2 with a margin of +15/-16
+ if ((f.eq("1") || f.eq("2") || f.eq("4") || f.eq("8")) && m.chars().all(|c| c == '0'))
+ || ((f.eq("1") || f.eq("3") || f.eq("7") || f.eq("F")) && m.chars().all(|c| c == 'F'))
+ // Lint for representations with only 0s and Fs, while allowing 7 as the first
+ // digit
+ || ((f.eq("7") || f.eq("F")) && s.chars().all(|c| c == '0' || c == 'F'))
+ {
+ return Err(WarningType::DecimalRepresentation);
+ }
+ }
+
+ Ok(())
+ }
+}
--- /dev/null
- use clippy_utils::diagnostics::span_lint_and_sugg;
+use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP};
- if let Some((name, initializer)) = initialize_visitor.get_result();
++use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{get_enclosing_block, is_integer_const};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr};
+use rustc_hir::{Expr, Pat};
+use rustc_lint::LateContext;
++use rustc_middle::ty::{self, UintTy};
+
+// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be
+// incremented exactly once in the loop body, and initialized to zero
+// at the start of the loop.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) {
+ // Look for variables that are incremented once per loop iteration.
+ let mut increment_visitor = IncrementVisitor::new(cx);
+ walk_expr(&mut increment_visitor, body);
+
+ // For each candidate, check the parent block to see if
+ // it's initialized to zero at the start of the loop.
+ if let Some(block) = get_enclosing_block(cx, expr.hir_id) {
+ for id in increment_visitor.into_results() {
+ let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
+ walk_block(&mut initialize_visitor, block);
+
+ if_chain! {
- span_lint_and_sugg(
++ if let Some((name, ty, initializer)) = initialize_visitor.get_result();
+ if is_integer_const(cx, initializer, 0);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+
- "consider using",
- format!(
- "for ({}, {}) in {}.enumerate()",
- name,
- snippet_with_applicability(cx, pat.span, "item", &mut applicability),
- make_iterator_snippet(cx, arg, &mut applicability),
- ),
- applicability,
++ let int_name = match ty.map(ty::TyS::kind) {
++ // usize or inferred
++ Some(ty::Uint(UintTy::Usize)) | None => {
++ span_lint_and_sugg(
++ cx,
++ EXPLICIT_COUNTER_LOOP,
++ expr.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,
++ );
++ return;
++ }
++ Some(ty::Int(int_ty)) => int_ty.name_str(),
++ Some(ty::Uint(uint_ty)) => uint_ty.name_str(),
++ _ => return,
++ };
++
++ span_lint_and_then(
+ cx,
+ EXPLICIT_COUNTER_LOOP,
+ expr.span.with_hi(arg.span.hi()),
+ &format!("the variable `{}` is used as a loop counter", name),
++ |diag| {
++ diag.span_suggestion(
++ expr.span.with_hi(arg.span.hi()),
++ "consider using",
++ format!(
++ "for ({}, {}) in (0_{}..).zip({})",
++ name,
++ snippet_with_applicability(cx, pat.span, "item", &mut applicability),
++ int_name,
++ make_iterator_snippet(cx, arg, &mut applicability),
++ ),
++ applicability,
++ );
++
++ diag.note(&format!(
++ "`{}` is of type `{}`, making it ineligible for `Iterator::enumerate`",
++ name, int_name
++ ));
++ },
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- initialize_visitor.get_result().map(|(_, initializer)| Start {
+use super::{IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_enclosing_block, higher, path_to_local, sugg};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::walk_block;
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Pat, PatKind, StmtKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::sym;
+use std::iter::Iterator;
+
+/// Checks for for loops that sequentially copy items from one slice-like
+/// object to another.
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) -> bool {
+ if let Some(higher::Range {
+ start: Some(start),
+ end: Some(end),
+ limits,
+ }) = higher::Range::hir(arg)
+ {
+ // the var must be a single name
+ if let PatKind::Binding(_, canonical_id, _, _) = pat.kind {
+ let mut starts = vec![Start {
+ id: canonical_id,
+ kind: StartKind::Range,
+ }];
+
+ // This is one of few ways to return different iterators
+ // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434
+ let mut iter_a = None;
+ let mut iter_b = None;
+
+ if let ExprKind::Block(block, _) = body.kind {
+ if let Some(loop_counters) = get_loop_counters(cx, block, expr) {
+ starts.extend(loop_counters);
+ }
+ iter_a = Some(get_assignments(block, &starts));
+ } else {
+ iter_b = Some(get_assignment(body));
+ }
+
+ let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter());
+
+ let big_sugg = assignments
+ // The only statements in the for loops can be indexed assignments from
+ // indexed retrievals (except increments of loop counters).
+ .map(|o| {
+ o.and_then(|(lhs, rhs)| {
+ let rhs = fetch_cloned_expr(rhs);
+ if_chain! {
+ if let ExprKind::Index(base_left, idx_left) = lhs.kind;
+ if let ExprKind::Index(base_right, idx_right) = rhs.kind;
+ if is_slice_like(cx, cx.typeck_results().expr_ty(base_left));
+ if is_slice_like(cx, cx.typeck_results().expr_ty(base_right));
+ if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts);
+ if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts);
+
+ // Source and destination must be different
+ if path_to_local(base_left) != path_to_local(base_right);
+ then {
+ Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left },
+ IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right }))
+ } else {
+ None
+ }
+ }
+ })
+ })
+ .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, &dst, &src)))
+ .collect::<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,
+ );
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn build_manual_memcpy_suggestion<'tcx>(
+ cx: &LateContext<'tcx>,
+ start: &Expr<'_>,
+ end: &Expr<'_>,
+ limits: ast::RangeLimits,
+ dst: &IndexExpr<'_>,
+ src: &IndexExpr<'_>,
+) -> String {
+ fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ if offset.as_str() == "0" {
+ sugg::EMPTY.into()
+ } else {
+ offset
+ }
+ }
+
+ let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
+ if_chain! {
+ if let ExprKind::MethodCall(method, _, len_args, _) = end.kind;
+ if method.ident.name == sym::len;
+ if len_args.len() == 1;
+ if let Some(arg) = len_args.get(0);
+ if path_to_local(arg) == path_to_local(base);
+ then {
+ if sugg.as_str() == end_str {
+ sugg::EMPTY.into()
+ } else {
+ sugg
+ }
+ } else {
+ match limits {
+ ast::RangeLimits::Closed => {
+ sugg + &sugg::ONE.into()
+ },
+ ast::RangeLimits::HalfOpen => sugg,
+ }
+ }
+ }
+ };
+
+ let start_str = Sugg::hir(cx, start, "").into();
+ let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into();
+
+ let print_offset_and_limit = |idx_expr: &IndexExpr<'_>| match idx_expr.idx {
+ StartKind::Range => (
+ print_offset(apply_offset(&start_str, &idx_expr.idx_offset)).into_sugg(),
+ print_limit(
+ end,
+ end_str.as_str(),
+ idx_expr.base,
+ apply_offset(&end_str, &idx_expr.idx_offset),
+ )
+ .into_sugg(),
+ ),
+ StartKind::Counter { initializer } => {
+ let counter_start = Sugg::hir(cx, initializer, "").into();
+ (
+ print_offset(apply_offset(&counter_start, &idx_expr.idx_offset)).into_sugg(),
+ print_limit(
+ end,
+ end_str.as_str(),
+ idx_expr.base,
+ apply_offset(&end_str, &idx_expr.idx_offset) + &counter_start - &start_str,
+ )
+ .into_sugg(),
+ )
+ },
+ };
+
+ let (dst_offset, dst_limit) = print_offset_and_limit(dst);
+ let (src_offset, src_limit) = print_offset_and_limit(src);
+
+ let dst_base_str = snippet(cx, dst.base.span, "???");
+ let src_base_str = snippet(cx, src.base.span, "???");
+
+ let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY {
+ dst_base_str
+ } else {
+ format!(
+ "{}[{}..{}]",
+ dst_base_str,
+ dst_offset.maybe_par(),
+ dst_limit.maybe_par()
+ )
+ .into()
+ };
+
+ format!(
+ "{}.clone_from_slice(&{}[{}..{}]);",
+ dst,
+ src_base_str,
+ src_offset.maybe_par(),
+ src_limit.maybe_par()
+ )
+}
+
+/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`;
+/// and also, it avoids subtracting a variable from the same one by replacing it with `0`.
+/// it exists for the convenience of the overloaded operators while normal functions can do the
+/// same.
+#[derive(Clone)]
+struct MinifyingSugg<'a>(Sugg<'a>);
+
+impl<'a> MinifyingSugg<'a> {
+ fn as_str(&self) -> &str {
+ // HACK: Don't sync to Clippy! Required because something with the `or_patterns` feature
+ // changed and this would now require parentheses.
+ match &self.0 {
+ Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s) => s.as_ref(),
+ }
+ }
+
+ fn into_sugg(self) -> Sugg<'a> {
+ self.0
+ }
+}
+
+impl<'a> From<Sugg<'a>> for MinifyingSugg<'a> {
+ fn from(sugg: Sugg<'a>) -> Self {
+ Self(sugg)
+ }
+}
+
+impl std::ops::Add for &MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.as_str(), rhs.as_str()) {
+ ("0", _) => rhs.clone(),
+ (_, "0") => self.clone(),
+ (_, _) => (&self.0 + &rhs.0).into(),
+ }
+ }
+}
+
+impl std::ops::Sub for &MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.as_str(), rhs.as_str()) {
+ (_, "0") => self.clone(),
+ ("0", _) => (-rhs.0.clone()).into(),
+ (x, y) if x == y => sugg::ZERO.into(),
+ (_, _) => (&self.0 - &rhs.0).into(),
+ }
+ }
+}
+
+impl std::ops::Add<&MinifyingSugg<'static>> for MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.as_str(), rhs.as_str()) {
+ ("0", _) => rhs.clone(),
+ (_, "0") => self,
+ (_, _) => (self.0 + &rhs.0).into(),
+ }
+ }
+}
+
+impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> {
+ type Output = MinifyingSugg<'static>;
+ fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> {
+ match (self.as_str(), rhs.as_str()) {
+ (_, "0") => self,
+ ("0", _) => (-rhs.0.clone()).into(),
+ (x, y) if x == y => sugg::ZERO.into(),
+ (_, _) => (self.0 - &rhs.0).into(),
+ }
+ }
+}
+
+/// a wrapper around `MinifyingSugg`, which carries an operator like currying
+/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`).
+struct Offset {
+ value: MinifyingSugg<'static>,
+ sign: OffsetSign,
+}
+
+#[derive(Clone, Copy)]
+enum OffsetSign {
+ Positive,
+ Negative,
+}
+
+impl Offset {
+ fn negative(value: Sugg<'static>) -> Self {
+ Self {
+ value: value.into(),
+ sign: OffsetSign::Negative,
+ }
+ }
+
+ fn positive(value: Sugg<'static>) -> Self {
+ Self {
+ value: value.into(),
+ sign: OffsetSign::Positive,
+ }
+ }
+
+ fn empty() -> Self {
+ Self::positive(sugg::ZERO)
+ }
+}
+
+fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> {
+ match rhs.sign {
+ OffsetSign::Positive => lhs + &rhs.value,
+ OffsetSign::Negative => lhs - &rhs.value,
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum StartKind<'hir> {
+ Range,
+ Counter { initializer: &'hir Expr<'hir> },
+}
+
+struct IndexExpr<'hir> {
+ base: &'hir Expr<'hir>,
+ idx: StartKind<'hir>,
+ idx_offset: Offset,
+}
+
+struct Start<'hir> {
+ id: HirId,
+ kind: StartKind<'hir>,
+}
+
+fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'_>) -> bool {
+ let is_slice = match ty.kind() {
+ ty::Ref(_, subty, _) => is_slice_like(cx, subty),
+ ty::Slice(..) | ty::Array(..) => true,
+ _ => false,
+ };
+
+ is_slice || is_type_diagnostic_item(cx, ty, sym::Vec) || is_type_diagnostic_item(cx, ty, sym::VecDeque)
+}
+
+fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
+ if_chain! {
+ if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
+ if method.ident.name == sym::clone;
+ if args.len() == 1;
+ if let Some(arg) = args.get(0);
+ then { arg } else { expr }
+ }
+}
+
+fn get_details_from_idx<'tcx>(
+ cx: &LateContext<'tcx>,
+ idx: &Expr<'_>,
+ starts: &[Start<'tcx>],
+) -> Option<(StartKind<'tcx>, Offset)> {
+ fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<StartKind<'tcx>> {
+ let id = path_to_local(e)?;
+ starts.iter().find(|start| start.id == id).map(|start| start.kind)
+ }
+
+ fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<Sugg<'static>> {
+ match &e.kind {
+ ExprKind::Lit(l) => match l.node {
+ ast::LitKind::Int(x, _ty) => Some(Sugg::NonParen(x.to_string().into())),
+ _ => None,
+ },
+ ExprKind::Path(..) if get_start(e, starts).is_none() => Some(Sugg::hir(cx, e, "???")),
+ _ => None,
+ }
+ }
+
+ match idx.kind {
+ ExprKind::Binary(op, lhs, rhs) => match op.node {
+ BinOpKind::Add => {
+ let offset_opt = get_start(lhs, starts)
+ .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o)))
+ .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o))));
+
+ offset_opt.map(|(s, o)| (s, Offset::positive(o)))
+ },
+ BinOpKind::Sub => {
+ get_start(lhs, starts).and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, Offset::negative(o))))
+ },
+ _ => None,
+ },
+ ExprKind::Path(..) => get_start(idx, starts).map(|s| (s, Offset::empty())),
+ _ => None,
+ }
+}
+
+fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+ if let ExprKind::Assign(lhs, rhs, _) = e.kind {
+ Some((lhs, rhs))
+ } else {
+ None
+ }
+}
+
+/// Get assignments from the given block.
+/// The returned iterator yields `None` if no assignment expressions are there,
+/// filtering out the increments of the given whitelisted loop counters;
+/// because its job is to make sure there's nothing other than assignments and the increments.
+fn get_assignments<'a, 'tcx>(
+ Block { stmts, expr, .. }: &'tcx Block<'tcx>,
+ loop_counters: &'a [Start<'tcx>],
+) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> + 'a {
+ // As the `filter` and `map` below do different things, I think putting together
+ // just increases complexity. (cc #3188 and #4193)
+ stmts
+ .iter()
+ .filter_map(move |stmt| match stmt.kind {
+ StmtKind::Local(..) | StmtKind::Item(..) => None,
+ StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e),
+ })
+ .chain((*expr).into_iter())
+ .filter(move |e| {
+ if let ExprKind::AssignOp(_, place, _) = e.kind {
+ path_to_local(place).map_or(false, |id| {
+ !loop_counters
+ .iter()
+ // skip the first item which should be `StartKind::Range`
+ // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop.
+ .skip(1)
+ .any(|counter| counter.id == id)
+ })
+ } else {
+ true
+ }
+ })
+ .map(get_assignment)
+}
+
+fn get_loop_counters<'a, 'tcx>(
+ cx: &'a LateContext<'tcx>,
+ body: &'tcx Block<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<impl Iterator<Item = Start<'tcx>> + 'a> {
+ // Look for variables that are incremented once per loop iteration.
+ let mut increment_visitor = IncrementVisitor::new(cx);
+ walk_block(&mut increment_visitor, body);
+
+ // For each candidate, check the parent block to see if
+ // it's initialized to zero at the start of the loop.
+ get_enclosing_block(cx, expr.hir_id).and_then(|block| {
+ increment_visitor
+ .into_results()
+ .filter_map(move |var_id| {
+ let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id);
+ walk_block(&mut initialize_visitor, block);
+
++ initialize_visitor.get_result().map(|(_, _, initializer)| Start {
+ id: var_id,
+ kind: StartKind::Counter { initializer },
+ })
+ })
+ .into()
+ })
+}
--- /dev/null
+mod empty_loop;
+mod explicit_counter_loop;
+mod explicit_into_iter_loop;
+mod explicit_iter_loop;
+mod for_kv_map;
+mod for_loops_over_fallibles;
+mod iter_next_loop;
+mod manual_flatten;
+mod manual_memcpy;
+mod mut_range_bound;
+mod needless_collect;
+mod needless_range_loop;
+mod never_loop;
+mod same_item_push;
+mod single_element_loop;
+mod utils;
+mod while_immutable_condition;
+mod while_let_loop;
+mod while_let_on_iterator;
+
+use clippy_utils::higher;
+use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for for-loops that manually copy items between
+ /// slices that could be optimized by having a memcpy.
+ ///
+ /// ### Why is this bad?
+ /// It is not as fast as a memcpy.
+ ///
+ /// ### 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[..]);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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);
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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 {
+ /// // ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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 {
+ /// // ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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).
+ ///
+ /// ### Example
+ /// ```ignore
+ /// for x in y.next() {
+ /// ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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`.
+ ///
+ /// ### 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 {
+ /// // ..
+ /// }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub FOR_LOOPS_OVER_FALLIBLES,
+ suspicious,
+ "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `loop + match` combinations that are easier
+ /// written as a `while let` loop.
+ ///
+ /// ### Why is this bad?
+ /// The `while let` loop is usually shorter and more
+ /// readable.
+ ///
+ /// ### Known problems
+ /// Sometimes the wrong binding is displayed ([#383](https://github.com/rust-lang/rust-clippy/issues/383)).
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # let y = Some(1);
+ /// loop {
+ /// let x = match y {
+ /// Some(x) => x,
+ /// None => break,
+ /// };
+ /// // .. do something with x
+ /// }
+ /// // is easier written as
+ /// while let Some(x) = y {
+ /// // .. do something with x
+ /// };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iterator = vec![1].into_iter();
+ /// let len = iterator.clone().collect::<Vec<_>>().len();
+ /// // should be
+ /// let len = iterator.count();
+ /// ```
++ #[clippy::version = "1.30.0"]
+ 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 this bad?
+ /// Using `.enumerate()` makes the intent more clear,
+ /// declutters the code and may be faster in some instances.
+ ///
+ /// ### 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); }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EXPLICIT_COUNTER_LOOP,
+ complexity,
+ "for-looping with an explicit counter when `_.enumerate()` would do"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for empty `loop` expressions.
+ ///
+ /// ### Why is this bad?
+ /// These busy loops burn CPU cycles without doing
+ /// anything. It is _almost always_ a better idea to `panic!` than to have
+ /// a busy loop.
+ ///
+ /// If panicking isn't possible, think of the environment and either:
+ /// - block on something
+ /// - sleep the thread for some microseconds
+ /// - yield or pause the thread
+ ///
+ /// For `std` targets, this can be done with
+ /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html)
+ /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html).
+ ///
+ /// For `no_std` targets, doing this is more complicated, especially because
+ /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will
+ /// probably need to invoke some target-specific intrinsic. Examples include:
+ /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html)
+ /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html)
+ ///
+ /// ### Example
+ /// ```no_run
+ /// loop {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub EMPTY_LOOP,
+ suspicious,
+ "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.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// while let Some(val) = iter() {
+ /// ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// for (k, _) in &map {
+ /// ..
+ /// }
+ /// ```
+ ///
+ /// could be replaced by
+ ///
+ /// ```ignore
+ /// for k in map.keys() {
+ /// ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// loop {
+ /// ..;
+ /// break;
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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
+ /// False positive when mutation is followed by a `break`, but the `break` is not immediately
+ /// after the mutation:
+ ///
+ /// ```rust
+ /// let mut x = 5;
+ /// for _ in 0..x {
+ /// x += 1; // x is a range bound that is mutated
+ /// ..; // some other expression
+ /// break; // leaves the loop, so mutation is not an issue
+ /// }
+ /// ```
+ ///
+ /// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072))
+ ///
+ /// ### 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
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MUT_RANGE_BOUND,
+ suspicious,
+ "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!");
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub WHILE_IMMUTABLE_CONDITION,
+ correctness,
+ "variables used within while expression are not mutated in the body"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether a for loop is being used to push a constant
+ /// value into a Vec.
+ ///
+ /// ### Why is this bad?
+ /// This kind of operation can be expressed more succinctly with
+ /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also
+ /// have better performance.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let item1 = 2;
+ /// let item2 = 3;
+ /// let mut vec: Vec<u8> = Vec::new();
+ /// for _ in 0..20 {
+ /// vec.push(item1);
+ /// }
+ /// for _ in 0..30 {
+ /// vec.push(item2);
+ /// }
+ /// ```
+ /// could be written as
+ /// ```rust
+ /// let item1 = 2;
+ /// let item2 = 3;
+ /// let mut vec: Vec<u8> = vec![item1; 20];
+ /// vec.resize(20 + 30, item2);
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub SAME_ITEM_PUSH,
+ style,
+ "the same item is pushed inside of a for loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks whether a for loop has a single element.
+ ///
+ /// ### Why is this bad?
+ /// There is no reason to have a loop of a
+ /// single element.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let item1 = 2;
+ /// for item in &[item1] {
+ /// println!("{}", item);
+ /// }
+ /// ```
+ /// could be written as
+ /// ```rust
+ /// let item1 = 2;
+ /// let item = &item1;
+ /// println!("{}", item);
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub SINGLE_ELEMENT_LOOP,
+ complexity,
+ "there is no reason to have a single element loop"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check for unnecessary `if let` usage in a for loop
+ /// where only the `Some` or `Ok` variant of the iterator element is used.
+ ///
+ /// ### Why is this bad?
+ /// It is verbose and can be simplified
+ /// by first calling the `flatten` method on the `Iterator`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// let x = vec![Some(1), Some(2), Some(3)];
+ /// for n in x {
+ /// if let Some(n) = n {
+ /// println!("{}", n);
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let x = vec![Some(1), Some(2), Some(3)];
+ /// for n in x.into_iter().flatten() {
+ /// println!("{}", n);
+ /// }
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub MANUAL_FLATTEN,
+ complexity,
+ "for loops over `Option`s or `Result`s with a single expression can be simplified"
+}
+
+declare_lint_pass!(Loops => [
+ MANUAL_MEMCPY,
+ MANUAL_FLATTEN,
+ NEEDLESS_RANGE_LOOP,
+ EXPLICIT_ITER_LOOP,
+ EXPLICIT_INTO_ITER_LOOP,
+ ITER_NEXT_LOOP,
+ FOR_LOOPS_OVER_FALLIBLES,
+ WHILE_LET_LOOP,
+ NEEDLESS_COLLECT,
+ EXPLICIT_COUNTER_LOOP,
+ EMPTY_LOOP,
+ WHILE_LET_ON_ITERATOR,
+ FOR_KV_MAP,
+ NEVER_LOOP,
+ MUT_RANGE_BOUND,
+ WHILE_IMMUTABLE_CONDITION,
+ SAME_ITEM_PUSH,
+ SINGLE_ELEMENT_LOOP,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Loops {
+ #[allow(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let for_loop = higher::ForLoop::hir(expr);
+ if let Some(higher::ForLoop {
+ pat,
+ arg,
+ body,
+ loop_id,
+ span,
+ }) = for_loop
+ {
+ // we don't want to check expanded macros
+ // this check is not at the top of the function
+ // since higher::for_loop expressions are marked as expansions
+ if body.span.from_expansion() {
+ return;
+ }
+ check_for_loop(cx, pat, arg, body, expr, span);
+ if let ExprKind::Block(block, _) = body.kind {
+ never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
+ }
+ }
+
+ // we don't want to check expanded macros
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ // check for never_loop
+ if let ExprKind::Loop(block, ..) = expr.kind {
+ never_loop::check(cx, block, expr.hir_id, expr.span, None);
+ }
+
+ // 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(block, _, LoopSource::Loop, _) = expr.kind {
+ // also check for empty `loop {}` statements, skipping those in #[panic_handler]
+ empty_loop::check(cx, expr, block);
+ while_let_loop::check(cx, expr, block);
+ }
+
+ while_let_on_iterator::check(cx, expr);
+
+ if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
+ while_immutable_condition::check(cx, condition, body);
+ }
+
+ needless_collect::check(expr, cx);
+ }
+}
+
+fn check_for_loop<'tcx>(
+ cx: &LateContext<'tcx>,
+ pat: &'tcx Pat<'_>,
+ arg: &'tcx Expr<'_>,
+ body: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+ span: Span,
+) {
+ let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
+ if !is_manual_memcpy_triggered {
+ needless_range_loop::check(cx, pat, arg, body, expr);
+ explicit_counter_loop::check(cx, pat, arg, body, expr);
+ }
+ check_for_loop_arg(cx, pat, arg);
+ for_kv_map::check(cx, pat, arg, body);
+ mut_range_bound::check(cx, arg, body);
+ single_element_loop::check(cx, pat, arg, body, expr);
+ same_item_push::check(cx, pat, arg, body, expr);
+ manual_flatten::check(cx, pat, arg, body, span);
+}
+
+fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {
+ let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
+
+ if let ExprKind::MethodCall(method, _, [self_arg], _) = arg.kind {
+ let method_name = &*method.ident.as_str();
+ // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
+ match method_name {
+ "iter" | "iter_mut" => explicit_iter_loop::check(cx, self_arg, arg, method_name),
+ "into_iter" => {
+ explicit_iter_loop::check(cx, self_arg, arg, method_name);
+ explicit_into_iter_loop::check(cx, self_arg, arg);
+ },
+ "next" => {
+ next_loop_linted = iter_next_loop::check(cx, arg);
+ },
+ _ => {},
+ }
+ }
+
+ if !next_loop_linted {
+ for_loops_over_fallibles::check(cx, pat, arg);
+ }
+}
--- /dev/null
- use clippy_utils::{is_trait_method, path_to_local_id};
+use super::NEEDLESS_COLLECT;
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
- use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, StmtKind};
++use clippy_utils::{can_move_expr_to_closure, is_trait_method, path_to_local, path_to_local_id, CaptureKind};
+use if_chain::if_chain;
++use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
- if let Some(iter_calls) = detect_iter_and_into_iters(block, id);
++use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, PatKind, Stmt, StmtKind};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
++use rustc_middle::ty::subst::GenericArgKind;
++use rustc_middle::ty::{self, TyS};
+use rustc_span::sym;
+use rustc_span::{MultiSpan, Span};
+
+const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
+
+pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ check_needless_collect_direct_usage(expr, cx);
+ check_needless_collect_indirect_usage(expr, cx);
+}
+fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ if_chain! {
+ if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
+ if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind;
+ if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
+ then {
+ let ty = cx.typeck_results().expr_ty(&args[0]);
+ let mut applicability = Applicability::MaybeIncorrect;
+ let is_empty_sugg = "next().is_none()".to_string();
+ let method_name = &*method.ident.name.as_str();
+ let sugg = if is_type_diagnostic_item(cx, ty, sym::Vec) ||
+ is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
+ is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
+ is_type_diagnostic_item(cx, ty, sym::BinaryHeap) {
+ match method_name {
+ "len" => "count()".to_string(),
+ "is_empty" => is_empty_sugg,
+ "contains" => {
+ let contains_arg = snippet_with_applicability(cx, args[1].span, "??", &mut applicability);
+ let (arg, pred) = contains_arg
+ .strip_prefix('&')
+ .map_or(("&x", &*contains_arg), |s| ("x", s));
+ format!("any(|{}| x == {})", arg, pred)
+ }
+ _ => return,
+ }
+ }
+ else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
+ is_type_diagnostic_item(cx, ty, sym::HashMap) {
+ match method_name {
+ "is_empty" => is_empty_sugg,
+ _ => return,
+ }
+ }
+ else {
+ return;
+ };
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_COLLECT,
+ method0_span.with_hi(expr.span.hi()),
+ NEEDLESS_COLLECT_MSG,
+ "replace with",
+ sugg,
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
+ if let ExprKind::Block(block, _) = expr.kind {
+ for stmt in block.stmts {
+ if_chain! {
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(_, id, ..) = local.pat.kind;
+ if let Some(init_expr) = local.init;
+ if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
+ if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
+ let ty = cx.typeck_results().expr_ty(init_expr);
+ if is_type_diagnostic_item(cx, ty, sym::Vec) ||
+ is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
+ is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
+ is_type_diagnostic_item(cx, ty, sym::LinkedList);
- struct IterFunctionVisitor {
- uses: Vec<IterFunction>,
++ let iter_ty = cx.typeck_results().expr_ty(iter_source);
++ if let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty));
+ if let [iter_call] = &*iter_calls;
+ then {
+ let mut used_count_visitor = UsedCountVisitor {
+ cx,
+ id,
+ count: 0,
+ };
+ walk_block(&mut used_count_visitor, block);
+ if used_count_visitor.count > 1 {
+ return;
+ }
+
+ // Suggest replacing iter_call with iter_replacement, and removing stmt
+ let mut span = MultiSpan::from_span(collect_span);
+ span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
+ span_lint_hir_and_then(
+ cx,
+ super::NEEDLESS_COLLECT,
+ init_expr.hir_id,
+ span,
+ NEEDLESS_COLLECT_MSG,
+ |diag| {
+ let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
+ diag.multipart_suggestion(
+ iter_call.get_suggestion_text(),
+ vec![
+ (stmt.span, String::new()),
+ (iter_call.span, iter_replacement)
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+struct IterFunction {
+ func: IterFunctionKind,
+ span: Span,
+}
+impl IterFunction {
+ fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
+ match &self.func {
+ IterFunctionKind::IntoIter => String::new(),
+ IterFunctionKind::Len => String::from(".count()"),
+ IterFunctionKind::IsEmpty => String::from(".next().is_none()"),
+ IterFunctionKind::Contains(span) => {
+ let s = snippet(cx, *span, "..");
+ if let Some(stripped) = s.strip_prefix('&') {
+ format!(".any(|x| x == {})", stripped)
+ } else {
+ format!(".any(|x| x == *{})", s)
+ }
+ },
+ }
+ }
+ fn get_suggestion_text(&self) -> &'static str {
+ match &self.func {
+ IterFunctionKind::IntoIter => {
+ "use the original Iterator instead of collecting it and then producing a new one"
+ },
+ IterFunctionKind::Len => {
+ "take the original Iterator's count instead of collecting it and finding the length"
+ },
+ IterFunctionKind::IsEmpty => {
+ "check if the original Iterator has anything instead of collecting it and seeing if it's empty"
+ },
+ IterFunctionKind::Contains(_) => {
+ "check if the original Iterator contains an element instead of collecting then checking"
+ },
+ }
+ }
+}
+enum IterFunctionKind {
+ IntoIter,
+ Len,
+ IsEmpty,
+ Contains(Span),
+}
+
- impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
++struct IterFunctionVisitor<'a, 'tcx> {
++ illegal_mutable_capture_ids: HirIdSet,
++ current_mutably_captured_ids: HirIdSet,
++ cx: &'a LateContext<'tcx>,
++ uses: Vec<Option<IterFunction>>,
++ hir_id_uses_map: FxHashMap<HirId, usize>,
++ current_statement_hir_id: Option<HirId>,
+ seen_other: bool,
+ target: HirId,
+}
- match &*method_name.ident.name.as_str() {
- "into_iter" => self.uses.push(IterFunction {
- func: IterFunctionKind::IntoIter,
- span: expr.span,
- }),
- "len" => self.uses.push(IterFunction {
- func: IterFunctionKind::Len,
- span: expr.span,
- }),
- "is_empty" => self.uses.push(IterFunction {
- func: IterFunctionKind::IsEmpty,
- span: expr.span,
- }),
- "contains" => self.uses.push(IterFunction {
- func: IterFunctionKind::Contains(args[0].span),
- span: expr.span,
- }),
- _ => self.seen_other = true,
++impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
++ fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
++ for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) {
++ self.visit_block_expr(expr, hir_id);
++ }
++ if let Some(expr) = block.expr {
++ self.visit_block_expr(expr, None);
++ }
++ }
++
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ // Check function calls on our collection
+ if let ExprKind::MethodCall(method_name, _, [recv, args @ ..], _) = &expr.kind {
++ if method_name.ident.name == sym!(collect) && is_trait_method(self.cx, expr, sym::Iterator) {
++ self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv));
++ self.visit_expr(recv);
++ return;
++ }
++
+ if path_to_local_id(recv, self.target) {
- fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, id: HirId) -> Option<Vec<IterFunction>> {
++ if self
++ .illegal_mutable_capture_ids
++ .intersection(&self.current_mutably_captured_ids)
++ .next()
++ .is_none()
++ {
++ if let Some(hir_id) = self.current_statement_hir_id {
++ self.hir_id_uses_map.insert(hir_id, self.uses.len());
++ }
++ match &*method_name.ident.name.as_str() {
++ "into_iter" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::IntoIter,
++ span: expr.span,
++ })),
++ "len" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::Len,
++ span: expr.span,
++ })),
++ "is_empty" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::IsEmpty,
++ span: expr.span,
++ })),
++ "contains" => self.uses.push(Some(IterFunction {
++ func: IterFunctionKind::Contains(args[0].span),
++ span: expr.span,
++ })),
++ _ => {
++ self.seen_other = true;
++ if let Some(hir_id) = self.current_statement_hir_id {
++ self.hir_id_uses_map.remove(&hir_id);
++ }
++ },
++ }
+ }
+ return;
+ }
++
++ if let Some(hir_id) = path_to_local(recv) {
++ if let Some(index) = self.hir_id_uses_map.remove(&hir_id) {
++ if self
++ .illegal_mutable_capture_ids
++ .intersection(&self.current_mutably_captured_ids)
++ .next()
++ .is_none()
++ {
++ if let Some(hir_id) = self.current_statement_hir_id {
++ self.hir_id_uses_map.insert(hir_id, index);
++ }
++ } else {
++ self.uses[index] = None;
++ }
++ }
++ }
+ }
+ // Check if the collection is used for anything else
+ if path_to_local_id(expr, self.target) {
+ self.seen_other = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ type Map = Map<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
++impl<'tcx> IterFunctionVisitor<'_, 'tcx> {
++ fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option<HirId>) {
++ self.current_statement_hir_id = hir_id;
++ self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr));
++ self.visit_expr(expr);
++ }
++}
++
++fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option<HirId>)> {
++ match stmt.kind {
++ StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)),
++ StmtKind::Item(..) => None,
++ StmtKind::Local(Local { init, pat, .. }) => {
++ if let PatKind::Binding(_, hir_id, ..) = pat.kind {
++ init.map(|init_expr| (init_expr, Some(hir_id)))
++ } else {
++ init.map(|init_expr| (init_expr, None))
++ }
++ },
++ }
++}
++
+struct UsedCountVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ id: HirId,
+ count: usize,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if path_to_local_id(expr, self.id) {
+ self.count += 1;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+/// Detect the occurrences of calls to `iter` or `into_iter` for the
+/// given identifier
- if visitor.seen_other { None } else { Some(visitor.uses) }
++fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
++ block: &'tcx Block<'tcx>,
++ id: HirId,
++ cx: &'a LateContext<'tcx>,
++ captured_ids: HirIdSet,
++) -> Option<Vec<IterFunction>> {
+ let mut visitor = IterFunctionVisitor {
+ uses: Vec::new(),
+ target: id,
+ seen_other: false,
++ cx,
++ current_mutably_captured_ids: HirIdSet::default(),
++ illegal_mutable_capture_ids: captured_ids,
++ hir_id_uses_map: FxHashMap::default(),
++ current_statement_hir_id: None,
+ };
+ visitor.visit_block(block);
++ if visitor.seen_other {
++ None
++ } else {
++ Some(visitor.uses.into_iter().flatten().collect())
++ }
++}
++
++fn get_captured_ids(cx: &LateContext<'tcx>, ty: &'_ TyS<'_>) -> HirIdSet {
++ fn get_captured_ids_recursive(cx: &LateContext<'tcx>, ty: &'_ TyS<'_>, set: &mut HirIdSet) {
++ match ty.kind() {
++ ty::Adt(_, generics) => {
++ for generic in *generics {
++ if let GenericArgKind::Type(ty) = generic.unpack() {
++ get_captured_ids_recursive(cx, ty, set);
++ }
++ }
++ },
++ ty::Closure(def_id, _) => {
++ let closure_hir_node = cx.tcx.hir().get_if_local(*def_id).unwrap();
++ if let Node::Expr(closure_expr) = closure_hir_node {
++ can_move_expr_to_closure(cx, closure_expr)
++ .unwrap()
++ .into_iter()
++ .for_each(|(hir_id, capture_kind)| {
++ if matches!(capture_kind, CaptureKind::Ref(Mutability::Mut)) {
++ set.insert(hir_id);
++ }
++ });
++ }
++ },
++ _ => (),
++ }
++ }
++
++ let mut set = HirIdSet::default();
++
++ get_captured_ids_recursive(cx, ty, &mut set);
++
++ set
+}
--- /dev/null
- use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
- use rustc_hir::HirIdMap;
- use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind};
+use clippy_utils::ty::{has_iter_method, implements_trait};
+use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
+use if_chain::if_chain;
++use rustc_ast::ast::{LitIntType, LitKind};
+use rustc_errors::Applicability;
- Initial, // Not examined yet
- Declared(Symbol), // Declared but not (yet) initialized
++use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
++use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
++use rustc_middle::ty::Ty;
++use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{sym, Symbol};
++use rustc_typeck::hir_ty_to_ty;
+use std::iter::Iterator;
+
+#[derive(Debug, PartialEq)]
+enum IncrementVisitorVarState {
+ Initial, // Not examined yet
+ IncrOnce, // Incremented exactly once, may be a loop counter
+ DontWarn,
+}
+
+/// Scan a for loop for variables that are incremented exactly once and not used after that.
+pub(super) struct IncrementVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>, // context reference
+ states: HirIdMap<IncrementVisitorVarState>, // incremented variables
+ depth: u32, // depth of conditional expressions
+ done: bool,
+}
+
+impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> {
+ pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ states: HirIdMap::default(),
+ depth: 0,
+ done: false,
+ }
+ }
+
+ pub(super) fn into_results(self) -> impl Iterator<Item = HirId> {
+ self.states.into_iter().filter_map(|(id, state)| {
+ if state == IncrementVisitorVarState::IncrOnce {
+ Some(id)
+ } else {
+ None
+ }
+ })
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.done {
+ return;
+ }
+
+ // If node is a variable
+ if let Some(def_id) = path_to_local(expr) {
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial);
+ if *state == IncrementVisitorVarState::IncrOnce {
+ *state = IncrementVisitorVarState::DontWarn;
+ return;
+ }
+
+ match parent.kind {
+ ExprKind::AssignOp(op, lhs, rhs) => {
+ if lhs.hir_id == expr.hir_id {
+ *state = if op.node == BinOpKind::Add
+ && is_integer_const(self.cx, rhs, 1)
+ && *state == IncrementVisitorVarState::Initial
+ && self.depth == 0
+ {
+ IncrementVisitorVarState::IncrOnce
+ } else {
+ // Assigned some other value or assigned multiple times
+ IncrementVisitorVarState::DontWarn
+ };
+ }
+ },
+ ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
+ *state = IncrementVisitorVarState::DontWarn;
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ *state = IncrementVisitorVarState::DontWarn;
+ },
+ _ => (),
+ }
+ }
+
+ walk_expr(self, expr);
+ } else if is_loop(expr) || is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ } else if let ExprKind::Continue(_) = expr.kind {
+ self.done = true;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+enum InitializeVisitorState<'hir> {
- pub(super) fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> {
- if let InitializeVisitorState::Initialized { name, initializer } = self.state {
- Some((name, initializer))
++ Initial, // Not examined yet
++ Declared(Symbol, Option<Ty<'hir>>), // Declared but not (yet) initialized
+ Initialized {
+ name: Symbol,
++ ty: Option<Ty<'hir>>,
+ initializer: &'hir Expr<'hir>,
+ },
+ DontWarn,
+}
+
+/// Checks whether a variable is initialized at the start of a loop and not modified
+/// and used after the loop.
+pub(super) struct InitializeVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>, // context reference
+ end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here.
+ var_id: HirId,
+ state: InitializeVisitorState<'tcx>,
+ depth: u32, // depth of conditional expressions
+ past_loop: bool,
+}
+
+impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> {
+ pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self {
+ Self {
+ cx,
+ end_expr,
+ var_id,
+ state: InitializeVisitorState::Initial,
+ depth: 0,
+ past_loop: false,
+ }
+ }
+
- fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
++ pub(super) fn get_result(&self) -> Option<(Symbol, Option<Ty<'tcx>>, &'tcx Expr<'tcx>)> {
++ if let InitializeVisitorState::Initialized { name, ty, initializer } = self.state {
++ Some((name, ty, initializer))
+ } else {
+ None
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
- if let StmtKind::Local(local) = stmt.kind;
- if local.pat.hir_id == self.var_id;
- if let PatKind::Binding(.., ident, _) = local.pat.kind;
++ fn visit_local(&mut self, l: &'tcx Local<'_>) {
+ // Look for declarations of the variable
+ if_chain! {
- self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| {
++ if l.pat.hir_id == self.var_id;
++ if let PatKind::Binding(.., ident, _) = l.pat.kind;
+ then {
- walk_stmt(self, stmt);
++ let ty = l.ty.map(|ty| hir_ty_to_ty(self.cx.tcx, ty));
++
++ self.state = l.init.map_or(InitializeVisitorState::Declared(ident.name, ty), |init| {
+ InitializeVisitorState::Initialized {
+ initializer: init,
++ ty,
+ name: ident.name,
+ }
+ })
+ }
+ }
- self.state = if_chain! {
- if self.depth == 0;
- if let InitializeVisitorState::Declared(name)
- | InitializeVisitorState::Initialized { name, ..} = self.state;
- then {
- InitializeVisitorState::Initialized { initializer: rhs, name }
- } else {
- InitializeVisitorState::DontWarn
++
++ walk_local(self, l);
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if matches!(self.state, InitializeVisitorState::DontWarn) {
+ return;
+ }
+ if expr.hir_id == self.end_expr.hir_id {
+ self.past_loop = true;
+ return;
+ }
+ // No need to visit expressions before the variable is
+ // declared
+ if matches!(self.state, InitializeVisitorState::Initial) {
+ return;
+ }
+
+ // If node is the desired variable, see how it's used
+ if path_to_local_id(expr, self.var_id) {
+ if self.past_loop {
+ self.state = InitializeVisitorState::DontWarn;
+ return;
+ }
+
+ if let Some(parent) = get_parent_expr(self.cx, expr) {
+ match parent.kind {
+ ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => {
+ self.state = InitializeVisitorState::DontWarn;
+ },
+ ExprKind::Assign(lhs, rhs, _) if lhs.hir_id == expr.hir_id => {
++ self.state = if self.depth == 0 {
++ match self.state {
++ InitializeVisitorState::Declared(name, mut ty) => {
++ if ty.is_none() {
++ if let ExprKind::Lit(Spanned {
++ node: LitKind::Int(_, LitIntType::Unsuffixed),
++ ..
++ }) = rhs.kind
++ {
++ ty = None;
++ } else {
++ ty = self.cx.typeck_results().expr_ty_opt(rhs);
++ }
++ }
++
++ InitializeVisitorState::Initialized {
++ initializer: rhs,
++ ty,
++ name,
++ }
++ },
++ InitializeVisitorState::Initialized { ty, name, .. } => {
++ InitializeVisitorState::Initialized {
++ initializer: rhs,
++ ty,
++ name,
++ }
++ },
++ _ => InitializeVisitorState::DontWarn,
+ }
++ } else {
++ InitializeVisitorState::DontWarn
+ }
+ },
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
+ self.state = InitializeVisitorState::DontWarn;
+ },
+ _ => (),
+ }
+ }
+
+ walk_expr(self, expr);
+ } else if !self.past_loop && is_loop(expr) {
+ self.state = InitializeVisitorState::DontWarn;
+ } else if is_conditional(expr) {
+ self.depth += 1;
+ walk_expr(self, expr);
+ self.depth -= 1;
+ } else {
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+fn is_loop(expr: &Expr<'_>) -> bool {
+ matches!(expr.kind, ExprKind::Loop(..))
+}
+
+fn is_conditional(expr: &Expr<'_>) -> bool {
+ matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..))
+}
+
+#[derive(PartialEq, Eq)]
+pub(super) enum Nesting {
+ Unknown, // no nesting detected yet
+ RuledOut, // the iterator is initialized or assigned within scope
+ LookFurther, // no nesting detected, no further walk required
+}
+
+use self::Nesting::{LookFurther, RuledOut, Unknown};
+
+pub(super) struct LoopNestVisitor {
+ pub(super) hir_id: HirId,
+ pub(super) iterator: HirId,
+ pub(super) nesting: Nesting,
+}
+
+impl<'tcx> Visitor<'tcx> for LoopNestVisitor {
+ type Map = Map<'tcx>;
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ if stmt.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ } else if self.nesting == Unknown {
+ walk_stmt(self, stmt);
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if expr.hir_id == self.hir_id {
+ self.nesting = LookFurther;
+ return;
+ }
+ match expr.kind {
+ ExprKind::Assign(path, _, _) | ExprKind::AssignOp(_, path, _) => {
+ if path_to_local_id(path, self.iterator) {
+ self.nesting = RuledOut;
+ }
+ },
+ _ => walk_expr(self, expr),
+ }
+ }
+
+ fn visit_pat(&mut self, pat: &'tcx Pat<'_>) {
+ if self.nesting != Unknown {
+ return;
+ }
+ if let PatKind::Binding(_, id, ..) = pat.kind {
+ if id == self.iterator {
+ self.nesting = RuledOut;
+ return;
+ }
+ }
+ walk_pat(self, pat);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the
+/// actual `Iterator` that the loop uses.
+pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String {
+ let impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
+ implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[])
+ });
+ if impls_iterator {
+ format!(
+ "{}",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ )
+ } else {
+ // (&x).into_iter() ==> x.iter()
+ // (&mut x).into_iter() ==> x.iter_mut()
+ match &arg.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner)
+ if has_iter_method(cx, cx.typeck_results().expr_ty(arg_inner)).is_some() =>
+ {
+ let meth_name = match mutability {
+ Mutability::Mut => "iter_mut",
+ Mutability::Not => "iter",
+ };
+ format!(
+ "{}.{}()",
+ sugg::Sugg::hir_with_applicability(cx, arg_inner, "_", applic_ref).maybe_par(),
+ meth_name,
+ )
+ },
+ _ => format!(
+ "{}.into_iter()",
+ sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par()
+ ),
+ }
+ }
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use hir::def::{DefKind, Res};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
++use rustc_span::hygiene::ExpnKind;
+use rustc_span::{edition::Edition, sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `#[macro_use] use...`.
+ ///
+ /// ### Why is this bad?
+ /// Since the Rust 2018 edition you can import
+ /// macro's directly, this is considered idiomatic.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[macro_use]
+ /// use some_macro;
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub MACRO_USE_IMPORTS,
+ pedantic,
+ "#[macro_use] is no longer needed"
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+struct PathAndSpan {
+ path: String,
+ span: Span,
+}
+
+/// `MacroRefData` includes the name of the macro.
+#[derive(Debug, Clone)]
+pub struct MacroRefData {
+ name: String,
+}
+
+impl MacroRefData {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+#[derive(Default)]
+#[allow(clippy::module_name_repetitions)]
+pub struct MacroUseImports {
+ /// the actual import path used and the span of the attribute above it.
+ imports: Vec<(String, Span)>,
+ /// the span of the macro reference, kept to ensure only one reference is used per macro call.
+ collected: FxHashSet<Span>,
+ mac_refs: Vec<MacroRefData>,
+}
+
+impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]);
+
+impl MacroUseImports {
+ fn push_unique_macro(&mut self, cx: &LateContext<'_>, span: Span) {
+ let call_site = span.source_callsite();
+ let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
+ if span.source_callee().is_some() && !self.collected.contains(&call_site) {
+ let name = if name.contains("::") {
+ name.split("::").last().unwrap().to_string()
+ } else {
+ name.to_string()
+ };
+
+ self.mac_refs.push(MacroRefData::new(name));
+ self.collected.insert(call_site);
+ }
+ }
+
+ fn push_unique_macro_pat_ty(&mut self, cx: &LateContext<'_>, span: Span) {
+ let call_site = span.source_callsite();
+ let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_");
+ if span.source_callee().is_some() && !self.collected.contains(&call_site) {
+ self.mac_refs.push(MacroRefData::new(name.to_string()));
+ self.collected.insert(call_site);
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ if_chain! {
+ if cx.sess().opts.edition >= Edition::Edition2018;
+ if let hir::ItemKind::Use(path, _kind) = &item.kind;
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
+ if let Res::Def(DefKind::Mod, id) = path.res;
+ if !id.is_local();
+ then {
+ for kid in cx.tcx.item_children(id).iter() {
+ if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {
+ let span = mac_attr.span;
+ let def_path = cx.tcx.def_path_str(mac_id);
+ self.imports.push((def_path, span));
+ }
+ }
+ } else {
+ if in_macro(item.span) {
+ self.push_unique_macro_pat_ty(cx, item.span);
+ }
+ }
+ }
+ }
+ fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+ if in_macro(attr.span) {
+ self.push_unique_macro(cx, attr.span);
+ }
+ }
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ if in_macro(expr.span) {
+ self.push_unique_macro(cx, expr.span);
+ }
+ }
+ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
+ if in_macro(stmt.span) {
+ self.push_unique_macro(cx, stmt.span);
+ }
+ }
+ fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) {
+ if in_macro(pat.span) {
+ self.push_unique_macro_pat_ty(cx, pat.span);
+ }
+ }
+ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) {
+ if in_macro(ty.span) {
+ self.push_unique_macro_pat_ty(cx, ty.span);
+ }
+ }
+ #[allow(clippy::too_many_lines)]
+ fn check_crate_post(&mut self, cx: &LateContext<'_>) {
+ let mut used = FxHashMap::default();
+ let mut check_dup = vec![];
+ for (import, span) in &self.imports {
+ let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name));
+
+ if let Some(idx) = found_idx {
+ self.mac_refs.remove(idx);
+ let seg = import.split("::").collect::<Vec<_>>();
+
+ match seg.as_slice() {
+ // an empty path is impossible
+ // a path should always consist of 2 or more segments
+ [] | [_] => return,
+ [root, item] => {
+ if !check_dup.contains(&(*item).to_string()) {
+ used.entry(((*root).to_string(), span))
+ .or_insert_with(Vec::new)
+ .push((*item).to_string());
+ check_dup.push((*item).to_string());
+ }
+ },
+ [root, rest @ ..] => {
+ if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) {
+ let filtered = rest
+ .iter()
+ .filter_map(|item| {
+ if check_dup.contains(&(*item).to_string()) {
+ None
+ } else {
+ Some((*item).to_string())
+ }
+ })
+ .collect::<Vec<_>>();
+ used.entry(((*root).to_string(), span))
+ .or_insert_with(Vec::new)
+ .push(filtered.join("::"));
+ check_dup.extend(filtered);
+ } else {
+ let rest = rest.to_vec();
+ used.entry(((*root).to_string(), span))
+ .or_insert_with(Vec::new)
+ .push(rest.join("::"));
+ check_dup.extend(rest.iter().map(ToString::to_string));
+ }
+ },
+ }
+ }
+ }
+
+ let mut suggestions = vec![];
+ for ((root, span), path) in used {
+ if path.len() == 1 {
+ suggestions.push((span, format!("{}::{}", root, path[0])));
+ } else {
+ suggestions.push((span, format!("{}::{{{}}}", root, path.join(", "))));
+ }
+ }
+
+ // If mac_refs is not empty we have encountered an import we could not handle
+ // such as `std::prelude::v1::foo` or some other macro that expands to an import.
+ if self.mac_refs.is_empty() {
+ for (span, import) in suggestions {
+ let help = format!("use {};", import);
+ span_lint_and_sugg(
+ cx,
+ MACRO_USE_IMPORTS,
+ *span,
+ "`macro_use` attributes are no longer needed in the Rust 2018 edition",
+ "remove the attribute and import the macro directly, try",
+ help,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
++
++fn in_macro(span: Span) -> bool {
++ span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
++}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::snippet;
+use clippy_utils::{is_entrypoint_fn, is_no_std_crate};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for recursion using the entrypoint.
+ ///
+ /// ### Why is this bad?
+ /// Apart from special setups (which we could detect following attributes like #![no_std]),
+ /// recursing into main() seems like an unintuitive antipattern we should be able to detect.
+ ///
+ /// ### Example
+ /// ```no_run
+ /// fn main() {
+ /// main();
+ /// }
+ /// ```
++ #[clippy::version = "1.38.0"]
+ pub MAIN_RECURSION,
+ style,
+ "recursion using the entrypoint"
+}
+
+#[derive(Default)]
+pub struct MainRecursion {
+ has_no_std_attr: bool,
+}
+
+impl_lint_pass!(MainRecursion => [MAIN_RECURSION]);
+
+impl LateLintPass<'_> for MainRecursion {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ self.has_no_std_attr = is_no_std_crate(cx);
+ }
+
+ fn check_expr_post(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if self.has_no_std_attr {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Call(func, _) = &expr.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind;
+ if let Some(def_id) = path.res.opt_def_id();
+ if is_entrypoint_fn(cx, def_id);
+ then {
+ span_lint_and_help(
+ cx,
+ MAIN_RECURSION,
+ func.span,
+ &format!("recursing into entrypoint `{}`", snippet(cx, func.span, "main")),
+ None,
+ "consider using another function for this recursion"
+ )
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::PanicExpn;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::{is_expn_of, sugg};
+use rustc_errors::Applicability;
+use rustc_hir::{Block, Expr, ExprKind, StmtKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `if`-then-`panic!` that can be replaced with `assert!`.
+ ///
+ /// ### Why is this bad?
+ /// `assert!` is simpler than `if`-then-`panic!`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let sad_people: Vec<&str> = vec![];
+ /// if !sad_people.is_empty() {
+ /// panic!("there are sad people: {:?}", sad_people);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let sad_people: Vec<&str> = vec![];
+ /// assert!(sad_people.is_empty(), "there are sad people: {:?}", sad_people);
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub MANUAL_ASSERT,
+ pedantic,
+ "`panic!` and only a `panic!` in `if`-then statement"
+}
+
+declare_lint_pass!(ManualAssert => [MANUAL_ASSERT]);
+
+impl LateLintPass<'_> for ManualAssert {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Expr {
+ kind: ExprKind:: If(cond, Expr {
+ kind: ExprKind::Block(
+ Block {
+ stmts: [stmt],
+ ..
+ },
+ _),
+ ..
+ }, None),
+ ..
+ } = &expr;
+ if is_expn_of(stmt.span, "panic").is_some();
+ if !matches!(cond.kind, ExprKind::Let(_, _, _));
+ if let StmtKind::Semi(semi) = stmt.kind;
+ if !cx.tcx.sess.source_map().is_multiline(cond.span);
+
+ then {
+ let call = if_chain! {
+ if let ExprKind::Block(block, _) = semi.kind;
+ if let Some(init) = block.expr;
+ then {
+ init
+ } else {
+ semi
+ }
+ };
+ let span = if let Some(panic_expn) = PanicExpn::parse(call) {
+ match *panic_expn.format_args.value_args {
+ [] => panic_expn.format_args.format_string_span,
+ [.., last] => panic_expn.format_args.format_string_span.to(last.span),
+ }
+ } else if let ExprKind::Call(_, [format_args]) = call.kind {
+ format_args.span
+ } else {
+ return
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
+ let cond_sugg = if let ExprKind::DropTemps(e, ..) = cond.kind {
+ if let Expr{kind: ExprKind::Unary(UnOp::Not, not_expr), ..} = e {
+ sugg::Sugg::hir_with_applicability(cx, not_expr, "..", &mut applicability).maybe_par().to_string()
+ } else {
+ format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par())
+ }
+ } else {
+ format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par())
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_ASSERT,
+ expr.span,
+ "only a `panic!` in `if`-then statement",
+ "try",
+ format!("assert!({}, {});", cond_sugg, sugg),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::match_function_call;
+use clippy_utils::paths::FUTURE_FROM_GENERATOR;
+use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId,
+ IsAsync, ItemKind, LifetimeName, TraitRef, Ty, TyKind, TypeBindingKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It checks for manual implementations of `async` functions.
+ ///
+ /// ### Why is this bad?
+ /// It's more idiomatic to use the dedicated syntax.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::future::Future;
+ ///
+ /// fn foo() -> impl Future<Output = i32> { async { 42 } }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// async fn foo() -> i32 { 42 }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub MANUAL_ASYNC_FN,
+ style,
+ "manual implementations of `async` functions can be simplified using the dedicated syntax"
+}
+
+declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if_chain! {
+ if let Some(header) = kind.header();
+ if header.asyncness == IsAsync::NotAsync;
+ // Check that this function returns `impl Future`
+ if let FnRetTy::Return(ret_ty) = decl.output;
+ if let Some((trait_ref, output_lifetimes)) = future_trait_ref(cx, ret_ty);
+ if let Some(output) = future_output_ty(trait_ref);
+ if captures_all_lifetimes(decl.inputs, &output_lifetimes);
+ // Check that the body of the function consists of one async block
+ if let ExprKind::Block(block, _) = body.value.kind;
+ if block.stmts.is_empty();
+ if let Some(closure_body) = desugared_async_block(cx, block);
+ then {
+ let header_span = span.with_hi(ret_ty.span.hi());
+
+ span_lint_and_then(
+ cx,
+ MANUAL_ASYNC_FN,
+ header_span,
+ "this function can be simplified using the `async fn` syntax",
+ |diag| {
+ if_chain! {
+ if let Some(header_snip) = snippet_opt(cx, header_span);
+ if let Some(ret_pos) = position_before_rarrow(&header_snip);
+ if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
+ then {
+ let help = format!("make the function `async` and {}", ret_sugg);
+ diag.span_suggestion(
+ header_span,
+ &help,
+ format!("async {}{}", &header_snip[..ret_pos], ret_snip),
+ Applicability::MachineApplicable
+ );
+
+ let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
+ diag.span_suggestion(
+ block.span,
+ "move the body of the async block to the enclosing function",
+ body_snip.to_string(),
+ Applicability::MachineApplicable
+ );
+ }
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+fn future_trait_ref<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: &'tcx Ty<'tcx>,
+) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> {
+ if_chain! {
+ if let TyKind::OpaqueDef(item_id, bounds) = ty.kind;
+ let item = cx.tcx.hir().item(item_id);
+ if let ItemKind::OpaqueTy(opaque) = &item.kind;
+ if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
+ if let GenericBound::Trait(poly, _) = bound {
+ Some(&poly.trait_ref)
+ } else {
+ None
+ }
+ });
+ if trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait();
+ then {
+ let output_lifetimes = bounds
+ .iter()
+ .filter_map(|bound| {
+ if let GenericArg::Lifetime(lt) = bound {
+ Some(lt.name)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ return Some((trait_ref, output_lifetimes));
+ }
+ }
+
+ None
+}
+
+fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
+ if_chain! {
+ if let Some(segment) = trait_ref.path.segments.last();
+ if let Some(args) = segment.args;
+ if args.bindings.len() == 1;
+ let binding = &args.bindings[0];
+ if binding.ident.name == sym::Output;
+ if let TypeBindingKind::Equality{ty: output} = binding.kind;
+ then {
+ return Some(output)
+ }
+ }
+
+ None
+}
+
+fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName]) -> bool {
+ let input_lifetimes: Vec<LifetimeName> = inputs
+ .iter()
+ .filter_map(|ty| {
+ if let TyKind::Rptr(lt, _) = ty.kind {
+ Some(lt.name)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ // The lint should trigger in one of these cases:
+ // - There are no input lifetimes
+ // - There's only one output lifetime bound using `+ '_`
+ // - All input lifetimes are explicitly bound to the output
+ input_lifetimes.is_empty()
+ || (output_lifetimes.len() == 1 && matches!(output_lifetimes[0], LifetimeName::Underscore))
+ || input_lifetimes
+ .iter()
+ .all(|in_lt| output_lifetimes.iter().any(|out_lt| in_lt == out_lt))
+}
+
+fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
+ if_chain! {
+ if let Some(block_expr) = block.expr;
+ if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR);
+ if args.len() == 1;
+ if let Expr{kind: ExprKind::Closure(_, _, body_id, ..), ..} = args[0];
+ let closure_body = cx.tcx.hir().body(body_id);
+ if closure_body.generator_kind == Some(GeneratorKind::Async(AsyncGeneratorKind::Block));
+ then {
+ return Some(closure_body);
+ }
+ }
+
+ None
+}
+
+fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, String)> {
+ match output.kind {
+ TyKind::Tup(tys) if tys.is_empty() => {
+ let sugg = "remove the return type";
+ Some((sugg, "".into()))
+ },
+ _ => {
+ let sugg = "return the output of the future directly";
+ snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {}", snip)))
+ },
+ }
+}
--- /dev/null
- use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
+use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::IfLetOrMatch;
+use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
- def::Res, Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath,
++use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
+use clippy_utils::{
+ can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id,
+ peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
+};
+use rustc_ast::util::parser::PREC_POSTFIX;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{
- let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) {
++ def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path,
++ QPath, UnsafeSource,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `match` which could be implemented using `map`
+ ///
+ /// ### Why is this bad?
+ /// Using the `map` method is clearer and more concise.
+ ///
+ /// ### Example
+ /// ```rust
+ /// match Some(0) {
+ /// Some(x) => Some(x + 1),
+ /// None => None,
+ /// };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// Some(0).map(|x| x + 1);
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub MANUAL_MAP,
+ style,
+ "reimplementation of `map`"
+}
+
+declare_lint_pass!(ManualMap => [MANUAL_MAP]);
+
+impl LateLintPass<'_> for ManualMap {
+ #[allow(clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (scrutinee, then_pat, then_body, else_pat, else_body) = match IfLetOrMatch::parse(cx, expr) {
+ Some(IfLetOrMatch::IfLet(scrutinee, pat, body, Some(r#else))) => (scrutinee, pat, body, None, r#else),
+ Some(IfLetOrMatch::Match(
+ scrutinee,
+ [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }],
+ _,
+ )) => (scrutinee, arm1.pat, arm1.body, Some(arm2.pat), arm2.body),
+ _ => return,
+ };
+ if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ let (scrutinee_ty, ty_ref_count, ty_mutability) =
+ peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
+ if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
+ && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
+ {
+ return;
+ }
+
+ let expr_ctxt = expr.span.ctxt();
+ let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
+ try_parse_pattern(cx, then_pat, expr_ctxt),
+ else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
+ ) {
+ (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
+ (else_body, pattern, ref_count, true)
+ },
+ (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
+ (else_body, pattern, ref_count, false)
+ },
+ (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
+ (then_body, pattern, ref_count, true)
+ },
+ (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
+ (then_body, pattern, ref_count, false)
+ },
+ _ => return,
+ };
+
+ // Top level or patterns aren't allowed in closures.
+ if matches!(some_pat.kind, PatKind::Or(_)) {
+ return;
+ }
+
- if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
++ let some_expr = match get_some_expr(cx, some_expr, false, expr_ctxt) {
+ Some(expr) => expr,
+ None => return,
+ };
+
+ // These two lints will go back and forth with each other.
- if !cx.typeck_results().expr_adjustments(some_expr).is_empty() {
++ if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
+ && !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
+ {
+ return;
+ }
+
+ // `map` won't perform any adjustments.
- match can_move_expr_to_closure(cx, some_expr) {
++ if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
+ return;
+ }
+
+ // Determine which binding mode to use.
+ let explicit_ref = some_pat.contains_explicit_ref_binding();
+ let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
+
+ let as_ref_str = match binding_ref {
+ Some(Mutability::Mut) => ".as_mut()",
+ Some(Mutability::Not) => ".as_ref()",
+ None => "",
+ };
+
- match can_pass_as_func(cx, id, some_expr) {
- Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
++ match can_move_expr_to_closure(cx, some_expr.expr) {
+ Some(captures) => {
+ // Check if captures the closure will need conflict with borrows made in the scrutinee.
+ // TODO: check all the references made in the scrutinee expression. This will require interacting
+ // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
+ if let Some(binding_ref_mutability) = binding_ref {
+ let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
+ ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
+ _ => None,
+ });
+ if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
+ match captures.get(l) {
+ Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
+ Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
+ return;
+ },
+ Some(CaptureKind::Ref(Mutability::Not)) | None => (),
+ }
+ }
+ }
+ },
+ None => return,
+ };
+
+ let mut app = Applicability::MachineApplicable;
+
+ // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
+ // it's being passed by value.
+ let scrutinee = peel_hir_expr_refs(scrutinee).0;
+ let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
+ let scrutinee_str =
+ if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
+ format!("({})", scrutinee_str)
+ } else {
+ scrutinee_str.into()
+ };
+
+ let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
- },
- _ => {
- if path_to_local_id(some_expr, id)
++ if_chain! {
++ if !some_expr.needs_unsafe_block;
++ if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
++ if func.span.ctxt() == some_expr.expr.span.ctxt();
++ then {
+ snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
- format!(
- "|{}{}| {}",
- annotation,
- some_binding,
- snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
- )
- },
++ } else {
++ if path_to_local_id(some_expr.expr, id)
+ && !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
+ && binding_ref.is_some()
+ {
+ return;
+ }
+
+ // `ref` and `ref mut` annotations were handled earlier.
+ let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
+ "mut "
+ } else {
+ ""
+ };
- format!(
- "|{}| {}",
- snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
- snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
- )
++ let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
++ if some_expr.needs_unsafe_block {
++ format!("|{}{}| unsafe {{ {} }}", annotation, some_binding, expr_snip)
++ } else {
++ format!("|{}{}| {}", annotation, some_binding, expr_snip)
++ }
++ }
+ }
+ } else if !is_wild_none && explicit_ref.is_none() {
+ // TODO: handle explicit reference annotations.
- if path_to_local_id(arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
++ let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
++ let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
++ if some_expr.needs_unsafe_block {
++ format!("|{}| unsafe {{ {} }}", pat_snip, expr_snip)
++ } else {
++ format!("|{}| {}", pat_snip, expr_snip)
++ }
+ } else {
+ // Refutable bindings and mixed reference annotations can't be handled by `map`.
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_MAP,
+ expr.span,
+ "manual implementation of `Option::map`",
+ "try this",
+ if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
+ format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
+ } else {
+ format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)
+ },
+ app,
+ );
+ }
+}
+
+// Checks whether the expression could be passed as a function, or whether a closure is needed.
+// Returns the function to be passed to `map` if it exists.
+fn can_pass_as_func(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ match expr.kind {
+ ExprKind::Call(func, [arg])
- fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxContext) -> Option<&'tcx Expr<'tcx>> {
++ if path_to_local_id(arg, binding)
++ && cx.typeck_results().expr_adjustments(arg).is_empty()
++ && !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
+ {
+ Some(func)
+ },
+ _ => None,
+ }
+}
+
+enum OptionPat<'a> {
+ Wild,
+ None,
+ Some {
+ // The pattern contained in the `Some` tuple.
+ pattern: &'a Pat<'a>,
+ // The number of references before the `Some` tuple.
+ // e.g. `&&Some(_)` has a ref count of 2.
+ ref_count: usize,
+ },
+}
+
++struct SomeExpr<'tcx> {
++ expr: &'tcx Expr<'tcx>,
++ needs_unsafe_block: bool,
++}
++
+// Try to parse into a recognized `Option` pattern.
+// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
+fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
+ fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
+ match pat.kind {
+ PatKind::Wild => Some(OptionPat::Wild),
+ PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
+ PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
+ PatKind::TupleStruct(ref qpath, [pattern], _)
+ if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
+ {
+ Some(OptionPat::Some { pattern, ref_count })
+ },
+ _ => None,
+ }
+ }
+ f(cx, pat, 0, ctxt)
+}
+
+// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
- ) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(arg),
++fn get_some_expr(
++ cx: &LateContext<'tcx>,
++ expr: &'tcx Expr<'_>,
++ needs_unsafe_block: bool,
++ ctxt: SyntaxContext,
++) -> Option<SomeExpr<'tcx>> {
+ // TODO: Allow more complex expressions.
+ match expr.kind {
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(ref qpath),
+ ..
+ },
+ [arg],
- ) => get_some_expr(cx, expr, ctxt),
++ ) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(SomeExpr {
++ expr: arg,
++ needs_unsafe_block,
++ }),
+ ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(expr),
++ rules,
+ ..
+ },
+ _,
++ ) => get_some_expr(
++ cx,
++ expr,
++ needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
++ ctxt,
++ ),
+ _ => None,
+ }
+}
+
+// Checks for the `None` value.
+fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ match expr.kind {
+ ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
+ ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: Some(expr),
+ ..
+ },
+ _,
+ ) => is_none_expr(cx, expr),
+ _ => false,
+ }
+}
--- /dev/null
+use clippy_utils::attrs::is_doc_hidden;
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_ast::ast::{FieldDef, Item, ItemKind, Variant, VariantData, VisibilityKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual implementations of the non-exhaustive pattern.
+ ///
+ /// ### Why is this bad?
+ /// Using the #[non_exhaustive] attribute expresses better the intent
+ /// and allows possible optimizations when applied to enums.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct S {
+ /// pub a: i32,
+ /// pub b: i32,
+ /// _c: (),
+ /// }
+ ///
+ /// enum E {
+ /// A,
+ /// B,
+ /// #[doc(hidden)]
+ /// _C,
+ /// }
+ ///
+ /// struct T(pub i32, pub i32, ());
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[non_exhaustive]
+ /// struct S {
+ /// pub a: i32,
+ /// pub b: i32,
+ /// }
+ ///
+ /// #[non_exhaustive]
+ /// enum E {
+ /// A,
+ /// B,
+ /// }
+ ///
+ /// #[non_exhaustive]
+ /// struct T(pub i32, pub i32);
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub MANUAL_NON_EXHAUSTIVE,
+ style,
+ "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
+}
+
+#[derive(Clone)]
+pub struct ManualNonExhaustive {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualNonExhaustive {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
+
+impl EarlyLintPass for ManualNonExhaustive {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
+ return;
+ }
+
+ match &item.kind {
+ ItemKind::Enum(def, _) => {
+ check_manual_non_exhaustive_enum(cx, item, &def.variants);
+ },
+ ItemKind::Struct(variant_data, _) => {
+ if let VariantData::Unit(..) = variant_data {
+ return;
+ }
+
+ check_manual_non_exhaustive_struct(cx, item, variant_data);
+ },
+ _ => {},
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
+
+fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {
+ fn is_non_exhaustive_marker(variant: &Variant) -> bool {
+ matches!(variant.data, VariantData::Unit(_))
+ && variant.ident.as_str().starts_with('_')
+ && is_doc_hidden(&variant.attrs)
+ }
+
+ let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
+ if_chain! {
+ if let Some(marker) = markers.next();
+ if markers.count() == 0 && variants.len() > 1;
+ then {
+ span_lint_and_then(
+ cx,
+ MANUAL_NON_EXHAUSTIVE,
+ item.span,
+ "this seems like a manual implementation of the non-exhaustive pattern",
+ |diag| {
+ if_chain! {
+ if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
+ let header_span = cx.sess.source_map().span_until_char(item.span, '{');
+ if let Some(snippet) = snippet_opt(cx, header_span);
+ then {
+ diag.span_suggestion(
+ header_span,
+ "add the attribute",
+ format!("#[non_exhaustive] {}", snippet),
+ Applicability::Unspecified,
+ );
+ }
+ }
+ diag.span_help(marker.span, "remove this variant");
+ });
+ }
+ }
+}
+
+fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) {
+ fn is_private(field: &FieldDef) -> bool {
+ matches!(field.vis.kind, VisibilityKind::Inherited)
+ }
+
+ fn is_non_exhaustive_marker(field: &FieldDef) -> bool {
+ is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_'))
+ }
+
+ fn find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span {
+ let delimiter = match data {
+ VariantData::Struct(..) => '{',
+ VariantData::Tuple(..) => '(',
+ VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"),
+ };
+
+ cx.sess.source_map().span_until_char(item.span, delimiter)
+ }
+
+ let fields = data.fields();
+ let private_fields = fields.iter().filter(|f| is_private(f)).count();
+ let public_fields = fields.iter().filter(|f| f.vis.kind.is_pub()).count();
+
+ if_chain! {
+ if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1;
+ if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f));
+ then {
+ span_lint_and_then(
+ cx,
+ MANUAL_NON_EXHAUSTIVE,
+ item.span,
+ "this seems like a manual implementation of the non-exhaustive pattern",
+ |diag| {
+ if_chain! {
+ if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
+ let header_span = find_header_span(cx, item, data);
+ if let Some(snippet) = snippet_opt(cx, header_span);
+ then {
+ diag.span_suggestion(
+ header_span,
+ "add the attribute",
+ format!("#[non_exhaustive] {}", snippet),
+ Applicability::Unspecified,
+ );
+ }
+ }
+ diag.span_help(marker.span, "remove this field");
+ });
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_lang_ctor, path_to_local_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{ResultErr, ResultOk};
+use rustc_hir::{Expr, ExprKind, PatKind};
+use rustc_lint::LintContext;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Finds patterns that reimplement `Option::ok_or`.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// Concise code helps focusing on behavior instead of boilerplate.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// foo.map_or(Err("error"), |v| Ok(v));
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// foo.ok_or("error");
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub MANUAL_OK_OR,
+ pedantic,
+ "finds patterns that can be encoded more concisely with `Option::ok_or`"
+}
+
+declare_lint_pass!(ManualOkOr => [MANUAL_OK_OR]);
+
+impl LateLintPass<'_> for ManualOkOr {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>) {
+ if in_external_macro(cx.sess(), scrutinee.span) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(method_segment, _, args, _) = scrutinee.kind;
+ if method_segment.ident.name == sym!(map_or);
+ if args.len() == 3;
+ let method_receiver = &args[0];
+ let ty = cx.typeck_results().expr_ty(method_receiver);
+ if is_type_diagnostic_item(cx, ty, sym::Option);
+ let or_expr = &args[1];
+ if is_ok_wrapping(cx, &args[2]);
+ if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind;
+ if is_lang_ctor(cx, err_path, ResultErr);
+ if let Some(method_receiver_snippet) = snippet_opt(cx, method_receiver.span);
+ if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span);
+ if let Some(indent) = indent_of(cx, scrutinee.span);
+ then {
+ let reindented_err_arg_snippet =
+ reindent_multiline(err_arg_snippet.into(), true, Some(indent + 4));
+ span_lint_and_sugg(
+ cx,
+ MANUAL_OK_OR,
+ scrutinee.span,
+ "this pattern reimplements `Option::ok_or`",
+ "replace with",
+ format!(
+ "{}.ok_or({})",
+ method_receiver_snippet,
+ reindented_err_arg_snippet
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
+ if let ExprKind::Path(ref qpath) = map_expr.kind {
+ if is_lang_ctor(cx, qpath, ResultOk) {
+ return true;
+ }
+ }
+ if_chain! {
+ if let ExprKind::Closure(_, _, body_id, ..) = map_expr.kind;
+ let body = cx.tcx.hir().body(body_id);
+ if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind;
+ if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind;
+ if is_lang_ctor(cx, ok_path, ResultOk);
+ then { path_to_local_id(ok_arg, param_id) } else { false }
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::source::snippet;
+use clippy_utils::usage::mutated_variables;
+use clippy_utils::{eq_expr_value, higher, match_def_path, meets_msrv, msrvs, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_hir::def::Res;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::BinOpKind;
+use rustc_hir::{BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Spanned;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing using
+ /// the pattern's length.
+ ///
+ /// ### Why is this bad?
+ /// Using `str:strip_{prefix,suffix}` is safer and may have better performance as there is no
+ /// slicing which may panic and the compiler does not need to insert this panic code. It is
+ /// also sometimes more readable as it removes the need for duplicating or storing the pattern
+ /// used by `str::{starts,ends}_with` and in the slicing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s = "hello, world!";
+ /// if s.starts_with("hello, ") {
+ /// assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let s = "hello, world!";
+ /// if let Some(end) = s.strip_prefix("hello, ") {
+ /// assert_eq!(end.to_uppercase(), "WORLD!");
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub MANUAL_STRIP,
+ complexity,
+ "suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing"
+}
+
+pub struct ManualStrip {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualStrip {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualStrip => [MANUAL_STRIP]);
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum StripKind {
+ Prefix,
+ Suffix,
+}
+
+impl<'tcx> LateLintPass<'tcx> for ManualStrip {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::STR_STRIP_PREFIX) {
+ return;
+ }
+
+ if_chain! {
+ if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
+ if let ExprKind::MethodCall(_, _, [target_arg, pattern], _) = cond.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id);
+ if let ExprKind::Path(target_path) = &target_arg.kind;
+ then {
+ let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) {
+ StripKind::Prefix
+ } else if match_def_path(cx, method_def_id, &paths::STR_ENDS_WITH) {
+ StripKind::Suffix
+ } else {
+ return;
+ };
+ let target_res = cx.qpath_res(target_path, target_arg.hir_id);
+ if target_res == Res::Err {
+ return;
+ };
+
+ if_chain! {
+ if let Res::Local(hir_id) = target_res;
+ if let Some(used_mutably) = mutated_variables(then, cx);
+ if used_mutably.contains(&hir_id);
+ then {
+ return;
+ }
+ }
+
+ let strippings = find_stripping(cx, strip_kind, target_res, pattern, then);
+ if !strippings.is_empty() {
+
+ let kind_word = match strip_kind {
+ StripKind::Prefix => "prefix",
+ StripKind::Suffix => "suffix",
+ };
+
+ let test_span = expr.span.until(then.span);
+ span_lint_and_then(cx, MANUAL_STRIP, strippings[0], &format!("stripping a {} manually", kind_word), |diag| {
+ diag.span_note(test_span, &format!("the {} was tested here", kind_word));
+ multispan_sugg(
+ diag,
+ &format!("try using the `strip_{}` method", kind_word),
+ vec![(test_span,
+ format!("if let Some(<stripped>) = {}.strip_{}({}) ",
+ snippet(cx, target_arg.span, ".."),
+ kind_word,
+ snippet(cx, pattern.span, "..")))]
+ .into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
+ );
+ });
+ }
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
+fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if_chain! {
+ if let ExprKind::MethodCall(_, _, [arg], _) = expr.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if match_def_path(cx, method_def_id, &paths::STR_LEN);
+ then {
+ Some(arg)
+ } else {
+ None
+ }
+ }
+}
+
+// Returns the length of the `expr` if it's a constant string or char.
+fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
+ let (value, _) = constant(cx, cx.typeck_results(), expr)?;
+ match value {
+ Constant::Str(value) => Some(value.len() as u128),
+ Constant::Char(value) => Some(value.len_utf8() as u128),
+ _ => None,
+ }
+}
+
+// Tests if `expr` equals the length of the pattern.
+fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Int(n, _),
+ ..
+ }) = expr.kind
+ {
+ constant_length(cx, pattern).map_or(false, |length| length == n)
+ } else {
+ len_arg(cx, expr).map_or(false, |arg| eq_expr_value(cx, pattern, arg))
+ }
+}
+
+// Tests if `expr` is a `&str`.
+fn is_ref_str(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match cx.typeck_results().expr_ty_adjusted(expr).kind() {
+ ty::Ref(_, ty, _) => ty.is_str(),
+ _ => false,
+ }
+}
+
+// Removes the outer `AddrOf` expression if needed.
+fn peel_ref<'a>(expr: &'a Expr<'_>) -> &'a Expr<'a> {
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, unref) = &expr.kind {
+ unref
+ } else {
+ expr
+ }
+}
+
+// Find expressions where `target` is stripped using the length of `pattern`.
+// We'll suggest replacing these expressions with the result of the `strip_{prefix,suffix}`
+// method.
+fn find_stripping<'tcx>(
+ cx: &LateContext<'tcx>,
+ strip_kind: StripKind,
+ target: Res,
+ pattern: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+) -> Vec<Span> {
+ struct StrippingFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ strip_kind: StripKind,
+ target: Res,
+ pattern: &'tcx Expr<'tcx>,
+ results: Vec<Span>,
+ }
+
+ impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> {
+ type Map = Map<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ if_chain! {
+ if is_ref_str(self.cx, ex);
+ let unref = peel_ref(ex);
+ if let ExprKind::Index(indexed, index) = &unref.kind;
+ if let Some(higher::Range { start, end, .. }) = higher::Range::hir(index);
+ if let ExprKind::Path(path) = &indexed.kind;
+ if self.cx.qpath_res(path, ex.hir_id) == self.target;
+ then {
+ match (self.strip_kind, start, end) {
+ (StripKind::Prefix, Some(start), None) => {
+ if eq_pattern_length(self.cx, self.pattern, start) {
+ self.results.push(ex.span);
+ return;
+ }
+ },
+ (StripKind::Suffix, None, Some(end)) => {
+ if_chain! {
+ if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, left, right) = end.kind;
+ if let Some(left_arg) = len_arg(self.cx, left);
+ if let ExprKind::Path(left_path) = &left_arg.kind;
+ if self.cx.qpath_res(left_path, left_arg.hir_id) == self.target;
+ if eq_pattern_length(self.cx, self.pattern, right);
+ then {
+ self.results.push(ex.span);
+ return;
+ }
+ }
+ },
+ _ => {}
+ }
+ }
+ }
+
+ walk_expr(self, ex);
+ }
+ }
+
+ let mut finder = StrippingFinder {
+ cx,
+ strip_kind,
+ target,
+ pattern,
+ results: vec![],
+ };
+ walk_expr(&mut finder, expr);
+ finder.results
+}
--- /dev/null
+use clippy_utils::consts::constant_simple;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::usage::contains_return_break_continue_macro;
+use clippy_utils::{in_constant, is_lang_ctor, path_to_local_id, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
+use rustc_hir::{Arm, Expr, ExprKind, PatKind};
+use rustc_lint::LintContext;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
+ ///
+ /// ### Why is this bad?
+ /// Concise code helps focusing on behavior instead of boilerplate.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// match foo {
+ /// Some(v) => v,
+ /// None => 1,
+ /// };
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// let foo: Option<i32> = None;
+ /// foo.unwrap_or(1);
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub MANUAL_UNWRAP_OR,
+ complexity,
+ "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
+}
+
+declare_lint_pass!(ManualUnwrapOr => [MANUAL_UNWRAP_OR]);
+
+impl LateLintPass<'_> for ManualUnwrapOr {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
+ return;
+ }
+ lint_manual_unwrap_or(cx, expr);
+ }
+}
+
+fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
+ if_chain! {
+ if arms.len() == 2;
+ if arms.iter().all(|arm| arm.guard.is_none());
+ if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| {
+ match arm.pat.kind {
+ PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
+ PatKind::TupleStruct(ref qpath, [pat], _) =>
+ matches!(pat.kind, PatKind::Wild) && is_lang_ctor(cx, qpath, ResultErr),
+ _ => false,
+ }
+ });
+ let unwrap_arm = &arms[1 - idx];
+ if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind;
+ if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
+ if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
+ if path_to_local_id(unwrap_arm.body, binding_hir_id);
+ if cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty();
+ if !contains_return_break_continue_macro(or_arm.body);
+ then {
+ Some(or_arm)
+ } else {
+ None
+ }
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind;
+ let ty = cx.typeck_results().expr_ty(scrutinee);
+ if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) {
+ Some("Option")
+ } else if is_type_diagnostic_item(cx, ty, sym::Result) {
+ Some("Result")
+ } else {
+ None
+ };
+ if let Some(or_arm) = applicable_or_arm(cx, match_arms);
+ if let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span);
+ if let Some(indent) = indent_of(cx, expr.span);
+ if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();
+ then {
+ let reindented_or_body =
+ reindent_multiline(or_body_snippet.into(), true, Some(indent));
+
+ let suggestion = if scrutinee.span.from_expansion() {
+ // we don't want parentheses around macro, e.g. `(some_macro!()).unwrap_or(0)`
+ sugg::Sugg::hir_with_macro_callsite(cx, scrutinee, "..")
+ }
+ else {
+ sugg::Sugg::hir(cx, scrutinee, "..").maybe_par()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MANUAL_UNWRAP_OR, expr.span,
+ &format!("this pattern reimplements `{}::unwrap_or`", ty_name),
+ "replace with",
+ format!(
+ "{}.unwrap_or({})",
+ suggestion,
+ reindented_or_body,
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_trait_method;
+use clippy_utils::remove_blocks;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::Mutability;
+use rustc_middle::ty;
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Ident;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `map(|x| x.clone())` or
+ /// dereferencing closures for `Copy` types, on `Iterator` or `Option`,
+ /// and suggests `cloned()` or `copied()` instead
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.map(|i| *i);
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// let x = vec![42, 43];
+ /// let y = x.iter();
+ /// let z = y.cloned();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MAP_CLONE,
+ style,
+ "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types"
+}
+
+declare_lint_pass!(MapClone => [MAP_CLONE]);
+
+impl<'tcx> LateLintPass<'tcx> for MapClone {
+ fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+
+ if_chain! {
+ if let hir::ExprKind::MethodCall(method, _, args, _) = e.kind;
+ if args.len() == 2;
+ if method.ident.name == sym::map;
+ let ty = cx.typeck_results().expr_ty(&args[0]);
+ if is_type_diagnostic_item(cx, ty, sym::Option) || is_trait_method(cx, e, sym::Iterator);
+ if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind;
+ then {
+ let closure_body = cx.tcx.hir().body(body_id);
+ let closure_expr = remove_blocks(&closure_body.value);
+ match closure_body.params[0].pat.kind {
+ hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding(
+ hir::BindingAnnotation::Unannotated, .., name, None
+ ) = inner.kind {
+ if ident_eq(name, closure_expr) {
+ lint(cx, e.span, args[0].span, true);
+ }
+ },
+ hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => {
+ match closure_expr.kind {
+ hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
+ if ident_eq(name, inner) {
+ if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
+ lint(cx, e.span, args[0].span, true);
+ }
+ }
+ },
+ hir::ExprKind::MethodCall(method, _, [obj], _) => if_chain! {
+ if ident_eq(name, obj) && method.ident.name == sym::clone;
+ if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id);
+ if let Some(trait_id) = cx.tcx.trait_of_item(fn_id);
+ if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id);
+ // no autoderefs
+ if !cx.typeck_results().expr_adjustments(obj).iter()
+ .any(|a| matches!(a.kind, Adjust::Deref(Some(..))));
+ then {
+ let obj_ty = cx.typeck_results().expr_ty(obj);
+ if let ty::Ref(_, ty, mutability) = obj_ty.kind() {
+ if matches!(mutability, Mutability::Not) {
+ let copy = is_copy(cx, ty);
+ lint(cx, e.span, args[0].span, copy);
+ }
+ } else {
+ lint_needless_cloning(cx, e.span, args[0].span);
+ }
+ }
+ },
+ _ => {},
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool {
+ if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind {
+ path.segments.len() == 1 && path.segments[0].ident == name
+ } else {
+ false
+ }
+}
+
+fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
+ span_lint_and_sugg(
+ cx,
+ MAP_CLONE,
+ root.trim_start(receiver).unwrap(),
+ "you are needlessly cloning iterator elements",
+ "remove the `map` call",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn lint(cx: &LateContext<'_>, replace: Span, root: Span, copied: bool) {
+ let mut applicability = Applicability::MachineApplicable;
+ if copied {
+ span_lint_and_sugg(
+ cx,
+ MAP_CLONE,
+ replace,
+ "you are using an explicit closure for copying elements",
+ "consider calling the dedicated `copied` method",
+ format!(
+ "{}.copied()",
+ snippet_with_applicability(cx, root, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ } else {
+ span_lint_and_sugg(
+ cx,
+ MAP_CLONE,
+ replace,
+ "you are using an explicit closure for cloning elements",
+ "consider calling the dedicated `cloned` method",
+ format!(
+ "{}.cloned()",
+ snippet_with_applicability(cx, root, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{CaptureBy, Expr, ExprKind, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `map_err(|_| Some::Enum)`
+ ///
+ /// ### Why is this bad?
+ /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error
+ ///
+ /// ### Example
+ /// Before:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// #[derive(Debug)]
+ /// enum Error {
+ /// Indivisible,
+ /// Remainder(u8),
+ /// }
+ ///
+ /// impl fmt::Display for Error {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// match self {
+ /// Error::Indivisible => write!(f, "could not divide input by three"),
+ /// Error::Remainder(remainder) => write!(
+ /// f,
+ /// "input is not divisible by three, remainder = {}",
+ /// remainder
+ /// ),
+ /// }
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for Error {}
+ ///
+ /// fn divisible_by_3(input: &str) -> Result<(), Error> {
+ /// input
+ /// .parse::<i32>()
+ /// .map_err(|_| Error::Indivisible)
+ /// .map(|v| v % 3)
+ /// .and_then(|remainder| {
+ /// if remainder == 0 {
+ /// Ok(())
+ /// } else {
+ /// Err(Error::Remainder(remainder as u8))
+ /// }
+ /// })
+ /// }
+ /// ```
+ ///
+ /// After:
+ /// ```rust
+ /// use std::{fmt, num::ParseIntError};
+ ///
+ /// #[derive(Debug)]
+ /// enum Error {
+ /// Indivisible(ParseIntError),
+ /// Remainder(u8),
+ /// }
+ ///
+ /// impl fmt::Display for Error {
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ /// match self {
+ /// Error::Indivisible(_) => write!(f, "could not divide input by three"),
+ /// Error::Remainder(remainder) => write!(
+ /// f,
+ /// "input is not divisible by three, remainder = {}",
+ /// remainder
+ /// ),
+ /// }
+ /// }
+ /// }
+ ///
+ /// impl std::error::Error for Error {
+ /// fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ /// match self {
+ /// Error::Indivisible(source) => Some(source),
+ /// _ => None,
+ /// }
+ /// }
+ /// }
+ ///
+ /// fn divisible_by_3(input: &str) -> Result<(), Error> {
+ /// input
+ /// .parse::<i32>()
+ /// .map_err(Error::Indivisible)
+ /// .map(|v| v % 3)
+ /// .and_then(|remainder| {
+ /// if remainder == 0 {
+ /// Ok(())
+ /// } else {
+ /// Err(Error::Remainder(remainder as u8))
+ /// }
+ /// })
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub MAP_ERR_IGNORE,
+ restriction,
+ "`map_err` should not ignore the original error"
+}
+
+declare_lint_pass!(MapErrIgnore => [MAP_ERR_IGNORE]);
+
+impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
+ // do not try to lint if this is from a macro or desugaring
+ fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+
+ // check if this is a method call (e.g. x.foo())
+ if let ExprKind::MethodCall(method, _t_span, args, _) = e.kind {
+ // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1]
+ // Enum::Variant[2]))
+ if method.ident.as_str() == "map_err" && args.len() == 2 {
+ // make sure the first argument is a closure, and grab the CaptureRef, body_id, and body_span fields
+ if let ExprKind::Closure(capture, _, body_id, body_span, _) = args[1].kind {
+ // check if this is by Reference (meaning there's no move statement)
+ if capture == CaptureBy::Ref {
+ // Get the closure body to check the parameters and values
+ let closure_body = cx.tcx.hir().body(body_id);
+ // make sure there's only one parameter (`|_|`)
+ if closure_body.params.len() == 1 {
+ // make sure that parameter is the wild token (`_`)
+ if let PatKind::Wild = closure_body.params[0].pat.kind {
+ // span the area of the closure capture and warn that the
+ // original error will be thrown away
+ span_lint_and_help(
+ cx,
+ MAP_ERR_IGNORE,
+ body_span,
+ "`map_err(|_|...` wildcard pattern discards the original error",
+ None,
+ "consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)",
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace(".", "_"),
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{iter_input_pats, method_chain_args};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `option.map(f)` where f is a function
+ /// or closure that returns the unit type `()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more clearly with
+ /// an if let statement
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn do_stuff() -> Option<String> { Some(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Option<String> { Some(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Option<String> = do_stuff();
+ /// x.map(log_err_msg);
+ /// # let x: Option<String> = do_stuff();
+ /// x.map(|msg| log_err_msg(format_msg(msg)));
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn do_stuff() -> Option<String> { Some(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Option<String> { Some(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Option<String> = do_stuff();
+ /// if let Some(msg) = x {
+ /// log_err_msg(msg);
+ /// }
+ ///
+ /// # let x: Option<String> = do_stuff();
+ /// if let Some(msg) = x {
+ /// log_err_msg(format_msg(msg));
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub OPTION_MAP_UNIT_FN,
+ complexity,
+ "using `option.map(f)`, where `f` is a function or closure that returns `()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `result.map(f)` where f is a function
+ /// or closure that returns the unit type `()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more clearly with
+ /// an if let statement
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn do_stuff() -> Result<String, String> { Ok(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Result<String, String> { Ok(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Result<String, String> = do_stuff();
+ /// x.map(log_err_msg);
+ /// # let x: Result<String, String> = do_stuff();
+ /// x.map(|msg| log_err_msg(format_msg(msg)));
+ /// ```
+ ///
+ /// The correct use would be:
+ ///
+ /// ```rust
+ /// # fn do_stuff() -> Result<String, String> { Ok(String::new()) }
+ /// # fn log_err_msg(foo: String) -> Result<String, String> { Ok(foo) }
+ /// # fn format_msg(foo: String) -> String { String::new() }
+ /// let x: Result<String, String> = do_stuff();
+ /// if let Ok(msg) = x {
+ /// log_err_msg(msg);
+ /// };
+ /// # let x: Result<String, String> = do_stuff();
+ /// if let Ok(msg) = x {
+ /// log_err_msg(format_msg(msg));
+ /// };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub RESULT_MAP_UNIT_FN,
+ complexity,
+ "using `result.map(f)`, where `f` is a function or closure that returns `()`"
+}
+
+declare_lint_pass!(MapUnit => [OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN]);
+
+fn is_unit_type(ty: Ty<'_>) -> bool {
+ match ty.kind() {
+ ty::Tuple(slice) => slice.is_empty(),
+ ty::Never => true,
+ _ => false,
+ }
+}
+
+fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+
+ if let ty::FnDef(id, _) = *ty.kind() {
+ if let Some(fn_type) = cx.tcx.fn_sig(id).no_bound_vars() {
+ return is_unit_type(fn_type.output());
+ }
+ }
+ false
+}
+
+fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+ is_unit_type(cx.typeck_results().expr_ty(expr))
+}
+
+/// The expression inside a closure may or may not have surrounding braces and
+/// semicolons, which causes problems when generating a suggestion. Given an
+/// expression that evaluates to '()' or '!', recursively remove useless braces
+/// and semi-colons until is suitable for including in the suggestion template
+fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<Span> {
+ if !is_unit_expression(cx, expr) {
+ return None;
+ }
+
+ match expr.kind {
+ hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(_, _, _, _) => {
+ // Calls can't be reduced any more
+ Some(expr.span)
+ },
+ hir::ExprKind::Block(block, _) => {
+ match (block.stmts, block.expr.as_ref()) {
+ (&[], Some(inner_expr)) => {
+ // If block only contains an expression,
+ // reduce `{ X }` to `X`
+ reduce_unit_expression(cx, inner_expr)
+ },
+ (&[ref inner_stmt], None) => {
+ // If block only contains statements,
+ // reduce `{ X; }` to `X` or `X;`
+ match inner_stmt.kind {
+ hir::StmtKind::Local(local) => Some(local.span),
+ hir::StmtKind::Expr(e) => Some(e.span),
+ hir::StmtKind::Semi(..) => Some(inner_stmt.span),
+ hir::StmtKind::Item(..) => None,
+ }
+ },
+ _ => {
+ // For closures that contain multiple statements
+ // it's difficult to get a correct suggestion span
+ // for all cases (multi-line closures specifically)
+ //
+ // We do not attempt to build a suggestion for those right now.
+ None
+ },
+ }
+ },
+ _ => None,
+ }
+}
+
+fn unit_closure<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
+ if_chain! {
+ if let hir::ExprKind::Closure(_, decl, inner_expr_id, _, _) = expr.kind;
+ let body = cx.tcx.hir().body(inner_expr_id);
+ let body_expr = &body.value;
+ if decl.inputs.len() == 1;
+ if is_unit_expression(cx, body_expr);
+ if let Some(binding) = iter_input_pats(decl, body).next();
+ then {
+ return Some((binding, body_expr));
+ }
+ }
+ None
+}
+
+/// Builds a name for the let binding variable (`var_arg`)
+///
+/// `x.field` => `x_field`
+/// `y` => `_y`
+///
+/// Anything else will return `a`.
+fn let_binding_name(cx: &LateContext<'_>, var_arg: &hir::Expr<'_>) -> String {
+ match &var_arg.kind {
++ hir::ExprKind::Field(_, _) => snippet(cx, var_arg.span, "_").replace('.', "_"),
+ hir::ExprKind::Path(_) => format!("_{}", snippet(cx, var_arg.span, "")),
+ _ => "a".to_string(),
+ }
+}
+
+#[must_use]
+fn suggestion_msg(function_type: &str, map_type: &str) -> String {
+ format!(
+ "called `map(f)` on an `{0}` value where `f` is a {1} that returns the unit type `()`",
+ map_type, function_type
+ )
+}
+
+fn lint_map_unit_fn(cx: &LateContext<'_>, stmt: &hir::Stmt<'_>, expr: &hir::Expr<'_>, map_args: &[hir::Expr<'_>]) {
+ let var_arg = &map_args[0];
+
+ let (map_type, variant, lint) = if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::Option) {
+ ("Option", "Some", OPTION_MAP_UNIT_FN)
+ } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(var_arg), sym::Result) {
+ ("Result", "Ok", RESULT_MAP_UNIT_FN)
+ } else {
+ return;
+ };
+ let fn_arg = &map_args[1];
+
+ if is_unit_function(cx, fn_arg) {
+ let msg = suggestion_msg("function", map_type);
+ let suggestion = format!(
+ "if let {0}({binding}) = {1} {{ {2}({binding}) }}",
+ variant,
+ snippet(cx, var_arg.span, "_"),
+ snippet(cx, fn_arg.span, "_"),
+ binding = let_binding_name(cx, var_arg)
+ );
+
+ span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
+ diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::MachineApplicable);
+ });
+ } else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
+ let msg = suggestion_msg("closure", map_type);
+
+ span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
+ if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
+ let suggestion = format!(
+ "if let {0}({1}) = {2} {{ {3} }}",
+ variant,
+ snippet(cx, binding.pat.span, "_"),
+ snippet(cx, var_arg.span, "_"),
+ snippet(cx, reduced_expr_span, "_")
+ );
+ diag.span_suggestion(
+ stmt.span,
+ "try this",
+ suggestion,
+ Applicability::MachineApplicable, // snippet
+ );
+ } else {
+ let suggestion = format!(
+ "if let {0}({1}) = {2} {{ ... }}",
+ variant,
+ snippet(cx, binding.pat.span, "_"),
+ snippet(cx, var_arg.span, "_"),
+ );
+ diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::HasPlaceholders);
+ }
+ });
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MapUnit {
+ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) {
+ if stmt.span.from_expansion() {
+ return;
+ }
+
+ if let hir::StmtKind::Semi(expr) = stmt.kind {
+ if let Some(arglists) = method_chain_args(expr, &["map"]) {
+ lint_map_unit_fn(cx, stmt, expr, arglists[0]);
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, LangItem, MatchSource};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `match vec[idx]` or `match vec[n..m]`.
+ ///
+ /// ### Why is this bad?
+ /// This can panic at runtime.
+ ///
+ /// ### Example
+ /// ```rust, no_run
+ /// let arr = vec![0, 1, 2, 3];
+ /// let idx = 1;
+ ///
+ /// // Bad
+ /// match arr[idx] {
+ /// 0 => println!("{}", 0),
+ /// 1 => println!("{}", 3),
+ /// _ => {},
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust, no_run
+ /// let arr = vec![0, 1, 2, 3];
+ /// let idx = 1;
+ ///
+ /// // Good
+ /// match arr.get(idx) {
+ /// Some(0) => println!("{}", 0),
+ /// Some(1) => println!("{}", 3),
+ /// _ => {},
+ /// }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub MATCH_ON_VEC_ITEMS,
+ pedantic,
+ "matching on vector elements can panic"
+}
+
+declare_lint_pass!(MatchOnVecItems => [MATCH_ON_VEC_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for MatchOnVecItems {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if_chain! {
+ if !in_external_macro(cx.sess(), expr.span);
+ if let ExprKind::Match(match_expr, _, MatchSource::Normal) = expr.kind;
+ if let Some(idx_expr) = is_vec_indexing(cx, match_expr);
+ if let ExprKind::Index(vec, idx) = idx_expr.kind;
+
+ then {
+ // FIXME: could be improved to suggest surrounding every pattern with Some(_),
+ // but only when `or_patterns` are stabilized.
+ span_lint_and_sugg(
+ cx,
+ MATCH_ON_VEC_ITEMS,
+ match_expr.span,
+ "indexing into a vector may panic",
+ "try this",
+ format!(
+ "{}.get({})",
+ snippet(cx, vec.span, ".."),
+ snippet(cx, idx.span, "..")
+ ),
+ Applicability::MaybeIncorrect
+ );
+ }
+ }
+ }
+}
+
+fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ if_chain! {
+ if let ExprKind::Index(array, index) = expr.kind;
+ if is_vector(cx, array);
+ if !is_full_range(cx, index);
+
+ then {
+ return Some(expr);
+ }
+ }
+
+ None
+}
+
+fn is_vector(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ let ty = ty.peel_refs();
+ is_type_diagnostic_item(cx, ty, sym::Vec)
+}
+
+fn is_full_range(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ let ty = ty.peel_refs();
+ is_type_lang_item(cx, ty, LangItem::RangeFull)
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::method_chain_args;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, PatKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary `ok()` in `while let`.
+ ///
+ /// ### Why is this bad?
+ /// Calling `ok()` in `while let` is unnecessary, instead match
+ /// on `Ok(pat)`
+ ///
+ /// ### Example
+ /// ```ignore
+ /// while let Some(value) = iter.next().ok() {
+ /// vec.push(value)
+ /// }
+ ///
+ /// if let Some(valie) = iter.next().ok() {
+ /// vec.push(value)
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// while let Ok(value) = iter.next() {
+ /// vec.push(value)
+ /// }
+ ///
+ /// if let Ok(value) = iter.next() {
+ /// vec.push(value)
+ /// }
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub MATCH_RESULT_OK,
+ style,
+ "usage of `ok()` in `let Some(pat)` statements is unnecessary, match on `Ok(pat)` instead"
+}
+
+declare_lint_pass!(MatchResultOk => [MATCH_RESULT_OK]);
+
+impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let (let_pat, let_expr, ifwhile) =
+ if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) {
+ (let_pat, let_expr, "if")
+ } else if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
+ (let_pat, let_expr, "while")
+ } else {
+ return;
+ };
+
+ if_chain! {
+ if let ExprKind::MethodCall(_, ok_span, [ref result_types_0, ..], _) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
+ if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation
+ if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized;
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::Result);
+ if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";
+
+ then {
+
+ let mut applicability = Applicability::MachineApplicable;
+ let some_expr_string = snippet_with_applicability(cx, y[0].span, "", &mut applicability);
+ let trimmed_ok = snippet_with_applicability(cx, let_expr.span.until(ok_span), "", &mut applicability);
+ let sugg = format!(
+ "{} let Ok({}) = {}",
+ ifwhile,
+ some_expr_string,
+ trimmed_ok.trim().trim_end_matches('.'),
+ );
+ span_lint_and_sugg(
+ cx,
+ MATCH_RESULT_OK,
+ expr.span.with_hi(let_expr.span.hi()),
+ "matching on `Some` with `ok()` is redundant",
+ &format!("consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string),
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::SymbolStr;
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `match` expressions modifying the case of a string with non-compliant arms
+ ///
+ /// ### Why is this bad?
+ /// The arm is unreachable, which is likely a mistake
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let text = "Foo";
+ ///
+ /// match &*text.to_ascii_lowercase() {
+ /// "foo" => {},
+ /// "Bar" => {},
+ /// _ => {},
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let text = "Foo";
+ ///
+ /// match &*text.to_ascii_lowercase() {
+ /// "foo" => {},
+ /// "bar" => {},
+ /// _ => {},
+ /// }
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub MATCH_STR_CASE_MISMATCH,
+ correctness,
+ "creation of a case altering match expression with non-compliant arms"
+}
+
+declare_lint_pass!(MatchStrCaseMismatch => [MATCH_STR_CASE_MISMATCH]);
+
+#[derive(Debug)]
+enum CaseMethod {
+ LowerCase,
+ AsciiLowerCase,
+ UpperCase,
+ AsciiUppercase,
+}
+
+impl LateLintPass<'_> for MatchStrCaseMismatch {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.tcx.sess, expr.span);
+ if let ExprKind::Match(match_expr, arms, MatchSource::Normal) = expr.kind;
+ if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(match_expr).kind();
+ if let ty::Str = ty.kind();
+ then {
+ let mut visitor = MatchExprVisitor {
+ cx,
+ case_method: None,
+ };
+
+ visitor.visit_expr(match_expr);
+
+ if let Some(case_method) = visitor.case_method {
+ if let Some((bad_case_span, bad_case_str)) = verify_case(&case_method, arms) {
+ lint(cx, &case_method, bad_case_span, &bad_case_str);
+ }
+ }
+ }
+ }
+ }
+}
+
+struct MatchExprVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ case_method: Option<CaseMethod>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for MatchExprVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
+ match ex.kind {
+ ExprKind::MethodCall(segment, _, [receiver], _)
+ if self.case_altered(&*segment.ident.as_str(), receiver) => {},
+ _ => walk_expr(self, ex),
+ }
+ }
+}
+
+impl<'a, 'tcx> MatchExprVisitor<'a, 'tcx> {
+ fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool {
+ if let Some(case_method) = get_case_method(segment_ident) {
+ let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs();
+
+ if is_type_diagnostic_item(self.cx, ty, sym::String) || ty.kind() == &ty::Str {
+ self.case_method = Some(case_method);
+ return true;
+ }
+ }
+
+ false
+ }
+}
+
+fn get_case_method(segment_ident_str: &str) -> Option<CaseMethod> {
+ match segment_ident_str {
+ "to_lowercase" => Some(CaseMethod::LowerCase),
+ "to_ascii_lowercase" => Some(CaseMethod::AsciiLowerCase),
+ "to_uppercase" => Some(CaseMethod::UpperCase),
+ "to_ascii_uppercase" => Some(CaseMethod::AsciiUppercase),
+ _ => None,
+ }
+}
+
+fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(Span, SymbolStr)> {
+ let case_check = match case_method {
+ CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(|c| c.to_lowercase().next() == Some(c)) },
+ CaseMethod::AsciiLowerCase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_uppercase()) },
+ CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(|c| c.to_uppercase().next() == Some(c)) },
+ CaseMethod::AsciiUppercase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_lowercase()) },
+ };
+
+ for arm in arms {
+ if_chain! {
+ if let PatKind::Lit(Expr {
+ kind: ExprKind::Lit(lit),
+ ..
+ }) = arm.pat.kind;
+ if let LitKind::Str(symbol, _) = lit.node;
+ let input = symbol.as_str();
+ if !case_check(&input);
+ then {
+ return Some((lit.span, input));
+ }
+ }
+ }
+
+ None
+}
+
+fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad_case_str: &str) {
+ let (method_str, suggestion) = match case_method {
+ CaseMethod::LowerCase => ("to_lowercase", bad_case_str.to_lowercase()),
+ CaseMethod::AsciiLowerCase => ("to_ascii_lowercase", bad_case_str.to_ascii_lowercase()),
+ CaseMethod::UpperCase => ("to_uppercase", bad_case_str.to_uppercase()),
+ CaseMethod::AsciiUppercase => ("to_ascii_uppercase", bad_case_str.to_ascii_uppercase()),
+ };
+
+ span_lint_and_sugg(
+ cx,
+ MATCH_STR_CASE_MISMATCH,
+ bad_case_span,
+ "this `match` arm has a differing case than its expression",
+ &*format!("consider changing the case of this arm to respect `{}`", method_str),
+ format!("\"{}\"", suggestion),
+ Applicability::MachineApplicable,
+ );
+}
--- /dev/null
- get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild,
- meets_msrv, msrvs, path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns,
- remove_blocks, strip_pat_refs,
+use clippy_utils::consts::{constant, constant_full_int, miri_to_const, FullInt};
+use clippy_utils::diagnostics::{
+ multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
+};
+use clippy_utils::higher;
+use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
+use clippy_utils::visitors::is_local_used;
+use clippy_utils::{
- use rustc_middle::lint::in_external_macro;
++ get_parent_expr, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs,
++ path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks,
++ strip_pat_refs,
+};
+use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
+use core::iter::{once, ExactSizeIterator};
+use if_chain::if_chain;
+use rustc_ast::ast::{Attribute, LitKind};
+use rustc_errors::Applicability;
+use rustc_hir::def::{CtorKind, DefKind, Res};
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_hir::{
+ self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource,
+ Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind,
+};
+use rustc_hir::{HirIdMap, HirIdSet};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
- use std::iter;
- use std::ops::Bound;
+use rustc_middle::ty::{self, Ty, TyS, VariantDef};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::{Span, Spanned};
+use rustc_span::sym;
+use std::cmp::Ordering;
+use std::collections::hash_map::Entry;
- if in_external_macro(cx.sess(), expr.span) || in_macro(expr.span) {
+
+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`.
+ ///
+ /// ### 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);
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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);
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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),
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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();
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 5;
+ /// match x {
+ /// 1..=10 => println!("1 ... 10"),
+ /// 5..=15 => println!("5 ... 15"),
+ /// _ => (),
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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)`
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: Result<i32, &str> = Ok(3);
+ /// match x {
+ /// Ok(_) => println!("ok"),
+ /// Err(_) => panic!("err"),
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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(_) => {},
+ /// }
+ /// ```
++ #[clippy::version = "1.34.0"]
+ 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 => {},
+ /// }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// match "foo" {
+ /// "a" => {},
+ /// "bar" | _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match "foo" {
+ /// "a" => {},
+ /// _ => {},
+ /// }
+ /// ```
++ #[clippy::version = "1.42.0"]
+ 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.
+ ///
+ /// ### 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;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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);
+ /// ```
++ #[clippy::version = "1.43.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct A { a: i32 }
+ /// let a = A { a: 5 };
+ ///
+ /// // Bad
+ /// match a {
+ /// A { a: 5, .. } => {},
+ /// _ => {},
+ /// }
+ ///
+ /// // Good
+ /// match a {
+ /// A { a: 5 } => {},
+ /// _ => {},
+ /// }
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ restriction,
+ "a match on a struct that binds all fields but still uses the wildcard pattern"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lint for redundant pattern matching over `Result`, `Option`,
+ /// `std::task::Poll` or `std::net::IpAddr`
+ ///
+ /// ### Why is this bad?
+ /// It's more concise and clear to just use the proper
+ /// utility function
+ ///
+ /// ### Known problems
+ /// This will change the drop order for the matched type. Both `if let` and
+ /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
+ /// value before entering the block. For most types this change will not matter, but for a few
+ /// types this will not be an acceptable change (e.g. locks). See the
+ /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
+ /// drop order.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::task::Poll;
+ /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ /// 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 Poll::Pending = Poll::Pending::<()> {}
+ /// if let Poll::Ready(_) = Poll::Ready(42) {}
+ /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
+ /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
+ /// match Ok::<i32, i32>(42) {
+ /// Ok(_) => true,
+ /// Err(_) => false,
+ /// };
+ /// ```
+ ///
+ /// The more idiomatic use would be:
+ ///
+ /// ```rust
+ /// # use std::task::Poll;
+ /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+ /// if Ok::<i32, i32>(42).is_ok() {}
+ /// if Err::<i32, i32>(42).is_err() {}
+ /// if None::<()>.is_none() {}
+ /// if Some(42).is_some() {}
+ /// if Poll::Pending::<()>.is_pending() {}
+ /// if Poll::Ready(42).is_ready() {}
+ /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
+ /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
+ /// Ok::<i32, i32>(42).is_ok();
+ /// ```
++ #[clippy::version = "1.31.0"]
+ pub REDUNDANT_PATTERN_MATCHING,
+ style,
+ "use the proper utility function avoiding an `if let`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `match` or `if let` expressions producing a
+ /// `bool` that could be written using `matches!`
+ ///
+ /// ### Why is this bad?
+ /// Readability and needless complexity.
+ ///
+ /// ### Known problems
+ /// This lint falsely triggers, if there are arms with
+ /// `cfg` attributes that remove an arm evaluating to `false`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = Some(5);
+ ///
+ /// // Bad
+ /// let a = match x {
+ /// Some(0) => true,
+ /// _ => false,
+ /// };
+ ///
+ /// let a = if let Some(0) = x {
+ /// true
+ /// } else {
+ /// false
+ /// };
+ ///
+ /// // Good
+ /// let a = matches!(x, Some(0));
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub MATCH_LIKE_MATCHES_MACRO,
+ style,
+ "a match that could be written with the matches! macro"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `match` with identical arm bodies.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a copy & paste error. If arm bodies
+ /// are the same on purpose, you can factor them
+ /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
+ ///
+ /// ### Known problems
+ /// False positive possible with order dependent `match`
+ /// (see issue
+ /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar => bar(),
+ /// Quz => quz(),
+ /// Baz => bar(), // <= oops
+ /// }
+ /// ```
+ ///
+ /// This should probably be
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar => bar(),
+ /// Quz => quz(),
+ /// Baz => baz(), // <= fixed
+ /// }
+ /// ```
+ ///
+ /// or if the original code was not a typo:
+ /// ```rust,ignore
+ /// match foo {
+ /// Bar | Baz => bar(), // <= shows the intent better
+ /// Quz => quz(),
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MATCH_SAME_ARMS,
+ pedantic,
+ "`match` with identical arm bodies"
+}
+
+#[derive(Default)]
+pub struct Matches {
+ msrv: Option<RustcVersion>,
+ infallible_destructuring_match_linted: bool,
+}
+
+impl Matches {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Matches::default()
+ }
+ }
+}
+
+impl_lint_pass!(Matches => [
+ SINGLE_MATCH,
+ MATCH_REF_PATS,
+ MATCH_BOOL,
+ SINGLE_MATCH_ELSE,
+ MATCH_OVERLAPPING_ARM,
+ MATCH_WILD_ERR_ARM,
+ MATCH_AS_REF,
+ WILDCARD_ENUM_MATCH_ARM,
+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ WILDCARD_IN_OR_PATTERNS,
+ MATCH_SINGLE_BINDING,
+ INFALLIBLE_DESTRUCTURING_MATCH,
+ REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ REDUNDANT_PATTERN_MATCHING,
+ MATCH_LIKE_MATCHES_MACRO,
+ MATCH_SAME_ARMS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Matches {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) {
- check_match_ref_pats(cx, let_expr, once(let_pat), expr);
- }
++ if expr.span.from_expansion() {
+ return;
+ }
+
+ redundant_pattern_match::check(cx, expr);
+
+ if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
+ if !check_match_like_matches(cx, expr) {
+ lint_match_arms(cx, expr);
+ }
+ } else {
+ lint_match_arms(cx, expr);
+ }
+
+ if let ExprKind::Match(ex, 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(ex, arms, _) = expr.kind {
+ check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr);
+ }
- if !in_external_macro(cx.sess(), local.span);
- if !in_macro(local.span);
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
+ if_chain! {
- if !in_external_macro(cx.sess(), pat.span);
- if !in_macro(pat.span);
++ if !local.span.from_expansion();
+ if let Some(expr) = local.init;
+ if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
+ if arms.len() == 1 && arms[0].guard.is_none();
+ if let PatKind::TupleStruct(
+ QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
+ if args.len() == 1;
+ if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind;
+ let body = remove_blocks(arms[0].body);
+ if path_to_local_id(body, arg);
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ self.infallible_destructuring_match_linted = true;
+ span_lint_and_sugg(
+ cx,
+ INFALLIBLE_DESTRUCTURING_MATCH,
+ local.span,
+ "you seem to be trying to use `match` to destructure a single infallible pattern. \
+ Consider using `let`",
+ "try this",
+ format!(
+ "let {}({}) = {};",
+ snippet_with_applicability(cx, variant_name.span, "..", &mut applicability),
+ snippet_with_applicability(cx, local.pat.span, "..", &mut applicability),
+ snippet_with_applicability(cx, target.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ if_chain! {
- if in_macro(expr.span) {
++ if !pat.span.from_expansion();
+ if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
+ if let Some(def_id) = path.res.opt_def_id();
+ let ty = cx.tcx.type_of(def_id);
+ if let ty::Adt(def, _) = ty.kind();
+ if def.is_struct() || def.is_union();
+ if fields.len() == def.non_enum_variant().fields.len();
+
+ then {
+ span_lint_and_help(
+ cx,
+ REST_PAT_IN_FULLY_BOUND_STRUCTS,
+ pat.span,
+ "unnecessary use of `..` pattern in struct binding. All fields were already bound",
+ None,
+ "consider removing `..` from this binding",
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+#[rustfmt::skip]
+fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
- if in_macro(expr.span) || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
++ if expr.span.from_expansion() {
+ // Don't lint match expressions present in
+ // macro_rules! block
+ return;
+ }
+ if let PatKind::Or(..) = arms[0].pat.kind {
+ // don't lint for or patterns for now, this makes
+ // the lint noisy in unnecessary situations
+ return;
+ }
+ let els = arms[1].body;
+ let els = if is_unit_expr(remove_blocks(els)) {
+ None
+ } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind {
+ if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() {
+ // single statement/expr "else" block, don't lint
+ return;
+ }
+ // block with 2+ statements or 1 expr and 1+ statement
+ Some(els)
+ } else {
+ // not a block, don't lint
+ return;
+ };
+
+ let ty = cx.typeck_results().expr_ty(ex);
+ if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) {
+ check_single_match_single_pattern(cx, ex, arms, expr, els);
+ check_single_match_opt_like(cx, ex, arms, expr, ty, els);
+ }
+ }
+}
+
+fn check_single_match_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ if is_wild(arms[1].pat) {
+ report_single_match_single_pattern(cx, ex, arms, expr, els);
+ }
+}
+
+fn report_single_match_single_pattern(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
+ let els_str = els.map_or(String::new(), |els| {
+ format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
+ });
+
+ let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
+ let (msg, sugg) = if_chain! {
+ if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
+ let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
+ if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait();
+ if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait();
+ if ty.is_integral() || ty.is_char() || ty.is_str()
+ || (implements_trait(cx, ty, spe_trait_id, &[])
+ && implements_trait(cx, ty, pe_trait_id, &[ty.into()]));
+ then {
+ // scrutinee derives PartialEq and the pattern is a constant.
+ let pat_ref_count = match pat.kind {
+ // string literals are already a reference.
+ PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1,
+ _ => pat_ref_count,
+ };
+ // References are only implicitly added to the pattern, so no overflow here.
+ // e.g. will work: match &Some(_) { Some(_) => () }
+ // will not: match Some(_) { &Some(_) => () }
+ let ref_count_diff = ty_ref_count - pat_ref_count;
+
+ // Try to remove address of expressions first.
+ let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff);
+ let ref_count_diff = ref_count_diff - removed;
+
+ let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`";
+ let sugg = format!(
+ "if {} == {}{} {}{}",
+ snippet(cx, ex.span, ".."),
+ // PartialEq for different reference counts may not exist.
+ "&".repeat(ref_count_diff),
+ snippet(cx, arms[0].pat.span, ".."),
+ expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ els_str,
+ );
+ (msg, sugg)
+ } else {
+ let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`";
+ let sugg = format!(
+ "if let {} = {} {}{}",
+ snippet(cx, arms[0].pat.span, ".."),
+ snippet(cx, ex.span, ".."),
+ expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ els_str,
+ );
+ (msg, sugg)
+ }
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ expr.span,
+ msg,
+ "try this",
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+}
+
+fn check_single_match_opt_like(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ arms: &[Arm<'_>],
+ expr: &Expr<'_>,
+ ty: Ty<'_>,
+ els: Option<&Expr<'_>>,
+) {
+ // list of candidate `Enum`s we know will never get any more members
+ let candidates = &[
+ (&paths::COW, "Borrowed"),
+ (&paths::COW, "Cow::Borrowed"),
+ (&paths::COW, "Cow::Owned"),
+ (&paths::COW, "Owned"),
+ (&paths::OPTION, "None"),
+ (&paths::RESULT, "Err"),
+ (&paths::RESULT, "Ok"),
+ ];
+
+ let path = match arms[1].pat.kind {
+ PatKind::TupleStruct(ref path, inner, _) => {
+ // Contains any non wildcard patterns (e.g., `Err(err)`)?
+ if !inner.iter().all(is_wild) {
+ return;
+ }
+ rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
+ },
+ PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(),
+ PatKind::Path(ref path) => {
+ rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false))
+ },
+ _ => return,
+ };
+
+ for &(ty_path, pat_path) in candidates {
+ if path == *pat_path && match_type(cx, ty, ty_path) {
+ report_single_match_single_pattern(cx, ex, arms, expr, els);
+ }
+ }
+}
+
+fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+ // Type of expression is `bool`.
+ if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool {
+ span_lint_and_then(
+ cx,
+ MATCH_BOOL,
+ expr.span,
+ "you seem to be trying to match on a boolean expression",
+ move |diag| {
+ if arms.len() == 2 {
+ // no guards
+ let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind {
+ if let ExprKind::Lit(ref lit) = arm_bool.kind {
+ match lit.node {
+ LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
+ LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ if let Some((true_expr, false_expr)) = exprs {
+ let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
+ (false, false) => Some(format!(
+ "if {} {} else {}",
+ snippet(cx, ex.span, "b"),
+ expr_block(cx, true_expr, None, "..", Some(expr.span)),
+ expr_block(cx, false_expr, None, "..", Some(expr.span))
+ )),
+ (false, true) => Some(format!(
+ "if {} {}",
+ snippet(cx, ex.span, "b"),
+ expr_block(cx, true_expr, None, "..", Some(expr.span))
+ )),
+ (true, false) => {
+ let test = Sugg::hir(cx, ex, "..");
+ Some(format!(
+ "if {} {}",
+ !test,
+ expr_block(cx, false_expr, None, "..", Some(expr.span))
+ ))
+ },
+ (true, true) => None,
+ };
+
+ if let Some(sugg) = sugg {
+ diag.span_suggestion(
+ expr.span,
+ "consider using an `if`/`else` expression",
+ sugg,
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+ },
+ );
+ }
+}
+
+fn check_overlapping_arms<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
+ if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() {
+ let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex));
+ if !ranges.is_empty() {
+ if let Some((start, end)) = overlapping(&ranges) {
+ span_lint_and_note(
+ cx,
+ MATCH_OVERLAPPING_ARM,
+ start.span,
+ "some ranges overlap",
+ Some(end.span),
+ "overlaps with this",
+ );
+ }
+ }
+ }
+}
+
+fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) {
+ let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs();
+ if is_type_diagnostic_item(cx, ex_ty, sym::Result) {
+ for arm in arms {
+ if let PatKind::TupleStruct(ref path, 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`)
+ for pat in inner.iter() {
+ if let PatKind::Binding(_, id, ident, None) = pat.kind {
+ if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) {
+ ident_bind_name = (&ident.name.as_str()).to_string();
+ matching_wild = true;
+ }
+ }
+ }
+ }
+ if_chain! {
+ if matching_wild;
+ if is_panic_call(arm.body);
+ 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",
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+enum CommonPrefixSearcher<'a> {
+ None,
+ Path(&'a [PathSegment<'a>]),
+ Mixed,
+}
+impl CommonPrefixSearcher<'a> {
+ fn with_path(&mut self, path: &'a [PathSegment<'a>]) {
+ match path {
+ [path @ .., _] => self.with_prefix(path),
+ [] => (),
+ }
+ }
+
+ fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) {
+ match self {
+ Self::None => *self = Self::Path(path),
+ Self::Path(self_path)
+ if path
+ .iter()
+ .map(|p| p.ident.name)
+ .eq(self_path.iter().map(|p| p.ident.name)) => {},
+ Self::Path(_) => *self = Self::Mixed,
+ Self::Mixed => (),
+ }
+ }
+}
+
+fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool {
+ let attrs = cx.tcx.get_attrs(variant_def.def_id);
+ clippy_utils::attrs::is_doc_hidden(attrs) || clippy_utils::attrs::is_unstable(attrs)
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
+ let ty = cx.typeck_results().expr_ty(ex).peel_refs();
+ let adt_def = match ty.kind() {
+ ty::Adt(adt_def, _)
+ if adt_def.is_enum()
+ && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) =>
+ {
+ adt_def
+ },
+ _ => 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;
+ let mut has_non_wild = false;
+ for arm in arms {
+ match peel_hir_pat_refs(arm.pat).0.kind {
+ PatKind::Wild => wildcard_span = Some(arm.pat.span),
+ PatKind::Binding(_, _, ident, None) => {
+ wildcard_span = Some(arm.pat.span);
+ wildcard_ident = Some(ident);
+ },
+ _ => has_non_wild = true,
+ }
+ }
+ let wildcard_span = match wildcard_span {
+ Some(x) if has_non_wild => x,
+ _ => return,
+ };
+
+ // Accumulate the variants which should be put in place of the wildcard because they're not
+ // already covered.
+ let has_hidden = adt_def.variants.iter().any(|x| is_hidden(cx, x));
+ let mut missing_variants: Vec<_> = adt_def.variants.iter().filter(|x| !is_hidden(cx, x)).collect();
+
+ let mut path_prefix = CommonPrefixSearcher::None;
+ for arm in arms {
+ // 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.
+ recurse_or_patterns(arm.pat, |pat| {
+ let path = match &peel_hir_pat_refs(pat).0.kind {
+ PatKind::Path(path) => {
+ #[allow(clippy::match_same_arms)]
+ let id = match cx.qpath_res(path, pat.hir_id) {
+ Res::Def(
+ DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst,
+ _,
+ ) => return,
+ Res::Def(_, id) => id,
+ _ => return,
+ };
+ if arm.guard.is_none() {
+ missing_variants.retain(|e| e.ctor_def_id != Some(id));
+ }
+ path
+ },
+ PatKind::TupleStruct(path, patterns, ..) => {
+ if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) {
+ missing_variants.retain(|e| e.ctor_def_id != Some(id));
+ }
+ }
+ path
+ },
+ PatKind::Struct(path, patterns, ..) => {
+ if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) {
+ missing_variants.retain(|e| e.def_id != id);
+ }
+ }
+ path
+ },
+ _ => return,
+ };
+ match path {
+ QPath::Resolved(_, path) => path_prefix.with_path(path.segments),
+ QPath::TypeRelative(
+ hir::Ty {
+ kind: TyKind::Path(QPath::Resolved(_, path)),
+ ..
+ },
+ _,
+ ) => path_prefix.with_prefix(path.segments),
+ _ => (),
+ }
+ });
+ }
+
+ let format_suggestion = |variant: &VariantDef| {
+ format!(
+ "{}{}{}{}",
+ if let Some(ident) = wildcard_ident {
+ format!("{} @ ", ident.name)
+ } else {
+ String::new()
+ },
+ if let CommonPrefixSearcher::Path(path_prefix) = path_prefix {
+ let mut s = String::new();
+ for seg in path_prefix {
+ s.push_str(&seg.ident.as_str());
+ s.push_str("::");
+ }
+ s
+ } else {
+ let mut s = cx.tcx.def_path_str(adt_def.did);
+ s.push_str("::");
+ s
+ },
+ variant.ident.name,
+ match variant.ctor_kind {
+ CtorKind::Fn if variant.fields.len() == 1 => "(_)",
+ CtorKind::Fn => "(..)",
+ CtorKind::Const => "",
+ CtorKind::Fictive => "{ .. }",
+ }
+ )
+ };
+
+ match missing_variants.as_slice() {
+ [] => (),
+ [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg(
+ cx,
+ MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
+ wildcard_span,
+ "wildcard matches only a single variant and will also match any future added variants",
+ "try this",
+ format_suggestion(x),
+ Applicability::MaybeIncorrect,
+ ),
+ variants => {
+ let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect();
+ let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden {
+ suggestions.push("_".into());
+ "wildcard matches known variants and will also match future added variants"
+ } else {
+ "wildcard match will also match any future added variants"
+ };
+
+ span_lint_and_sugg(
+ cx,
+ WILDCARD_ENUM_MATCH_ARM,
+ wildcard_span,
+ message,
+ "try this",
+ suggestions.join(" | "),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ };
+}
+
+// If the block contains only a `panic!` macro (as expression or statement)
+fn is_panic_call(expr: &Expr<'_>) -> bool {
+ // Unwrap any wrapping blocks
+ let span = if let ExprKind::Block(block, _) = expr.kind {
+ match (&block.expr, block.stmts.len(), block.stmts.first()) {
+ (&Some(exp), 0, _) => exp.span,
+ (&None, 1, Some(stmt)) => stmt.span,
+ _ => return false,
+ }
+ } else {
+ expr.span
+ };
+
+ is_expn_of(span, "panic").is_some() && is_expn_of(span, "unreachable").is_none()
+}
+
+fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>)
+where
+ 'b: 'a,
+ I: Clone + Iterator<Item = &'a Pat<'b>>,
+{
+ if !has_multiple_ref_pats(pats.clone()) {
+ return;
+ }
+
+ let (first_sugg, msg, title);
+ let span = ex.span.source_callsite();
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
+ first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
+ msg = "try";
+ title = "you don't need to add `&` to both the expression and the patterns";
+ } else {
+ first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
+ msg = "instead of prefixing all patterns with `&`, you can dereference the expression";
+ title = "you don't need to add `&` to all patterns";
+ }
+
+ let remaining_suggs = pats.filter_map(|pat| {
+ if let PatKind::Ref(refp, _) = pat.kind {
+ Some((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, first_sugg.chain(remaining_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(cx, &arms[0]) {
+ is_ref_some_arm(cx, &arms[1])
+ } else if is_none_arm(cx, &arms[1]) {
+ is_ref_some_arm(cx, &arms[0])
+ } else {
+ None
+ };
+ if let Some(rb) = arm_ref {
+ let suggestion = if rb == BindingAnnotation::Ref {
+ "as_ref"
+ } else {
+ "as_mut"
+ };
+
+ let output_ty = cx.typeck_results().expr_ty(expr);
+ let input_ty = cx.typeck_results().expr_ty(ex);
+
+ let cast = if_chain! {
+ if let ty::Adt(_, substs) = input_ty.kind();
+ let input_ty = substs.type_at(0);
+ if let ty::Adt(_, substs) = output_ty.kind();
+ let output_ty = substs.type_at(0);
+ if let ty::Ref(_, output_ty, _) = *output_ty.kind();
+ if input_ty != output_ty;
+ then {
+ ".map(|x| x as _)"
+ } else {
+ ""
+ }
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MATCH_AS_REF,
+ expr.span,
+ &format!("use `{}()` instead", suggestion),
+ "try this",
+ format!(
+ "{}.{}(){}",
+ snippet_with_applicability(cx, ex.span, "_", &mut applicability),
+ suggestion,
+ cast,
+ ),
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
+ for arm in arms {
+ if let PatKind::Or(fields) = arm.pat.kind {
+ // look for multiple fields in this arm that contains at least one Wild pattern
+ if fields.len() > 1 && fields.iter().any(is_wild) {
+ span_lint_and_help(
+ cx,
+ WILDCARD_IN_OR_PATTERNS,
+ arm.pat.span,
+ "wildcard pattern covers any other pattern as it will match anyway",
+ None,
+ "consider handling `_` separately",
+ );
+ }
+ }
+ }
+}
+
+/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
+fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ if let Some(higher::IfLet {
+ let_pat,
+ let_expr,
+ if_then,
+ if_else: Some(if_else),
+ }) = higher::IfLet::hir(cx, expr)
+ {
+ return find_matches_sugg(
+ cx,
+ let_expr,
+ IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
+ expr,
+ true,
+ );
+ }
+
+ if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind {
+ return find_matches_sugg(
+ cx,
+ scrut,
+ arms.iter().map(|arm| {
+ (
+ cx.tcx.hir().attrs(arm.hir_id),
+ Some(arm.pat),
+ arm.body,
+ arm.guard.as_ref(),
+ )
+ }),
+ expr,
+ false,
+ );
+ }
+
+ false
+}
+
+/// Lint a `match` or `if let` for replacement by `matches!`
+fn find_matches_sugg<'a, 'b, I>(
+ cx: &LateContext<'_>,
+ ex: &Expr<'_>,
+ mut iter: I,
+ expr: &Expr<'_>,
+ is_if_let: bool,
+) -> bool
+where
+ 'b: 'a,
+ I: Clone
+ + DoubleEndedIterator
+ + ExactSizeIterator
+ + Iterator<
+ Item = (
+ &'a [Attribute],
+ Option<&'a Pat<'b>>,
+ &'a Expr<'b>,
+ Option<&'a Guard<'b>>,
+ ),
+ >,
+{
+ if_chain! {
+ if iter.len() >= 2;
+ if cx.typeck_results().expr_ty(expr).is_bool();
+ if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
+ let iter_without_last = iter.clone();
+ if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
+ if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let);
+ if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let);
+ if b0 != b1;
+ if first_guard.is_none() || iter.len() == 0;
+ if first_attrs.is_empty();
+ if iter
+ .all(|arm| {
+ find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
+ });
+ then {
+ if let Some(last_pat) = last_pat_opt {
+ if !is_wild(last_pat) {
+ return false;
+ }
+ }
+
+ // The suggestion may be incorrect, because some arms can have `cfg` attributes
+ // evaluated into `false` and so such arms will be stripped before.
+ let mut applicability = Applicability::MaybeIncorrect;
+ let pat = {
+ use itertools::Itertools as _;
+ iter_without_last
+ .filter_map(|arm| {
+ let pat_span = arm.1?.span;
+ Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability))
+ })
+ .join(" | ")
+ };
+ let pat_and_guard = if let Some(Guard::If(g)) = first_guard {
+ format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability))
+ } else {
+ pat
+ };
+
+ // strip potential borrows (#6503), but only if the type is a reference
+ let mut ex_new = ex;
+ if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
+ if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
+ ex_new = ex_inner;
+ }
+ };
+ span_lint_and_sugg(
+ cx,
+ MATCH_LIKE_MATCHES_MACRO,
+ expr.span,
+ &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }),
+ "try this",
+ format!(
+ "{}matches!({}, {})",
+ if b0 { "" } else { "!" },
+ snippet_with_applicability(cx, ex_new.span, "..", &mut applicability),
+ pat_and_guard,
+ ),
+ applicability,
+ );
+ true
+ } else {
+ false
+ }
+ }
+}
+
+/// Extract a `bool` or `{ bool }`
+fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
+ match ex {
+ ExprKind::Lit(Spanned {
+ node: LitKind::Bool(b), ..
+ }) => Some(*b),
+ ExprKind::Block(
+ rustc_hir::Block {
+ stmts: &[],
+ expr: Some(exp),
+ ..
+ },
+ _,
+ ) if is_if_let => {
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Bool(b), ..
+ }) = exp.kind
+ {
+ Some(b)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) {
- /// Gets all arms that are unbounded `PatRange`s.
++ if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) {
+ return;
+ }
+
+ // HACK:
+ // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here
+ // to prevent false positives as there is currently no better way to detect if code was excluded by
+ // a macro. See PR #6435
+ if_chain! {
+ if let Some(match_snippet) = snippet_opt(cx, expr.span);
+ if let Some(arm_snippet) = snippet_opt(cx, arms[0].span);
+ if let Some(ex_snippet) = snippet_opt(cx, ex.span);
+ let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, "");
+ if rest_snippet.contains("=>");
+ then {
+ // The code it self contains another thick arrow "=>"
+ // -> Either another arm or a comment
+ return;
+ }
+ }
+
+ let matched_vars = ex.span;
+ let bind_names = arms[0].pat.span;
+ let match_body = remove_blocks(arms[0].body);
+ let mut snippet_body = if match_body.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
+ } else {
+ snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
+ };
+
+ // Do we need to add ';' to suggestion ?
+ match match_body.kind {
+ ExprKind::Block(block, _) => {
+ // macro + expr_ty(body) == ()
+ if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ _ => {
+ // expr_ty(body) == ()
+ if cx.typeck_results().expr_ty(match_body).is_unit() {
+ snippet_body.push(';');
+ }
+ },
+ }
+
+ let mut applicability = Applicability::MaybeIncorrect;
+ match arms[0].pat.kind {
+ PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
+ // If this match is in a local (`let`) stmt
+ let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) {
+ (
+ parent_let_node.span,
+ format!(
+ "let {} = {};\n{}let {} = {};",
+ snippet_with_applicability(cx, bind_names, "..", &mut applicability),
+ snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ " ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
+ snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability),
+ snippet_body
+ ),
+ )
+ } else {
+ // If we are in closure, we need curly braces around suggestion
+ let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0));
+ let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string());
+ if let Some(parent_expr) = get_parent_expr(cx, expr) {
+ if let ExprKind::Closure(..) = parent_expr.kind {
+ cbrace_end = format!("\n{}}}", indent);
+ // Fix body indent due to the closure
+ indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0));
+ cbrace_start = format!("{{\n{}", indent);
+ }
+ }
+ // If the parent is already an arm, and the body is another match statement,
+ // we need curly braces around suggestion
+ let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id);
+ if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) {
+ if let ExprKind::Match(..) = arm.body.kind {
+ cbrace_end = format!("\n{}}}", indent);
+ // Fix body indent due to the match
+ 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 => {
+ if ex.can_have_side_effects() {
+ let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0));
+ let sugg = format!(
+ "{};\n{}{}",
+ snippet_with_applicability(cx, ex.span, "..", &mut applicability),
+ indent,
+ snippet_body
+ );
+ span_lint_and_sugg(
+ cx,
+ MATCH_SINGLE_BINDING,
+ expr.span,
+ "this match could be replaced by its scrutinee and body",
+ "consider using the scrutinee and body instead",
+ sugg,
+ applicability,
+ );
+ } else {
+ 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>> {
+ let map = &cx.tcx.hir();
+ if_chain! {
+ 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
+}
+
- let lhs = match lhs {
++/// Gets the ranges for each range pattern arm. Applies `ty` bounds for open ranges.
+fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<FullInt>> {
+ arms.iter()
+ .filter_map(|arm| {
+ if let Arm { pat, guard: None, .. } = *arm {
+ if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
- let rhs = match rhs {
++ let lhs_const = match lhs {
+ Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0,
+ None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?,
+ };
- let lhs_val = lhs.int_value(cx, ty)?;
- let rhs_val = rhs.int_value(cx, ty)?;
++ let rhs_const = match rhs {
+ Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0,
+ None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?,
+ };
+
- RangeEnd::Included => Bound::Included(rhs_val),
- RangeEnd::Excluded => Bound::Excluded(rhs_val),
++ let lhs_val = lhs_const.int_value(cx, ty)?;
++ let rhs_val = rhs_const.int_value(cx, ty)?;
+
+ let rhs_bound = match range_end {
- node: (value, Bound::Included(value)),
++ RangeEnd::Included => EndBound::Included(rhs_val),
++ RangeEnd::Excluded => EndBound::Excluded(rhs_val),
+ };
+ return Some(SpannedRange {
+ span: pat.span,
+ node: (lhs_val, rhs_bound),
+ });
+ }
+
+ if let PatKind::Lit(value) = pat.kind {
+ let value = constant_full_int(cx, cx.typeck_results(), value)?;
+ return Some(SpannedRange {
+ span: pat.span,
- pub struct SpannedRange<T> {
++ node: (value, EndBound::Included(value)),
+ });
+ }
+ }
+ None
+ })
+ .collect()
+}
+
++#[derive(Clone, Copy, Debug, Eq, PartialEq)]
++pub enum EndBound<T> {
++ Included(T),
++ Excluded(T),
++}
++
+#[derive(Debug, Eq, PartialEq)]
- pub node: (T, Bound<T>),
++struct SpannedRange<T> {
+ pub span: Span,
- pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
++ pub node: (T, EndBound<T>),
+}
+
+// Checks if arm has the form `None => None`
+fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
+}
+
+// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
+fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> {
+ if_chain! {
+ if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind;
+ if is_lang_ctor(cx, qpath, OptionSome);
+ if let PatKind::Binding(rb, .., ident, _) = first_pat.kind;
+ if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
+ if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind;
+ if let ExprKind::Path(ref some_path) = e.kind;
+ if is_lang_ctor(cx, some_path, OptionSome) && args.len() == 1;
+ if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind;
+ if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
+ then {
+ return Some(rb)
+ }
+ }
+ None
+}
+
+fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool
+where
+ 'b: 'a,
+ I: Iterator<Item = &'a Pat<'b>>,
+{
+ let mut ref_count = 0;
+ for opt in pats.map(|pat| match pat.kind {
+ PatKind::Ref(..) => Some(true), // &-patterns
+ PatKind::Wild => Some(false), // an "anything" wildcard is also fine
+ _ => None, // any other pattern is not fine
+ }) {
+ if let Some(inner) = opt {
+ if inner {
+ ref_count += 1;
+ }
+ } else {
+ return false;
+ }
+ }
+ ref_count > 1
+}
+
- #[derive(Copy, Clone, Debug, Eq, PartialEq)]
- enum Kind<'a, T> {
- Start(T, &'a SpannedRange<T>),
- End(Bound<T>, &'a SpannedRange<T>),
++fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
+where
+ T: Copy + Ord,
+{
- 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,
- }
- }
- }
++ #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
++ enum BoundKind {
++ EndExcluded,
++ Start,
++ EndIncluded,
+ }
+
- impl<'a, T: Copy + Ord> PartialOrd for Kind<'a, T> {
++ #[derive(Copy, Clone, Debug, Eq, PartialEq)]
++ struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange<T>);
+
- 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,
- },
- }
++ impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+ }
+
- for r in ranges {
- values.push(Kind::Start(r.node.0, r));
- values.push(Kind::End(r.node.1, r));
++ impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> {
++ fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering {
++ let RangeBound(self_value, self_kind, _) = *self;
++ (self_value, self_kind).cmp(&(*other_value, *other_kind))
+ }
+ }
+
+ let mut values = Vec::with_capacity(2 * ranges.len());
+
- for (a, b) in iter::zip(&values, values.iter().skip(1)) {
- match (a, b) {
- (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
- if ra.node != rb.node {
- return Some((ra, rb));
- }
- },
- (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (),
- _ => {
- // skip if the range `a` is completely included into the range `b`
- if let Ordering::Equal | Ordering::Less = a.cmp(b) {
- let kind_a = Kind::End(a.range().node.1, a.range());
- let kind_b = Kind::End(b.range().node.1, b.range());
- if let Ordering::Equal | Ordering::Greater = kind_a.cmp(&kind_b) {
- return None;
++ for r @ SpannedRange { node: (start, end), .. } in ranges {
++ values.push(RangeBound(*start, BoundKind::Start, r));
++ values.push(match end {
++ EndBound::Excluded(val) => RangeBound(*val, BoundKind::EndExcluded, r),
++ EndBound::Included(val) => RangeBound(*val, BoundKind::EndIncluded, r),
++ });
+ }
+
+ values.sort();
+
- return Some((a.range(), b.range()));
++ let mut started = vec![];
++
++ for RangeBound(_, kind, range) in values {
++ match kind {
++ BoundKind::Start => started.push(range),
++ BoundKind::EndExcluded | BoundKind::EndIncluded => {
++ let mut overlap = None;
++
++ while let Some(last_started) = started.pop() {
++ if last_started == range {
++ break;
+ }
++ overlap = Some(last_started);
++ }
++
++ if let Some(first_overlapping) = overlap {
++ return Some((range, first_overlapping));
+ }
- use clippy_utils::source::{snippet, snippet_with_applicability};
+ },
+ }
+ }
+
+ None
+}
+
+mod redundant_pattern_match {
+ use super::REDUNDANT_PATTERN_MATCHING;
+ use clippy_utils::diagnostics::span_lint_and_then;
+ use clippy_utils::higher;
- Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath,
++ use clippy_utils::source::snippet;
++ use clippy_utils::sugg::Sugg;
+ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
+ use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths};
+ use if_chain::if_chain;
+ use rustc_ast::ast::LitKind;
+ use rustc_data_structures::fx::FxHashSet;
+ use rustc_errors::Applicability;
+ use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
+ use rustc_hir::{
+ intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
- let mut app = if needs_drop {
++ Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp,
+ };
+ use rustc_lint::LateContext;
+ use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
+ use rustc_span::sym;
+
+ pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let Some(higher::IfLet {
+ if_else,
+ let_pat,
+ let_expr,
+ ..
+ }) = higher::IfLet::hir(cx, expr)
+ {
+ find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some());
+ }
+ if let ExprKind::Match(op, arms, MatchSource::Normal) = &expr.kind {
+ find_sugg_for_match(cx, expr, op, arms);
+ }
+ if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
+ find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
+ }
+ }
+
+ /// Checks if the drop order for a type matters. Some std types implement drop solely to
+ /// deallocate memory. For these types, and composites containing them, changing the drop order
+ /// won't result in any observable side effects.
+ fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
+ }
+
+ fn type_needs_ordered_drop_inner(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool {
+ if !seen.insert(ty) {
+ return false;
+ }
+ if !ty.needs_drop(cx.tcx, cx.param_env) {
+ false
+ } else if !cx
+ .tcx
+ .lang_items()
+ .drop_trait()
+ .map_or(false, |id| implements_trait(cx, ty, id, &[]))
+ {
+ // This type doesn't implement drop, so no side effects here.
+ // Check if any component type has any.
+ match ty.kind() {
+ ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
+ ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen),
+ ty::Adt(adt, subs) => adt
+ .all_fields()
+ .map(|f| f.ty(cx.tcx, subs))
+ .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
+ _ => true,
+ }
+ }
+ // Check for std types which implement drop, but only for memory allocation.
+ else if is_type_diagnostic_item(cx, ty, sym::Vec)
+ || is_type_lang_item(cx, ty, LangItem::OwnedBox)
+ || is_type_diagnostic_item(cx, ty, sym::Rc)
+ || is_type_diagnostic_item(cx, ty, sym::Arc)
+ || is_type_diagnostic_item(cx, ty, sym::cstring_type)
+ || is_type_diagnostic_item(cx, ty, sym::BTreeMap)
+ || is_type_diagnostic_item(cx, ty, sym::LinkedList)
+ || match_type(cx, ty, &paths::WEAK_RC)
+ || match_type(cx, ty, &paths::WEAK_ARC)
+ {
+ // Check all of the generic arguments.
+ if let ty::Adt(_, subs) = ty.kind() {
+ subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen))
+ } else {
+ true
+ }
+ } else {
+ true
+ }
+ }
+
+ // Extract the generic arguments out of a type
+ fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
+ if_chain! {
+ if let ty::Adt(_, subs) = ty.kind();
+ if let Some(sub) = subs.get(index);
+ if let GenericArgKind::Type(sub_ty) = sub.unpack();
+ then {
+ Some(sub_ty)
+ } else {
+ None
+ }
+ }
+ }
+
+ // Checks if there are any temporaries created in the given expression for which drop order
+ // matters.
+ fn temporaries_need_ordered_drop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ res: bool,
+ }
+ impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
+ type Map = ErasedMap<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+ match expr.kind {
+ // Taking the reference of a value leaves a temporary
+ // e.g. In `&String::new()` the string is a temporary value.
+ // Remaining fields are temporary values
+ // e.g. In `(String::new(), 0).1` the string is a temporary value.
+ ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
+ if !matches!(expr.kind, ExprKind::Path(_)) {
+ if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
+ self.res = true;
+ } else {
+ self.visit_expr(expr);
+ }
+ }
+ },
+ // the base type is alway taken by reference.
+ // e.g. In `(vec![0])[0]` the vector is a temporary value.
+ ExprKind::Index(base, index) => {
+ if !matches!(base.kind, ExprKind::Path(_)) {
+ if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
+ self.res = true;
+ } else {
+ self.visit_expr(base);
+ }
+ }
+ self.visit_expr(index);
+ },
+ // Method calls can take self by reference.
+ // e.g. In `String::new().len()` the string is a temporary value.
+ ExprKind::MethodCall(_, _, [self_arg, args @ ..], _) => {
+ if !matches!(self_arg.kind, ExprKind::Path(_)) {
+ let self_by_ref = self
+ .cx
+ .typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
+ if self_by_ref
+ && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg))
+ {
+ self.res = true;
+ } else {
+ self.visit_expr(self_arg);
+ }
+ }
+ args.iter().for_each(|arg| self.visit_expr(arg));
+ },
+ // Either explicitly drops values, or changes control flow.
+ ExprKind::DropTemps(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Break(..)
+ | ExprKind::Yield(..)
+ | ExprKind::Block(Block { expr: None, .. }, _)
+ | ExprKind::Loop(..) => (),
+
+ // Only consider the final expression.
+ ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
+
+ _ => walk_expr(self, expr),
+ }
+ }
+ }
+
+ let mut v = V { cx, res: false };
+ v.visit_expr(expr);
+ v.res
+ }
+
+ fn find_sugg_for_if_let<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ let_pat: &Pat<'_>,
+ let_expr: &'tcx Expr<'_>,
+ keyword: &'static str,
+ has_else: bool,
+ ) {
+ // also look inside refs
+ let mut kind = &let_pat.kind;
+ // if we have &None for example, peel it so we can detect "if let None = x"
+ if let PatKind::Ref(inner, _mutability) = kind {
+ kind = &inner.kind;
+ }
+ let op_ty = cx.typeck_results().expr_ty(let_expr);
+ // Determine which function should be used, and the type contained by the corresponding
+ // variant.
+ let (good_method, inner_ty) = match kind {
+ PatKind::TupleStruct(ref path, [sub_pat], _) => {
+ if let PatKind::Wild = sub_pat.kind {
+ if is_lang_ctor(cx, path, ResultOk) {
+ ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
+ } else if is_lang_ctor(cx, path, ResultErr) {
+ ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
+ } else if is_lang_ctor(cx, path, OptionSome) {
+ ("is_some()", op_ty)
+ } else if is_lang_ctor(cx, path, PollReady) {
+ ("is_ready()", op_ty)
+ } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V4) {
+ ("is_ipv4()", op_ty)
+ } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V6) {
+ ("is_ipv6()", op_ty)
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ },
+ PatKind::Path(ref path) => {
+ let method = if is_lang_ctor(cx, path, OptionNone) {
+ "is_none()"
+ } else if is_lang_ctor(cx, path, PollPending) {
+ "is_pending()"
+ } else {
+ return;
+ };
+ // `None` and `Pending` don't have an inner type.
+ (method, cx.tcx.types.unit)
+ },
+ _ => return,
+ };
+
+ // If this is the last expression in a block or there is an else clause then the whole
+ // type needs to be considered, not just the inner type of the branch being matched on.
+ // Note the last expression in a block is dropped after all local bindings.
+ let check_ty = if has_else
+ || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
+ {
+ op_ty
+ } else {
+ inner_ty
+ };
+
+ // All temporaries created in the scrutinee expression are dropped at the same time as the
+ // scrutinee would be, so they have to be considered as well.
+ // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
+ // for the duration if body.
+ let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr);
+
+ // check that `while_let_on_iterator` lint does not trigger
+ if_chain! {
+ if keyword == "while";
+ if let ExprKind::MethodCall(method_path, _, _, _) = let_expr.kind;
+ if method_path.ident.name == sym::next;
+ if is_trait_method(cx, let_expr, sym::Iterator);
+ then {
+ return;
+ }
+ }
+
+ let result_expr = match &let_expr.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
++ ExprKind::Unary(UnOp::Deref, deref) => deref,
+ _ => let_expr,
+ };
++
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ let_pat.span,
+ &format!("redundant pattern matching, consider using `{}`", good_method),
+ |diag| {
+ // if/while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ let expr_span = expr.span;
+
+ // if/while let ... = ... { ... }
+ // ^^^
+ let op_span = result_expr.span.source_callsite();
+
+ // if/while let ... = ... { ... }
+ // ^^^^^^^^^^^^^^^^^^^
+ let span = expr_span.until(op_span.shrink_to_hi());
+
- let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
++ let app = if needs_drop {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ };
- assert_eq!(None, overlapping(&[sp(1, Bound::Included(4))]));
++
++ let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
++ .maybe_par()
++ .to_string();
+
+ diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
+
+ if needs_drop {
+ diag.note("this will change drop order of the result, as well as all temporaries");
+ diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
+ }
+ },
+ );
+ }
+
+ fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) {
+ if arms.len() == 2 {
+ let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
+
+ let found_good_method = match node_pair {
+ (
+ PatKind::TupleStruct(ref path_left, patterns_left, _),
+ PatKind::TupleStruct(ref path_right, 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(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::RESULT_OK,
+ &paths::RESULT_ERR,
+ "is_ok()",
+ "is_err()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::IPADDR_V4,
+ &paths::IPADDR_V6,
+ "is_ipv4()",
+ "is_ipv6()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
+ | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
+ if patterns.len() == 1 =>
+ {
+ if let PatKind::Wild = patterns[0].kind {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::OPTION_SOME,
+ &paths::OPTION_NONE,
+ "is_some()",
+ "is_none()",
+ )
+ .or_else(|| {
+ find_good_method_for_match(
+ cx,
+ arms,
+ path_left,
+ path_right,
+ &paths::POLL_READY,
+ &paths::POLL_PENDING,
+ "is_ready()",
+ "is_pending()",
+ )
+ })
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+
+ if let Some(good_method) = found_good_method {
+ let span = expr.span.to(op.span);
+ let result_expr = match &op.kind {
+ ExprKind::AddrOf(_, _, borrowed) => borrowed,
+ _ => op,
+ };
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PATTERN_MATCHING,
+ expr.span,
+ &format!("redundant pattern matching, consider using `{}`", good_method),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "try this",
+ format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method),
+ Applicability::MaybeIncorrect, // snippet
+ );
+ },
+ );
+ }
+ }
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn find_good_method_for_match<'a>(
+ cx: &LateContext<'_>,
+ arms: &[Arm<'_>],
+ path_left: &QPath<'_>,
+ path_right: &QPath<'_>,
+ expected_left: &[&str],
+ expected_right: &[&str],
+ should_be_left: &'a str,
+ should_be_right: &'a str,
+ ) -> Option<&'a str> {
+ let body_node_pair = if is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_left)
+ && is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_right)
+ {
+ (&(*arms[0].body).kind, &(*arms[1].body).kind)
+ } else if is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_left)
+ && is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_right)
+ {
+ (&(*arms[1].body).kind, &(*arms[0].body).kind)
+ } else {
+ return None;
+ };
+
+ match body_node_pair {
+ (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) {
+ (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
+ (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
+ _ => None,
+ },
+ _ => None,
+ }
+ }
+}
+
+#[test]
+fn test_overlapping() {
+ use rustc_span::source_map::DUMMY_SP;
+
+ let sp = |s, e| SpannedRange {
+ span: DUMMY_SP,
+ node: (s, e),
+ };
+
+ assert_eq!(None, overlapping::<u8>(&[]));
- overlapping(&[sp(1, Bound::Included(4)), sp(5, Bound::Included(6))])
++ assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))]));
+ assert_eq!(
+ None,
- sp(1, Bound::Included(4)),
- sp(5, Bound::Included(6)),
- sp(10, Bound::Included(11))
++ overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))])
+ );
+ assert_eq!(
+ None,
+ overlapping(&[
- Some((&sp(1, Bound::Included(4)), &sp(3, Bound::Included(6)))),
- overlapping(&[sp(1, Bound::Included(4)), sp(3, Bound::Included(6))])
++ sp(1, EndBound::Included(4)),
++ sp(5, EndBound::Included(6)),
++ sp(10, EndBound::Included(11))
+ ],)
+ );
+ assert_eq!(
- Some((&sp(5, Bound::Included(6)), &sp(6, Bound::Included(11)))),
++ Some((&sp(1, EndBound::Included(4)), &sp(3, EndBound::Included(6)))),
++ overlapping(&[sp(1, EndBound::Included(4)), sp(3, EndBound::Included(6))])
+ );
+ assert_eq!(
- sp(1, Bound::Included(4)),
- sp(5, Bound::Included(6)),
- sp(6, Bound::Included(11))
++ Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))),
+ overlapping(&[
++ sp(1, EndBound::Included(4)),
++ sp(5, EndBound::Included(6)),
++ sp(6, EndBound::Included(11))
+ ],)
+ );
+}
+
+/// Implementation of `MATCH_SAME_ARMS`.
+fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+ if let ExprKind::Match(_, arms, MatchSource::Normal) = expr.kind {
+ let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
+ let mut h = SpanlessHash::new(cx);
+ h.hash_expr(arm.body);
+ h.finish()
+ };
+
+ let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool {
+ let min_index = usize::min(lindex, rindex);
+ let max_index = usize::max(lindex, rindex);
+
+ let mut local_map: HirIdMap<HirId> = HirIdMap::default();
+ let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
+ if_chain! {
+ if let Some(a_id) = path_to_local(a);
+ if let Some(b_id) = path_to_local(b);
+ let entry = match local_map.entry(a_id) {
+ Entry::Vacant(entry) => entry,
+ // check if using the same bindings as before
+ Entry::Occupied(entry) => return *entry.get() == b_id,
+ };
+ // the names technically don't have to match; this makes the lint more conservative
+ if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
+ if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b));
+ if pat_contains_local(lhs.pat, a_id);
+ if pat_contains_local(rhs.pat, b_id);
+ then {
+ entry.insert(b_id);
+ true
+ } else {
+ false
+ }
+ }
+ };
+ // Arms with a guard are ignored, those can’t always be merged together
+ // This is also the case for arms in-between each there is an arm with a guard
+ (min_index..=max_index).all(|index| arms[index].guard.is_none())
+ && SpanlessEq::new(cx)
+ .expr_fallback(eq_fallback)
+ .eq_expr(lhs.body, rhs.body)
+ // these checks could be removed to allow unused bindings
+ && bindings_eq(lhs.pat, local_map.keys().copied().collect())
+ && bindings_eq(rhs.pat, local_map.values().copied().collect())
+ };
+
+ let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
+ for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) {
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ j.body.span,
+ "this `match` has identical arm bodies",
+ |diag| {
+ diag.span_note(i.body.span, "same as this");
+
+ // Note: this does not use `span_suggestion` on purpose:
+ // there is no clean way
+ // to remove the other arm. Building a span and suggest to replace it to ""
+ // makes an even more confusing error message. Also in order not to make up a
+ // span for the whole pattern, the suggestion is only shown when there is only
+ // one pattern. The user should know about `|` if they are already using it…
+
+ let lhs = snippet(cx, i.pat.span, "<pat1>");
+ let rhs = snippet(cx, j.pat.span, "<pat2>");
+
+ if let PatKind::Wild = j.pat.kind {
+ // if the last arm is _, then i could be integrated into _
+ // note that i.pat cannot be _, because that would mean that we're
+ // hiding all the subsequent arms, and rust won't compile
+ diag.span_note(
+ i.body.span,
+ &format!(
+ "`{}` has the same arm body as the `_` wildcard, consider removing it",
+ lhs
+ ),
+ );
+ } else {
+ diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,))
+ .help("...or consider changing the match arm bodies");
+ }
+ },
+ );
+ }
+ }
+}
+
+fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
+ let mut result = false;
+ pat.walk_short(|p| {
+ result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id);
+ !result
+ });
+ result
+}
+
+/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
+fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
+ let mut result = true;
+ pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
+ result && ids.is_empty()
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{match_def_path, paths};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `std::mem::forget(t)` where `t` is
+ /// `Drop`.
+ ///
+ /// ### Why is this bad?
+ /// `std::mem::forget(t)` prevents `t` from running its
+ /// destructor, possibly causing leaks.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::mem;
+ /// # use std::rc::Rc;
+ /// mem::forget(Rc::new(55))
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MEM_FORGET,
+ restriction,
+ "`mem::forget` usage on `Drop` types, likely to cause memory leaks"
+}
+
+declare_lint_pass!(MemForget => [MEM_FORGET]);
+
+impl<'tcx> LateLintPass<'tcx> for MemForget {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Call(path_expr, [ref first_arg, ..]) = e.kind {
+ if let ExprKind::Path(ref qpath) = path_expr.kind {
+ if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
+ if match_def_path(cx, def_id, &paths::MEM_FORGET) {
+ let forgot_ty = cx.typeck_results().expr_ty(first_arg);
+
+ if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) {
+ span_lint(cx, MEM_FORGET, e.span, "usage of `mem::forget` on `Drop` type");
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{in_macro, is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::ty::is_non_aggregate_primitive_type;
- if !in_macro(expr_span) {
++use clippy_utils::{is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::OptionNone;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `mem::replace()` on an `Option` with
+ /// `None`.
+ ///
+ /// ### Why is this bad?
+ /// `Option` already has the method `take()` for
+ /// taking its current value (Some(..) or None) and replacing it with
+ /// `None`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::mem;
+ ///
+ /// let mut an_option = Some(0);
+ /// let replaced = mem::replace(&mut an_option, None);
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// let mut an_option = Some(0);
+ /// let taken = an_option.take();
+ /// ```
++ #[clippy::version = "1.31.0"]
+ pub MEM_REPLACE_OPTION_WITH_NONE,
+ style,
+ "replacing an `Option` with `None` instead of `take()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `mem::replace(&mut _, mem::uninitialized())`
+ /// and `mem::replace(&mut _, mem::zeroed())`.
+ ///
+ /// ### Why is this bad?
+ /// This will lead to undefined behavior even if the
+ /// value is overwritten later, because the uninitialized value may be
+ /// observed in the case of a panic.
+ ///
+ /// ### Example
+ /// ```
+ /// use std::mem;
+ ///# fn may_panic(v: Vec<i32>) -> Vec<i32> { v }
+ ///
+ /// #[allow(deprecated, invalid_value)]
+ /// fn myfunc (v: &mut Vec<i32>) {
+ /// let taken_v = unsafe { mem::replace(v, mem::uninitialized()) };
+ /// let new_v = may_panic(taken_v); // undefined behavior on panic
+ /// mem::forget(mem::replace(v, new_v));
+ /// }
+ /// ```
+ ///
+ /// The [take_mut](https://docs.rs/take_mut) crate offers a sound solution,
+ /// at the cost of either lazily creating a replacement value or aborting
+ /// on panic, to ensure that the uninitialized value cannot be observed.
++ #[clippy::version = "1.39.0"]
+ pub MEM_REPLACE_WITH_UNINIT,
+ correctness,
+ "`mem::replace(&mut _, mem::uninitialized())` or `mem::replace(&mut _, mem::zeroed())`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `std::mem::replace` on a value of type
+ /// `T` with `T::default()`.
+ ///
+ /// ### Why is this bad?
+ /// `std::mem` module already has the method `take` to
+ /// take the current value and replace it with the default value of that type.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut text = String::from("foo");
+ /// let replaced = std::mem::replace(&mut text, String::default());
+ /// ```
+ /// Is better expressed with:
+ /// ```rust
+ /// let mut text = String::from("foo");
+ /// let taken = std::mem::take(&mut text);
+ /// ```
++ #[clippy::version = "1.42.0"]
+ pub MEM_REPLACE_WITH_DEFAULT,
+ style,
+ "replacing a value of type `T` with `T::default()` instead of using `std::mem::take`"
+}
+
+impl_lint_pass!(MemReplace =>
+ [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]);
+
+fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ if let ExprKind::Path(ref replacement_qpath) = src.kind {
+ // Check that second argument is `Option::None`
+ if is_lang_ctor(cx, replacement_qpath, OptionNone) {
+ // Since this is a late pass (already type-checked),
+ // and we already know that the second argument is an
+ // `Option`, we do not need to check the first
+ // argument's type. All that's left is to get
+ // replacee's path.
+ let replaced_path = match dest.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
+ if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
+ replaced_path
+ } else {
+ return;
+ }
+ },
+ ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
+ _ => return,
+ };
+
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_OPTION_WITH_NONE,
+ expr_span,
+ "replacing an `Option` with `None`",
+ "consider `Option::take()` instead",
+ format!(
+ "{}.take()",
+ snippet_with_applicability(cx, replaced_path.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ if_chain! {
+ // check if replacement is mem::MaybeUninit::uninit().assume_init()
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(src.hir_id);
+ if cx.tcx.is_diagnostic_item(sym::assume_init, method_def_id);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::MaybeUninit::uninit().assume_init()`",
+ "consider using",
+ format!(
+ "std::ptr::read({})",
+ snippet_with_applicability(cx, dest.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ return;
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::Call(repl_func, repl_args) = src.kind;
+ if repl_args.is_empty();
+ if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
+ if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
+ then {
+ if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::uninitialized()`",
+ "consider using",
+ format!(
+ "std::ptr::read({})",
+ snippet_with_applicability(cx, dest.span, "", &mut applicability)
+ ),
+ applicability,
+ );
+ } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) &&
+ !cx.typeck_results().expr_ty(src).is_primitive() {
+ span_lint_and_help(
+ cx,
+ MEM_REPLACE_WITH_UNINIT,
+ expr_span,
+ "replacing with `mem::zeroed()`",
+ None,
+ "consider using a default value or the `take_mut` crate instead",
+ );
+ }
+ }
+ }
+}
+
+fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
+ // disable lint for primitives
+ let expr_type = cx.typeck_results().expr_ty_adjusted(src);
+ if is_non_aggregate_primitive_type(expr_type) {
+ return;
+ }
+ // disable lint for Option since it is covered in another lint
+ if let ExprKind::Path(q) = &src.kind {
+ if is_lang_ctor(cx, q, OptionNone) {
+ return;
+ }
+ }
+ if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
+ span_lint_and_then(
+ cx,
+ MEM_REPLACE_WITH_DEFAULT,
+ expr_span,
+ "replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
+ |diag| {
++ if !expr_span.from_expansion() {
+ let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
+
+ diag.span_suggestion(
+ expr_span,
+ "consider using",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ );
+ }
+}
+
+pub struct MemReplace {
+ msrv: Option<RustcVersion>,
+}
+
+impl MemReplace {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MemReplace {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ // Check that `expr` is a call to `mem::replace()`
+ if let ExprKind::Call(func, func_args) = expr.kind;
+ if let ExprKind::Path(ref func_qpath) = func.kind;
+ if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::MEM_REPLACE);
+ if let [dest, src] = func_args;
+ then {
+ check_replace_option_with_none(cx, src, dest, expr.span);
+ check_replace_with_uninit(cx, src, dest, expr.span);
+ if meets_msrv(self.msrv.as_ref(), &msrvs::MEM_TAKE) {
+ check_replace_with_default(cx, src, dest, expr.span);
+ }
+ }
+ }
+ }
+ extract_msrv_attr!(LateContext);
+}
--- /dev/null
- use clippy_utils::{in_macro, remove_blocks, visitors::find_all_ret_expressions};
+use super::{contains_return, BIND_INSTEAD_OF_MAP};
+use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
- if !in_macro(ret_expr.span);
++use clippy_utils::{remove_blocks, visitors::find_all_ret_expressions};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::{LangItem, QPath};
+use rustc_lint::LateContext;
+use rustc_middle::ty::DefIdTree;
+use rustc_span::Span;
+
+pub(crate) struct OptionAndThenSome;
+
+impl BindInsteadOfMap for OptionAndThenSome {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::OptionSome;
+ const BAD_METHOD_NAME: &'static str = "and_then";
+ const GOOD_METHOD_NAME: &'static str = "map";
+}
+
+pub(crate) struct ResultAndThenOk;
+
+impl BindInsteadOfMap for ResultAndThenOk {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::ResultOk;
+ const BAD_METHOD_NAME: &'static str = "and_then";
+ const GOOD_METHOD_NAME: &'static str = "map";
+}
+
+pub(crate) struct ResultOrElseErrInfo;
+
+impl BindInsteadOfMap for ResultOrElseErrInfo {
+ const VARIANT_LANG_ITEM: LangItem = LangItem::ResultErr;
+ const BAD_METHOD_NAME: &'static str = "or_else";
+ const GOOD_METHOD_NAME: &'static str = "map_err";
+}
+
+pub(crate) trait BindInsteadOfMap {
+ const VARIANT_LANG_ITEM: LangItem;
+ const BAD_METHOD_NAME: &'static str;
+ const GOOD_METHOD_NAME: &'static str;
+
+ fn no_op_msg(cx: &LateContext<'_>) -> Option<String> {
+ let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
+ let item_id = cx.tcx.parent(variant_id)?;
+ Some(format!(
+ "using `{}.{}({})`, which is a no-op",
+ cx.tcx.item_name(item_id),
+ Self::BAD_METHOD_NAME,
+ cx.tcx.item_name(variant_id),
+ ))
+ }
+
+ fn lint_msg(cx: &LateContext<'_>) -> Option<String> {
+ let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
+ let item_id = cx.tcx.parent(variant_id)?;
+ Some(format!(
+ "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
+ cx.tcx.item_name(item_id),
+ Self::BAD_METHOD_NAME,
+ cx.tcx.item_name(variant_id),
+ Self::GOOD_METHOD_NAME
+ ))
+ }
+
+ fn lint_closure_autofixable(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ recv: &hir::Expr<'_>,
+ closure_expr: &hir::Expr<'_>,
+ closure_args_span: Span,
+ ) -> bool {
+ if_chain! {
+ if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind;
+ if let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind;
+ if Self::is_variant(cx, path.res);
+ if !contains_return(inner_expr);
+ if let Some(msg) = Self::lint_msg(cx);
+ then {
+ let some_inner_snip = if inner_expr.span.from_expansion() {
+ snippet_with_macro_callsite(cx, inner_expr.span, "_")
+ } else {
+ snippet(cx, inner_expr.span, "_")
+ };
+
+ let closure_args_snip = snippet(cx, closure_args_span, "..");
+ let option_snip = snippet(cx, recv.span, "..");
+ let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip);
+ span_lint_and_sugg(
+ cx,
+ BIND_INSTEAD_OF_MAP,
+ expr.span,
+ &msg,
+ "try this",
+ note,
+ Applicability::MachineApplicable,
+ );
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ fn lint_closure(cx: &LateContext<'_>, expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>) -> bool {
+ let mut suggs = Vec::new();
+ let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
+ if_chain! {
++ if !ret_expr.span.from_expansion();
+ if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind;
+ if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind;
+ if Self::is_variant(cx, path.res);
+ if !contains_return(arg);
+ then {
+ suggs.push((ret_expr.span, arg.span.source_callsite()));
+ true
+ } else {
+ false
+ }
+ }
+ });
+ let (span, msg) = if_chain! {
+ if can_sugg;
+ if let hir::ExprKind::MethodCall(_, span, ..) = expr.kind;
+ if let Some(msg) = Self::lint_msg(cx);
+ then { (span, msg) } else { return false; }
+ };
+ span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, |diag| {
+ multispan_sugg_with_applicability(
+ diag,
+ "try this",
+ Applicability::MachineApplicable,
+ std::iter::once((span, Self::GOOD_METHOD_NAME.into())).chain(
+ suggs
+ .into_iter()
+ .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())),
+ ),
+ );
+ });
+ true
+ }
+
+ /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
+ fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) -> bool {
+ if_chain! {
+ if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
+ if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM);
+ if Some(adt.did) == cx.tcx.parent(vid);
+ then {} else { return false; }
+ }
+
+ match arg.kind {
+ hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
+ let closure_body = cx.tcx.hir().body(body_id);
+ let closure_expr = remove_blocks(&closure_body.value);
+
+ if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, closure_args_span) {
+ true
+ } else {
+ Self::lint_closure(cx, expr, closure_expr)
+ }
+ },
+ // `_.and_then(Some)` case, which is no-op.
+ hir::ExprKind::Path(QPath::Resolved(_, path)) if Self::is_variant(cx, path.res) => {
+ if let Some(msg) = Self::no_op_msg(cx) {
+ span_lint_and_sugg(
+ cx,
+ BIND_INSTEAD_OF_MAP,
+ expr.span,
+ &msg,
+ "use the expression directly",
+ snippet(cx, recv.span, "..").into(),
+ Applicability::MachineApplicable,
+ );
+ }
+ true
+ },
+ _ => false,
+ }
+ }
+
+ fn is_variant(cx: &LateContext<'_>, res: Res) -> bool {
+ if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
+ if let Ok(variant_id) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM) {
+ return cx.tcx.parent(id) == Some(variant_id);
+ }
+ }
+ false
+ }
+}
--- /dev/null
- let end = ty_str.find('>').unwrap_or_else(|| ty_str.len());
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{is_expr_path_def_path, paths, sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::Ty;
+use rustc_span::sym;
+
+use super::FROM_ITER_INSTEAD_OF_COLLECT;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func: &hir::Expr<'_>) {
+ if_chain! {
+ if is_expr_path_def_path(cx, func, &paths::FROM_ITERATOR_METHOD);
+ let ty = cx.typeck_results().expr_ty(expr);
+ let arg_ty = cx.typeck_results().expr_ty(&args[0]);
+ if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+
+ if implements_trait(cx, arg_ty, iter_id, &[]);
+ then {
+ // `expr` implements `FromIterator` trait
+ let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par();
+ let turbofish = extract_turbofish(cx, expr, ty);
+ let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish);
+ span_lint_and_sugg(
+ cx,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ expr.span,
+ "usage of `FromIterator::from_iter`",
+ "use `.collect()` instead of `::from_iter()`",
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+}
+
+fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String {
+ fn strip_angle_brackets(s: &str) -> Option<&str> {
+ s.strip_prefix('<')?.strip_suffix('>')
+ }
+
+ let call_site = expr.span.source_callsite();
+ if_chain! {
+ if let Some(snippet) = snippet_opt(cx, call_site);
+ let snippet_split = snippet.split("::").collect::<Vec<_>>();
+ if let Some((_, elements)) = snippet_split.split_last();
+
+ then {
+ if_chain! {
+ if let [type_specifier, _] = snippet_split.as_slice();
+ if let Some(type_specifier) = strip_angle_brackets(type_specifier);
+ if let Some((type_specifier, ..)) = type_specifier.split_once(" as ");
+ then {
+ type_specifier.to_string()
+ } else {
+ // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`)
+ if let Some(type_specifier) = snippet_split.iter().find(|e| strip_angle_brackets(e).is_some()) {
+ // remove the type specifier from the path elements
+ let without_ts = elements.iter().filter_map(|e| {
+ if e == type_specifier { None } else { Some((*e).to_string()) }
+ }).collect::<Vec<_>>();
+ // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`)
+ format!("{}{}", without_ts.join("::"), type_specifier)
+ } else {
+ // type is not explicitly specified so wildcards are needed
+ // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>`
+ let ty_str = ty.to_string();
+ let start = ty_str.find('<').unwrap_or(0);
++ let end = ty_str.find('>').unwrap_or(ty_str.len());
+ let nb_wildcard = ty_str[start..end].split(',').count();
+ let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1));
+ format!("{}<{}>", elements.join("::"), wildcards)
+ }
+ }
+ }
+ } else {
+ ty.to_string()
+ }
+ }
+}
--- /dev/null
- pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) {
+use crate::methods::utils::derefs_to_slice;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::ITER_CLONED_COLLECT;
+
- "called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
- more readable",
++pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, method_name: &str, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec);
+ if let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv));
+ if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite());
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ ITER_CLONED_COLLECT,
+ to_replace,
++ &format!("called `iter().{}().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \
++ more readable", method_name),
+ "try",
+ ".to_vec()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
--- /dev/null
- mod manual_split_once;
+mod bind_instead_of_map;
+mod bytes_nth;
+mod chars_cmp;
+mod chars_cmp_with_unwrap;
+mod chars_last_cmp;
+mod chars_last_cmp_with_unwrap;
+mod chars_next_cmp;
+mod chars_next_cmp_with_unwrap;
+mod clone_on_copy;
+mod clone_on_ref_ptr;
+mod cloned_instead_of_copied;
+mod expect_fun_call;
+mod expect_used;
+mod extend_with_drain;
+mod filetype_is_file;
+mod filter_map;
+mod filter_map_identity;
+mod filter_map_next;
+mod filter_next;
+mod flat_map_identity;
+mod flat_map_option;
+mod from_iter_instead_of_collect;
+mod get_unwrap;
+mod implicit_clone;
+mod inefficient_to_string;
+mod inspect_for_each;
+mod into_iter_on_ref;
+mod iter_cloned_collect;
+mod iter_count;
+mod iter_next_slice;
+mod iter_nth;
+mod iter_nth_zero;
+mod iter_skip_next;
+mod iterator_step_by_zero;
+mod manual_saturating_arithmetic;
- use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
+mod manual_str_repeat;
+mod map_collect_result_unit;
+mod map_flatten;
+mod map_identity;
+mod map_unwrap_or;
+mod ok_expect;
+mod option_as_ref_deref;
+mod option_map_or_none;
+mod option_map_unwrap_or;
+mod or_fun_call;
+mod search_is_some;
+mod single_char_add_str;
+mod single_char_insert_string;
+mod single_char_pattern;
+mod single_char_push_string;
+mod skip_while_next;
++mod str_splitn;
+mod string_extend_chars;
+mod suspicious_map;
+mod suspicious_splitn;
+mod uninit_assumed_init;
+mod unnecessary_filter_map;
+mod unnecessary_fold;
+mod unnecessary_lazy_eval;
+mod unwrap_or_else_default;
+mod unwrap_used;
+mod useless_asref;
+mod utils;
+mod wrong_self_convention;
+mod zst_offset;
+
+use bind_instead_of_map::BindInsteadOfMap;
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
- pedantic,
++use clippy_utils::{contains_return, get_trait_def_id, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, TraitRef, Ty, TyS};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::SymbolStr;
+use rustc_span::{sym, Span};
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `cloned()` on an `Iterator` or `Option` where
+ /// `copied()` could be used instead.
+ ///
+ /// ### Why is this bad?
+ /// `copied()` is better because it guarantees that the type being cloned
+ /// implements `Copy`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1, 2, 3].iter().cloned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// [1, 2, 3].iter().copied();
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub CLONED_INSTEAD_OF_COPIED,
+ pedantic,
+ "used `cloned` where `copied` could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
+ /// used instead.
+ ///
+ /// ### Why is this bad?
+ /// When applicable, `filter_map()` is more clear since it shows that
+ /// `Option` is used to produce 0 or 1 items.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub FLAT_MAP_OPTION,
+ pedantic,
+ "used `flat_map` where `filter_map` could be used instead"
+}
+
+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.
+ ///
+ /// ### 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");
+ /// ```
++ #[clippy::version = "1.45.0"]
+ 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.
+ ///
+ /// ### 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::<(), ()>(())
+ /// ```
++ #[clippy::version = "1.45.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct X;
+ /// impl X {
+ /// fn add(&self, other: &X) -> X {
+ /// // ..
+ /// # X
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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 |Postfix |`self` taken | `self` type |
+ /// |-------|------------|-----------------------|--------------|
+ /// |`as_` | none |`&self` or `&mut self` | any |
+ /// |`from_`| none | none | any |
+ /// |`into_`| none |`self` | any |
+ /// |`is_` | none |`&self` or none | any |
+ /// |`to_` | `_mut` |`&mut self` | any |
+ /// |`to_` | not `_mut` |`self` | `Copy` |
+ /// |`to_` | not `_mut` |`&self` | not `Copy` |
+ ///
+ /// Note: Clippy doesn't trigger methods with `to_` prefix in:
+ /// - Traits definition.
+ /// Clippy can not tell if a type that implements a trait is `Copy` or not.
+ /// - Traits implementation, when `&self` is taken.
+ /// The method signature is controlled by the trait and often `&self` is required for all types that implement the trait
+ /// (see e.g. the `std::string::ToString` trait).
+ ///
+ /// Clippy allows `Pin<&Self>` and `Pin<&mut Self>` if `&self` and `&mut self` is required.
+ ///
+ /// Please find more info here:
+ /// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
+ ///
+ /// ### 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct X;
+ /// impl X {
+ /// fn as_str(self) -> &'static str {
+ /// // ..
+ /// # ""
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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
+ /// 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?");
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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 usages of `_.unwrap_or_else(Default::default)` on `Option` and
+ /// `Result` values.
+ ///
+ /// ### Why is this bad?
+ /// Readability, these can be written as `_.unwrap_or_default`, which is
+ /// simpler and more concise.
+ ///
+ /// ### Examples
+ /// ```rust
+ /// # let x = Some(1);
+ ///
+ /// // Bad
+ /// x.unwrap_or_else(Default::default);
+ /// x.unwrap_or_else(u32::default);
+ ///
+ /// // Good
+ /// x.unwrap_or_default();
+ /// ```
++ #[clippy::version = "1.56.0"]
+ pub UNWRAP_OR_ELSE_DEFAULT,
+ style,
+ "using `.unwrap_or_else(Default::default)`, which is more succinctly expressed as `.unwrap_or_default()`"
+}
+
+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);
+ /// ```
++ #[clippy::version = "1.45.0"]
+ 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));
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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()`.
+ ///
+ /// ### 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());
+ /// ```
++ #[clippy::version = "1.44.0"]
+ 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)`.
+ ///
+ /// ### 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 });
+ /// ```
++ #[clippy::version = "1.45.0"]
+ 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(_)`.
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "pre 1.29.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)`.
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "1.42.0"]
+ pub SKIP_WHILE_NEXT,
+ complexity,
+ "using `skip_while(p).next()`, which is more succinctly expressed as `.find(!p)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option`
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.flat_map(_)`
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![vec![1]];
+ ///
+ /// // Bad
+ /// vec.iter().map(|x| x.iter()).flatten();
+ ///
+ /// // Good
+ /// vec.iter().flat_map(|x| x.iter());
+ /// ```
++ #[clippy::version = "1.31.0"]
+ pub MAP_FLATTEN,
- MANUAL_SPLIT_ONCE
++ complexity,
+ "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(_)` that can be written more simply
+ /// as `filter_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `filter` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// (0_i32..10)
+ /// .filter(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// (0_i32..10).filter_map(|n| n.checked_add(1));
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub MANUAL_FILTER_MAP,
+ complexity,
+ "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.find(_).map(_)` that can be written more simply
+ /// as `find_map(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code in the `find` and `map` operations is poor style and
+ /// less performant.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// (0_i32..10)
+ /// .find(|n| n.checked_add(1).is_some())
+ /// .map(|n| n.checked_add(1).unwrap());
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// (0_i32..10).find_map(|n| n.checked_add(1));
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub MANUAL_FIND_MAP,
+ complexity,
+ "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.filter_map(_).next()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.find_map(_)`.
+ ///
+ /// ### 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 });
+ /// ```
++ #[clippy::version = "1.36.0"]
+ 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`.
+ ///
+ /// ### 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();
+ /// ```
++ #[clippy::version = "1.39.0"]
+ pub FLAT_MAP_IDENTITY,
+ complexity,
+ "call to `flat_map` where `flatten` is sufficient"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for an iterator or string search (such as `find()`,
+ /// `position()`, or `rposition()`) followed by a call to `is_some()` or `is_none()`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as:
+ /// * `_.any(_)`, or `_.contains(_)` for `is_some()`,
+ /// * `!_.any(_)`, or `!_.contains(_)` for `is_none()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().find(|x| **x == 0).is_some();
+ ///
+ /// let _ = "hello world".find("world").is_none();
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let vec = vec![1];
+ /// vec.iter().any(|x| *x == 0);
+ ///
+ /// let _ = !"hello world".contains("world");
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub SEARCH_IS_SOME,
+ complexity,
+ "using an iterator or string search followed by `is_some()` or `is_none()`, which is more succinctly expressed as a call to `any()` or `contains()` (with negation in case of `is_none()`)"
+}
+
+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(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let name = "foo";
+ /// if name.chars().next() == Some('_') {};
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let name = "foo";
+ /// if name.starts_with('_') {};
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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));
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// 42u64.clone();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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`.
+ ///
+ /// ### 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
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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());
+ /// ```
++ #[clippy::version = "1.40.0"]
+ 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.
+ ///
+ /// ### Example
+ /// In an impl block:
+ /// ```rust
+ /// # struct Foo;
+ /// # struct NotAFoo;
+ /// impl Foo {
+ /// fn new() -> NotAFoo {
+ /// # NotAFoo
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// struct Bar(Foo);
+ /// impl Foo {
+ /// // Bad. The type name must contain `Self`
+ /// fn new() -> Bar {
+ /// # Bar(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// # struct Foo;
+ /// # struct FooError;
+ /// impl Foo {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Result<Foo, FooError> {
+ /// # Ok(Foo)
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Or in a trait definition:
+ /// ```rust
+ /// pub trait Trait {
+ /// // Bad. The type name must contain `Self`
+ /// fn new();
+ /// }
+ /// ```
+ ///
+ /// ```rust
+ /// pub trait Trait {
+ /// // Good. Return type contains `Self`
+ /// fn new() -> Self;
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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');
++ #[clippy::version = "pre 1.29.0"]
+ pub SINGLE_CHAR_PATTERN,
+ perf,
+ "using a single-character str where a char could be used, e.g., `_.split(\"x\")`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calling `.step_by(0)` on iterators which panics.
+ ///
+ /// ### Why is this bad?
+ /// This very much looks like an oversight. Use `panic!()` instead if you
+ /// actually intend to panic.
+ ///
+ /// ### Example
+ /// ```rust,should_panic
+ /// for x in (0..100).step_by(0) {
+ /// //..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.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 indirect collection of populated `Option`
+ ///
+ /// ### Why is this bad?
+ /// `Option` is like a collection of 0-1 things, so `flatten`
+ /// automatically does this without suspicious-looking `unwrap` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().filter(Option::is_some).map(Option::unwrap);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let _ = std::iter::empty::<Option<i32>>().flatten();
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub OPTION_FILTER_MAP,
+ complexity,
+ "filtering `Option` for `Some` then force-unwrapping, which can be one type-safe operation"
+}
+
+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.
+ ///
+ /// ### 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();
+ /// ```
++ #[clippy::version = "1.42.0"]
+ 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.
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub GET_UNWRAP,
+ restriction,
+ "using `.get().unwrap()` or `.get_mut().unwrap()` when using `[]` would work instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for occurrences where one vector gets extended instead of append
+ ///
+ /// ### Why is this bad?
+ /// Using `append` instead of `extend` is more concise and faster
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = vec![1, 2, 3];
+ /// let mut b = vec![4, 5, 6];
+ ///
+ /// // Bad
+ /// a.extend(b.drain(..));
+ ///
+ /// // Good
+ /// a.append(&mut b);
+ /// ```
++ #[clippy::version = "1.55.0"]
+ pub EXTEND_WITH_DRAIN,
+ perf,
+ "using vec.append(&mut vec) to move the full range of a vecor to another"
+}
+
+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
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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
+ ///
+ /// ### 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();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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(_)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let name = "_";
+ ///
+ /// // Bad
+ /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-');
+ ///
+ /// // Good
+ /// name.ends_with('_') || name.ends_with('-');
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "1.31.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let _ = (&vec![3, 4, 5]).into_iter();
+ ///
+ /// // Good
+ /// let _ = (&vec![3, 4, 5]).iter();
+ /// ```
++ #[clippy::version = "1.32.0"]
+ 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
+ /// using `inspect`. Or, if you intend to drive the iterator to
+ /// completion, you can just use `for_each` instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = (0..3).map(|x| x + 2).count();
+ /// ```
++ #[clippy::version = "1.39.0"]
+ pub SUSPICIOUS_MAP,
+ suspicious,
+ "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()
+ /// };
+ /// ```
++ #[clippy::version = "1.39.0"]
+ 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);
+ /// ```
++ #[clippy::version = "1.39.0"]
+ 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
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe { (&() as *const ()).offset(1) };
+ /// ```
++ #[clippy::version = "1.41.0"]
+ 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>(())
+ /// # };
+ /// ```
++ #[clippy::version = "1.42.0"]
+ pub FILETYPE_IS_FILE,
+ restriction,
+ "`FileType::is_file` is not recommended to test for readable file type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely as
+ /// `_.as_deref()`.
+ ///
+ /// ### 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()
+ /// # ;
+ /// ```
++ #[clippy::version = "1.42.0"]
+ 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()`
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "1.46.0"]
+ pub ITER_NEXT_SLICE,
+ style,
+ "using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns when using `push_str`/`insert_str` with a single-character string literal
+ /// where `push`/`insert` with a `char` would work fine.
+ ///
+ /// ### Why is this bad?
+ /// It's less clear that we are pushing a single character.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut string = String::new();
+ /// string.insert_str(0, "R");
+ /// string.push_str("R");
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// let mut string = String::new();
+ /// string.insert(0, 'R');
+ /// string.push('R');
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub SINGLE_CHAR_ADD_STR,
+ style,
+ "`push_str()` or `insert_str()` used with a single-character string literal as parameter"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// As the counterpart to `or_fun_call`, this lint looks for unnecessary
+ /// lazily evaluated closures on `Option` and `Result`.
+ ///
+ /// This lint suggests changing the following functions, when eager evaluation results in
+ /// simpler code:
+ /// - `unwrap_or_else` to `unwrap_or`
+ /// - `and_then` to `and`
+ /// - `or_else` to `or`
+ /// - `get_or_insert_with` to `get_or_insert`
+ /// - `ok_or_else` to `ok_or`
+ ///
+ /// ### Why is this bad?
+ /// Using eager evaluation is shorter and simpler in some cases.
+ ///
+ /// ### Known problems
+ /// It is possible, but not recommended for `Deref` and `Index` to have
+ /// side effects. Eagerly evaluating them can change the semantics of the program.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or_else(|| 42);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let opt: Option<u32> = None;
+ ///
+ /// opt.unwrap_or(42);
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub UNNECESSARY_LAZY_EVALUATIONS,
+ style,
+ "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `_.map(_).collect::<Result<(), _>()`.
+ ///
+ /// ### Why is this bad?
+ /// Using `try_for_each` instead is more readable and idiomatic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0..3).map(|t| Err(t)).collect::<Result<(), _>>();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// (0..3).try_for_each(|t| Err(t));
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub MAP_COLLECT_RESULT_UNIT,
+ style,
+ "using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `from_iter()` function calls on types that implement the `FromIterator`
+ /// trait.
+ ///
+ /// ### Why is this bad?
+ /// It is recommended style to use collect. See
+ /// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html)
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::iter::FromIterator;
+ ///
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v = Vec::from_iter(five_fives);
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let five_fives = std::iter::repeat(5).take(5);
+ ///
+ /// let v: Vec<i32> = five_fives.collect();
+ ///
+ /// assert_eq!(v, vec![5, 5, 5, 5, 5]);
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub FROM_ITER_INSTEAD_OF_COLLECT,
+ pedantic,
+ "use `.collect()` instead of `::from_iter()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `inspect().for_each()`.
+ ///
+ /// ### Why is this bad?
+ /// It is the same as performing the computation
+ /// inside `inspect` at the beginning of the closure in `for_each`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .inspect(|&x| println!("inspect the number: {}", x))
+ /// .for_each(|&x| {
+ /// assert!(x >= 0);
+ /// });
+ /// ```
+ /// Can be written as
+ /// ```rust
+ /// [1,2,3,4,5].iter()
+ /// .for_each(|&x| {
+ /// println!("inspect the number: {}", x);
+ /// assert!(x >= 0);
+ /// });
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub INSPECT_FOR_EACH,
+ complexity,
+ "using `.inspect().for_each()`, which can be replaced with `.for_each()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `filter_map(|x| x)`.
+ ///
+ /// ### Why is this bad?
+ /// Readability, this can be written more concisely by using `flatten`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.filter_map(|x| x);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let iter = vec![Some(1)].into_iter();
+ /// iter.flatten();
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub FILTER_MAP_IDENTITY,
+ complexity,
+ "call to `filter_map` where `flatten` is sufficient"
+}
+
+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`.
+ ///
+ /// ### 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();
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub MAP_IDENTITY,
+ complexity,
+ "using iterator.map(|x| x)"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.bytes().nth()`.
+ ///
+ /// ### Why is this bad?
+ /// `.as_bytes().get()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let _ = "Hello".bytes().nth(3);
+ ///
+ /// // Good
+ /// let _ = "Hello".as_bytes().get(3);
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub BYTES_NTH,
+ style,
+ "replace `.bytes().nth()` with `.as_bytes().get()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer.
+ ///
+ /// ### Why is this bad?
+ /// These methods do the same thing as `_.clone()` but may be confusing as
+ /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.to_vec();
+ /// let c = a.to_owned();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = vec![1, 2, 3];
+ /// let b = a.clone();
+ /// let c = a.clone();
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub IMPLICIT_CLONE,
+ pedantic,
+ "implicitly cloning a value by invoking a function on its dereferenced type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of `.iter().count()`.
+ ///
+ /// ### Why is this bad?
+ /// `.len()` is more efficient and more
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let _ = some_vec.iter().count();
+ /// let _ = &some_vec[..].iter().count();
+ ///
+ /// // Good
+ /// let some_vec = vec![0, 1, 2, 3];
+ /// let _ = some_vec.len();
+ /// let _ = &some_vec[..].len();
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub ITER_COUNT,
+ complexity,
+ "replace `.iter().count()` with `.len()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to [`splitn`]
+ /// (https://doc.rust-lang.org/std/primitive.str.html#method.splitn) and
+ /// related functions with either zero or one splits.
+ ///
+ /// ### Why is this bad?
+ /// These calls don't actually split the value and are
+ /// likely to be intended as a different number.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let s = "";
+ /// for x in s.splitn(1, ":") {
+ /// // use x
+ /// }
+ ///
+ /// // Good
+ /// let s = "";
+ /// for x in s.splitn(2, ":") {
+ /// // use x
+ /// }
+ /// ```
++ #[clippy::version = "1.54.0"]
+ pub SUSPICIOUS_SPLITN,
+ correctness,
+ "checks for `.splitn(0, ..)` and `.splitn(1, ..)`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual implementations of `str::repeat`
+ ///
+ /// ### Why is this bad?
+ /// These are both harder to read, as well as less performant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let x: String = std::iter::repeat('x').take(10).collect();
+ ///
+ /// // Good
+ /// let x: String = "x".repeat(10);
+ /// ```
++ #[clippy::version = "1.54.0"]
+ pub MANUAL_STR_REPEAT,
+ perf,
+ "manual implementation of `str::repeat`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `str::splitn(2, _)`
+ ///
+ /// ### Why is this bad?
+ /// `split_once` is both clearer in intent and slightly more efficient.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// let (key, value) = _.splitn(2, '=').next_tuple()?;
+ /// let value = _.splitn(2, '=').nth(1)?;
+ ///
+ /// // Good
+ /// let (key, value) = _.split_once('=')?;
+ /// let value = _.split_once('=')?.1;
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub MANUAL_SPLIT_ONCE,
+ complexity,
+ "replace `.splitn(2, pat)` with `.split_once(pat)`"
+}
+
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for usages of `str::splitn` (or `str::rsplitn`) where using `str::split` would be the same.
++ /// ### Why is this bad?
++ /// The function `split` is simpler and there is no performance difference in these cases, considering
++ /// that both functions return a lazy iterator.
++ /// ### Example
++ /// ```rust
++ /// // Bad
++ /// let str = "key=value=add";
++ /// let _ = str.splitn(3, '=').next().unwrap();
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// // Good
++ /// let str = "key=value=add";
++ /// let _ = str.split('=').next().unwrap();
++ /// ```
++ #[clippy::version = "1.58.0"]
++ pub NEEDLESS_SPLITN,
++ complexity,
++ "usages of `str::splitn` that can be replaced with `str::split`"
++}
++
+pub struct Methods {
+ avoid_breaking_exported_api: bool,
+ msrv: Option<RustcVersion>,
+}
+
+impl Methods {
+ #[must_use]
+ pub fn new(avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ msrv,
+ }
+ }
+}
+
+impl_lint_pass!(Methods => [
+ UNWRAP_USED,
+ EXPECT_USED,
+ SHOULD_IMPLEMENT_TRAIT,
+ WRONG_SELF_CONVENTION,
+ OK_EXPECT,
+ UNWRAP_OR_ELSE_DEFAULT,
+ 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,
+ CLONED_INSTEAD_OF_COPIED,
+ FLAT_MAP_OPTION,
+ INEFFICIENT_TO_STRING,
+ NEW_RET_NO_SELF,
+ SINGLE_CHAR_PATTERN,
+ SINGLE_CHAR_ADD_STR,
+ SEARCH_IS_SOME,
+ FILTER_NEXT,
+ SKIP_WHILE_NEXT,
+ FILTER_MAP_IDENTITY,
+ MAP_IDENTITY,
+ MANUAL_FILTER_MAP,
+ MANUAL_FIND_MAP,
+ OPTION_FILTER_MAP,
+ FILTER_MAP_NEXT,
+ FLAT_MAP_IDENTITY,
+ MAP_FLATTEN,
+ ITERATOR_STEP_BY_ZERO,
+ ITER_NEXT_SLICE,
+ ITER_COUNT,
+ ITER_NTH,
+ ITER_NTH_ZERO,
+ BYTES_NTH,
+ ITER_SKIP_NEXT,
+ GET_UNWRAP,
+ STRING_EXTEND_CHARS,
+ ITER_CLONED_COLLECT,
+ USELESS_ASREF,
+ UNNECESSARY_FOLD,
+ UNNECESSARY_FILTER_MAP,
+ INTO_ITER_ON_REF,
+ SUSPICIOUS_MAP,
+ UNINIT_ASSUMED_INIT,
+ MANUAL_SATURATING_ARITHMETIC,
+ ZST_OFFSET,
+ FILETYPE_IS_FILE,
+ OPTION_AS_REF_DEREF,
+ UNNECESSARY_LAZY_EVALUATIONS,
+ MAP_COLLECT_RESULT_UNIT,
+ FROM_ITER_INSTEAD_OF_COLLECT,
+ INSPECT_FOR_EACH,
+ IMPLICIT_CLONE,
+ SUSPICIOUS_SPLITN,
+ MANUAL_STR_REPEAT,
+ EXTEND_WITH_DRAIN,
- if in_macro(expr.span) {
++ MANUAL_SPLIT_ONCE,
++ NEEDLESS_SPLITN
+]);
+
+/// Extracts a method call name, args, and `Span` of the method name.
+fn method_call<'tcx>(recv: &'tcx hir::Expr<'tcx>) -> Option<(SymbolStr, &'tcx [hir::Expr<'tcx>], Span)> {
+ if let ExprKind::MethodCall(path, span, args, _) = recv.kind {
+ if !args.iter().any(|e| e.span.from_expansion()) {
+ return Some((path.ident.name.as_str(), args, span));
+ }
+ }
+ None
+}
+
+/// Same as `method_call` but the `SymbolStr` is dereferenced into a temporary `&str`
+macro_rules! method_call {
+ ($expr:expr) => {
+ method_call($expr)
+ .as_ref()
+ .map(|&(ref name, args, span)| (&**name, args, span))
+ };
+}
+
+impl<'tcx> LateLintPass<'tcx> for Methods {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
- Some(("cloned", [recv2], _)) => iter_cloned_collect::check(cx, expr, recv2),
++ if expr.span.from_expansion() {
+ return;
+ }
+
+ check_methods(cx, expr, self.msrv.as_ref());
+
+ match expr.kind {
+ hir::ExprKind::Call(func, args) => {
+ from_iter_instead_of_collect::check(cx, expr, args, func);
+ },
+ hir::ExprKind::MethodCall(method_call, ref method_span, args, _) => {
+ or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
+ expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
+ clone_on_copy::check(cx, expr, method_call.ident.name, args);
+ clone_on_ref_ptr::check(cx, expr, method_call.ident.name, args);
+ inefficient_to_string::check(cx, expr, method_call.ident.name, args);
+ single_char_add_str::check(cx, expr, args);
+ into_iter_on_ref::check(cx, expr, *method_span, method_call.ident.name, args);
+ single_char_pattern::check(cx, expr, method_call.ident.name, args);
+ },
+ hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
+ let mut info = BinaryExprInfo {
+ expr,
+ chain: lhs,
+ other: rhs,
+ eq: op.node == hir::BinOpKind::Eq,
+ };
+ lint_binary_expr_with_method_call(cx, &mut info);
+ },
+ _ => (),
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if in_external_macro(cx.sess(), impl_item.span) {
+ return;
+ }
+ let name = impl_item.ident.name.as_str();
+ let parent = cx.tcx.hir().get_parent_did(impl_item.hir_id());
+ let item = cx.tcx.hir().expect_item(parent);
+ let self_ty = cx.tcx.type_of(item.def_id);
+
+ let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
+ if_chain! {
+ if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind;
+ if let Some(first_arg) = iter_input_pats(sig.decl, cx.tcx.hir().body(id)).next();
+
+ let method_sig = cx.tcx.fn_sig(impl_item.def_id);
+ let method_sig = cx.tcx.erase_late_bound_regions(method_sig);
+
+ let first_arg_ty = &method_sig.inputs().iter().next();
+
+ // check conventions w.r.t. conversion method names and predicates
+ if let Some(first_arg_ty) = first_arg_ty;
+
+ then {
+ // if this impl block implements a trait, lint in trait definition instead
+ if !implements_trait && cx.access_levels.is_exported(impl_item.def_id) {
+ // check missing trait implementations
+ for method_config in &TRAIT_METHODS {
+ if name == method_config.method_name &&
+ sig.decl.inputs.len() == method_config.param_count &&
+ method_config.output_type.matches(&sig.decl.output) &&
+ method_config.self_kind.matches(cx, self_ty, first_arg_ty) &&
+ fn_header_equals(method_config.fn_header, sig.header) &&
+ method_config.lifetime_param_cond(impl_item)
+ {
+ span_lint_and_help(
+ cx,
+ SHOULD_IMPLEMENT_TRAIT,
+ impl_item.span,
+ &format!(
+ "method `{}` can be confused for the standard trait method `{}::{}`",
+ method_config.method_name,
+ method_config.trait_name,
+ method_config.method_name
+ ),
+ None,
+ &format!(
+ "consider implementing the trait `{}` or choosing a less ambiguous method name",
+ method_config.trait_name
+ )
+ );
+ }
+ }
+ }
+
+ if sig.decl.implicit_self.has_implicit_self()
+ && !(self.avoid_breaking_exported_api
+ && cx.access_levels.is_exported(impl_item.def_id))
+ {
+ wrong_self_convention::check(
+ cx,
+ &name,
+ self_ty,
+ first_arg_ty,
+ first_arg.pat.span,
+ implements_trait,
+ false
+ );
+ }
+ }
+ }
+
+ // if this impl block implements a trait, lint in trait definition instead
+ if implements_trait {
+ return;
+ }
+
+ if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
+ let ret_ty = return_ty(cx, impl_item.hir_id());
+
+ // walk the return type and check for Self (this does not check associated types)
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(cx.tcx, ret_ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(cx.tcx, ret_ty, self_ty) {
+ return;
+ }
+
+ // if return type is impl trait, check the associated types
+ if let ty::Opaque(def_id, _) = *ret_ty.kind() {
+ // one of the associated types must be Self
+ for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) {
+ if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() {
+ // walk the associated type and check for Self
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if contains_adt_constructor(cx.tcx, projection_predicate.ty, self_adt) {
+ return;
+ }
+ } else if contains_ty(cx.tcx, projection_predicate.ty, self_ty) {
+ return;
+ }
+ }
+ }
+ }
+
+ if name == "new" && !TyS::same_type(ret_ty, self_ty) {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ impl_item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if in_external_macro(cx.tcx.sess, item.span) {
+ return;
+ }
+
+ if_chain! {
+ if let TraitItemKind::Fn(ref sig, _) = item.kind;
+ if sig.decl.implicit_self.has_implicit_self();
+ if let Some(first_arg_ty) = sig.decl.inputs.iter().next();
+
+ then {
+ let first_arg_span = first_arg_ty.span;
+ let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty);
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
+ wrong_self_convention::check(
+ cx,
+ &item.ident.name.as_str(),
+ self_ty,
+ first_arg_ty,
+ first_arg_span,
+ false,
+ true
+ );
+ }
+ }
+
+ if_chain! {
+ if item.ident.name == sym::new;
+ if let TraitItemKind::Fn(_, _) = item.kind;
+ let ret_ty = return_ty(cx, item.hir_id());
+ let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty().skip_binder();
+ if !contains_ty(cx.tcx, ret_ty, self_ty);
+
+ then {
+ span_lint(
+ cx,
+ NEW_RET_NO_SELF,
+ item.span,
+ "methods called `new` usually return `Self`",
+ );
+ }
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Option<&RustcVersion>) {
+ if let Some((name, [recv, args @ ..], span)) = method_call!(expr) {
+ match (name, args) {
+ ("add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub", [_arg]) => {
+ zst_offset::check(cx, expr, recv);
+ },
+ ("and_then", [arg]) => {
+ let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, recv, arg);
+ let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, recv, arg);
+ if !biom_option_linted && !biom_result_linted {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
+ }
+ },
+ ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
+ ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
+ ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
+ ("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, msrv),
+ ("collect", []) => match method_call!(recv) {
- manual_split_once::check(cx, name, expr, recv, pat_arg);
++ Some((name @ ("cloned" | "copied"), [recv2], _)) => {
++ iter_cloned_collect::check(cx, name, expr, recv2);
++ },
+ Some(("map", [m_recv, m_arg], _)) => {
+ map_collect_result_unit::check(cx, expr, m_recv, m_arg, recv);
+ },
+ Some(("take", [take_self_arg, take_arg], _)) => {
+ if meets_msrv(msrv, &msrvs::STR_REPEAT) {
+ manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
+ }
+ },
+ _ => {},
+ },
+ ("count", []) => match method_call!(recv) {
+ Some((name @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => {
+ iter_count::check(cx, expr, recv2, name);
+ },
+ Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
+ _ => {},
+ },
+ ("expect", [_]) => match method_call!(recv) {
+ Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
+ _ => expect_used::check(cx, expr, recv),
+ },
+ ("extend", [arg]) => {
+ string_extend_chars::check(cx, expr, recv, arg);
+ extend_with_drain::check(cx, expr, recv, arg);
+ },
+ ("filter_map", [arg]) => {
+ unnecessary_filter_map::check(cx, expr, arg);
+ filter_map_identity::check(cx, expr, arg, span);
+ },
+ ("flat_map", [arg]) => {
+ flat_map_identity::check(cx, expr, arg, span);
+ flat_map_option::check(cx, expr, arg, span);
+ },
+ ("flatten", []) => {
+ if let Some(("map", [recv, map_arg], _)) = method_call!(recv) {
+ map_flatten::check(cx, expr, recv, map_arg);
+ }
+ },
+ ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span),
+ ("for_each", [_]) => {
+ if let Some(("inspect", [_, _], span2)) = method_call!(recv) {
+ inspect_for_each::check(cx, expr, span2);
+ }
+ },
+ ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
+ ("is_file", []) => filetype_is_file::check(cx, expr, recv),
+ ("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
+ ("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
+ ("map", [m_arg]) => {
+ if let Some((name, [recv2, args @ ..], span2)) = method_call!(recv) {
+ match (name, args) {
+ ("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, msrv),
+ ("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, msrv),
+ ("filter", [f_arg]) => {
+ filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
+ },
+ ("find", [f_arg]) => filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true),
+ _ => {},
+ }
+ }
+ map_identity::check(cx, expr, recv, m_arg, span);
+ },
+ ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
+ ("next", []) => {
+ if let Some((name, [recv, args @ ..], _)) = method_call!(recv) {
+ match (name, args) {
+ ("filter", [arg]) => filter_next::check(cx, expr, recv, arg),
+ ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv, arg, msrv),
+ ("iter", []) => iter_next_slice::check(cx, expr, recv),
+ ("skip", [arg]) => iter_skip_next::check(cx, expr, recv, arg),
+ ("skip_while", [_]) => skip_while_next::check(cx, expr),
+ _ => {},
+ }
+ }
+ },
+ ("nth", [n_arg]) => match method_call!(recv) {
+ Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg),
+ Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false),
+ Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true),
+ _ => iter_nth_zero::check(cx, expr, recv, n_arg),
+ },
+ ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"),
+ ("or_else", [arg]) => {
+ if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, recv, arg) {
+ unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
+ }
+ },
+ ("splitn" | "rsplitn", [count_arg, pat_arg]) => {
+ if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
+ suspicious_splitn::check(cx, name, expr, recv, count);
+ if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
++ str_splitn::check_manual_split_once(cx, name, expr, recv, pat_arg);
++ }
++ if count >= 2 {
++ str_splitn::check_needless_splitn(cx, name, expr, recv, pat_arg, count);
+ }
+ }
+ },
+ ("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
+ if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
+ suspicious_splitn::check(cx, name, expr, recv, count);
+ }
+ },
+ ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
+ ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
+ implicit_clone::check(cx, name, expr, recv, span);
+ },
+ ("unwrap", []) => match method_call!(recv) {
+ Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false),
+ Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true),
+ _ => unwrap_used::check(cx, expr, recv),
+ },
+ ("unwrap_or", [u_arg]) => match method_call!(recv) {
+ Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
+ manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
+ },
+ Some(("map", [m_recv, m_arg], span)) => {
+ option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
+ },
+ _ => {},
+ },
+ ("unwrap_or_else", [u_arg]) => match method_call!(recv) {
+ Some(("map", [recv, map_arg], _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, msrv) => {},
+ _ => {
+ unwrap_or_else_default::check(cx, expr, recv, u_arg);
+ unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
+ },
+ },
+ _ => {},
+ }
+ }
+}
+
+fn check_is_some_is_none(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, is_some: bool) {
+ if let Some((name @ ("find" | "position" | "rposition"), [f_recv, arg], span)) = method_call!(recv) {
+ search_is_some::check(cx, expr, name, is_some, f_recv, arg, recv, span);
+ }
+}
+
+/// 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:expr, $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!(chars_next_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_next_cmp_with_unwrap::check, cx, info);
+ lint_with_both_lhs_and_rhs!(chars_last_cmp_with_unwrap::check, cx, info);
+}
+
+const FN_HEADER: hir::FnHeader = hir::FnHeader {
+ unsafety: hir::Unsafety::Normal,
+ constness: hir::Constness::NotConst,
+ asyncness: hir::IsAsync::NotAsync,
+ abi: rustc_target::spec::abi::Abi::Rust,
+};
+
+struct ShouldImplTraitCase {
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ // implicit self kind expected (none, self, &self, ...)
+ self_kind: SelfKind,
+ // checks against the output type
+ output_type: OutType,
+ // certain methods with explicit lifetimes can't implement the equivalent trait method
+ lint_explicit_lifetime: bool,
+}
+impl ShouldImplTraitCase {
+ const fn new(
+ trait_name: &'static str,
+ method_name: &'static str,
+ param_count: usize,
+ fn_header: hir::FnHeader,
+ self_kind: SelfKind,
+ output_type: OutType,
+ lint_explicit_lifetime: bool,
+ ) -> ShouldImplTraitCase {
+ ShouldImplTraitCase {
+ trait_name,
+ method_name,
+ param_count,
+ fn_header,
+ self_kind,
+ output_type,
+ lint_explicit_lifetime,
+ }
+ }
+
+ fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
+ self.lint_explicit_lifetime
+ || !impl_item.generics.params.iter().any(|p| {
+ matches!(
+ p.kind,
+ hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Explicit
+ }
+ )
+ })
+ }
+}
+
+#[rustfmt::skip]
+const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
+ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
+ // FIXME: default doesn't work
+ ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
+ ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
+ ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
+ ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
+ ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
+ ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
+];
+
+#[derive(Clone, Copy, PartialEq, Debug)]
+enum SelfKind {
+ Value,
+ Ref,
+ RefMut,
+ No,
+}
+
+impl SelfKind {
+ fn matches<'a>(self, cx: &LateContext<'a>, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ fn matches_value<'a>(cx: &LateContext<'a>, parent_ty: Ty<'_>, ty: Ty<'_>) -> bool {
+ if ty == parent_ty {
+ true
+ } else if ty.is_box() {
+ ty.boxed_ty() == parent_ty
+ } else if is_type_diagnostic_item(cx, ty, sym::Rc) || is_type_diagnostic_item(cx, ty, sym::Arc) {
+ if let ty::Adt(_, substs) = ty.kind() {
+ substs.types().next().map_or(false, |t| t == parent_ty)
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ fn matches_ref<'a>(cx: &LateContext<'a>, mutability: hir::Mutability, parent_ty: Ty<'a>, ty: Ty<'a>) -> bool {
+ if let ty::Ref(_, t, m) = *ty.kind() {
+ return m == mutability && t == parent_ty;
+ }
+
+ let trait_path = match mutability {
+ hir::Mutability::Not => &paths::ASREF_TRAIT,
+ hir::Mutability::Mut => &paths::ASMUT_TRAIT,
+ };
+
+ let trait_def_id = match get_trait_def_id(cx, trait_path) {
+ Some(did) => did,
+ None => return false,
+ };
+ implements_trait(cx, ty, trait_def_id, &[parent_ty.into()])
+ }
+
+ match self {
+ Self::Value => matches_value(cx, parent_ty, ty),
+ Self::Ref => matches_ref(cx, hir::Mutability::Not, parent_ty, ty) || ty == parent_ty && is_copy(cx, ty),
+ Self::RefMut => matches_ref(cx, hir::Mutability::Mut, parent_ty, ty),
+ Self::No => ty != parent_ty,
+ }
+ }
+
+ #[must_use]
+ fn description(self) -> &'static str {
+ match self {
+ Self::Value => "`self` by value",
+ Self::Ref => "`self` by reference",
+ Self::RefMut => "`self` by mutable reference",
+ Self::No => "no `self`",
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum OutType {
+ Unit,
+ Bool,
+ Any,
+ Ref,
+}
+
+impl OutType {
+ fn matches(self, ty: &hir::FnRetTy<'_>) -> bool {
+ let is_unit = |ty: &hir::Ty<'_>| matches!(ty.kind, hir::TyKind::Tup(&[]));
+ match (self, ty) {
+ (Self::Unit, &hir::FnRetTy::DefaultReturn(_)) => true,
+ (Self::Unit, &hir::FnRetTy::Return(ty)) if is_unit(ty) => true,
+ (Self::Bool, &hir::FnRetTy::Return(ty)) if is_bool(ty) => true,
+ (Self::Any, &hir::FnRetTy::Return(ty)) if !is_unit(ty) => true,
+ (Self::Ref, &hir::FnRetTy::Return(ty)) => matches!(ty.kind, hir::TyKind::Rptr(_, _)),
+ _ => false,
+ }
+ }
+}
+
+fn is_bool(ty: &hir::Ty<'_>) -> bool {
+ if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ matches!(path.res, Res::PrimTy(PrimTy::Bool))
+ } else {
+ false
+ }
+}
+
+fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
+ expected.constness == actual.constness
+ && expected.unsafety == actual.unsafety
+ && expected.asyncness == actual.asyncness
+}
--- /dev/null
- use clippy_utils::is_lang_ctor;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- let (lint_name, msg, instead, hint) = {
- let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
- is_lang_ctor(cx, qpath, OptionNone)
- } else {
- return;
- };
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
++use clippy_utils::{is_lang_ctor, single_segment_path};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::LangItem::{OptionNone, OptionSome};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+
+use super::OPTION_MAP_OR_NONE;
+use super::RESULT_MAP_OR_INTO_OPTION;
+
++// The expression inside a closure may or may not have surrounding braces
++// which causes problems when generating a suggestion.
++fn reduce_unit_expression<'a>(
++ cx: &LateContext<'_>,
++ expr: &'a hir::Expr<'_>,
++) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
++ match expr.kind {
++ hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
++ hir::ExprKind::Block(block, _) => {
++ match (block.stmts, block.expr) {
++ (&[], Some(inner_expr)) => {
++ // If block only contains an expression,
++ // reduce `|x| { x + 1 }` to `|x| x + 1`
++ reduce_unit_expression(cx, inner_expr)
++ },
++ _ => None,
++ }
++ },
++ _ => None,
++ }
++}
++
+/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ def_arg: &'tcx hir::Expr<'_>,
+ map_arg: &'tcx hir::Expr<'_>,
+) {
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+
+ // 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;
+ }
+
- if !default_arg_is_none {
- // nothing to lint!
- return;
- }
++ let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
++ is_lang_ctor(cx, qpath, OptionNone)
++ } else {
++ return;
++ };
+
- let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
- is_lang_ctor(cx, qpath, OptionSome)
- } else {
- false
- };
++ if !default_arg_is_none {
++ // nothing to lint!
++ return;
++ }
+
- if is_option {
- let self_snippet = snippet(cx, recv.span, "..");
- let func_snippet = snippet(cx, map_arg.span, "..");
- let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
- `and_then(..)` instead";
- (
- OPTION_MAP_OR_NONE,
- msg,
- "try using `and_then` instead",
- format!("{0}.and_then({1})", self_snippet, func_snippet),
- )
- } else if f_arg_is_some {
- let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
- `ok()` instead";
- let self_snippet = snippet(cx, recv.span, "..");
- (
- RESULT_MAP_OR_INTO_OPTION,
- msg,
- "try using `ok` instead",
- format!("{0}.ok()", self_snippet),
- )
- } else {
- // nothing to lint!
- return;
++ let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
++ is_lang_ctor(cx, qpath, OptionSome)
++ } else {
++ false
++ };
++
++ if is_option {
++ let self_snippet = snippet(cx, recv.span, "..");
++ if_chain! {
++ if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
++ let arg_snippet = snippet(cx, span, "..");
++ let body = cx.tcx.hir().body(id);
++ if let Some((func, arg_char)) = reduce_unit_expression(cx, &body.value);
++ if arg_char.len() == 1;
++ if let hir::ExprKind::Path(ref qpath) = func.kind;
++ if let Some(segment) = single_segment_path(qpath);
++ if segment.ident.name == sym::Some;
++ then {
++ let func_snippet = snippet(cx, arg_char[0].span, "..");
++ let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
++ `map(..)` instead";
++ return span_lint_and_sugg(
++ cx,
++ OPTION_MAP_OR_NONE,
++ expr.span,
++ msg,
++ "try using `map` instead",
++ format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet),
++ Applicability::MachineApplicable,
++ );
++ }
+
- };
+ }
- span_lint_and_sugg(
- cx,
- lint_name,
- expr.span,
- msg,
- instead,
- hint,
- Applicability::MachineApplicable,
- );
+
++ let func_snippet = snippet(cx, map_arg.span, "..");
++ let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
++ `and_then(..)` instead";
++ return span_lint_and_sugg(
++ cx,
++ OPTION_MAP_OR_NONE,
++ expr.span,
++ msg,
++ "try using `and_then` instead",
++ format!("{0}.and_then({1})", self_snippet, func_snippet),
++ Applicability::MachineApplicable,
++ );
++ } 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, recv.span, "..");
++ return span_lint_and_sugg(
++ cx,
++ RESULT_MAP_OR_INTO_OPTION,
++ expr.span,
++ msg,
++ "try using `ok` instead",
++ format!("{0}.ok()", self_snippet),
++ Applicability::MachineApplicable,
++ );
++ }
+}
--- /dev/null
- use clippy_utils::eager_or_lazy::is_lazyness_candidate;
- use clippy_utils::is_trait_item;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- use clippy_utils::ty::implements_trait;
- use clippy_utils::ty::{is_type_diagnostic_item, match_type};
- use clippy_utils::{contains_return, last_path_segment, paths};
++use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
+use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite};
- use rustc_hir::{BlockCheckMode, UnsafeSource};
++use clippy_utils::ty::{implements_trait, match_type};
++use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
- use rustc_middle::ty;
+use rustc_lint::LateContext;
- if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &arg.kind {
- if path.ident.name == sym::len {
- let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
-
- match ty.kind() {
- ty::Slice(_) | ty::Array(_, _) | ty::Str => return,
- _ => (),
- }
-
- if is_type_diagnostic_item(cx, ty, sym::Vec) {
- return;
- }
- }
- }
-
+use rustc_span::source_map::Span;
+use rustc_span::symbol::{kw, sym};
+use std::borrow::Cow;
+
+use super::OR_FUN_CALL;
+
+/// Checks for the `OR_FUN_CALL` lint.
+#[allow(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ method_span: Span,
+ name: &str,
+ args: &'tcx [hir::Expr<'_>],
+) {
+ /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`.
+ fn check_unwrap_or_default(
+ cx: &LateContext<'_>,
+ name: &str,
+ fun: &hir::Expr<'_>,
+ self_expr: &hir::Expr<'_>,
+ arg: &hir::Expr<'_>,
+ or_has_args: bool,
+ span: Span,
+ ) -> bool {
+ let is_default_default = || is_trait_item(cx, fun, sym::Default);
+
+ let implements_default = |arg, default_trait_id| {
+ let arg_ty = cx.typeck_results().expr_ty(arg);
+ implements_trait(cx, arg_ty, default_trait_id, &[])
+ };
+
+ if_chain! {
+ if !or_has_args;
+ if name == "unwrap_or";
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
+ let path = last_path_segment(qpath).ident.name;
+ // needs to target Default::default in particular or be *::new and have a Default impl
+ // available
+ if (matches!(path, kw::Default) && is_default_default())
+ || (matches!(path, sym::new) && implements_default(arg, default_trait_id));
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ span,
+ &format!("use of `{}` followed by a call to `{}`", name, path),
+ "try this",
+ format!(
+ "{}.unwrap_or_default()",
+ snippet_with_applicability(cx, self_expr.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+
+ true
+ } else {
+ false
+ }
+ }
+ }
+
+ /// Checks for `*or(foo())`.
+ #[allow(clippy::too_many_arguments)]
+ fn check_general_case<'tcx>(
+ cx: &LateContext<'tcx>,
+ name: &str,
+ method_span: Span,
+ self_expr: &hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ span: Span,
+ // None if lambda is required
+ fun_span: Option<Span>,
+ ) {
+ // (path, fn_has_argument, methods, suffix)
+ static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [
+ (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"),
+ (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"),
+ (&paths::RESULT, true, &["or", "unwrap_or"], "else"),
+ ];
+
- if is_lazyness_candidate(cx, arg);
+ if_chain! {
+ if KNOW_TYPES.iter().any(|k| k.2.contains(&name));
+
- if args.len() == 2 {
- match args[1].kind {
++ if switch_to_lazy_eval(cx, arg);
+ if !contains_return(arg);
+
+ let self_ty = cx.typeck_results().expr_ty(self_expr);
+
+ if let Some(&(_, fn_has_arguments, poss, suffix)) =
+ KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0));
+
+ if poss.contains(&name);
+
+ then {
+ let macro_expanded_snipped;
+ let sugg: Cow<'_, str> = {
+ let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
+ (false, Some(fun_span)) => (fun_span, false),
+ _ => (arg.span, true),
+ };
+ let snippet = {
+ let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, "..");
+ if not_macro_argument_snippet == "vec![]" {
+ macro_expanded_snipped = snippet(cx, snippet_span, "..");
+ match macro_expanded_snipped.strip_prefix("$crate::vec::") {
+ Some(stripped) => Cow::from(stripped),
+ None => macro_expanded_snipped
+ }
+ }
+ else {
+ not_macro_argument_snippet
+ }
+ };
+
+ if use_lambda {
+ let l_arg = if fn_has_arguments { "_" } else { "" };
+ format!("|{}| {}", l_arg, snippet).into()
+ } else {
+ snippet
+ }
+ };
+ let span_replace_word = method_span.with_hi(span.hi());
+ span_lint_and_sugg(
+ cx,
+ OR_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{}` followed by a function call", name),
+ "try this",
+ format!("{}_{}({})", name, suffix, sugg),
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+ }
+
- if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) {
++ if let [self_arg, arg] = args {
++ let inner_arg = if let hir::ExprKind::Block(
++ hir::Block {
++ stmts: [],
++ expr: Some(expr),
++ ..
++ },
++ _,
++ ) = arg.kind
++ {
++ expr
++ } else {
++ arg
++ };
++ match inner_arg.kind {
+ hir::ExprKind::Call(fun, or_args) => {
+ let or_has_args = !or_args.is_empty();
- check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, fun_span);
++ if !check_unwrap_or_default(cx, name, fun, self_arg, arg, or_has_args, expr.span) {
+ let fun_span = if or_has_args { None } else { Some(fun.span) };
- check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None);
- },
- hir::ExprKind::Block(block, _)
- if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) =>
- {
- if let Some(block_expr) = block.expr {
- if let hir::ExprKind::MethodCall(..) = block_expr.kind {
- check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None);
- }
- }
++ check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span);
+ }
+ },
+ hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => {
++ check_general_case(cx, name, method_span, self_arg, arg, expr.span, None);
+ },
+ _ => (),
+ }
+ }
+}
--- /dev/null
- } else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(closure_arg.pat).kind {
- let name = &*ident.name.as_str();
- Some(search_snippet.replace(&format!("*{}", name), name))
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_applicability};
++use clippy_utils::sugg::deref_closure_args;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{is_trait_method, strip_pat_refs};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::PatKind;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+
+use super::SEARCH_IS_SOME;
+
+/// lint searching an Iterator followed by `is_some()`
+/// or calling `find()` on a string followed by `is_some()` or `is_none()`
+#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'_>,
+ expr: &'tcx hir::Expr<'_>,
+ search_method: &str,
+ is_some: bool,
+ search_recv: &hir::Expr<'_>,
+ search_arg: &'tcx hir::Expr<'_>,
+ is_some_recv: &hir::Expr<'_>,
+ method_span: Span,
+) {
+ let option_check_method = if is_some { "is_some" } else { "is_none" };
+ // lint if caller of search is an Iterator
+ if is_trait_method(cx, is_some_recv, sym::Iterator) {
+ let msg = format!(
+ "called `{}()` after searching an `Iterator` with `{}`",
+ option_check_method, search_method
+ );
+ let search_snippet = snippet(cx, search_arg.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 mut applicability = Applicability::MachineApplicable;
+ let any_search_snippet = if_chain! {
+ if search_method == "find";
+ if let hir::ExprKind::Closure(_, _, body_id, ..) = search_arg.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))
- Applicability::MachineApplicable,
++ } else if let PatKind::Binding(..) = strip_pat_refs(closure_arg.pat).kind {
++ // `find()` provides a reference to the item, but `any` does not,
++ // so we should fix item usages for suggestion
++ if let Some(closure_sugg) = deref_closure_args(cx, search_arg) {
++ applicability = closure_sugg.applicability;
++ Some(closure_sugg.suggestion)
++ } else {
++ Some(search_snippet.to_string())
++ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ };
+ // add note if not multi-line
+ if is_some {
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ method_span.with_hi(expr.span.hi()),
+ &msg,
+ "use `any()` instead",
+ format!(
+ "any({})",
+ any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
+ ),
- Applicability::MachineApplicable,
++ applicability,
+ );
+ } else {
+ let iter = snippet(cx, search_recv.span, "..");
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ expr.span,
+ &msg,
+ "use `!_.any()` instead",
+ format!(
+ "!{}.any({})",
+ iter,
+ any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str)
+ ),
++ applicability,
+ );
+ }
+ } else {
+ let hint = format!(
+ "this is more succinctly expressed by calling `any()`{}",
+ if option_check_method == "is_none" {
+ " with negation"
+ } else {
+ ""
+ }
+ );
+ span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, &hint);
+ }
+ }
+ // lint if `find()` is called by `String` or `&str`
+ else if search_method == "find" {
+ let is_string_or_str_slice = |e| {
+ let self_ty = cx.typeck_results().expr_ty(e).peel_refs();
+ if is_type_diagnostic_item(cx, self_ty, sym::String) {
+ true
+ } else {
+ *self_ty.kind() == ty::Str
+ }
+ };
+ if_chain! {
+ if is_string_or_str_slice(search_recv);
+ if is_string_or_str_slice(search_arg);
+ then {
+ let msg = format!("called `{}()` after calling `find()` on a string", option_check_method);
+ match option_check_method {
+ "is_some" => {
+ let mut applicability = Applicability::MachineApplicable;
+ let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ method_span.with_hi(expr.span.hi()),
+ &msg,
+ "use `contains()` instead",
+ format!("contains({})", find_arg),
+ applicability,
+ );
+ },
+ "is_none" => {
+ let string = snippet(cx, search_recv.span, "..");
+ let mut applicability = Applicability::MachineApplicable;
+ let find_arg = snippet_with_applicability(cx, search_arg.span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ SEARCH_IS_SOME,
+ expr.span,
+ &msg,
+ "use `!_.contains()` instead",
+ format!("!{}.contains({})", string, find_arg),
+ applicability,
+ );
+ },
+ _ => (),
+ }
+ }
+ }
+ }
+}
--- /dev/null
- const PATTERN_METHODS: [(&str, usize); 19] = [
+use super::utils::get_hint_if_single_char_arg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::symbol::Symbol;
+
+use super::SINGLE_CHAR_PATTERN;
+
++const PATTERN_METHODS: [(&str, usize); 24] = [
+ ("contains", 1),
+ ("starts_with", 1),
+ ("ends_with", 1),
+ ("find", 1),
+ ("rfind", 1),
+ ("split", 1),
++ ("split_inclusive", 1),
+ ("rsplit", 1),
+ ("split_terminator", 1),
+ ("rsplit_terminator", 1),
+ ("splitn", 2),
+ ("rsplitn", 2),
++ ("split_once", 1),
++ ("rsplit_once", 1),
+ ("matches", 1),
+ ("rmatches", 1),
+ ("match_indices", 1),
+ ("rmatch_indices", 1),
+ ("strip_prefix", 1),
+ ("strip_suffix", 1),
+ ("trim_start_matches", 1),
+ ("trim_end_matches", 1),
++ ("replace", 1),
++ ("replacen", 1),
+];
+
+/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
+pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) {
+ for &(method, pos) in &PATTERN_METHODS {
+ if_chain! {
+ if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(&args[0]).kind();
+ if *ty.kind() == ty::Str;
+ if method_name.as_str() == method && args.len() > pos;
+ let arg = &args[pos];
+ let mut applicability = Applicability::MachineApplicable;
+ if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability);
+ then {
+ span_lint_and_sugg(
+ cx,
+ SINGLE_CHAR_PATTERN,
+ arg.span,
+ "single-character string constant used as pattern",
+ "try using a `char` instead",
+ hint,
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::consts::{constant, Constant};
++use clippy_utils::diagnostics::span_lint_and_sugg;
++use clippy_utils::source::snippet_with_context;
++use clippy_utils::{is_diag_item_method, match_def_path, paths};
++use if_chain::if_chain;
++use rustc_errors::Applicability;
++use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
++use rustc_lint::LateContext;
++use rustc_middle::ty::{self, adjustment::Adjust};
++use rustc_span::{symbol::sym, Span, SyntaxContext};
++
++use super::MANUAL_SPLIT_ONCE;
++
++pub(super) fn check_manual_split_once(
++ cx: &LateContext<'_>,
++ method_name: &str,
++ expr: &Expr<'_>,
++ self_arg: &Expr<'_>,
++ pat_arg: &Expr<'_>,
++) {
++ if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
++ return;
++ }
++
++ let ctxt = expr.span.ctxt();
++ let (method_name, msg, reverse) = if method_name == "splitn" {
++ ("split_once", "manual implementation of `split_once`", false)
++ } else {
++ ("rsplit_once", "manual implementation of `rsplit_once`", true)
++ };
++ let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), reverse) {
++ Some(x) => x,
++ None => return,
++ };
++
++ let mut app = Applicability::MachineApplicable;
++ let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
++ let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
++
++ let sugg = match usage.kind {
++ IterUsageKind::NextTuple => {
++ format!("{}.{}({})", self_snip, method_name, pat_snip)
++ },
++ IterUsageKind::RNextTuple => format!("{}.{}({}).map(|(x, y)| (y, x))", self_snip, method_name, pat_snip),
++ IterUsageKind::Next | IterUsageKind::Second => {
++ let self_deref = {
++ let adjust = cx.typeck_results().expr_adjustments(self_arg);
++ if adjust.is_empty() {
++ String::new()
++ } else if cx.typeck_results().expr_ty(self_arg).is_box()
++ || adjust
++ .iter()
++ .any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
++ {
++ format!("&{}", "*".repeat(adjust.len() - 1))
++ } else {
++ "*".repeat(adjust.len() - 2)
++ }
++ };
++ if matches!(usage.kind, IterUsageKind::Next) {
++ match usage.unwrap_kind {
++ Some(UnwrapKind::Unwrap) => {
++ if reverse {
++ format!("{}.{}({}).unwrap().0", self_snip, method_name, pat_snip)
++ } else {
++ format!(
++ "{}.{}({}).map_or({}{}, |x| x.0)",
++ self_snip, method_name, pat_snip, self_deref, &self_snip
++ )
++ }
++ },
++ Some(UnwrapKind::QuestionMark) => {
++ format!(
++ "{}.{}({}).map_or({}{}, |x| x.0)",
++ self_snip, method_name, pat_snip, self_deref, &self_snip
++ )
++ },
++ None => {
++ format!(
++ "Some({}.{}({}).map_or({}{}, |x| x.0))",
++ &self_snip, method_name, pat_snip, self_deref, &self_snip
++ )
++ },
++ }
++ } else {
++ match usage.unwrap_kind {
++ Some(UnwrapKind::Unwrap) => {
++ if reverse {
++ // In this case, no better suggestion is offered.
++ return;
++ }
++ format!("{}.{}({}).unwrap().1", self_snip, method_name, pat_snip)
++ },
++ Some(UnwrapKind::QuestionMark) => {
++ format!("{}.{}({})?.1", self_snip, method_name, pat_snip)
++ },
++ None => {
++ format!("{}.{}({}).map(|x| x.1)", self_snip, method_name, pat_snip)
++ },
++ }
++ }
++ },
++ };
++
++ span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
++}
++
++enum IterUsageKind {
++ Next,
++ Second,
++ NextTuple,
++ RNextTuple,
++}
++
++enum UnwrapKind {
++ Unwrap,
++ QuestionMark,
++}
++
++struct IterUsage {
++ kind: IterUsageKind,
++ unwrap_kind: Option<UnwrapKind>,
++ span: Span,
++}
++
++#[allow(clippy::too_many_lines)]
++fn parse_iter_usage(
++ cx: &LateContext<'tcx>,
++ ctxt: SyntaxContext,
++ mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
++ reverse: bool,
++) -> Option<IterUsage> {
++ let (kind, span) = match iter.next() {
++ Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
++ let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind {
++ (name, args)
++ } else {
++ return None;
++ };
++ let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
++ let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
++
++ match (&*name.ident.as_str(), args) {
++ ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
++ if reverse {
++ (IterUsageKind::Second, e.span)
++ } else {
++ (IterUsageKind::Next, e.span)
++ }
++ },
++ ("next_tuple", []) => {
++ return if_chain! {
++ if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
++ if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
++ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did);
++ if let ty::Tuple(subs) = subs.type_at(0).kind();
++ if subs.len() == 2;
++ then {
++ Some(IterUsage {
++ kind: if reverse { IterUsageKind::RNextTuple } else { IterUsageKind::NextTuple },
++ span: e.span,
++ unwrap_kind: None
++ })
++ } else {
++ None
++ }
++ };
++ },
++ ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
++ if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
++ let span = if name.ident.as_str() == "nth" {
++ e.span
++ } else {
++ if_chain! {
++ if let Some((_, Node::Expr(next_expr))) = iter.next();
++ if let ExprKind::MethodCall(next_name, _, [_], _) = next_expr.kind;
++ if next_name.ident.name == sym::next;
++ if next_expr.span.ctxt() == ctxt;
++ if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
++ if cx.tcx.trait_of_item(next_id) == Some(iter_id);
++ then {
++ next_expr.span
++ } else {
++ return None;
++ }
++ }
++ };
++ match if reverse { idx ^ 1 } else { idx } {
++ 0 => (IterUsageKind::Next, span),
++ 1 => (IterUsageKind::Second, span),
++ _ => return None,
++ }
++ } else {
++ return None;
++ }
++ },
++ _ => return None,
++ }
++ },
++ _ => return None,
++ };
++
++ let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
++ match e.kind {
++ ExprKind::Call(
++ Expr {
++ kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)),
++ ..
++ },
++ _,
++ ) => {
++ let parent_span = e.span.parent_callsite().unwrap();
++ if parent_span.ctxt() == ctxt {
++ (Some(UnwrapKind::QuestionMark), parent_span)
++ } else {
++ (None, span)
++ }
++ },
++ _ if e.span.ctxt() != ctxt => (None, span),
++ ExprKind::MethodCall(name, _, [_], _)
++ if name.ident.name == sym::unwrap
++ && cx
++ .typeck_results()
++ .type_dependent_def_id(e.hir_id)
++ .map_or(false, |id| is_diag_item_method(cx, id, sym::Option)) =>
++ {
++ (Some(UnwrapKind::Unwrap), e.span)
++ },
++ _ => (None, span),
++ }
++ } else {
++ (None, span)
++ };
++
++ Some(IterUsage {
++ kind,
++ unwrap_kind,
++ span,
++ })
++}
++
++use super::NEEDLESS_SPLITN;
++
++pub(super) fn check_needless_splitn(
++ cx: &LateContext<'_>,
++ method_name: &str,
++ expr: &Expr<'_>,
++ self_arg: &Expr<'_>,
++ pat_arg: &Expr<'_>,
++ count: u128,
++) {
++ if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
++ return;
++ }
++ let ctxt = expr.span.ctxt();
++ let mut app = Applicability::MachineApplicable;
++ let (reverse, message) = if method_name == "splitn" {
++ (false, "unnecessary use of `splitn`")
++ } else {
++ (true, "unnecessary use of `rsplitn`")
++ };
++ if_chain! {
++ if count >= 2;
++ if check_iter(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id), count);
++ then {
++ span_lint_and_sugg(
++ cx,
++ NEEDLESS_SPLITN,
++ expr.span,
++ message,
++ "try this",
++ format!(
++ "{}.{}({})",
++ snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0,
++ if reverse {"rsplit"} else {"split"},
++ snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0
++ ),
++ app,
++ );
++ }
++ }
++}
++
++fn check_iter(
++ cx: &LateContext<'tcx>,
++ ctxt: SyntaxContext,
++ mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
++ count: u128,
++) -> bool {
++ match iter.next() {
++ Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
++ let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind {
++ (name, args)
++ } else {
++ return false;
++ };
++ if_chain! {
++ if let Some(did) = cx.typeck_results().type_dependent_def_id(e.hir_id);
++ if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
++ then {
++ match (&*name.ident.as_str(), args) {
++ ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
++ return true;
++ },
++ ("next_tuple", []) if count > 2 => {
++ return true;
++ },
++ ("nth", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
++ if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
++ if count > idx + 1 {
++ return true;
++ }
++ }
++ },
++ _ => return false,
++ }
++ }
++ }
++ },
++ _ => return false,
++ };
++ false
++}
--- /dev/null
- if eager_or_lazy::is_eagerness_candidate(cx, body_expr) {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{eager_or_lazy, usage};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_span::sym;
+
+use super::UNNECESSARY_LAZY_EVALUATIONS;
+
+/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
+/// replaced with `<fn>(return value of simple closure)`
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx hir::Expr<'_>,
+ recv: &'tcx hir::Expr<'_>,
+ arg: &'tcx hir::Expr<'_>,
+ simplify_using: &str,
+) {
+ let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
+ let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+
+ if is_option || is_result {
+ if let hir::ExprKind::Closure(_, _, eid, _, _) = arg.kind {
+ let body = cx.tcx.hir().body(eid);
+ let body_expr = &body.value;
+
+ if usage::BindingUsageFinder::are_params_used(cx, body) {
+ return;
+ }
+
++ if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
+ let msg = if is_option {
+ "unnecessary closure used to substitute value for `Option::None`"
+ } else {
+ "unnecessary closure used to substitute value for `Result::Err`"
+ };
+ let applicability = if body
+ .params
+ .iter()
+ // bindings are checked to be unused above
+ .all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild))
+ {
+ Applicability::MachineApplicable
+ } else {
+ // replacing the lambda may break type inference
+ Applicability::MaybeIncorrect
+ };
+
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_LAZY_EVALUATIONS,
+ expr.span,
+ msg,
+ &format!("use `{}` instead", simplify_using),
+ format!(
+ "{0}.{1}({2})",
+ snippet(cx, recv.span, ".."),
+ simplify_using,
+ snippet(cx, body_expr.span, ".."),
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- let hint = format!("'{}'", if ch == "'" { "\\'" } else { ch });
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::sym;
+
+pub(super) 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),
+ ty::Array(_, size) => size.try_eval_usize(cx.tcx, cx.param_env).is_some(),
+ ty::Ref(_, inner, _) => may_slice(cx, inner),
+ _ => false,
+ }
+ }
+
+ if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind {
+ if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(self_arg)) {
+ Some(self_arg)
+ } 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,
+ }
+ }
+}
+
+pub(super) fn get_hint_if_single_char_arg(
+ cx: &LateContext<'_>,
+ arg: &hir::Expr<'_>,
+ applicability: &mut Applicability,
+) -> Option<String> {
+ if_chain! {
+ if let hir::ExprKind::Lit(lit) = &arg.kind;
+ if let ast::LitKind::Str(r, style) = lit.node;
+ let string = r.as_str();
+ if string.chars().count() == 1;
+ then {
+ let snip = snippet_with_applicability(cx, arg.span, &string, applicability);
+ let ch = if let ast::StrStyle::Raw(nhash) = style {
+ let nhash = nhash as usize;
+ // for raw string: r##"a"##
+ &snip[(nhash + 2)..(snip.len() - 1 - nhash)]
+ } else {
+ // for regular string: "a"
+ &snip[1..(snip.len() - 1)]
+ };
++
++ let hint = format!("'{}'", match ch {
++ "'" => "\\'" ,
++ r"\" => "\\\\",
++ _ => ch,
++ });
++
+ Some(hint)
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{match_def_path, match_trait_method, paths};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+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.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// min(0, max(100, x))
+ /// ```
+ /// or
+ /// ```ignore
+ /// x.max(100).min(0)
+ /// ```
+ /// It will always be equal to `0`. Probably the author meant to clamp the value
+ /// between 0 and 100, but has erroneously swapped `min` and `max`.
++ #[clippy::version = "pre 1.29.0"]
+ pub MIN_MAX,
+ correctness,
+ "`min(_, max(_, _))` (or vice versa) with bounds clamping the result to a constant"
+}
+
+declare_lint_pass!(MinMaxPass => [MIN_MAX]);
+
+impl<'tcx> LateLintPass<'tcx> for MinMaxPass {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) {
+ if let Some((inner_max, inner_c, ie)) = min_max(cx, oe) {
+ if outer_max == inner_max {
+ return;
+ }
+ match (
+ outer_max,
+ Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c),
+ ) {
+ (_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (),
+ _ => {
+ span_lint(
+ cx,
+ MIN_MAX,
+ expr.span,
+ "this `min`/`max` combination leads to constant result",
+ );
+ },
+ }
+ }
+ }
+ }
+}
+
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
+enum MinMax {
+ Min,
+ Max,
+}
+
+fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
+ match expr.kind {
+ ExprKind::Call(path, args) => {
+ if let ExprKind::Path(ref qpath) = path.kind {
+ cx.typeck_results()
+ .qpath_res(qpath, path.hir_id)
+ .opt_def_id()
+ .and_then(|def_id| {
+ if match_def_path(cx, def_id, &paths::CMP_MIN) {
+ fetch_const(cx, args, MinMax::Min)
+ } else if match_def_path(cx, def_id, &paths::CMP_MAX) {
+ fetch_const(cx, args, MinMax::Max)
+ } else {
+ None
+ }
+ })
+ } else {
+ None
+ }
+ },
+ ExprKind::MethodCall(path, _, args, _) => {
+ if_chain! {
+ if let [obj, _] = args;
+ if cx.typeck_results().expr_ty(obj).is_floating_point() || match_trait_method(cx, expr, &paths::ORD);
+ then {
+ if path.ident.name == sym!(max) {
+ fetch_const(cx, args, MinMax::Max)
+ } else if path.ident.name == sym!(min) {
+ fetch_const(cx, args, MinMax::Min)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+ },
+ _ => None,
+ }
+}
+
+fn fetch_const<'a>(cx: &LateContext<'_>, args: &'a [Expr<'a>], m: MinMax) -> Option<(MinMax, Constant, &'a Expr<'a>)> {
+ if args.len() != 2 {
+ return None;
+ }
+ constant_simple(cx, cx.typeck_results(), &args[0]).map_or_else(
+ || constant_simple(cx, cx.typeck_results(), &args[1]).map(|c| (m, c, &args[0])),
+ |c| {
+ if constant_simple(cx, cx.typeck_results(), &args[1]).is_none() {
+ // otherwise ignore
+ Some((m, c, &args[1]))
+ } else {
+ None
+ }
+ },
+ )
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then};
+use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::ty::implements_trait;
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ self as hir, def, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, Stmt,
+ StmtKind, TyKind, UnOp,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::hygiene::DesugaringKind;
+use rustc_span::source_map::{ExpnKind, Span};
+use rustc_span::symbol::sym;
+
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{
+ expr_path_res, get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats,
+ last_path_segment, match_any_def_paths, paths, unsext, SpanlessEq,
+};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for function arguments and let bindings denoted as
+ /// `ref`.
+ ///
+ /// ### Why is this bad?
+ /// The `ref` declaration makes the function take an owned
+ /// value, but turns the argument into a reference (which means that the value
+ /// is destroyed when exiting the function). This adds not much value: either
+ /// take a reference type, or take an owned value and create references in the
+ /// body.
+ ///
+ /// For let bindings, `let x = &foo;` is preferred over `let ref x = foo`. The
+ /// type of `x` is more obvious with the former.
+ ///
+ /// ### Known problems
+ /// If the argument is dereferenced within the function,
+ /// removing the `ref` will lead to errors. This can be fixed by removing the
+ /// dereferences, e.g., changing `*x` to `x` within the function.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// fn foo(ref x: u8) -> bool {
+ /// true
+ /// }
+ ///
+ /// // Good
+ /// fn foo(x: &u8) -> bool {
+ /// true
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1.0;
+ ///
+ /// // Bad
+ /// if x == f32::NAN { }
+ ///
+ /// // Good
+ /// if x.is_nan() { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1.2331f64;
+ /// let y = 1.2332f64;
+ ///
+ /// // Bad
+ /// if y == 1.23f64 { }
+ /// if y != x {} // where both are floats
+ ///
+ /// // Good
+ /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
+ /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
+ /// // let error_margin = std::f64::EPSILON;
+ /// if (y - 1.23f64).abs() < error_margin { }
+ /// if (y - x).abs() > error_margin { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_CMP,
+ pedantic,
+ "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.
+ ///
+ /// ### 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 {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub CMP_OWNED,
+ perf,
+ "creating owned instances for comparing with others, e.g., `x == \"foo\".to_string()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for getting the remainder of a division by one or minus
+ /// one.
+ ///
+ /// ### Why is this bad?
+ /// The result for a divisor of one can only ever be zero; for
+ /// minus one it can cause panic/overflow (if the left operand is the minimal value of
+ /// the respective integer type) or results in zero. No one will write such code
+ /// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
+ /// contest, it's probably a bad idea. Use something more underhanded.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// let a = x % 1;
+ /// let a = x % -1;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MODULO_ONE,
+ correctness,
+ "taking a number modulo +/-1, which can either panic/overflow or always returns 0"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the use of bindings with a single leading
+ /// underscore.
+ ///
+ /// ### Why is this bad?
+ /// A single leading underscore is usually used to indicate
+ /// that a binding will not be used. Using such a binding breaks this
+ /// expectation.
+ ///
+ /// ### Known problems
+ /// The lint does not work properly with desugaring and
+ /// macro, it has been allowed in the mean time.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _x = 0;
+ /// let y = _x + 1; // Here we are using `_x`, even though it has a leading
+ /// // underscore. We should rename `_x` to `x`
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// f() && g(); // We should write `if f() { g(); }`.
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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`}.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let a = 0 as *const u32;
+ ///
+ /// // Good
+ /// let a = std::ptr::null::<u32>();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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).
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x: f64 = 1.0;
+ /// const ONE: f64 = 1.00;
+ ///
+ /// // Bad
+ /// if x == ONE { } // where both are floats
+ ///
+ /// // Good
+ /// let error_margin = f64::EPSILON; // Use an epsilon for comparison
+ /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
+ /// // let error_margin = std::f64::EPSILON;
+ /// if (x - ONE).abs() < error_margin { }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub FLOAT_CMP_CONST,
+ restriction,
+ "using `==` or `!=` on float constants instead of comparing difference with an epsilon"
+}
+
+declare_lint_pass!(MiscLints => [
+ TOPLEVEL_REF_ARG,
+ CMP_NAN,
+ FLOAT_CMP,
+ CMP_OWNED,
+ MODULO_ONE,
+ USED_UNDERSCORE_BINDING,
+ SHORT_CIRCUIT_STATEMENT,
+ ZERO_PTR,
+ FLOAT_CMP_CONST
+]);
+
+impl<'tcx> LateLintPass<'tcx> for MiscLints {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ k: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ _: HirId,
+ ) {
+ if let FnKind::Closure = k {
+ // Does not apply to closures
+ return;
+ }
+ if in_external_macro(cx.tcx.sess, span) {
+ return;
+ }
+ for arg in iter_input_pats(decl, body) {
+ if let PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..) = arg.pat.kind {
+ span_lint(
+ cx,
+ TOPLEVEL_REF_ARG,
+ arg.pat.span,
+ "`ref` directly on a function argument is ignored. \
+ Consider using a reference type instead",
+ );
+ }
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if_chain! {
+ if !in_external_macro(cx.tcx.sess, stmt.span);
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(an, .., name, None) = local.pat.kind;
+ if let Some(init) = local.init;
+ if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut;
+ then {
+ // use the macro callsite when the init span (but not the whole local span)
+ // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
+ let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() {
+ Sugg::hir_with_macro_callsite(cx, init, "..")
+ } else {
+ Sugg::hir(cx, init, "..")
+ };
+ let (mutopt, initref) = if an == BindingAnnotation::RefMut {
+ ("mut ", sugg_init.mut_addr())
+ } else {
+ ("", sugg_init.addr())
+ };
+ let tyopt = if let Some(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(expr) = stmt.kind;
+ if let ExprKind::Binary(ref binop, a, b) = expr.kind;
+ if binop.node == BinOpKind::And || binop.node == BinOpKind::Or;
+ if let Some(sugg) = Sugg::hir_opt(cx, a);
+ then {
+ span_lint_hir_and_then(
+ cx,
+ SHORT_CIRCUIT_STATEMENT,
+ expr.hir_id,
+ 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(e, ty) => {
+ check_cast(cx, expr.span, e, ty);
+ return;
+ },
+ ExprKind::Binary(ref cmp, left, right) => {
+ check_binary(cx, expr, cmp, left, right);
+ return;
+ },
+ _ => {},
+ }
+ if in_attributes_expansion(expr) || expr.span.is_desugaring(DesugaringKind::Await) {
+ // Don't lint things expanded by #[derive(...)], etc or `await` desugaring
+ return;
+ }
+ let binding = match expr.kind {
+ ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
+ let binding = last_path_segment(qpath).ident.as_str();
+ if binding.starts_with('_') &&
+ !binding.starts_with("__") &&
+ binding != "_result" && // FIXME: #944
+ is_used(cx, expr) &&
+ // don't lint if the declaration is in a macro
+ non_macro_local(cx, cx.qpath_res(qpath, expr.hir_id))
+ {
+ Some(binding)
+ } else {
+ None
+ }
+ },
+ ExprKind::Field(_, ident) => {
+ let name = ident.as_str();
+ if name.starts_with('_') && !name.starts_with("__") {
+ Some(name)
+ } else {
+ None
+ }
+ },
+ _ => None,
+ };
+ if let Some(binding) = binding {
+ span_lint(
+ cx,
+ USED_UNDERSCORE_BINDING,
+ expr.span,
+ &format!(
+ "used binding `{}` which is prefixed with an underscore. A leading \
+ underscore signals that a binding will not be used",
+ binding
+ ),
+ );
+ }
+ }
+}
+
+fn get_lint_and_message(
+ is_comparing_constants: bool,
+ is_comparing_arrays: bool,
+) -> (&'static rustc_lint::Lint, &'static str) {
+ if is_comparing_constants {
+ (
+ FLOAT_CMP_CONST,
+ if is_comparing_arrays {
+ "strict comparison of `f32` or `f64` constant arrays"
+ } else {
+ "strict comparison of `f32` or `f64` constant"
+ },
+ )
+ } else {
+ (
+ FLOAT_CMP,
+ if is_comparing_arrays {
+ "strict comparison of `f32` or `f64` arrays"
+ } else {
+ "strict comparison of `f32` or `f64`"
+ },
+ )
+ }
+}
+
+fn check_nan(cx: &LateContext<'_>, expr: &Expr<'_>, cmp_expr: &Expr<'_>) {
+ if_chain! {
+ if !in_constant(cx, cmp_expr.hir_id);
+ if let Some((value, _)) = constant(cx, cx.typeck_results(), expr);
+ if match value {
+ Constant::F32(num) => num.is_nan(),
+ Constant::F64(num) => num.is_nan(),
+ _ => false,
+ };
+ then {
+ span_lint(
+ cx,
+ CMP_NAN,
+ cmp_expr.span,
+ "doomed comparison with `NAN`, use `{f32,f64}::is_nan()` instead",
+ );
+ }
+ }
+}
+
+fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
+ res
+ } else {
+ false
+ }
+}
+
+fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
+ match constant(cx, cx.typeck_results(), expr) {
+ Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
+ Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
+ Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
+ Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
+ Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
+ _ => false,
+ }),
+ _ => false,
+ }
+}
+
+// Return true if `expr` is the result of `signum()` invoked on a float value.
+fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ // The negation of a signum is still a signum
+ if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind {
+ return is_signum(cx, child_expr);
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, _, [ref self_arg, ..], _) = 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, self_arg);
+ }
+ }
+ false
+}
+
+fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind();
+
+ if let ty::Array(arr_ty, _) = value {
+ return matches!(arr_ty.kind(), ty::Float(_));
+ };
+
+ matches!(value, ty::Float(_))
+}
+
+fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
+}
+
+fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) {
+ #[derive(Default)]
+ struct EqImpl {
+ ty_eq_other: bool,
+ other_eq_ty: bool,
+ }
+
+ impl EqImpl {
+ fn is_implemented(&self) -> bool {
+ self.ty_eq_other || self.other_eq_ty
+ }
+ }
+
+ fn symmetric_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: Ty<'tcx>) -> Option<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(.., args, _) if args.len() == 1 => {
+ if_chain!(
+ if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if is_diag_trait_item(cx, expr_def_id, sym::ToString)
+ || is_diag_trait_item(cx, expr_def_id, sym::ToOwned);
+ then {
+ (cx.typeck_results().expr_ty(&args[0]), snippet(cx, args[0].span, ".."))
+ } else {
+ return;
+ }
+ )
+ },
+ ExprKind::Call(path, [arg]) => {
+ if expr_path_res(cx, path)
+ .opt_def_id()
+ .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
+ .is_some()
+ {
+ (cx.typeck_results().expr_ty(arg), snippet(cx, arg.span, ".."))
+ } else {
+ return;
+ }
+ },
+ _ => return,
+ };
+
+ let other_ty = cx.typeck_results().expr_ty(other);
+
+ let without_deref = symmetric_partial_eq(cx, arg_ty, other_ty).unwrap_or_default();
+ let with_deref = arg_ty
+ .builtin_deref(true)
+ .and_then(|tam| symmetric_partial_eq(cx, tam.ty, other_ty))
+ .unwrap_or_default();
+
+ if !with_deref.is_implemented() && !without_deref.is_implemented() {
+ return;
+ }
+
+ let other_gets_derefed = matches!(other.kind, ExprKind::Unary(UnOp::Deref, _));
+
+ let lint_span = if other_gets_derefed {
+ expr.span.to(other.span)
+ } else {
+ expr.span
+ };
+
+ span_lint_and_then(
+ cx,
+ CMP_OWNED,
+ lint_span,
+ "this creates an owned instance just for comparison",
+ |diag| {
+ // This also catches `PartialEq` implementations that call `to_owned`.
+ if other_gets_derefed {
+ diag.span_label(lint_span, "try implementing the comparison without allocating");
+ return;
+ }
+
+ let expr_snip;
+ let eq_impl;
+ if with_deref.is_implemented() {
+ expr_snip = format!("*{}", snip);
+ eq_impl = with_deref;
+ } else {
+ expr_snip = snip.to_string();
+ eq_impl = without_deref;
+ };
+
+ let span;
+ let hint;
+ if (eq_impl.ty_eq_other && left) || (eq_impl.other_eq_ty && !left) {
+ span = expr.span;
+ hint = expr_snip;
+ } else {
+ span = expr.span.to(other.span);
+ if eq_impl.ty_eq_other {
+ hint = format!("{} == {}", expr_snip, snippet(cx, other.span, ".."));
+ } else {
+ hint = format!("{} == {}", snippet(cx, other.span, ".."), expr_snip);
+ }
+ }
+
+ diag.span_suggestion(
+ span,
+ "try",
+ hint,
+ Applicability::MachineApplicable, // snippet
+ );
+ },
+ );
+}
+
+/// Heuristic to see if an expression is used. Should be compatible with
+/// `unused_variables`'s idea
+/// of what it means for an expression to be "used".
+fn is_used(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ get_parent_expr(cx, expr).map_or(true, |parent| match parent.kind {
+ ExprKind::Assign(_, rhs, _) | ExprKind::AssignOp(_, _, rhs) => SpanlessEq::new(cx).eq_expr(rhs, expr),
+ _ => is_used(cx, parent),
+ })
+}
+
+/// Tests whether an expression is in a macro expansion (e.g., something
+/// generated by `#[derive(...)]` or the like).
+fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
+ use rustc_span::hygiene::MacroKind;
+ if expr.span.from_expansion() {
+ let data = expr.span.ctxt().outer_expn_data();
+ matches!(data.kind, ExpnKind::Macro(MacroKind::Attr, _))
+ } else {
+ false
+ }
+}
+
+/// Tests whether `res` is a variable defined outside a macro.
+fn non_macro_local(cx: &LateContext<'_>, res: def::Res) -> bool {
+ if let def::Res::Local(id) = res {
+ !cx.tcx.hir().span(id).from_expansion()
+ } else {
+ false
+ }
+}
+
+fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>) {
+ if_chain! {
+ if let TyKind::Ptr(ref mut_ty) = ty.kind;
+ if let ExprKind::Lit(ref lit) = e.kind;
+ if let LitKind::Int(0, _) = lit.node;
+ if !in_constant(cx, e.hir_id);
+ then {
+ let (msg, sugg_fn) = match mut_ty.mutbl {
+ Mutability::Mut => ("`0 as *mut _` detected", "std::ptr::null_mut"),
+ Mutability::Not => ("`0 as *const _` detected", "std::ptr::null"),
+ };
+
+ let (sugg, appl) = if let TyKind::Infer = mut_ty.ty.kind {
+ (format!("{}()", sugg_fn), Applicability::MachineApplicable)
+ } else if let Some(mut_ty_snip) = snippet_opt(cx, mut_ty.ty.span) {
+ (format!("{}::<{}>()", sugg_fn, mut_ty_snip), Applicability::MachineApplicable)
+ } else {
+ // `MaybeIncorrect` as type inference may not work with the suggested code
+ (format!("{}()", sugg_fn), Applicability::MaybeIncorrect)
+ };
+ span_lint_and_sugg(cx, ZERO_PTR, span, msg, "try", sugg, appl);
+ }
+ }
+}
+
+fn check_binary(
+ cx: &LateContext<'a>,
+ expr: &Expr<'_>,
+ cmp: &rustc_span::source_map::Spanned<rustc_hir::BinOpKind>,
+ left: &'a Expr<'_>,
+ right: &'a Expr<'_>,
+) {
+ let op = cmp.node;
+ if op.is_comparison() {
+ check_nan(cx, left, expr);
+ check_nan(cx, right, expr);
+ check_to_owned(cx, left, right, true);
+ check_to_owned(cx, right, left, false);
+ }
+ if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
+ if is_allowed(cx, left) || is_allowed(cx, right) {
+ return;
+ }
+
+ // Allow comparing the results of signum()
+ if is_signum(cx, left) && is_signum(cx, right) {
+ return;
+ }
+
+ if let Some(name) = get_item_name(cx, expr) {
+ let name = name.as_str();
+ if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
+ return;
+ }
+ }
+ let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
+ let (lint, msg) = get_lint_and_message(
+ is_named_constant(cx, left) || is_named_constant(cx, right),
+ is_comparing_arrays,
+ );
+ span_lint_and_then(cx, lint, expr.span, msg, |diag| {
+ let lhs = Sugg::hir(cx, left, "..");
+ let rhs = Sugg::hir(cx, right, "..");
+
+ if !is_comparing_arrays {
+ diag.span_suggestion(
+ expr.span,
+ "consider comparing them within some margin of error",
+ format!(
+ "({}).abs() {} error_margin",
+ lhs - rhs,
+ if op == BinOpKind::Eq { '<' } else { '>' }
+ ),
+ Applicability::HasPlaceholders, // snippet
+ );
+ }
+ diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
+ });
+ } else if op == BinOpKind::Rem {
+ if is_integer_const(cx, right, 1) {
+ span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
+ }
+
+ if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() {
+ if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) {
+ span_lint(
+ cx,
+ MODULO_ONE,
+ expr.span,
+ "any number modulo -1 will panic/overflow or result in 0",
+ );
+ }
+ };
+ }
+}
--- /dev/null
+mod builtin_type_shadow;
+mod double_neg;
+mod literal_suffix;
+mod mixed_case_hex_literals;
+mod redundant_pattern;
+mod unneeded_field_pattern;
+mod unneeded_wildcard_pattern;
+mod zero_prefixed_literal;
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::source::snippet_opt;
+use rustc_ast::ast::{Expr, ExprKind, Generics, Lit, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
+use rustc_ast::visit::FnKind;
+use rustc_data_structures::fx::FxHashMap;
+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 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.
+ ///
+ /// ### 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 { .. } => {},
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// fn foo(a: i32, _a: i32) {}
+ ///
+ /// // Good
+ /// fn bar(a: i32, _b: i32) {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DUPLICATE_UNDERSCORE_ARGUMENT,
+ style,
+ "function arguments having names which only differ by an underscore"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects expressions of the form `--x`.
+ ///
+ /// ### Why is this bad?
+ /// It can mislead C/C++ programmers to think `x` was
+ /// decremented.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut x = 3;
+ /// --x;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let y = 0x1a9BAcD;
+ ///
+ /// // Good
+ /// let y = 0x1A9BACD;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ /// To enforce unseparated literal suffix style,
+ /// see the `separated_literal_suffix` lint.
+ ///
+ /// ### Why is this bad?
+ /// Suffix style should be consistent.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let y = 123832i32;
+ ///
+ /// // Good
+ /// let y = 123832_i32;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNSEPARATED_LITERAL_SUFFIX,
+ restriction,
+ "literals whose suffix is not separated by an underscore"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if literal suffixes are separated by an underscore.
+ /// To enforce separated literal suffix style,
+ /// see the `unseparated_literal_suffix` lint.
+ ///
+ /// ### Why is this bad?
+ /// Suffix style should be consistent.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let y = 123832_i32;
+ ///
+ /// // Good
+ /// let y = 123832i32;
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub SEPARATED_LITERAL_SUFFIX,
+ restriction,
+ "literals whose suffix is 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.
+ ///
+ /// ### 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`).
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ ///
+ /// ```ignore
+ /// impl<u32> Foo<u32> {
+ /// fn impl_func(&self) -> u32 {
+ /// 42
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let v = Some("abc");
+ ///
+ /// // Bad
+ /// match v {
+ /// Some(x) => (),
+ /// y @ _ => (),
+ /// }
+ ///
+ /// // Good
+ /// match v {
+ /// Some(x) => (),
+ /// y => (),
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct TupleStruct(u32, u32, u32);
+ /// # let t = TupleStruct(1, 2, 3);
+ /// // Bad
+ /// match t {
+ /// TupleStruct(0, .., _) => (),
+ /// _ => (),
+ /// }
+ ///
+ /// // Good
+ /// match t {
+ /// TupleStruct(0, ..) => (),
+ /// _ => (),
+ /// }
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub UNNEEDED_WILDCARD_PATTERN,
+ complexity,
+ "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
+}
+
+declare_lint_pass!(MiscEarlyLints => [
+ UNNEEDED_FIELD_PATTERN,
+ DUPLICATE_UNDERSCORE_ARGUMENT,
+ DOUBLE_NEG,
+ MIXED_CASE_HEX_LITERALS,
+ UNSEPARATED_LITERAL_SUFFIX,
+ SEPARATED_LITERAL_SUFFIX,
+ ZERO_PREFIXED_LITERAL,
+ BUILTIN_TYPE_SHADOW,
+ REDUNDANT_PATTERN,
+ UNNEEDED_WILDCARD_PATTERN,
+]);
+
+impl EarlyLintPass for MiscEarlyLints {
+ fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
+ for param in &gen.params {
+ builtin_type_shadow::check(cx, param);
+ }
+ }
+
+ fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
+ unneeded_field_pattern::check(cx, pat);
+ redundant_pattern::check(cx, pat);
+ unneeded_wildcard_pattern::check(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 let Some(arg_name) = arg_name.strip_prefix('_') {
+ if let Some(correspondence) = registered_names.get(arg_name) {
+ span_lint(
+ cx,
+ DUPLICATE_UNDERSCORE_ARGUMENT,
+ *correspondence,
+ &format!(
+ "`{}` already exists, having another argument having almost the same \
+ name makes code comprehension and documentation more difficult",
+ arg_name
+ ),
+ );
+ }
+ } else {
+ registered_names.insert(arg_name, arg.pat.span);
+ }
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if in_external_macro(cx.sess, expr.span) {
+ return;
+ }
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ MiscEarlyLints::check_lit(cx, lit);
+ }
+ double_neg::check(cx, expr);
+ }
+}
+
+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 => "",
+ };
+ literal_suffix::check(cx, lit, &lit_snip, suffix, "integer");
+ if lit_snip.starts_with("0x") {
+ mixed_case_hex_literals::check(cx, lit, suffix, &lit_snip);
+ } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
+ // nothing to do
+ } else if value != 0 && lit_snip.starts_with('0') {
+ zero_prefixed_literal::check(cx, lit, &lit_snip);
+ }
+ } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
+ let suffix = float_ty.name_str();
+ literal_suffix::check(cx, lit, &lit_snip, suffix, "float");
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::qualify_min_const_fn::is_min_const_fn;
+use clippy_utils::ty::has_drop;
+use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, meets_msrv, msrvs, trait_ref_of_method};
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests the use of `const` in functions and methods where possible.
+ ///
+ /// ### Why is this bad?
+ /// Not having the function const prevents callers of the function from being const as well.
+ ///
+ /// ### Known problems
+ /// Const functions are currently still being worked on, with some features only being available
+ /// on nightly. This lint does not consider all edge cases currently and the suggestions may be
+ /// incorrect if you are using this lint on stable.
+ ///
+ /// Also, the lint only runs one pass over the code. Consider these two non-const functions:
+ ///
+ /// ```rust
+ /// fn a() -> i32 {
+ /// 0
+ /// }
+ /// fn b() -> i32 {
+ /// a()
+ /// }
+ /// ```
+ ///
+ /// When running Clippy, the lint will only suggest to make `a` const, because `b` at this time
+ /// can't be const as it calls a non-const function. Making `a` const and running Clippy again,
+ /// will suggest to make `b` const, too.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Foo {
+ /// # random_number: usize,
+ /// # }
+ /// # impl Foo {
+ /// fn new() -> Self {
+ /// Self { random_number: 42 }
+ /// }
+ /// # }
+ /// ```
+ ///
+ /// Could be a const fn:
+ ///
+ /// ```rust
+ /// # struct Foo {
+ /// # random_number: usize,
+ /// # }
+ /// # impl Foo {
+ /// const fn new() -> Self {
+ /// Self { random_number: 42 }
+ /// }
+ /// # }
+ /// ```
++ #[clippy::version = "1.34.0"]
+ pub MISSING_CONST_FOR_FN,
+ nursery,
+ "Lint functions definitions that could be made `const fn`"
+}
+
+impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]);
+
+pub struct MissingConstForFn {
+ msrv: Option<RustcVersion>,
+}
+
+impl MissingConstForFn {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'_>,
+ kind: FnKind<'_>,
+ _: &FnDecl<'_>,
+ _: &Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::CONST_IF_MATCH) {
+ return;
+ }
+
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+
+ if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ // Building MIR for `fn`s with unsatisfiable preds results in ICE.
+ if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ // Perform some preliminary checks that rule out constness on the Clippy side. This way we
+ // can skip the actual const check and return early.
+ match kind {
+ FnKind::ItemFn(_, generics, header, ..) => {
+ let has_const_generic_params = generics
+ .params
+ .iter()
+ .any(|param| matches!(param.kind, GenericParamKind::Const { .. }));
+
+ if already_const(header) || has_const_generic_params {
+ return;
+ }
+ },
+ FnKind::Method(_, sig, ..) => {
+ if trait_ref_of_method(cx, hir_id).is_some()
+ || already_const(sig.header)
+ || method_accepts_dropable(cx, sig.decl.inputs)
+ {
+ return;
+ }
+ },
+ FnKind::Closure => return,
+ }
+
+ let mir = cx.tcx.optimized_mir(def_id);
+
+ if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) {
+ if cx.tcx.is_const_fn_raw(def_id.to_def_id()) {
+ cx.tcx.sess.span_err(span, &err);
+ }
+ } else {
+ span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`");
+ }
+ }
+ extract_msrv_attr!(LateContext);
+}
+
+/// Returns true if any of the method parameters is a type that implements `Drop`. The method
+/// can't be made const then, because `drop` can't be const-evaluated.
+fn method_accepts_dropable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool {
+ // If any of the params are droppable, return true
+ param_tys.iter().any(|hir_ty| {
+ let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ has_drop(cx, ty_ty)
+ })
+}
+
+// We don't have to lint on something that's already `const`
+#[must_use]
+fn already_const(header: hir::FnHeader) -> bool {
+ header.constness == Constness::Const
+}
--- /dev/null
+// Note: More specifically this lint is largely inspired (aka copied) from
+// *rustc*'s
+// [`missing_doc`].
+//
+// [`missing_doc`]: https://github.com/rust-lang/rust/blob/cf9cf7c923eb01146971429044f216a3ca905e06/compiler/rustc_lint/src/builtin.rs#L415
+//
+
+use clippy_utils::attrs::is_doc_hidden;
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::CRATE_DEF_ID;
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns if there is missing doc for any documentable item
+ /// (public or private).
+ ///
+ /// ### Why is this bad?
+ /// Doc is good. *rustc* has a `MISSING_DOCS`
+ /// allowed-by-default lint for
+ /// public members, but has no way to enforce documentation of private items.
+ /// This lint fixes that.
++ #[clippy::version = "pre 1.29.0"]
+ pub MISSING_DOCS_IN_PRIVATE_ITEMS,
+ restriction,
+ "detects missing documentation for public and private members"
+}
+
+pub struct MissingDoc {
+ /// Stack of whether #[doc(hidden)] is set
+ /// at each level which has lint attributes.
+ doc_hidden_stack: Vec<bool>,
+}
+
+impl Default for MissingDoc {
+ #[must_use]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl MissingDoc {
+ #[must_use]
+ pub fn new() -> Self {
+ Self {
+ doc_hidden_stack: vec![false],
+ }
+ }
+
+ fn doc_hidden(&self) -> bool {
+ *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
+ }
+
+ fn check_missing_docs_attrs(
+ &self,
+ cx: &LateContext<'_>,
+ attrs: &[ast::Attribute],
+ sp: Span,
+ article: &'static str,
+ desc: &'static str,
+ ) {
+ // If we're building a test harness, then warning about
+ // documentation is probably not really relevant right now.
+ if cx.sess().opts.test {
+ return;
+ }
+
+ // `#[doc(hidden)]` disables missing_docs check.
+ if self.doc_hidden() {
+ return;
+ }
+
+ if sp.from_expansion() {
+ return;
+ }
+
+ let has_doc = attrs.iter().any(|a| a.doc_str().is_some());
+ if !has_doc {
+ span_lint(
+ cx,
+ MISSING_DOCS_IN_PRIVATE_ITEMS,
+ sp,
+ &format!("missing documentation for {} {}", article, desc),
+ );
+ }
+ }
+}
+
+impl_lint_pass!(MissingDoc => [MISSING_DOCS_IN_PRIVATE_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingDoc {
+ fn enter_lint_attrs(&mut self, _: &LateContext<'tcx>, attrs: &'tcx [ast::Attribute]) {
+ let doc_hidden = self.doc_hidden() || is_doc_hidden(attrs);
+ self.doc_hidden_stack.push(doc_hidden);
+ }
+
+ fn exit_lint_attrs(&mut self, _: &LateContext<'tcx>, _: &'tcx [ast::Attribute]) {
+ self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
+ }
+
+ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
+ let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID);
+ self.check_missing_docs_attrs(cx, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate");
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
+ match it.kind {
+ hir::ItemKind::Fn(..) => {
+ // ignore main()
+ if it.ident.name == sym::main {
+ let def_key = cx.tcx.hir().def_key(it.def_id);
+ if def_key.parent == Some(hir::def_id::CRATE_DEF_INDEX) {
+ return;
+ }
+ }
+ },
+ hir::ItemKind::Const(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Macro(..)
+ | hir::ItemKind::Mod(..)
+ | hir::ItemKind::Static(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::Trait(..)
+ | hir::ItemKind::TraitAlias(..)
+ | hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Union(..)
+ | hir::ItemKind::OpaqueTy(..) => {},
+ hir::ItemKind::ExternCrate(..)
+ | hir::ItemKind::ForeignMod { .. }
+ | hir::ItemKind::GlobalAsm(..)
+ | hir::ItemKind::Impl { .. }
+ | hir::ItemKind::Use(..) => return,
+ };
+
+ let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id());
+
+ let attrs = cx.tcx.hir().attrs(it.hir_id());
+ self.check_missing_docs_attrs(cx, attrs, it.span, article, desc);
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) {
+ let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id());
+
+ let attrs = cx.tcx.hir().attrs(trait_item.hir_id());
+ self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc);
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ // If the method is an impl for a trait, don't doc.
+ match cx.tcx.associated_item(impl_item.def_id).container {
+ ty::TraitContainer(_) => return,
+ ty::ImplContainer(cid) => {
+ if cx.tcx.impl_trait_ref(cid).is_some() {
+ return;
+ }
+ },
+ }
+
+ let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id());
+ let attrs = cx.tcx.hir().attrs(impl_item.hir_id());
+ self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc);
+ }
+
+ fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
+ if !sf.is_positional() {
+ let attrs = cx.tcx.hir().attrs(sf.hir_id);
+ self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field");
+ }
+ }
+
+ fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) {
+ let attrs = cx.tcx.hir().attrs(v.id);
+ self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant");
+ }
+}
--- /dev/null
+use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_opt};
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::Applicability;
+use rustc_hir::{def::Res, def_id::DefId, Item, ItemKind, UseKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Symbol;
+
+use crate::utils::conf::Rename;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for imports that do not rename the item as specified
+ /// in the `enforce-import-renames` config option.
+ ///
+ /// ### Why is this bad?
+ /// Consistency is important, if a project has defined import
+ /// renames they should be followed. More practically, some item names are too
+ /// vague outside of their defining scope this can enforce a more meaningful naming.
+ ///
+ /// ### Example
+ /// An example clippy.toml configuration:
+ /// ```toml
+ /// # clippy.toml
+ /// enforced-import-renames = [ { path = "serde_json::Value", rename = "JsonValue" }]
+ /// ```
+ ///
+ /// ```rust,ignore
+ /// use serde_json::Value;
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// use serde_json::Value as JsonValue;
+ /// ```
++ #[clippy::version = "1.55.0"]
+ pub MISSING_ENFORCED_IMPORT_RENAMES,
+ restriction,
+ "enforce import renames"
+}
+
+pub struct ImportRename {
+ conf_renames: Vec<Rename>,
+ renames: FxHashMap<DefId, Symbol>,
+}
+
+impl ImportRename {
+ pub fn new(conf_renames: Vec<Rename>) -> Self {
+ Self {
+ conf_renames,
+ renames: FxHashMap::default(),
+ }
+ }
+}
+
+impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]);
+
+impl LateLintPass<'_> for ImportRename {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ for Rename { path, rename } in &self.conf_renames {
+ if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &path.split("::").collect::<Vec<_>>()) {
+ self.renames.insert(id, Symbol::intern(rename));
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if_chain! {
+ if let ItemKind::Use(path, UseKind::Single) = &item.kind;
+ if let Res::Def(_, id) = path.res;
+ if let Some(name) = self.renames.get(&id);
+ // Remove semicolon since it is not present for nested imports
+ let span_without_semi = cx.sess().source_map().span_until_char(item.span, ';');
+ if let Some(snip) = snippet_opt(cx, span_without_semi);
+ if let Some(import) = match snip.split_once(" as ") {
+ None => Some(snip.as_str()),
+ Some((import, rename)) => {
+ if rename.trim() == &*name.as_str() {
+ None
+ } else {
+ Some(import.trim())
+ }
+ },
+ };
+ then {
+ span_lint_and_sugg(
+ cx,
+ MISSING_ENFORCED_IMPORT_RENAMES,
+ span_without_semi,
+ "this import should be renamed",
+ "try",
+ format!(
+ "{} as {}",
+ import,
+ name,
+ ),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast;
+use rustc_hir as hir;
+use rustc_lint::{self, LateContext, LateLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It lints if an exported function, method, trait method with default impl,
+ /// or trait method impl is not `#[inline]`.
+ ///
+ /// ### Why is this bad?
+ /// In general, it is not. Functions can be inlined across
+ /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled,
+ /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates
+ /// might intend for most of the methods in their public API to be able to be inlined across
+ /// crates even when LTO is disabled. For these types of crates, enabling this lint might make
+ /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and
+ /// then opt out for specific methods where this might not make sense.
+ ///
+ /// ### 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]
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MISSING_INLINE_IN_PUBLIC_ITEMS,
+ restriction,
+ "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)"
+}
+
+fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[ast::Attribute], sp: Span, desc: &'static str) {
+ let has_inline = attrs.iter().any(|a| a.has_name(sym::inline));
+ if !has_inline {
+ span_lint(
+ cx,
+ MISSING_INLINE_IN_PUBLIC_ITEMS,
+ sp,
+ &format!("missing `#[inline]` for {}", desc),
+ );
+ }
+}
+
+fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool {
+ use rustc_session::config::CrateType;
+
+ cx.tcx
+ .sess
+ .crate_types()
+ .iter()
+ .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro))
+}
+
+declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingInline {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) {
+ if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable_or_proc_macro(cx) {
+ return;
+ }
+
+ if !cx.access_levels.is_exported(it.def_id) {
+ return;
+ }
+ match it.kind {
+ hir::ItemKind::Fn(..) => {
+ let desc = "a function";
+ let attrs = cx.tcx.hir().attrs(it.hir_id());
+ check_missing_inline_attrs(cx, attrs, it.span, desc);
+ },
+ hir::ItemKind::Trait(ref _is_auto, ref _unsafe, ref _generics, _bounds, trait_items) => {
+ // note: we need to check if the trait is exported so we can't use
+ // `LateLintPass::check_trait_item` here.
+ for tit in trait_items {
+ let tit_ = cx.tcx.hir().trait_item(tit.id);
+ match tit_.kind {
+ hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..) => {},
+ hir::TraitItemKind::Fn(..) => {
+ if tit.defaultness.has_value() {
+ // trait method with default body needs inline in case
+ // an impl is not provided
+ let desc = "a default trait method";
+ let item = cx.tcx.hir().trait_item(tit.id);
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ check_missing_inline_attrs(cx, attrs, item.span, desc);
+ }
+ },
+ }
+ }
+ },
+ hir::ItemKind::Const(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Macro(..)
+ | hir::ItemKind::Mod(..)
+ | hir::ItemKind::Static(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::TraitAlias(..)
+ | hir::ItemKind::GlobalAsm(..)
+ | hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Union(..)
+ | hir::ItemKind::OpaqueTy(..)
+ | hir::ItemKind::ExternCrate(..)
+ | hir::ItemKind::ForeignMod { .. }
+ | hir::ItemKind::Impl { .. }
+ | hir::ItemKind::Use(..) => {},
+ };
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ use rustc_middle::ty::{ImplContainer, TraitContainer};
+ if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable_or_proc_macro(cx) {
+ return;
+ }
+
+ // If the item being implemented is not exported, then we don't need #[inline]
+ if !cx.access_levels.is_exported(impl_item.def_id) {
+ return;
+ }
+
+ let desc = match impl_item.kind {
+ hir::ImplItemKind::Fn(..) => "a method",
+ hir::ImplItemKind::Const(..) | hir::ImplItemKind::TyAlias(_) => return,
+ };
+
+ let trait_def_id = match cx.tcx.associated_item(impl_item.def_id).container {
+ TraitContainer(cid) => Some(cid),
+ ImplContainer(cid) => cx.tcx.impl_trait_ref(cid).map(|t| t.def_id),
+ };
+
+ if let Some(trait_def_id) = trait_def_id {
+ if trait_def_id.is_local() && !cx.access_levels.is_exported(impl_item.def_id) {
+ // If a trait is being implemented for an item, and the
+ // trait is not exported, we don't need #[inline]
+ return;
+ }
+ }
+
+ let attrs = cx.tcx.hir().attrs(impl_item.hir_id());
+ check_missing_inline_attrs(cx, attrs, impl_item.span, desc);
+ }
+}
--- /dev/null
+use std::{
+ ffi::OsString,
+ path::{Component, Path},
+};
+
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks that module layout uses only self named module files, bans mod.rs files.
+ ///
+ /// ### Why is this bad?
+ /// Having multiple module layout styles in a project can be confusing.
+ ///
+ /// ### Example
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// mod.rs
+ /// lib.rs
+ /// ```
+ /// Use instead:
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// stuff.rs
+ /// lib.rs
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub MOD_MODULE_FILES,
+ restriction,
+ "checks that module layout is consistent"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks that module layout uses only mod.rs files.
+ ///
+ /// ### Why is this bad?
+ /// Having multiple module layout styles in a project can be confusing.
+ ///
+ /// ### Example
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// stuff.rs
+ /// lib.rs
+ /// ```
+ /// Use instead:
+ /// ```text
+ /// src/
+ /// stuff/
+ /// stuff_files.rs
+ /// mod.rs
+ /// lib.rs
+ /// ```
+
++ #[clippy::version = "1.57.0"]
+ pub SELF_NAMED_MODULE_FILES,
+ restriction,
+ "checks that module layout is consistent"
+}
+
+pub struct ModStyle;
+
+impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
+
+impl EarlyLintPass for ModStyle {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+ if cx.builder.lint_level(MOD_MODULE_FILES).0 == Level::Allow
+ && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).0 == Level::Allow
+ {
+ return;
+ }
+
+ let files = cx.sess.source_map().files();
+
+ let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess.opts.working_dir {
+ p.to_string_lossy()
+ } else {
+ return;
+ };
+
+ // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
+ // `[path, to]` but not foo
+ let mut folder_segments = FxHashSet::default();
+ // `mod_folders` is all the unique folder names that contain a mod.rs file
+ let mut mod_folders = FxHashSet::default();
+ // `file_map` maps file names to the full path including the file name
+ // `{ foo => path/to/foo.rs, .. }
+ let mut file_map = FxHashMap::default();
+ for file in files.iter() {
+ match &file.name {
+ FileName::Real(RealFileName::LocalPath(lp))
+ if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) =>
+ {
+ let p = lp.to_string_lossy();
+ let path = Path::new(p.trim_start_matches(trim_to_src.as_ref()));
+ if let Some(stem) = path.file_stem() {
+ file_map.insert(stem.to_os_string(), (file, path.to_owned()));
+ }
+ process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
+ check_self_named_mod_exists(cx, path, file);
+ },
+ _ => {},
+ }
+ }
+
+ for folder in &folder_segments {
+ if !mod_folders.contains(folder) {
+ if let Some((file, path)) = file_map.get(folder) {
+ let mut correct = path.clone();
+ correct.pop();
+ correct.push(folder);
+ correct.push("mod.rs");
+ cx.struct_span_lint(
+ SELF_NAMED_MODULE_FILES,
+ Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
+ |build| {
+ let mut lint =
+ build.build(&format!("`mod.rs` files are required, found `{}`", path.display()));
+ lint.help(&format!("move `{}` to `{}`", path.display(), correct.display(),));
+ lint.emit();
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+/// For each `path` we add each folder component to `folder_segments` and if the file name
+/// is `mod.rs` we add it's parent folder to `mod_folders`.
+fn process_paths_for_mod_files(
+ path: &Path,
+ folder_segments: &mut FxHashSet<OsString>,
+ mod_folders: &mut FxHashSet<OsString>,
+) {
+ let mut comp = path.components().rev().peekable();
+ let _ = comp.next();
+ if path.ends_with("mod.rs") {
+ mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default());
+ }
+ let folders = comp
+ .filter_map(|c| {
+ if let Component::Normal(s) = c {
+ Some(s.to_os_string())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+ folder_segments.extend(folders);
+}
+
+/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
+fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
+ if path.ends_with("mod.rs") {
+ let mut mod_file = path.to_path_buf();
+ mod_file.pop();
+ mod_file.set_extension("rs");
+
+ cx.struct_span_lint(
+ MOD_MODULE_FILES,
+ Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
+ |build| {
+ let mut lint = build.build(&format!("`mod.rs` files are not allowed, found `{}`", path.display()));
+ lint.help(&format!("move `{}` to `{}`", path.display(), mod_file.display(),));
+ lint.emit();
+ },
+ );
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sext;
+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};
+use std::fmt::Display;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for modulo arithmetic.
+ ///
+ /// ### Why is this bad?
+ /// The results of modulo (%) operation might differ
+ /// depending on the language, when negative numbers are involved.
+ /// If you interop with different languages it might be beneficial
+ /// to double check all places that use modulo arithmetic.
+ ///
+ /// For example, in Rust `17 % -3 = 2`, but in Python `17 % -3 = -1`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = -17 % 3;
+ /// ```
++ #[clippy::version = "1.42.0"]
+ pub MODULO_ARITHMETIC,
+ restriction,
+ "any modulo arithmetic statement"
+}
+
+declare_lint_pass!(ModuloArithmetic => [MODULO_ARITHMETIC]);
+
+struct OperandInfo {
+ string_representation: Option<String>,
+ is_negative: bool,
+ is_integral: bool,
+}
+
+fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<OperandInfo> {
+ match constant(cx, cx.typeck_results(), operand) {
+ Some((Constant::Int(v), _)) => match *cx.typeck_results().expr_ty(expr).kind() {
+ ty::Int(ity) => {
+ let value = sext(cx.tcx, v, ity);
+ return Some(OperandInfo {
+ string_representation: Some(value.to_string()),
+ is_negative: value < 0,
+ is_integral: true,
+ });
+ },
+ ty::Uint(_) => {
+ return Some(OperandInfo {
+ string_representation: None,
+ is_negative: false,
+ is_integral: true,
+ });
+ },
+ _ => {},
+ },
+ Some((Constant::F32(f), _)) => {
+ return Some(floating_point_operand_info(&f));
+ },
+ Some((Constant::F64(f), _)) => {
+ return Some(floating_point_operand_info(&f));
+ },
+ _ => {},
+ }
+ None
+}
+
+fn floating_point_operand_info<T: Display + PartialOrd + From<f32>>(f: &T) -> OperandInfo {
+ OperandInfo {
+ string_representation: Some(format!("{:.3}", *f)),
+ is_negative: *f < 0.0.into(),
+ is_integral: false,
+ }
+}
+
+fn might_have_negative_value(t: &ty::TyS<'_>) -> bool {
+ t.is_signed() || t.is_floating_point()
+}
+
+fn check_const_operands<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ lhs_operand: &OperandInfo,
+ rhs_operand: &OperandInfo,
+) {
+ if lhs_operand.is_negative ^ rhs_operand.is_negative {
+ span_lint_and_then(
+ cx,
+ MODULO_ARITHMETIC,
+ expr.span,
+ &format!(
+ "you are using modulo operator on constants with different signs: `{} % {}`",
+ lhs_operand.string_representation.as_ref().unwrap(),
+ rhs_operand.string_representation.as_ref().unwrap()
+ ),
+ |diag| {
+ diag.note("double check for expected result especially when interoperating with different languages");
+ if lhs_operand.is_integral {
+ diag.note("or consider using `rem_euclid` or similar function");
+ }
+ },
+ );
+ }
+}
+
+fn check_non_const_operands<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, operand: &Expr<'_>) {
+ let operand_type = cx.typeck_results().expr_ty(operand);
+ if might_have_negative_value(operand_type) {
+ span_lint_and_then(
+ cx,
+ MODULO_ARITHMETIC,
+ expr.span,
+ "you are using modulo operator on types that might have different signs",
+ |diag| {
+ diag.note("double check for expected result especially when interoperating with different languages");
+ if operand_type.is_integral() {
+ diag.note("or consider using `rem_euclid` or similar function");
+ }
+ },
+ );
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ModuloArithmetic {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ match &expr.kind {
+ ExprKind::Binary(op, lhs, rhs) | ExprKind::AssignOp(op, lhs, rhs) => {
+ if op.node == BinOpKind::Rem {
+ let lhs_operand = analyze_operand(lhs, cx, expr);
+ let rhs_operand = analyze_operand(rhs, cx, expr);
+ if_chain! {
+ if let Some(lhs_operand) = lhs_operand;
+ if let Some(rhs_operand) = rhs_operand;
+ then {
+ check_const_operands(cx, expr, &lhs_operand, &rhs_operand);
+ }
+ else {
+ check_non_const_operands(cx, expr, lhs);
+ }
+ }
+ };
+ },
+ _ => {},
+ }
+ }
+}
--- /dev/null
+//! lint on multiple versions of a crate being used
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::is_lint_allowed;
+use rustc_hir::def_id::LOCAL_CRATE;
+use rustc_hir::CRATE_HIR_ID;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::DUMMY_SP;
+
+use cargo_metadata::{DependencyKind, Node, Package, PackageId};
+use if_chain::if_chain;
+use itertools::Itertools;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks to see if multiple versions of a crate are being
+ /// used.
+ ///
+ /// ### Why is this bad?
+ /// This bloats the size of targets, and can lead to
+ /// confusing error messages when structs or traits are used interchangeably
+ /// between different versions of a crate.
+ ///
+ /// ### Known problems
+ /// Because this can be caused purely by the dependencies
+ /// themselves, it's not always possible to fix this issue.
+ ///
+ /// ### Example
+ /// ```toml
+ /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning.
+ /// [dependencies]
+ /// ctrlc = "=3.1.0"
+ /// ansi_term = "=0.11.0"
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MULTIPLE_CRATE_VERSIONS,
+ cargo,
+ "multiple versions of the same crate being used"
+}
+
+declare_lint_pass!(MultipleCrateVersions => [MULTIPLE_CRATE_VERSIONS]);
+
+impl LateLintPass<'_> for MultipleCrateVersions {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ if is_lint_allowed(cx, MULTIPLE_CRATE_VERSIONS, CRATE_HIR_ID) {
+ return;
+ }
+
+ let metadata = unwrap_cargo_metadata!(cx, MULTIPLE_CRATE_VERSIONS, true);
+ let local_name = cx.tcx.crate_name(LOCAL_CRATE).as_str();
+ let mut packages = metadata.packages;
+ packages.sort_by(|a, b| a.name.cmp(&b.name));
+
+ if_chain! {
+ if let Some(resolve) = &metadata.resolve;
+ if let Some(local_id) = packages
+ .iter()
+ .find_map(|p| if p.name == *local_name { Some(&p.id) } else { None });
+ then {
+ for (name, group) in &packages.iter().group_by(|p| p.name.clone()) {
+ let group: Vec<&Package> = group.collect();
+
+ if group.len() <= 1 {
+ continue;
+ }
+
+ if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) {
+ let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect();
+ versions.sort();
+ let versions = versions.iter().join(", ");
+
+ span_lint(
+ cx,
+ MULTIPLE_CRATE_VERSIONS,
+ DUMMY_SP,
+ &format!("multiple versions for dependency `{}`: {}", name, versions),
+ );
+ }
+ }
+ }
+ }
+ }
+}
+
+fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool {
+ fn depends_on(node: &Node, dep_id: &PackageId) -> bool {
+ node.deps.iter().any(|dep| {
+ dep.pkg == *dep_id
+ && dep
+ .dep_kinds
+ .iter()
+ .any(|info| matches!(info.kind, DependencyKind::Normal))
+ })
+ }
+
+ nodes
+ .iter()
+ .filter(|node| depends_on(node, dep_id))
+ .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id))
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::trait_ref_of_method;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::TypeFoldable;
+use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::sym;
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for sets/maps with mutable key types.
+ ///
+ /// ### Why is this bad?
+ /// All of `HashMap`, `HashSet`, `BTreeMap` and
+ /// `BtreeSet` rely on either the hash or the order of keys be unchanging,
+ /// so having types with interior mutability is a bad idea.
+ ///
+ /// ### Known problems
+ ///
+ /// #### False Positives
+ /// It's correct to use a struct that contains interior mutability as a key, when its
+ /// implementation of `Hash` or `Ord` doesn't access any of the interior mutable types.
+ /// However, this lint is unable to recognize this, so it will often cause false positives in
+ /// theses cases. The `bytes` crate is a great example of this.
+ ///
+ /// #### False Negatives
+ /// For custom `struct`s/`enum`s, this lint is unable to check for interior mutability behind
+ /// indirection. For example, `struct BadKey<'a>(&'a Cell<usize>)` will be seen as immutable
+ /// and cause a false negative if its implementation of `Hash`/`Ord` accesses the `Cell`.
+ ///
+ /// This lint does check a few cases for indirection. Firstly, using some standard library
+ /// types (`Option`, `Result`, `Box`, `Rc`, `Arc`, `Vec`, `VecDeque`, `BTreeMap` and
+ /// `BTreeSet`) directly as keys (e.g. in `HashMap<Box<Cell<usize>>, ()>`) **will** trigger the
+ /// lint, because the impls of `Hash`/`Ord` for these types directly call `Hash`/`Ord` on their
+ /// contained type.
+ ///
+ /// Secondly, the implementations of `Hash` and `Ord` for raw pointers (`*const T` or `*mut T`)
+ /// apply only to the **address** of the contained value. Therefore, interior mutability
+ /// behind raw pointers (e.g. in `HashSet<*mut Cell<usize>>`) can't impact the value of `Hash`
+ /// or `Ord`, and therefore will not trigger this link. For more info, see issue
+ /// [#6745](https://github.com/rust-lang/rust-clippy/issues/6745).
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::cmp::{PartialEq, Eq};
+ /// use std::collections::HashSet;
+ /// use std::hash::{Hash, Hasher};
+ /// use std::sync::atomic::AtomicUsize;
+ ///# #[allow(unused)]
+ ///
+ /// struct Bad(AtomicUsize);
+ /// impl PartialEq for Bad {
+ /// fn eq(&self, rhs: &Self) -> bool {
+ /// ..
+ /// ; unimplemented!();
+ /// }
+ /// }
+ ///
+ /// impl Eq for Bad {}
+ ///
+ /// impl Hash for Bad {
+ /// fn hash<H: Hasher>(&self, h: &mut H) {
+ /// ..
+ /// ; unimplemented!();
+ /// }
+ /// }
+ ///
+ /// fn main() {
+ /// let _: HashSet<Bad> = HashSet::new();
+ /// }
+ /// ```
++ #[clippy::version = "1.42.0"]
+ pub MUTABLE_KEY_TYPE,
+ suspicious,
+ "Check for mutable `Map`/`Set` key type"
+}
+
+declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);
+
+impl<'tcx> LateLintPass<'tcx> for MutableKeyType {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+ if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
+ check_sig(cx, item.hir_id(), sig.decl);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) {
+ if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind {
+ if trait_ref_of_method(cx, item.hir_id()).is_none() {
+ check_sig(cx, item.hir_id(), sig.decl);
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
+ if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
+ check_sig(cx, item.hir_id(), sig.decl);
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
+ if let hir::PatKind::Wild = local.pat.kind {
+ return;
+ }
+ check_ty(cx, local.span, cx.typeck_results().pat_ty(&*local.pat));
+ }
+}
+
+fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
+ let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
+ let fn_sig = cx.tcx.fn_sig(fn_def_id);
+ for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) {
+ check_ty(cx, hir_ty.span, ty);
+ }
+ check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
+}
+
+// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
+// generics (because the compiler cannot ensure immutability for unknown types).
+fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
+ let ty = ty.peel_refs();
+ if let Adt(def, substs) = ty.kind() {
+ let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet]
+ .iter()
+ .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
+ if is_keyed_type && is_interior_mutable_type(cx, substs.type_at(0), span) {
+ span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
+ }
+ }
+}
+
+/// Determines if a type contains interior mutability which would affect its implementation of
+/// [`Hash`] or [`Ord`].
+fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
+ match *ty.kind() {
+ Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || is_interior_mutable_type(cx, inner_ty, span),
+ Slice(inner_ty) => is_interior_mutable_type(cx, inner_ty, span),
+ Array(inner_ty, size) => {
+ size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0)
+ && is_interior_mutable_type(cx, inner_ty, span)
+ },
+ Tuple(..) => ty.tuple_fields().any(|ty| is_interior_mutable_type(cx, ty, span)),
+ Adt(def, substs) => {
+ // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
+ // that of their type parameters. Note: we don't include `HashSet` and `HashMap`
+ // because they have no impl for `Hash` or `Ord`.
+ let is_std_collection = [
+ sym::Option,
+ sym::Result,
+ sym::LinkedList,
+ sym::Vec,
+ sym::VecDeque,
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::Rc,
+ sym::Arc,
+ ]
+ .iter()
+ .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
+ let is_box = Some(def.did) == cx.tcx.lang_items().owned_box();
+ if is_std_collection || is_box {
+ // The type is mutable if any of its type parameters are
+ substs.types().any(|ty| is_interior_mutable_type(cx, ty, span))
+ } else {
+ !ty.has_escaping_bound_vars()
+ && cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
+ && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
+ }
+ },
+ _ => false,
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::higher;
+use rustc_hir as hir;
+use rustc_hir::intravisit;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for instances of `mut mut` references.
+ ///
+ /// ### Why is this bad?
+ /// Multiple `mut`s don't add anything meaningful to the
+ /// source. This is either a copy'n'paste error, or it shows a fundamental
+ /// misunderstanding of references.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let mut y = 1;
+ /// let x = &mut &mut y;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MUT_MUT,
+ pedantic,
+ "usage of double-mut refs, e.g., `&mut &mut ...`"
+}
+
+declare_lint_pass!(MutMut => [MUT_MUT]);
+
+impl<'tcx> LateLintPass<'tcx> for MutMut {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ intravisit::walk_block(&mut MutVisitor { cx }, block);
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'_>) {
+ use rustc_hir::intravisit::Visitor;
+
+ MutVisitor { cx }.visit_ty(ty);
+ }
+}
+
+pub struct MutVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ if in_external_macro(self.cx.sess(), expr.span) {
+ return;
+ }
+
+ if let Some(higher::ForLoop { arg, body, .. }) = higher::ForLoop::hir(expr) {
+ // A `for` loop lowers to:
+ // ```rust
+ // match ::std::iter::Iterator::next(&mut iter) {
+ // // ^^^^
+ // ```
+ // Let's ignore the generated code.
+ intravisit::walk_expr(self, arg);
+ intravisit::walk_expr(self, body);
+ } else if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, e) = expr.kind {
+ if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Mut, _) = e.kind {
+ span_lint(
+ self.cx,
+ MUT_MUT,
+ expr.span,
+ "generally you want to avoid `&mut &mut _` if possible",
+ );
+ } else if let ty::Ref(_, _, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() {
+ span_lint(
+ self.cx,
+ MUT_MUT,
+ expr.span,
+ "this expression mutably borrows a mutable reference. Consider reborrowing",
+ );
+ }
+ }
+ }
+
+ fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) {
+ if in_external_macro(self.cx.sess(), ty.span) {
+ return;
+ }
+
+ if let hir::TyKind::Rptr(
+ _,
+ hir::MutTy {
+ ty: pty,
+ mutbl: hir::Mutability::Mut,
+ },
+ ) = ty.kind
+ {
+ if let hir::TyKind::Rptr(
+ _,
+ hir::MutTy {
+ mutbl: hir::Mutability::Mut,
+ ..
+ },
+ ) = pty.kind
+ {
+ span_lint(
+ self.cx,
+ MUT_MUT,
+ ty.span,
+ "generally you want to avoid `&mut &mut _` if possible",
+ );
+ }
+ }
+
+ intravisit::walk_ty(self, ty);
+ }
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `&mut Mutex::lock` calls
+ ///
+ /// ### Why is this bad?
+ /// `Mutex::lock` is less efficient than
+ /// calling `Mutex::get_mut`. In addition you also have a statically
+ /// guarantee that the mutex isn't locked, instead of just a runtime
+ /// guarantee.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::sync::{Arc, Mutex};
+ ///
+ /// let mut value_rc = Arc::new(Mutex::new(42_u8));
+ /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap();
+ ///
+ /// let mut value = value_mutex.lock().unwrap();
+ /// *value += 1;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::sync::{Arc, Mutex};
+ ///
+ /// let mut value_rc = Arc::new(Mutex::new(42_u8));
+ /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap();
+ ///
+ /// let value = value_mutex.get_mut().unwrap();
+ /// *value += 1;
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub MUT_MUTEX_LOCK,
+ style,
+ "`&mut Mutex::lock` does unnecessary locking"
+}
+
+declare_lint_pass!(MutMutexLock => [MUT_MUTEX_LOCK]);
+
+impl<'tcx> LateLintPass<'tcx> for MutMutexLock {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) {
+ if_chain! {
+ if let ExprKind::MethodCall(path, method_span, [self_arg, ..], _) = &ex.kind;
+ if path.ident.name == sym!(lock);
+ let ty = cx.typeck_results().expr_ty(self_arg);
+ if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind();
+ if is_type_diagnostic_item(cx, inner_ty, sym::Mutex);
+ then {
+ span_lint_and_sugg(
+ cx,
+ MUT_MUTEX_LOCK,
+ *method_span,
+ "calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference",
+ "change this to",
+ "get_mut".to_owned(),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::subst::Subst;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects passing a mutable reference to a function that only
+ /// requires an immutable reference.
+ ///
+ /// ### Why is this bad?
+ /// The mutable reference rules out all other references to
+ /// the value. Also the code misleads about the intent of the call site.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// my_vec.push(&mut value)
+ ///
+ /// // Good
+ /// my_vec.push(&value)
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_MUT_PASSED,
+ style,
+ "an argument passed as a mutable reference although the callee only demands an immutable reference"
+}
+
+declare_lint_pass!(UnnecessaryMutPassed => [UNNECESSARY_MUT_PASSED]);
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ match e.kind {
+ ExprKind::Call(fn_expr, arguments) => {
+ if let ExprKind::Path(ref path) = fn_expr.kind {
+ check_arguments(
+ cx,
+ arguments,
+ cx.typeck_results().expr_ty(fn_expr),
+ &rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)),
+ "function",
+ );
+ }
+ },
+ ExprKind::MethodCall(path, _, arguments, _) => {
+ let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap();
+ let substs = cx.typeck_results().node_substs(e.hir_id);
+ let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs);
+ check_arguments(cx, arguments, method_type, &path.ident.as_str(), "method");
+ },
+ _ => (),
+ }
+ }
+}
+
+fn check_arguments<'tcx>(
+ cx: &LateContext<'tcx>,
+ arguments: &[Expr<'_>],
+ type_definition: Ty<'tcx>,
+ name: &str,
+ fn_kind: &str,
+) {
+ match type_definition.kind() {
+ ty::FnDef(..) | ty::FnPtr(_) => {
+ let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs();
+ for (argument, parameter) in iter::zip(arguments, parameters) {
+ match parameter.kind() {
+ ty::Ref(_, _, Mutability::Not)
+ | ty::RawPtr(ty::TypeAndMut {
+ mutbl: Mutability::Not, ..
+ }) => {
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind {
+ span_lint(
+ cx,
+ UNNECESSARY_MUT_PASSED,
+ argument.span,
+ &format!("the {} `{}` doesn't need a mutable reference", fn_kind, name),
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+ },
+ _ => (),
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{higher, is_direct_expn_of};
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{BorrowKind, Expr, ExprKind, MatchSource, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for function/method calls with a mutable
+ /// parameter in `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!` macros.
+ ///
+ /// ### Why is this bad?
+ /// In release builds `debug_assert!` macros are optimized out by the
+ /// compiler.
+ /// Therefore mutating something in a `debug_assert!` macro results in different behaviour
+ /// between a release and debug build.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// debug_assert_eq!(vec![3].pop(), Some(3));
+ /// // or
+ /// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
+ /// debug_assert!(take_a_mut_parameter(&mut 5));
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub DEBUG_ASSERT_WITH_MUT_CALL,
+ nursery,
+ "mutable arguments in `debug_assert{,_ne,_eq}!`"
+}
+
+declare_lint_pass!(DebugAssertWithMutCall => [DEBUG_ASSERT_WITH_MUT_CALL]);
+
+const DEBUG_MACRO_NAMES: [&str; 3] = ["debug_assert", "debug_assert_eq", "debug_assert_ne"];
+
+impl<'tcx> LateLintPass<'tcx> for DebugAssertWithMutCall {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ for dmn in &DEBUG_MACRO_NAMES {
+ if is_direct_expn_of(e.span, dmn).is_some() {
+ if let Some(macro_args) = higher::extract_assert_macro_args(e) {
+ for arg in macro_args {
+ let mut visitor = MutArgVisitor::new(cx);
+ visitor.visit_expr(arg);
+ if let Some(span) = visitor.expr_span() {
+ span_lint(
+ cx,
+ DEBUG_ASSERT_WITH_MUT_CALL,
+ span,
+ &format!("do not call a function with mutable arguments inside of `{}!`", dmn),
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+struct MutArgVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ expr_span: Option<Span>,
+ found: bool,
+}
+
+impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> {
+ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
+ cx,
+ expr_span: None,
+ found: false,
+ }
+ }
+
+ fn expr_span(&self) -> Option<Span> {
+ if self.found { self.expr_span } else { None }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ match expr.kind {
+ ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) => {
+ self.found = true;
+ return;
+ },
+ ExprKind::If(..) => {
+ self.found = true;
+ return;
+ },
+ ExprKind::Path(_) => {
+ if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
+ if adj
+ .iter()
+ .any(|a| matches!(a.target.kind(), ty::Ref(_, _, Mutability::Mut)))
+ {
+ self.found = true;
+ return;
+ }
+ }
+ },
+ // Don't check await desugars
+ ExprKind::Match(_, _, MatchSource::AwaitDesugar) => return,
+ _ if !self.found => self.expr_span = Some(expr.span),
+ _ => return,
+ }
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
--- /dev/null
+//! Checks for uses of mutex where an atomic value could be used
+//!
+//! This lint is **warn** by default
+
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_hir::Expr;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Mutex<X>` where an atomic will do.
+ ///
+ /// ### Why is this bad?
+ /// Using a mutex just to make access to a plain bool or
+ /// reference sequential is shooting flies with cannons.
+ /// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and
+ /// faster.
+ ///
+ /// ### Known problems
+ /// This lint cannot detect if the mutex is actually used
+ /// for waiting before a critical section.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let y = true;
+ ///
+ /// // Bad
+ /// # use std::sync::Mutex;
+ /// let x = Mutex::new(&y);
+ ///
+ /// // Good
+ /// # use std::sync::atomic::AtomicBool;
+ /// let x = AtomicBool::new(y);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MUTEX_ATOMIC,
+ perf,
+ "using a mutex where an atomic value could be used instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Mutex<X>` where `X` is an integral
+ /// type.
+ ///
+ /// ### Why is this bad?
+ /// Using a mutex just to make access to a plain integer
+ /// sequential is
+ /// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster.
+ ///
+ /// ### Known problems
+ /// This lint cannot detect if the mutex is actually used
+ /// for waiting before a critical section.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::sync::Mutex;
+ /// let x = Mutex::new(0usize);
+ ///
+ /// // Good
+ /// # use std::sync::atomic::AtomicUsize;
+ /// let x = AtomicUsize::new(0usize);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MUTEX_INTEGER,
+ nursery,
+ "using a mutex for an integer type"
+}
+
+declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]);
+
+impl<'tcx> LateLintPass<'tcx> for Mutex {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(_, subst) = ty.kind() {
+ if is_type_diagnostic_item(cx, ty, sym::Mutex) {
+ let mutex_param = subst.type_at(0);
+ if let Some(atomic_name) = get_atomic_name(mutex_param) {
+ let msg = format!(
+ "consider using an `{}` instead of a `Mutex` here; if you just want the locking \
+ behavior and not the internal type, consider using `Mutex<()>`",
+ atomic_name
+ );
+ match *mutex_param.kind() {
+ ty::Uint(t) if t != ty::UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg),
+ ty::Int(t) if t != ty::IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, &msg),
+ _ => span_lint(cx, MUTEX_ATOMIC, expr.span, &msg),
+ };
+ }
+ }
+ }
+ }
+}
+
+fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> {
+ match ty.kind() {
+ ty::Bool => Some("AtomicBool"),
+ ty::Uint(_) => Some("AtomicUsize"),
+ ty::Int(_) => Some("AtomicIsize"),
+ ty::RawPtr(_) => Some("AtomicPtr"),
+ _ => None,
+ }
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if in_macro(lifetime.ident.span) {
+use if_chain::if_chain;
+use rustc_ast::ast::{BindingMode, Lifetime, Mutability, Param, PatKind, Path, TyKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// The lint checks for `self` in fn parameters that
+ /// specify the `Self`-type explicitly
+ /// ### Why is this bad?
+ /// Increases the amount and decreases the readability of code
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum ValType {
+ /// I32,
+ /// I64,
+ /// F32,
+ /// F64,
+ /// }
+ ///
+ /// impl ValType {
+ /// pub fn bytes(self: Self) -> usize {
+ /// match self {
+ /// Self::I32 | Self::F32 => 4,
+ /// Self::I64 | Self::F64 => 8,
+ /// }
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Could be rewritten as
+ ///
+ /// ```rust
+ /// enum ValType {
+ /// I32,
+ /// I64,
+ /// F32,
+ /// F64,
+ /// }
+ ///
+ /// impl ValType {
+ /// pub fn bytes(self) -> usize {
+ /// match self {
+ /// Self::I32 | Self::F32 => 4,
+ /// Self::I64 | Self::F64 => 8,
+ /// }
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub NEEDLESS_ARBITRARY_SELF_TYPE,
+ complexity,
+ "type of `self` parameter is already by default `Self`"
+}
+
+declare_lint_pass!(NeedlessArbitrarySelfType => [NEEDLESS_ARBITRARY_SELF_TYPE]);
+
+enum Mode {
+ Ref(Option<Lifetime>),
+ Value,
+}
+
+fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mode: &Mode, mutbl: Mutability) {
+ if_chain! {
+ if let [segment] = &path.segments[..];
+ if segment.ident.name == kw::SelfUpper;
+ then {
+ // In case we have a named lifetime, we check if the name comes from expansion.
+ // If it does, at this point we know the rest of the parameter was written by the user,
+ // so let them decide what the name of the lifetime should be.
+ // See #6089 for more details.
+ let mut applicability = Applicability::MachineApplicable;
+ let self_param = match (binding_mode, mutbl) {
+ (Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(),
+ (Mode::Ref(Some(lifetime)), Mutability::Mut) => {
- if in_macro(lifetime.ident.span) {
++ if lifetime.ident.span.from_expansion() {
+ applicability = Applicability::HasPlaceholders;
+ "&'_ mut self".to_string()
+ } else {
+ format!("&{} mut self", &lifetime.ident.name)
+ }
+ },
+ (Mode::Ref(None), Mutability::Not) => "&self".to_string(),
+ (Mode::Ref(Some(lifetime)), Mutability::Not) => {
- if !p.is_self() || in_macro(p.span) {
++ if lifetime.ident.span.from_expansion() {
+ applicability = Applicability::HasPlaceholders;
+ "&'_ self".to_string()
+ } else {
+ format!("&{} self", &lifetime.ident.name)
+ }
+ },
+ (Mode::Value, Mutability::Mut) => "mut self".to_string(),
+ (Mode::Value, Mutability::Not) => "self".to_string(),
+ };
+
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_ARBITRARY_SELF_TYPE,
+ span,
+ "the type of the `self` parameter does not need to be arbitrary",
+ "consider to change this parameter to",
+ self_param,
+ applicability,
+ )
+ }
+ }
+}
+
+impl EarlyLintPass for NeedlessArbitrarySelfType {
+ fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) {
+ // Bail out if the parameter it's not a receiver or was not written by the user
++ if !p.is_self() || p.span.from_expansion() {
+ return;
+ }
+
+ match &p.ty.kind {
+ TyKind::Path(None, path) => {
+ if let PatKind::Ident(BindingMode::ByValue(mutbl), _, _) = p.pat.kind {
+ check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Value, mutbl);
+ }
+ },
+ TyKind::Rptr(lifetime, mut_ty) => {
+ if_chain! {
+ if let TyKind::Path(None, path) = &mut_ty.ty.kind;
+ if let PatKind::Ident(BindingMode::ByValue(Mutability::Not), _, _) = p.pat.kind;
+ then {
+ check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Ref(*lifetime), mut_ty.mutbl);
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_then;
- if !in_macro(expr.span);
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+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 uses of bitwise and/or operators between booleans, where performance may be improved by using
+ /// a lazy and.
+ ///
+ /// ### Why is this bad?
+ /// The bitwise operators do not support short-circuiting, so it may hinder code performance.
+ /// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold`
+ ///
+ /// ### Known problems
+ /// This lint evaluates only when the right side is determined to have no side effects. At this time, that
+ /// determination is quite conservative.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let (x,y) = (true, false);
+ /// if x & !y {} // where both x and y are booleans
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let (x,y) = (true, false);
+ /// if x && !y {}
+ /// ```
++ #[clippy::version = "1.54.0"]
+ pub NEEDLESS_BITWISE_BOOL,
+ pedantic,
+ "Boolean expressions that use bitwise rather than lazy operators"
+}
+
+declare_lint_pass!(NeedlessBitwiseBool => [NEEDLESS_BITWISE_BOOL]);
+
+fn is_bitwise_operation(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if_chain! {
++ if !expr.span.from_expansion();
+ if let (&ExprKind::Binary(ref op, _, right), &ty::Bool) = (&expr.kind, &ty.kind());
+ if op.node == BinOpKind::BitAnd || op.node == BinOpKind::BitOr;
+ if let ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) = right.kind;
+ if !right.can_have_side_effects();
+ then {
+ return true;
+ }
+ }
+ false
+}
+
+fn suggession_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ if let ExprKind::Binary(ref op, left, right) = expr.kind {
+ if let (Some(l_snippet), Some(r_snippet)) = (snippet_opt(cx, left.span), snippet_opt(cx, right.span)) {
+ let op_snippet = match op.node {
+ BinOpKind::BitAnd => "&&",
+ _ => "||",
+ };
+ return Some(format!("{} {} {}", l_snippet, op_snippet, r_snippet));
+ }
+ }
+ None
+}
+
+impl LateLintPass<'_> for NeedlessBitwiseBool {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if is_bitwise_operation(cx, expr) {
+ span_lint_and_then(
+ cx,
+ NEEDLESS_BITWISE_BOOL,
+ expr.span,
+ "use of bitwise operator instead of lazy operator between booleans",
+ |diag| {
+ if let Some(sugg) = suggession_snippet(cx, expr) {
+ diag.span_suggestion(expr.span, "try", sugg, Applicability::MachineApplicable);
+ }
+ },
+ );
+ }
+ }
+}
--- /dev/null
+//! Checks for needless boolean results of if-else expressions
+//!
+//! This lint is **warn** by default
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::higher;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{is_else_clause, is_expn_of};
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions of the form `if c { true } else {
+ /// false }` (or vice versa) and suggests using the condition directly.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code.
+ ///
+ /// ### Known problems
+ /// Maybe false positives: Sometimes, the two branches are
+ /// painstakingly documented (which we, of course, do not detect), so they *may*
+ /// have some value. Even then, the documentation can be rewritten to match the
+ /// shorter code.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// if x {
+ /// false
+ /// } else {
+ /// true
+ /// }
+ /// ```
+ /// Could be written as
+ /// ```rust,ignore
+ /// !x
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_BOOL,
+ complexity,
+ "if-statements with plain booleans in the then- and else-clause, e.g., `if p { true } else { false }`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions of the form `x == true`,
+ /// `x != true` and order comparisons such as `x < true` (or vice versa) and
+ /// suggest using the variable directly.
+ ///
+ /// ### Why is this bad?
+ /// Unnecessary code.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// if x == true {}
+ /// if y == false {}
+ /// ```
+ /// use `x` directly:
+ /// ```rust,ignore
+ /// if x {}
+ /// if !y {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub BOOL_COMPARISON,
+ complexity,
+ "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`"
+}
+
+declare_lint_pass!(NeedlessBool => [NEEDLESS_BOOL]);
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ use self::Expression::{Bool, RetBool};
+ if e.span.from_expansion() {
+ return;
+ }
+ if let Some(higher::If {
+ cond,
+ then,
+ r#else: Some(r#else),
+ }) = higher::If::hir(e)
+ {
+ let reduce = |ret, not| {
+ let mut applicability = Applicability::MachineApplicable;
+ let snip = Sugg::hir_with_applicability(cx, cond, "<predicate>", &mut applicability);
+ let mut snip = if not { !snip } else { snip };
+
+ if ret {
+ snip = snip.make_return();
+ }
+
+ if is_else_clause(cx.tcx, e) {
+ snip = snip.blockify();
+ }
+
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_BOOL,
+ e.span,
+ "this if-then-else expression returns a bool literal",
+ "you can reduce it to",
+ snip.to_string(),
+ applicability,
+ );
+ };
+ if let ExprKind::Block(then, _) = then.kind {
+ match (fetch_bool_block(then), fetch_bool_expr(r#else)) {
+ (RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => {
+ span_lint(
+ cx,
+ NEEDLESS_BOOL,
+ e.span,
+ "this if-then-else expression will always return true",
+ );
+ },
+ (RetBool(false), RetBool(false)) | (Bool(false), Bool(false)) => {
+ span_lint(
+ cx,
+ NEEDLESS_BOOL,
+ e.span,
+ "this if-then-else expression will always return false",
+ );
+ },
+ (RetBool(true), RetBool(false)) => reduce(true, false),
+ (Bool(true), Bool(false)) => reduce(false, false),
+ (RetBool(false), RetBool(true)) => reduce(true, true),
+ (Bool(false), Bool(true)) => reduce(false, true),
+ _ => (),
+ }
+ } else {
+ panic!("IfExpr `then` node is not an `ExprKind::Block`");
+ }
+ }
+ }
+}
+
+declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]);
+
+impl<'tcx> LateLintPass<'tcx> for BoolComparison {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind {
+ let ignore_case = None::<(fn(_) -> _, &str)>;
+ let ignore_no_literal = None::<(fn(_, _) -> _, &str)>;
+ match node {
+ BinOpKind::Eq => {
+ let true_case = Some((|h| h, "equality checks against true are unnecessary"));
+ let false_case = Some((
+ |h: Sugg<'_>| !h,
+ "equality checks against false can be replaced by a negation",
+ ));
+ check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
+ },
+ BinOpKind::Ne => {
+ let true_case = Some((
+ |h: Sugg<'_>| !h,
+ "inequality checks against true can be replaced by a negation",
+ ));
+ let false_case = Some((|h| h, "inequality checks against false are unnecessary"));
+ check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal);
+ },
+ BinOpKind::Lt => check_comparison(
+ cx,
+ e,
+ ignore_case,
+ Some((|h| h, "greater than checks against false are unnecessary")),
+ Some((
+ |h: Sugg<'_>| !h,
+ "less than comparison against true can be replaced by a negation",
+ )),
+ ignore_case,
+ Some((
+ |l: Sugg<'_>, r: Sugg<'_>| (!l).bit_and(&r),
+ "order comparisons between booleans can be simplified",
+ )),
+ ),
+ BinOpKind::Gt => check_comparison(
+ cx,
+ e,
+ Some((
+ |h: Sugg<'_>| !h,
+ "less than comparison against true can be replaced by a negation",
+ )),
+ ignore_case,
+ ignore_case,
+ Some((|h| h, "greater than checks against false are unnecessary")),
+ Some((
+ |l: Sugg<'_>, r: Sugg<'_>| l.bit_and(&(!r)),
+ "order comparisons between booleans can be simplified",
+ )),
+ ),
+ _ => (),
+ }
+ }
+ }
+}
+
+struct ExpressionInfoWithSpan {
+ one_side_is_unary_not: bool,
+ left_span: Span,
+ right_span: Span,
+}
+
+fn is_unary_not(e: &Expr<'_>) -> (bool, Span) {
+ if let ExprKind::Unary(UnOp::Not, operand) = e.kind {
+ return (true, operand.span);
+ }
+ (false, e.span)
+}
+
+fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan {
+ let left = is_unary_not(left_side);
+ let right = is_unary_not(right_side);
+
+ ExpressionInfoWithSpan {
+ one_side_is_unary_not: left.0 != right.0,
+ left_span: left.1,
+ right_span: right.1,
+ }
+}
+
+fn check_comparison<'a, 'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
+ left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
+ right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
+ right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>,
+ no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &str)>,
+) {
+ use self::Expression::{Bool, Other};
+
+ if let ExprKind::Binary(op, left_side, right_side) = e.kind {
+ let (l_ty, r_ty) = (
+ cx.typeck_results().expr_ty(left_side),
+ cx.typeck_results().expr_ty(right_side),
+ );
+ if is_expn_of(left_side.span, "cfg").is_some() || is_expn_of(right_side.span, "cfg").is_some() {
+ return;
+ }
+ if l_ty.is_bool() && r_ty.is_bool() {
+ let mut applicability = Applicability::MachineApplicable;
+
+ if op.node == BinOpKind::Eq {
+ let expression_info = one_side_is_unary_not(left_side, right_side);
+ if expression_info.one_side_is_unary_not {
+ span_lint_and_sugg(
+ cx,
+ BOOL_COMPARISON,
+ e.span,
+ "this comparison might be written more concisely",
+ "try simplifying it as shown",
+ format!(
+ "{} != {}",
+ snippet_with_applicability(cx, expression_info.left_span, "..", &mut applicability),
+ snippet_with_applicability(cx, expression_info.right_span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+
+ match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) {
+ (Bool(true), Other) => left_true.map_or((), |(h, m)| {
+ suggest_bool_comparison(cx, e, right_side, applicability, m, h);
+ }),
+ (Other, Bool(true)) => right_true.map_or((), |(h, m)| {
+ suggest_bool_comparison(cx, e, left_side, applicability, m, h);
+ }),
+ (Bool(false), Other) => left_false.map_or((), |(h, m)| {
+ suggest_bool_comparison(cx, e, right_side, applicability, m, h);
+ }),
+ (Other, Bool(false)) => right_false.map_or((), |(h, m)| {
+ suggest_bool_comparison(cx, e, left_side, applicability, m, h);
+ }),
+ (Other, Other) => no_literal.map_or((), |(h, m)| {
+ let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability);
+ let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ BOOL_COMPARISON,
+ e.span,
+ m,
+ "try simplifying it as shown",
+ h(left_side, right_side).to_string(),
+ applicability,
+ );
+ }),
+ _ => (),
+ }
+ }
+ }
+}
+
+fn suggest_bool_comparison<'a, 'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx Expr<'_>,
+ expr: &Expr<'_>,
+ mut applicability: Applicability,
+ message: &str,
+ conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>,
+) {
+ let hint = if expr.span.from_expansion() {
+ if applicability != Applicability::Unspecified {
+ applicability = Applicability::MaybeIncorrect;
+ }
+ Sugg::hir_with_macro_callsite(cx, expr, "..")
+ } else {
+ Sugg::hir_with_applicability(cx, expr, "..", &mut applicability)
+ };
+ span_lint_and_sugg(
+ cx,
+ BOOL_COMPARISON,
+ e.span,
+ message,
+ "try simplifying it as shown",
+ conv_hint(hint).to_string(),
+ applicability,
+ );
+}
+
+enum Expression {
+ Bool(bool),
+ RetBool(bool),
+ Other,
+}
+
+fn fetch_bool_block(block: &Block<'_>) -> Expression {
+ match (&*block.stmts, block.expr.as_ref()) {
+ (&[], Some(e)) => fetch_bool_expr(&**e),
+ (&[ref e], None) => {
+ if let StmtKind::Semi(e) = e.kind {
+ if let ExprKind::Ret(_) = e.kind {
+ fetch_bool_expr(e)
+ } else {
+ Expression::Other
+ }
+ } else {
+ Expression::Other
+ }
+ },
+ _ => Expression::Other,
+ }
+}
+
+fn fetch_bool_expr(expr: &Expr<'_>) -> Expression {
+ match expr.kind {
+ ExprKind::Block(block, _) => fetch_bool_block(block),
+ ExprKind::Lit(ref lit_ptr) => {
+ if let LitKind::Bool(value) = lit_ptr.node {
+ Expression::Bool(value)
+ } else {
+ Expression::Other
+ }
+ },
+ ExprKind::Ret(Some(expr)) => match fetch_bool_expr(expr) {
+ Expression::Bool(value) => Expression::RetBool(value),
+ _ => Expression::Other,
+ },
+ _ => Expression::Other,
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BindingAnnotation, Mutability, Node, Pat, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bindings that destructure a reference and borrow the inner
+ /// value with `&ref`.
+ ///
+ /// ### Why is this bad?
+ /// This pattern has no effect in almost all cases.
+ ///
+ /// ### Known problems
+ /// In some cases, `&ref` is needed to avoid a lifetime mismatch error.
+ /// Example:
+ /// ```rust
+ /// fn foo(a: &Option<String>, b: &Option<String>) {
+ /// match (a, b) {
+ /// (None, &ref c) | (&ref c, None) => (),
+ /// (&Some(ref c), _) => (),
+ /// };
+ /// }
+ /// ```
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust
+ /// let mut v = Vec::<String>::new();
+ /// let _ = v.iter_mut().filter(|&ref a| a.is_empty());
+ /// ```
+ ///
+ /// Good:
+ /// ```rust
+ /// let mut v = Vec::<String>::new();
+ /// let _ = v.iter_mut().filter(|a| a.is_empty());
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_BORROWED_REFERENCE,
+ complexity,
+ "destructuring a reference and borrowing the inner value"
+}
+
+declare_lint_pass!(NeedlessBorrowedRef => [NEEDLESS_BORROWED_REFERENCE]);
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowedRef {
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ if pat.span.from_expansion() {
+ // OK, simple enough, lints doesn't check in macro.
+ return;
+ }
+
+ if_chain! {
+ // Only lint immutable refs, because `&mut ref T` may be useful.
+ if let PatKind::Ref(sub_pat, Mutability::Not) = pat.kind;
+
+ // Check sub_pat got a `ref` keyword (excluding `ref mut`).
+ if let PatKind::Binding(BindingAnnotation::Ref, .., spanned_name, _) = sub_pat.kind;
+ let parent_id = cx.tcx.hir().get_parent_node(pat.hir_id);
+ if let Some(parent_node) = cx.tcx.hir().find(parent_id);
+ then {
+ // do not recurse within patterns, as they may have other references
+ // XXXManishearth we can relax this constraint if we only check patterns
+ // with a single ref pattern inside them
+ if let Node::Pat(_) = parent_node {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_then(cx, NEEDLESS_BORROWED_REFERENCE, pat.span,
+ "this pattern takes a reference on something that is being de-referenced",
+ |diag| {
+ let hint = snippet_with_applicability(cx, spanned_name.span, "..", &mut applicability).into_owned();
+ diag.span_suggestion(
+ pat.span,
+ "try removing the `&ref` part and just keep",
+ hint,
+ applicability,
+ );
+ });
+ }
+ }
+ }
+}
--- /dev/null
+//! Checks for continue statements in loops that are redundant.
+//!
+//! For example, the lint would catch
+//!
+//! ```rust
+//! let mut a = 1;
+//! let x = true;
+//!
+//! while a < 5 {
+//! a = 6;
+//! if x {
+//! // ...
+//! } else {
+//! continue;
+//! }
+//! println!("Hello, world");
+//! }
+//! ```
+//!
+//! And suggest something like this:
+//!
+//! ```rust
+//! let mut a = 1;
+//! let x = true;
+//!
+//! while a < 5 {
+//! a = 6;
+//! if x {
+//! // ...
+//! println!("Hello, world");
+//! }
+//! }
+//! ```
+//!
+//! This lint is **warn** by default.
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::{indent_of, snippet, snippet_block};
+use rustc_ast::ast;
+use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// The lint checks for `if`-statements appearing in loops
+ /// that contain a `continue` statement in either their main blocks or their
+ /// `else`-blocks, when omitting the `else`-block possibly with some
+ /// rearrangement of code can make the code easier to understand.
+ ///
+ /// ### Why is this bad?
+ /// Having explicit `else` blocks for `if` statements
+ /// containing `continue` in their THEN branch adds unnecessary branching and
+ /// nesting to the code. Having an else block containing just `continue` can
+ /// also be better written by grouping the statements following the whole `if`
+ /// statement within the THEN block and omitting the else block completely.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn condition() -> bool { false }
+ /// # fn update_condition() {}
+ /// # let x = false;
+ /// while condition() {
+ /// update_condition();
+ /// if x {
+ /// // ...
+ /// } else {
+ /// continue;
+ /// }
+ /// println!("Hello, world");
+ /// }
+ /// ```
+ ///
+ /// Could be rewritten as
+ ///
+ /// ```rust
+ /// # fn condition() -> bool { false }
+ /// # fn update_condition() {}
+ /// # let x = false;
+ /// while condition() {
+ /// update_condition();
+ /// if x {
+ /// // ...
+ /// println!("Hello, world");
+ /// }
+ /// }
+ /// ```
+ ///
+ /// As another example, the following code
+ ///
+ /// ```rust
+ /// # fn waiting() -> bool { false }
+ /// loop {
+ /// if waiting() {
+ /// continue;
+ /// } else {
+ /// // Do something useful
+ /// }
+ /// # break;
+ /// }
+ /// ```
+ /// Could be rewritten as
+ ///
+ /// ```rust
+ /// # fn waiting() -> bool { false }
+ /// loop {
+ /// if waiting() {
+ /// continue;
+ /// }
+ /// // Do something useful
+ /// # break;
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_CONTINUE,
+ pedantic,
+ "`continue` statements that can be replaced by a rearrangement of code"
+}
+
+declare_lint_pass!(NeedlessContinue => [NEEDLESS_CONTINUE]);
+
+impl EarlyLintPass for NeedlessContinue {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if !expr.span.from_expansion() {
+ check_and_warn(cx, expr);
+ }
+ }
+}
+
+/* This lint has to mainly deal with two cases of needless continue
+ * statements. */
+// Case 1 [Continue inside else block]:
+//
+// loop {
+// // region A
+// if cond {
+// // region B
+// } else {
+// continue;
+// }
+// // region C
+// }
+//
+// This code can better be written as follows:
+//
+// loop {
+// // region A
+// if cond {
+// // region B
+// // region C
+// }
+// }
+//
+// Case 2 [Continue inside then block]:
+//
+// loop {
+// // region A
+// if cond {
+// continue;
+// // potentially more code here.
+// } else {
+// // region B
+// }
+// // region C
+// }
+//
+//
+// This snippet can be refactored to:
+//
+// loop {
+// // region A
+// if !cond {
+// // region B
+// // region C
+// }
+// }
+//
+
+/// Given an expression, returns true if either of the following is true
+///
+/// - The expression is a `continue` node.
+/// - The expression node is a block with the first statement being a
+/// `continue`.
+fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool {
+ match else_expr.kind {
+ ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),
+ ast::ExprKind::Continue(l) => compare_labels(label, l.as_ref()),
+ _ => false,
+ }
+}
+
+fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
+ block.stmts.get(0).map_or(false, |stmt| match stmt.kind {
+ ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
+ if let ast::ExprKind::Continue(ref l) = e.kind {
+ compare_labels(label, l.as_ref())
+ } else {
+ false
+ }
+ },
+ _ => false,
+ })
+}
+
+/// If the `continue` has a label, check it matches the label of the loop.
+fn compare_labels(loop_label: Option<&ast::Label>, continue_label: Option<&ast::Label>) -> bool {
+ match (loop_label, continue_label) {
+ // `loop { continue; }` or `'a loop { continue; }`
+ (_, None) => true,
+ // `loop { continue 'a; }`
+ (None, _) => false,
+ // `'a loop { continue 'a; }` or `'a loop { continue 'b; }`
+ (Some(x), Some(y)) => x.ident == y.ident,
+ }
+}
+
+/// If `expr` is a loop expression (while/while let/for/loop), calls `func` with
+/// the AST object representing the loop block of `expr`.
+fn with_loop_block<F>(expr: &ast::Expr, mut func: F)
+where
+ F: FnMut(&ast::Block, Option<&ast::Label>),
+{
+ if let ast::ExprKind::While(_, loop_block, label)
+ | ast::ExprKind::ForLoop(_, _, loop_block, label)
+ | ast::ExprKind::Loop(loop_block, label, ..) = &expr.kind
+ {
+ func(loop_block, label.as_ref());
+ }
+}
+
+/// If `stmt` is an if expression node with an `else` branch, calls func with
+/// the
+/// following:
+///
+/// - The `if` expression itself,
+/// - The `if` condition expression,
+/// - The `then` block, and
+/// - The `else` expression.
+fn with_if_expr<F>(stmt: &ast::Stmt, mut func: F)
+where
+ F: FnMut(&ast::Expr, &ast::Expr, &ast::Block, &ast::Expr),
+{
+ match stmt.kind {
+ ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
+ if let ast::ExprKind::If(ref cond, ref if_block, Some(ref else_expr)) = e.kind {
+ func(e, cond, if_block, else_expr);
+ }
+ },
+ _ => {},
+ }
+}
+
+/// A type to distinguish between the two distinct cases this lint handles.
+#[derive(Copy, Clone, Debug)]
+enum LintType {
+ ContinueInsideElseBlock,
+ ContinueInsideThenBlock,
+}
+
+/// Data we pass around for construction of help messages.
+struct LintData<'a> {
+ /// The `if` expression encountered in the above loop.
+ if_expr: &'a ast::Expr,
+ /// The condition expression for the above `if`.
+ if_cond: &'a ast::Expr,
+ /// The `then` block of the `if` statement.
+ if_block: &'a ast::Block,
+ /// The `else` block of the `if` statement.
+ /// Note that we only work with `if` exprs that have an `else` branch.
+ else_expr: &'a ast::Expr,
+ /// The 0-based index of the `if` statement in the containing loop block.
+ stmt_idx: usize,
+ /// The statements of the loop block.
+ loop_block: &'a ast::Block,
+}
+
+const MSG_REDUNDANT_CONTINUE_EXPRESSION: &str = "this `continue` expression is redundant";
+
+const MSG_REDUNDANT_ELSE_BLOCK: &str = "this `else` block is redundant";
+
+const MSG_ELSE_BLOCK_NOT_NEEDED: &str = "there is no need for an explicit `else` block for this `if` \
+ expression";
+
+const DROP_ELSE_BLOCK_AND_MERGE_MSG: &str = "consider dropping the `else` clause and merging the code that \
+ follows (in the loop) with the `if` block";
+
+const DROP_ELSE_BLOCK_MSG: &str = "consider dropping the `else` clause";
+
+const DROP_CONTINUE_EXPRESSION_MSG: &str = "consider dropping the `continue` expression";
+
+fn emit_warning<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>, header: &str, typ: LintType) {
+ // snip is the whole *help* message that appears after the warning.
+ // message is the warning message.
+ // expr is the expression which the lint warning message refers to.
+ let (snip, message, expr) = match typ {
+ LintType::ContinueInsideElseBlock => (
+ suggestion_snippet_for_continue_inside_else(cx, data),
+ MSG_REDUNDANT_ELSE_BLOCK,
+ data.else_expr,
+ ),
+ LintType::ContinueInsideThenBlock => (
+ suggestion_snippet_for_continue_inside_if(cx, data),
+ MSG_ELSE_BLOCK_NOT_NEEDED,
+ data.if_expr,
+ ),
+ };
+ span_lint_and_help(
+ cx,
+ NEEDLESS_CONTINUE,
+ expr.span,
+ message,
+ None,
+ &format!("{}\n{}", header, snip),
+ );
+}
+
+fn suggestion_snippet_for_continue_inside_if<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String {
+ let cond_code = snippet(cx, data.if_cond.span, "..");
+
+ let continue_code = snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span));
+
+ let else_code = snippet_block(cx, data.else_expr.span, "..", Some(data.if_expr.span));
+
+ let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0);
+ format!(
+ "{indent}if {} {}\n{indent}{}",
+ cond_code,
+ continue_code,
+ else_code,
+ indent = " ".repeat(indent_if),
+ )
+}
+
+fn suggestion_snippet_for_continue_inside_else<'a>(cx: &EarlyContext<'_>, data: &'a LintData<'_>) -> String {
+ let cond_code = snippet(cx, data.if_cond.span, "..");
+
+ // Region B
+ let block_code = erode_from_back(&snippet_block(cx, data.if_block.span, "..", Some(data.if_expr.span)));
+
+ // Region C
+ // These is the code in the loop block that follows the if/else construction
+ // we are complaining about. We want to pull all of this code into the
+ // `then` block of the `if` statement.
+ let indent = span_of_first_expr_in_block(data.if_block)
+ .and_then(|span| indent_of(cx, span))
+ .unwrap_or(0);
+ let to_annex = data.loop_block.stmts[data.stmt_idx + 1..]
+ .iter()
+ .map(|stmt| {
+ let span = cx.sess().source_map().stmt_span(stmt.span, data.loop_block.span);
+ let snip = snippet_block(cx, span, "..", None).into_owned();
+ snip.lines()
+ .map(|line| format!("{}{}", " ".repeat(indent), line))
+ .collect::<Vec<_>>()
+ .join("\n")
+ })
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ let indent_if = indent_of(cx, data.if_expr.span).unwrap_or(0);
+ format!(
+ "{indent_if}if {} {}\n{indent}// merged code follows:\n{}\n{indent_if}}}",
+ cond_code,
+ block_code,
+ to_annex,
+ indent = " ".repeat(indent),
+ indent_if = " ".repeat(indent_if),
+ )
+}
+
+fn check_and_warn<'a>(cx: &EarlyContext<'_>, expr: &'a ast::Expr) {
+ if_chain! {
+ if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind;
+ if let Some(last_stmt) = loop_block.stmts.last();
+ if let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind;
+ if let ast::ExprKind::Continue(_) = inner_expr.kind;
+ then {
+ span_lint_and_help(
+ cx,
+ NEEDLESS_CONTINUE,
+ last_stmt.span,
+ MSG_REDUNDANT_CONTINUE_EXPRESSION,
+ None,
+ DROP_CONTINUE_EXPRESSION_MSG,
+ );
+ }
+ }
+ with_loop_block(expr, |loop_block, label| {
+ for (i, stmt) in loop_block.stmts.iter().enumerate() {
+ with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
+ let data = &LintData {
+ stmt_idx: i,
+ if_expr,
+ if_cond: cond,
+ if_block: then_block,
+ else_expr,
+ loop_block,
+ };
+ if needless_continue_in_else(else_expr, label) {
+ emit_warning(
+ cx,
+ data,
+ DROP_ELSE_BLOCK_AND_MERGE_MSG,
+ LintType::ContinueInsideElseBlock,
+ );
+ } else if is_first_block_stmt_continue(then_block, label) {
+ emit_warning(cx, data, DROP_ELSE_BLOCK_MSG, LintType::ContinueInsideThenBlock);
+ }
+ });
+ }
+ });
+}
+
+/// Eats at `s` from the end till a closing brace `}` is encountered, and then continues eating
+/// till a non-whitespace character is found. e.g., the string. If no closing `}` is present, the
+/// string will be preserved.
+///
+/// ```rust
+/// {
+/// let x = 5;
+/// }
+/// ```
+///
+/// is transformed to
+///
+/// ```text
+/// {
+/// let x = 5;
+/// ```
+#[must_use]
+fn erode_from_back(s: &str) -> String {
+ let mut ret = s.to_string();
+ while ret.pop().map_or(false, |c| c != '}') {}
+ while let Some(c) = ret.pop() {
+ if !c.is_whitespace() {
+ ret.push(c);
+ break;
+ }
+ }
+ if ret.is_empty() { s.to_string() } else { ret }
+}
+
+fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
+ block.stmts.get(0).map(|stmt| stmt.span)
+}
+
+#[cfg(test)]
+mod test {
+ use super::erode_from_back;
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_erode_from_back() {
+ let input = "\
+{
+ let x = 5;
+ let y = format!(\"{}\", 42);
+}";
+
+ let expected = "\
+{
+ let x = 5;
+ let y = format!(\"{}\", 42);";
+
+ let got = erode_from_back(input);
+ assert_eq!(expected, got);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_erode_from_back_no_brace() {
+ let input = "\
+let x = 5;
+let y = something();
+";
+ let expected = input;
+ let got = erode_from_back(input);
+ assert_eq!(expected, got);
+ }
+}
--- /dev/null
+use rustc_errors::Applicability;
+use rustc_hir::{
+ intravisit::{walk_expr, NestedVisitorMap, Visitor},
+ Expr, ExprKind, Stmt, StmtKind,
+};
+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, sym, Symbol};
+
+use if_chain::if_chain;
+
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::is_trait_method;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::has_iter_method;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `for_each` that would be more simply written as a
+ /// `for` loop.
+ ///
+ /// ### Why is this bad?
+ /// `for_each` may be used after applying iterator transformers like
+ /// `filter` for better readability and performance. It may also be used to fit a simple
+ /// operation on one line.
+ /// But when none of these apply, a simple `for` loop is more idiomatic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let v = vec![0, 1, 2];
+ /// v.iter().for_each(|elem| {
+ /// println!("{}", elem);
+ /// })
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let v = vec![0, 1, 2];
+ /// for elem in v.iter() {
+ /// println!("{}", elem);
+ /// }
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub NEEDLESS_FOR_EACH,
+ pedantic,
+ "using `for_each` where a `for` loop would be simpler"
+}
+
+declare_lint_pass!(NeedlessForEach => [NEEDLESS_FOR_EACH]);
+
+impl LateLintPass<'_> for NeedlessForEach {
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ let expr = match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => expr,
+ _ => return,
+ };
+
+ if_chain! {
+ // Check the method name is `for_each`.
+ if let ExprKind::MethodCall(method_name, _, [for_each_recv, for_each_arg], _) = expr.kind;
+ if method_name.ident.name == Symbol::intern("for_each");
+ // Check `for_each` is an associated function of `Iterator`.
+ if is_trait_method(cx, expr, sym::Iterator);
+ // Checks the receiver of `for_each` is also a method call.
+ if let ExprKind::MethodCall(_, _, [iter_recv], _) = for_each_recv.kind;
+ // Skip the lint if the call chain is too long. e.g. `v.field.iter().for_each()` or
+ // `v.foo().iter().for_each()` must be skipped.
+ if matches!(
+ iter_recv.kind,
+ ExprKind::Array(..) | ExprKind::Call(..) | ExprKind::Path(..)
+ );
+ // Checks the type of the `iter` method receiver is NOT a user defined type.
+ if has_iter_method(cx, cx.typeck_results().expr_ty(iter_recv)).is_some();
+ // Skip the lint if the body is not block because this is simpler than `for` loop.
+ // e.g. `v.iter().for_each(f)` is simpler and clearer than using `for` loop.
+ if let ExprKind::Closure(_, _, body_id, ..) = for_each_arg.kind;
+ let body = cx.tcx.hir().body(body_id);
+ if let ExprKind::Block(..) = body.value.kind;
+ then {
+ let mut ret_collector = RetCollector::default();
+ ret_collector.visit_expr(&body.value);
+
+ // Skip the lint if `return` is used in `Loop` in order not to suggest using `'label`.
+ if ret_collector.ret_in_loop {
+ return;
+ }
+
+ let (mut applicability, ret_suggs) = if ret_collector.spans.is_empty() {
+ (Applicability::MachineApplicable, None)
+ } else {
+ (
+ Applicability::MaybeIncorrect,
+ Some(
+ ret_collector
+ .spans
+ .into_iter()
+ .map(|span| (span, "continue".to_string()))
+ .collect(),
+ ),
+ )
+ };
+
+ let sugg = format!(
+ "for {} in {} {}",
+ snippet_with_applicability(cx, body.params[0].pat.span, "..", &mut applicability),
+ snippet_with_applicability(cx, for_each_recv.span, "..", &mut applicability),
+ snippet_with_applicability(cx, body.value.span, "..", &mut applicability),
+ );
+
+ span_lint_and_then(cx, NEEDLESS_FOR_EACH, stmt.span, "needless use of `for_each`", |diag| {
+ diag.span_suggestion(stmt.span, "try", sugg, applicability);
+ if let Some(ret_suggs) = ret_suggs {
+ diag.multipart_suggestion("...and replace `return` with `continue`", ret_suggs, applicability);
+ }
+ })
+ }
+ }
+ }
+}
+
+/// This type plays two roles.
+/// 1. Collect spans of `return` in the closure body.
+/// 2. Detect use of `return` in `Loop` in the closure body.
+///
+/// NOTE: The functionality of this type is similar to
+/// [`clippy_utils::visitors::find_all_ret_expressions`], but we can't use
+/// `find_all_ret_expressions` instead of this type. The reasons are:
+/// 1. `find_all_ret_expressions` passes the argument of `ExprKind::Ret` to a callback, but what we
+/// need here is `ExprKind::Ret` itself.
+/// 2. We can't trace current loop depth with `find_all_ret_expressions`.
+#[derive(Default)]
+struct RetCollector {
+ spans: Vec<Span>,
+ ret_in_loop: bool,
+ loop_depth: u16,
+}
+
+impl<'tcx> Visitor<'tcx> for RetCollector {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &Expr<'_>) {
+ match expr.kind {
+ ExprKind::Ret(..) => {
+ if self.loop_depth > 0 && !self.ret_in_loop {
+ self.ret_in_loop = true;
+ }
+
+ self.spans.push(expr.span);
+ },
+
+ ExprKind::Loop(..) => {
+ self.loop_depth += 1;
+ walk_expr(self, expr);
+ self.loop_depth -= 1;
+ return;
+ },
+
+ _ => {},
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use clippy_utils::path_to_local;
++use clippy_utils::source::snippet_opt;
++use clippy_utils::visitors::{expr_visitor, is_local_used};
++use rustc_errors::Applicability;
++use rustc_hir::intravisit::Visitor;
++use rustc_hir::{Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind};
++use rustc_lint::{LateContext, LateLintPass};
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_span::Span;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for late initializations that can be replaced by a `let` statement
++ /// with an initializer.
++ ///
++ /// ### Why is this bad?
++ /// Assigning in the `let` statement is less repetitive.
++ ///
++ /// ### Example
++ /// ```rust
++ /// let a;
++ /// a = 1;
++ ///
++ /// let b;
++ /// match 3 {
++ /// 0 => b = "zero",
++ /// 1 => b = "one",
++ /// _ => b = "many",
++ /// }
++ ///
++ /// let c;
++ /// if true {
++ /// c = 1;
++ /// } else {
++ /// c = -1;
++ /// }
++ /// ```
++ /// Use instead:
++ /// ```rust
++ /// let a = 1;
++ ///
++ /// let b = match 3 {
++ /// 0 => "zero",
++ /// 1 => "one",
++ /// _ => "many",
++ /// };
++ ///
++ /// let c = if true {
++ /// 1
++ /// } else {
++ /// -1
++ /// };
++ /// ```
++ #[clippy::version = "1.58.0"]
++ pub NEEDLESS_LATE_INIT,
++ style,
++ "late initializations that can be replaced by a `let` statement with an initializer"
++}
++declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
++
++fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
++ let mut seen = false;
++ expr_visitor(cx, |expr| {
++ if let ExprKind::Assign(..) = expr.kind {
++ seen = true;
++ }
++
++ !seen
++ })
++ .visit_stmt(stmt);
++
++ seen
++}
++
++#[derive(Debug)]
++struct LocalAssign {
++ lhs_id: HirId,
++ lhs_span: Span,
++ rhs_span: Span,
++ span: Span,
++}
++
++impl LocalAssign {
++ fn from_expr(expr: &Expr<'_>, span: Span) -> Option<Self> {
++ if let ExprKind::Assign(lhs, rhs, _) = expr.kind {
++ if lhs.span.from_expansion() {
++ return None;
++ }
++
++ Some(Self {
++ lhs_id: path_to_local(lhs)?,
++ lhs_span: lhs.span,
++ rhs_span: rhs.span.source_callsite(),
++ span,
++ })
++ } else {
++ None
++ }
++ }
++
++ fn new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, binding_id: HirId) -> Option<LocalAssign> {
++ let assign = match expr.kind {
++ ExprKind::Block(Block { expr: Some(expr), .. }, _) => Self::from_expr(expr, expr.span),
++ ExprKind::Block(block, _) => {
++ if_chain! {
++ if let Some((last, other_stmts)) = block.stmts.split_last();
++ if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = last.kind;
++
++ let assign = Self::from_expr(expr, last.span)?;
++
++ // avoid visiting if not needed
++ if assign.lhs_id == binding_id;
++ if other_stmts.iter().all(|stmt| !contains_assign_expr(cx, stmt));
++
++ then {
++ Some(assign)
++ } else {
++ None
++ }
++ }
++ },
++ ExprKind::Assign(..) => Self::from_expr(expr, expr.span),
++ _ => None,
++ }?;
++
++ if assign.lhs_id == binding_id {
++ Some(assign)
++ } else {
++ None
++ }
++ }
++}
++
++fn assignment_suggestions<'tcx>(
++ cx: &LateContext<'tcx>,
++ binding_id: HirId,
++ exprs: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
++) -> Option<(Applicability, Vec<(Span, String)>)> {
++ let mut assignments = Vec::new();
++
++ for expr in exprs {
++ let ty = cx.typeck_results().expr_ty(expr);
++
++ if ty.is_never() {
++ continue;
++ }
++ if !ty.is_unit() {
++ return None;
++ }
++
++ let assign = LocalAssign::new(cx, expr, binding_id)?;
++
++ assignments.push(assign);
++ }
++
++ let suggestions = assignments
++ .into_iter()
++ .map(|assignment| Some((assignment.span, snippet_opt(cx, assignment.rhs_span)?)))
++ .collect::<Option<Vec<(Span, String)>>>()?;
++
++ let applicability = if suggestions.len() > 1 {
++ // multiple suggestions don't work with rustfix in multipart_suggest
++ // https://github.com/rust-lang/rustfix/issues/141
++ Applicability::Unspecified
++ } else {
++ Applicability::MachineApplicable
++ };
++ Some((applicability, suggestions))
++}
++
++struct Usage<'tcx> {
++ stmt: &'tcx Stmt<'tcx>,
++ expr: &'tcx Expr<'tcx>,
++ needs_semi: bool,
++}
++
++fn first_usage<'tcx>(
++ cx: &LateContext<'tcx>,
++ binding_id: HirId,
++ local_stmt_id: HirId,
++ block: &'tcx Block<'tcx>,
++) -> Option<Usage<'tcx>> {
++ block
++ .stmts
++ .iter()
++ .skip_while(|stmt| stmt.hir_id != local_stmt_id)
++ .skip(1)
++ .find(|&stmt| is_local_used(cx, stmt, binding_id))
++ .and_then(|stmt| match stmt.kind {
++ StmtKind::Expr(expr) => Some(Usage {
++ stmt,
++ expr,
++ needs_semi: true,
++ }),
++ StmtKind::Semi(expr) => Some(Usage {
++ stmt,
++ expr,
++ needs_semi: false,
++ }),
++ _ => None,
++ })
++}
++
++fn local_snippet_without_semicolon(cx: &LateContext<'_>, local: &Local<'_>) -> Option<String> {
++ let span = local.span.with_hi(match local.ty {
++ // let <pat>: <ty>;
++ // ~~~~~~~~~~~~~~~
++ Some(ty) => ty.span.hi(),
++ // let <pat>;
++ // ~~~~~~~~~
++ None => local.pat.span.hi(),
++ });
++
++ snippet_opt(cx, span)
++}
++
++fn check<'tcx>(
++ cx: &LateContext<'tcx>,
++ local: &'tcx Local<'tcx>,
++ local_stmt: &'tcx Stmt<'tcx>,
++ block: &'tcx Block<'tcx>,
++ binding_id: HirId,
++) -> Option<()> {
++ let usage = first_usage(cx, binding_id, local_stmt.hir_id, block)?;
++ let binding_name = cx.tcx.hir().opt_name(binding_id)?;
++ let let_snippet = local_snippet_without_semicolon(cx, local)?;
++
++ match usage.expr.kind {
++ ExprKind::Assign(..) => {
++ let assign = LocalAssign::new(cx, usage.expr, binding_id)?;
++
++ span_lint_and_then(
++ cx,
++ NEEDLESS_LATE_INIT,
++ local_stmt.span,
++ "unneeded late initalization",
++ |diag| {
++ diag.tool_only_span_suggestion(
++ local_stmt.span,
++ "remove the local",
++ String::new(),
++ Applicability::MachineApplicable,
++ );
++
++ diag.span_suggestion(
++ assign.lhs_span,
++ &format!("declare `{}` here", binding_name),
++ let_snippet,
++ Applicability::MachineApplicable,
++ );
++ },
++ );
++ },
++ ExprKind::If(_, then_expr, Some(else_expr)) => {
++ let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
++
++ span_lint_and_then(
++ cx,
++ NEEDLESS_LATE_INIT,
++ local_stmt.span,
++ "unneeded late initalization",
++ |diag| {
++ diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
++
++ diag.span_suggestion_verbose(
++ usage.stmt.span.shrink_to_lo(),
++ &format!("declare `{}` here", binding_name),
++ format!("{} = ", let_snippet),
++ applicability,
++ );
++
++ diag.multipart_suggestion("remove the assignments from the branches", suggestions, applicability);
++
++ if usage.needs_semi {
++ diag.span_suggestion(
++ usage.stmt.span.shrink_to_hi(),
++ "add a semicolon after the `if` expression",
++ ";".to_string(),
++ applicability,
++ );
++ }
++ },
++ );
++ },
++ ExprKind::Match(_, arms, MatchSource::Normal) => {
++ let (applicability, suggestions) = assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
++
++ span_lint_and_then(
++ cx,
++ NEEDLESS_LATE_INIT,
++ local_stmt.span,
++ "unneeded late initalization",
++ |diag| {
++ diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
++
++ diag.span_suggestion_verbose(
++ usage.stmt.span.shrink_to_lo(),
++ &format!("declare `{}` here", binding_name),
++ format!("{} = ", let_snippet),
++ applicability,
++ );
++
++ diag.multipart_suggestion(
++ "remove the assignments from the `match` arms",
++ suggestions,
++ applicability,
++ );
++
++ if usage.needs_semi {
++ diag.span_suggestion(
++ usage.stmt.span.shrink_to_hi(),
++ "add a semicolon after the `match` expression",
++ ";".to_string(),
++ applicability,
++ );
++ }
++ },
++ );
++ },
++ _ => {},
++ };
++
++ Some(())
++}
++
++impl LateLintPass<'tcx> for NeedlessLateInit {
++ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
++ let mut parents = cx.tcx.hir().parent_iter(local.hir_id);
++
++ if_chain! {
++ if let Local {
++ init: None,
++ pat: &Pat {
++ kind: PatKind::Binding(_, binding_id, _, None),
++ ..
++ },
++ source: LocalSource::Normal,
++ ..
++ } = local;
++ if let Some((_, Node::Stmt(local_stmt))) = parents.next();
++ if let Some((_, Node::Block(block))) = parents.next();
++
++ then {
++ check(cx, local, local_stmt, block, binding_id);
++ }
++ }
++ }
++}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if expr.span.from_expansion() || in_macro(expr.span) {
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::is_type_diagnostic_item;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::TyS;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for no-op uses of Option::{as_deref,as_deref_mut},
+ /// for example, `Option<&T>::as_deref()` returns the same type.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code and improving readability.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let a = Some(&1);
+ /// let b = a;
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub NEEDLESS_OPTION_AS_DEREF,
+ complexity,
+ "no-op use of `deref` or `deref_mut` method to `Option`."
+}
+
+declare_lint_pass!(OptionNeedlessDeref=> [
+ NEEDLESS_OPTION_AS_DEREF,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for OptionNeedlessDeref {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if expr.span.from_expansion() {
+ return;
+ }
+ let typeck = cx.typeck_results();
+ let outer_ty = typeck.expr_ty(expr);
+
+ if_chain! {
+ if is_type_diagnostic_item(cx,outer_ty,sym::Option);
+ if let ExprKind::MethodCall(path, _, [sub_expr], _) = expr.kind;
+ let symbol = path.ident.as_str();
+ if symbol=="as_deref" || symbol=="as_deref_mut";
+ if TyS::same_type( outer_ty, typeck.expr_ty(sub_expr) );
+ then{
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_OPTION_AS_DEREF,
+ expr.span,
+ "derefed type is same as origin",
+ "try this",
+ snippet_opt(cx,sub_expr.span).unwrap(),
+ Applicability::MachineApplicable
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
+use clippy_utils::ptr::get_spans;
+use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::ty::{implements_trait, is_copy, is_type_diagnostic_item};
+use clippy_utils::{get_trait_def_id, is_self, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::Attribute;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Node, PatKind, QPath, TyKind};
+use rustc_hir::{HirIdMap, HirIdSet};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty::{self, TypeFoldable};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, Span};
+use rustc_target::spec::abi::Abi;
+use rustc_trait_selection::traits;
+use rustc_trait_selection::traits::misc::can_type_implement_copy;
+use rustc_typeck::expr_use_visitor as euv;
+use std::borrow::Cow;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by value, but not
+ /// consuming them in its
+ /// body.
+ ///
+ /// ### Why is this bad?
+ /// Taking arguments by reference is more flexible and can
+ /// sometimes avoid
+ /// unnecessary allocations.
+ ///
+ /// ### Known problems
+ /// * This lint suggests taking an argument by reference,
+ /// however sometimes it is better to let users decide the argument type
+ /// (by using `Borrow` trait, for example), depending on how the function is used.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(v: Vec<i32>) {
+ /// assert_eq!(v.len(), 42);
+ /// }
+ /// ```
+ /// should be
+ /// ```rust
+ /// fn foo(v: &[i32]) {
+ /// assert_eq!(v.len(), 42);
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_PASS_BY_VALUE,
+ pedantic,
+ "functions taking arguments by value, but not consuming them in its body"
+}
+
+declare_lint_pass!(NeedlessPassByValue => [NEEDLESS_PASS_BY_VALUE]);
+
+macro_rules! need {
+ ($e: expr) => {
+ if let Some(x) = $e {
+ x
+ } else {
+ return;
+ }
+ };
+}
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
+ #[allow(clippy::too_many_lines)]
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if span.from_expansion() {
+ return;
+ }
+
+ match kind {
+ FnKind::ItemFn(.., header, _) => {
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ if header.abi != Abi::Rust || requires_exact_signature(attrs) {
+ return;
+ }
+ },
+ FnKind::Method(..) => (),
+ FnKind::Closure => return,
+ }
+
+ // Exclude non-inherent impls
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ if matches!(
+ item.kind,
+ ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
+ ) {
+ return;
+ }
+ }
+
+ // Allow `Borrow` or functions to be taken by value
+ let allowed_traits = [
+ need!(cx.tcx.lang_items().fn_trait()),
+ need!(cx.tcx.lang_items().fn_once_trait()),
+ need!(cx.tcx.lang_items().fn_mut_trait()),
+ need!(get_trait_def_id(cx, &paths::RANGE_ARGUMENT_TRAIT)),
+ ];
+
+ let sized_trait = need!(cx.tcx.lang_items().sized_trait());
+
+ let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
+
+ let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter())
+ .filter(|p| !p.is_global(cx.tcx))
+ .filter_map(|obligation| {
+ // Note that we do not want to deal with qualified predicates here.
+ match obligation.predicate.kind().no_bound_vars() {
+ Some(ty::PredicateKind::Trait(pred)) if pred.def_id() != sized_trait => Some(pred),
+ _ => None,
+ }
+ })
+ .collect::<Vec<_>>();
+
+ // Collect moved variables and spans which will need dereferencings from the
+ // function body.
+ let MovedVariablesCtxt {
+ moved_vars,
+ spans_need_deref,
+ ..
+ } = {
+ let mut ctx = MovedVariablesCtxt::default();
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
+ .consume_body(body);
+ });
+ ctx
+ };
+
+ let fn_sig = cx.tcx.fn_sig(fn_def_id);
+ let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
+
+ for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() {
+ // All spans generated from a proc-macro invocation are the same...
+ if span == input.span {
+ return;
+ }
+
+ // Ignore `self`s.
+ if idx == 0 {
+ if let PatKind::Binding(.., ident, _) = arg.pat.kind {
+ if ident.name == kw::SelfLower {
+ continue;
+ }
+ }
+ }
+
+ //
+ // * Exclude a type that is specifically bounded by `Borrow`.
+ // * Exclude a type whose reference also fulfills its bound. (e.g., `std::convert::AsRef`,
+ // `serde::Serialize`)
+ let (implements_borrow_trait, all_borrowable_trait) = {
+ let preds = preds.iter().filter(|t| t.self_ty() == ty).collect::<Vec<_>>();
+
+ (
+ preds.iter().any(|t| cx.tcx.is_diagnostic_item(sym::Borrow, t.def_id())),
+ !preds.is_empty() && {
+ let ty_empty_region = cx.tcx.mk_imm_ref(cx.tcx.lifetimes.re_root_empty, ty);
+ preds.iter().all(|t| {
+ let ty_params = t.trait_ref.substs.iter().skip(1).collect::<Vec<_>>();
+ implements_trait(cx, ty_empty_region, t.def_id(), &ty_params)
+ })
+ },
+ )
+ };
+
+ if_chain! {
+ if !is_self(arg);
+ if !ty.is_mutable_ptr();
+ if !is_copy(cx, ty);
+ if !allowed_traits.iter().any(|&t| implements_trait(cx, ty, t, &[]));
+ if !implements_borrow_trait;
+ if !all_borrowable_trait;
+
+ if let PatKind::Binding(mode, canonical_id, ..) = arg.pat.kind;
+ if !moved_vars.contains(&canonical_id);
+ then {
+ if mode == BindingAnnotation::Mutable || mode == BindingAnnotation::RefMut {
+ continue;
+ }
+
+ // Dereference suggestion
+ let sugg = |diag: &mut DiagnosticBuilder<'_>| {
+ if let ty::Adt(def, ..) = ty.kind() {
+ if let Some(span) = cx.tcx.hir().span_if_local(def.did) {
+ if can_type_implement_copy(cx.tcx, cx.param_env, ty).is_ok() {
+ diag.span_help(span, "consider marking this type as `Copy`");
+ }
+ }
+ }
+
+ let deref_span = spans_need_deref.get(&canonical_id);
+ if_chain! {
+ if is_type_diagnostic_item(cx, ty, sym::Vec);
+ if let Some(clone_spans) =
+ get_spans(cx, Some(body.id()), idx, &[("clone", ".to_owned()")]);
+ if let TyKind::Path(QPath::Resolved(_, path)) = input.kind;
+ if let Some(elem_ty) = path.segments.iter()
+ .find(|seg| seg.ident.name == sym::Vec)
+ .and_then(|ps| ps.args.as_ref())
+ .map(|params| params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ }).unwrap());
+ then {
+ let slice_ty = format!("&[{}]", snippet(cx, elem_ty.span, "_"));
+ diag.span_suggestion(
+ input.span,
+ "consider changing the type to",
+ slice_ty,
+ Applicability::Unspecified,
+ );
+
+ for (span, suggestion) in clone_spans {
+ diag.span_suggestion(
+ span,
+ &snippet_opt(cx, span)
+ .map_or(
+ "change the call to".into(),
+ |x| Cow::from(format!("change `{}` to", x)),
+ ),
+ suggestion.into(),
+ Applicability::Unspecified,
+ );
+ }
+
+ // cannot be destructured, no need for `*` suggestion
+ assert!(deref_span.is_none());
+ return;
+ }
+ }
+
+ if is_type_diagnostic_item(cx, ty, sym::String) {
+ if let Some(clone_spans) =
+ get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) {
+ diag.span_suggestion(
+ input.span,
+ "consider changing the type to",
+ "&str".to_string(),
+ Applicability::Unspecified,
+ );
+
+ for (span, suggestion) in clone_spans {
+ diag.span_suggestion(
+ span,
+ &snippet_opt(cx, span)
+ .map_or(
+ "change the call to".into(),
+ |x| Cow::from(format!("change `{}` to", x))
+ ),
+ suggestion.into(),
+ Applicability::Unspecified,
+ );
+ }
+
+ assert!(deref_span.is_none());
+ return;
+ }
+ }
+
+ let mut spans = vec![(input.span, format!("&{}", snippet(cx, input.span, "_")))];
+
+ // Suggests adding `*` to dereference the added reference.
+ if let Some(deref_span) = deref_span {
+ spans.extend(
+ deref_span
+ .iter()
+ .copied()
+ .map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
+ );
+ spans.sort_by_key(|&(span, _)| span);
+ }
+ multispan_sugg(diag, "consider taking a reference instead", spans);
+ };
+
+ span_lint_and_then(
+ cx,
+ NEEDLESS_PASS_BY_VALUE,
+ input.span,
+ "this argument is passed by value, but not consumed in the function body",
+ sugg,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Functions marked with these attributes must have the exact signature.
+fn requires_exact_signature(attrs: &[Attribute]) -> bool {
+ attrs.iter().any(|attr| {
+ [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
+ .iter()
+ .any(|&allow| attr.has_name(allow))
+ })
+}
+
+#[derive(Default)]
+struct MovedVariablesCtxt {
+ moved_vars: HirIdSet,
+ /// Spans which need to be prefixed with `*` for dereferencing the
+ /// suggested additional reference.
+ spans_need_deref: HirIdMap<FxHashSet<Span>>,
+}
+
+impl MovedVariablesCtxt {
+ fn move_common(&mut self, cmt: &euv::PlaceWithHirId<'_>) {
+ if let euv::PlaceBase::Local(vid) = cmt.place.base {
+ self.moved_vars.insert(vid);
+ }
+ }
+}
+
+impl<'tcx> euv::Delegate<'tcx> for MovedVariablesCtxt {
+ fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _: HirId) {
+ self.move_common(cmt);
+ }
+
+ fn borrow(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}
+
+ fn mutate(&mut self, _: &euv::PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
+}
--- /dev/null
- let inner_expr = if_chain! {
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_lang_ctor;
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionSome, ResultOk};
+use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::TyS;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Suggests alternatives for useless applications of `?` in terminating expressions
+ ///
+ /// ### Why is this bad?
+ /// There's no reason to use `?` to short-circuit when execution of the body will end there anyway.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct TO {
+ /// magic: Option<usize>,
+ /// }
+ ///
+ /// fn f(to: TO) -> Option<usize> {
+ /// Some(to.magic?)
+ /// }
+ ///
+ /// struct TR {
+ /// magic: Result<usize, bool>,
+ /// }
+ ///
+ /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
+ /// tr.and_then(|t| Ok(t.magic?))
+ /// }
+ ///
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct TO {
+ /// magic: Option<usize>,
+ /// }
+ ///
+ /// fn f(to: TO) -> Option<usize> {
+ /// to.magic
+ /// }
+ ///
+ /// struct TR {
+ /// magic: Result<usize, bool>,
+ /// }
+ ///
+ /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
+ /// tr.and_then(|t| t.magic)
+ /// }
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub NEEDLESS_QUESTION_MARK,
+ complexity,
+ "Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result<T, E>`."
+}
+
+declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);
+
+impl LateLintPass<'_> for NeedlessQuestionMark {
+ /*
+ * The question mark operator is compatible with both Result<T, E> and Option<T>,
+ * from Rust 1.13 and 1.22 respectively.
+ */
+
+ /*
+ * What do we match:
+ * Expressions that look like this:
+ * Some(option?), Ok(result?)
+ *
+ * Where do we match:
+ * Last expression of a body
+ * Return statement
+ * A body's value (single line closure)
+ *
+ * What do we not match:
+ * Implicit calls to `from(..)` on the error value
+ */
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
+ if let ExprKind::Ret(Some(e)) = expr.kind {
+ check(cx, e);
+ }
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
+ check(cx, body.value.peel_blocks());
+ }
+}
+
+fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
- if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
++ if_chain! {
+ if let ExprKind::Call(path, [arg]) = &expr.kind;
+ if let ExprKind::Path(ref qpath) = &path.kind;
- then { inner_expr } else { return; }
- };
- span_lint_and_sugg(
- cx,
- NEEDLESS_QUESTION_MARK,
- expr.span,
- "question mark operator is useless here",
- "try",
- format!("{}", snippet(cx, inner_expr.span, r#""...""#)),
- Applicability::MachineApplicable,
- );
++ let sugg_remove = if is_lang_ctor(cx, qpath, OptionSome) {
++ "Some()"
++ } else if is_lang_ctor(cx, qpath, ResultOk) {
++ "Ok()"
++ } else {
++ return;
++ };
+ if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &arg.kind;
+ if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
+ if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)) = &called.kind;
+ if expr.span.ctxt() == inner_expr.span.ctxt();
+ let expr_ty = cx.typeck_results().expr_ty(expr);
+ let inner_ty = cx.typeck_results().expr_ty(inner_expr);
+ if TyS::same_type(expr_ty, inner_ty);
++ then {
++ span_lint_and_sugg(
++ cx,
++ NEEDLESS_QUESTION_MARK,
++ expr.span,
++ "question mark operator is useless here",
++ &format!("try removing question mark and `{}`", sugg_remove),
++ format!("{}", snippet(cx, inner_expr.span, r#""...""#)),
++ Applicability::MachineApplicable,
++ );
++ }
++ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for needlessly including a base struct on update
+ /// when all fields are changed anyway.
+ ///
+ /// This lint is not applied to structs marked with
+ /// [non_exhaustive](https://doc.rust-lang.org/reference/attributes/type_system.html).
+ ///
+ /// ### Why is this bad?
+ /// This will cost resources (because the base has to be
+ /// somewhere), and make the code less readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Point {
+ /// # x: i32,
+ /// # y: i32,
+ /// # z: i32,
+ /// # }
+ /// # let zero_point = Point { x: 0, y: 0, z: 0 };
+ ///
+ /// // Bad
+ /// Point {
+ /// x: 1,
+ /// y: 1,
+ /// z: 1,
+ /// ..zero_point
+ /// };
+ ///
+ /// // Ok
+ /// Point {
+ /// x: 1,
+ /// y: 1,
+ /// ..zero_point
+ /// };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_UPDATE,
+ complexity,
+ "using `Foo { ..base }` when there are no missing fields"
+}
+
+declare_lint_pass!(NeedlessUpdate => [NEEDLESS_UPDATE]);
+
+impl<'tcx> LateLintPass<'tcx> for NeedlessUpdate {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Struct(_, fields, Some(base)) = expr.kind {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if let ty::Adt(def, _) = ty.kind() {
+ if fields.len() == def.non_enum_variant().fields.len()
+ && !def.variants[0_usize.into()].is_field_list_non_exhaustive()
+ {
+ span_lint(
+ cx,
+ NEEDLESS_UPDATE,
+ base.span,
+ "struct update has no effect, all the fields in the struct have already been specified",
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::implements_trait;
+use clippy_utils::{self, get_trait_def_id, paths};
+use if_chain::if_chain;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the usage of negated comparison operators on types which only implement
+ /// `PartialOrd` (e.g., `f64`).
+ ///
+ /// ### Why is this bad?
+ /// These operators make it easy to forget that the underlying types actually allow not only three
+ /// potential Orderings (Less, Equal, Greater) but also a fourth one (Uncomparable). This is
+ /// especially easy to miss if the operator based comparison result is negated.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::cmp::Ordering;
+ ///
+ /// // Bad
+ /// let a = 1.0;
+ /// let b = f64::NAN;
+ ///
+ /// let _not_less_or_equal = !(a <= b);
+ ///
+ /// // Good
+ /// let a = 1.0;
+ /// let b = f64::NAN;
+ ///
+ /// let _not_less_or_equal = match a.partial_cmp(&b) {
+ /// None | Some(Ordering::Greater) => true,
+ /// _ => false,
+ /// };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEG_CMP_OP_ON_PARTIAL_ORD,
+ complexity,
+ "The use of negated comparison operators on partially ordered types may produce confusing code."
+}
+
+declare_lint_pass!(NoNegCompOpForPartialOrd => [NEG_CMP_OP_ON_PARTIAL_ORD]);
+
+impl<'tcx> LateLintPass<'tcx> for NoNegCompOpForPartialOrd {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+
+ if !in_external_macro(cx.sess(), expr.span);
+ if let ExprKind::Unary(UnOp::Not, inner) = expr.kind;
+ if let ExprKind::Binary(ref op, left, _) = inner.kind;
+ if let BinOpKind::Le | BinOpKind::Ge | BinOpKind::Lt | BinOpKind::Gt = op.node;
+
+ then {
+
+ let ty = cx.typeck_results().expr_ty(left);
+
+ let implements_ord = {
+ if let Some(id) = get_trait_def_id(cx, &paths::ORD) {
+ implements_trait(cx, ty, id, &[])
+ } else {
+ return;
+ }
+ };
+
+ let implements_partial_ord = {
+ if let Some(id) = cx.tcx.lang_items().partial_ord_trait() {
+ implements_trait(cx, ty, id, &[])
+ } else {
+ return;
+ }
+ };
+
+ if implements_partial_ord && !implements_ord {
+ span_lint(
+ cx,
+ NEG_CMP_OP_ON_PARTIAL_ORD,
+ expr.span,
+ "the use of negated comparison operators on partially ordered \
+ types produces code that is hard to read and refactor, please \
+ consider using the `partial_cmp` method instead, to make it \
+ clear that the two values could be incomparable"
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::consts::{self, Constant};
+use clippy_utils::diagnostics::span_lint;
+use if_chain::if_chain;
+use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for multiplication by -1 as a form of negation.
+ ///
+ /// ### Why is this bad?
+ /// It's more readable to just negate.
+ ///
+ /// ### Known problems
+ /// This only catches integers (for now).
+ ///
+ /// ### Example
+ /// ```ignore
+ /// x * -1
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEG_MULTIPLY,
+ style,
+ "multiplying integers with `-1`"
+}
+
+declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]);
+
+#[allow(clippy::match_same_arms)]
+impl<'tcx> LateLintPass<'tcx> for NegMultiply {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref op, left, right) = e.kind {
+ if BinOpKind::Mul == op.node {
+ match (&left.kind, &right.kind) {
+ (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {},
+ (&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e.span, lit, right),
+ (_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e.span, lit, left),
+ _ => {},
+ }
+ }
+ }
+ }
+}
+
+fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Lit(ref l) = lit.kind;
+ if consts::lit_to_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)) == Constant::Int(1);
+ if cx.typeck_results().expr_ty(exp).is_integral();
+ then {
+ span_lint(cx, NEG_MULTIPLY, span, "negation by multiplying with `-1`");
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_hir_and_then;
+use clippy_utils::return_ty;
+use clippy_utils::source::snippet;
+use clippy_utils::sugg::DiagnosticBuilderExt;
+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::TyS;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for types with a `fn new() -> Self` method and no
+ /// implementation of
+ /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html).
+ ///
+ /// ### Why is this bad?
+ /// The user might expect to be able to use
+ /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html) as the
+ /// type can be constructed without arguments.
+ ///
+ /// ### 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()
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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(hir::Impl {
+ of_trait: None,
+ ref generics,
+ self_ty: impl_self_ty,
+ items,
+ ..
+ }) = item.kind
+ {
+ for assoc_item in items {
+ if assoc_item.kind == (hir::AssocItemKind::Fn { has_self: false }) {
+ 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_chain! {
+ if sig.decl.inputs.is_empty();
+ if name == sym::new;
+ if cx.access_levels.is_reachable(impl_item.def_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 TyS::same_type(self_ty, return_ty(cx, id));
+ if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default);
+ then {
+ if self.impling_types.is_none() {
+ let mut impls = HirIdSet::default();
+ cx.tcx.for_each_impl(default_trait_id, |d| {
+ if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
+ if let Some(local_def_id) = ty_def.did.as_local() {
+ impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id));
+ }
+ }
+ });
+ self.impling_types = Some(impls);
+ }
+
+ // Check if a Default implementation exists for the Self type, regardless of
+ // generics
+ if_chain! {
+ if let Some(ref impling_types) = self.impling_types;
+ if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def();
+ if let Some(self_local_did) = self_def.did.as_local();
+ let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
+ if impling_types.contains(&self_id);
+ then {
+ return;
+ }
+ }
+
+ let generics_sugg = snippet(cx, generics.span, "");
+ let self_ty_fmt = self_ty.to_string();
+ let self_type_snip = snippet(cx, impl_self_ty.span, &self_ty_fmt);
+ span_lint_hir_and_then(
+ cx,
+ NEW_WITHOUT_DEFAULT,
+ id,
+ impl_item.span,
+ &format!(
+ "you should consider adding a `Default` implementation for `{}`",
+ self_type_snip
+ ),
+ |diag| {
+ diag.suggest_prepend_item(
+ cx,
+ item.span,
+ "try adding this",
+ &create_new_without_default_suggest_msg(&self_type_snip, &generics_sugg),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fn create_new_without_default_suggest_msg(self_type_snip: &str, generics_sugg: &str) -> String {
+ #[rustfmt::skip]
+ format!(
+"impl{} Default for {} {{
+ fn default() -> Self {{
+ Self::new()
+ }}
+}}", generics_sugg, self_type_snip)
+}
--- /dev/null
- let res = cx.qpath_res(qpath, callee.hir_id);
+use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
+use clippy_utils::is_lint_allowed;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::has_drop;
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, PatKind, Stmt, StmtKind, UnsafeSource};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use std::ops::Deref;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for statements which have no effect.
+ ///
+ /// ### Why is this bad?
+ /// Unlike dead code, these statements are actually
+ /// executed. However, as they have no effect, all they do is make the code less
+ /// readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// 0;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NO_EFFECT,
+ complexity,
+ "statements with no effect"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for binding to underscore prefixed variable without side-effects.
+ ///
+ /// ### Why is this bad?
+ /// Unlike dead code, these bindings are actually
+ /// executed. However, as they have no effect and shouldn't be used further on, all they
+ /// do is make the code less readable.
+ ///
+ /// ### Known problems
+ /// Further usage of this variable is not checked, which can lead to false positives if it is
+ /// used later in the code.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let _i_serve_no_purpose = 1;
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub NO_EFFECT_UNDERSCORE_BINDING,
+ pedantic,
+ "binding to `_` prefixed variable with no side-effect"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expression statements that can be reduced to a
+ /// sub-expression.
+ ///
+ /// ### Why is this bad?
+ /// Expressions by themselves often have no side-effects.
+ /// Having such expressions reduces readability.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// compute_array()[0];
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_OPERATION,
+ complexity,
+ "outer expressions with no effect"
+}
+
+declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]);
+
+impl<'tcx> LateLintPass<'tcx> for NoEffect {
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if check_no_effect(cx, stmt) {
+ return;
+ }
+ check_unnecessary_operation(cx, stmt);
+ }
+}
+
+fn check_no_effect(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> bool {
+ if let StmtKind::Semi(expr) = stmt.kind {
+ if has_no_effect(cx, expr) {
+ span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
+ return true;
+ }
+ } else if let StmtKind::Local(local) = stmt.kind {
+ if_chain! {
+ if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id);
+ if let Some(init) = local.init;
+ if !local.pat.span.from_expansion();
+ if has_no_effect(cx, init);
+ if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
+ if ident.name.to_ident_string().starts_with('_');
+ then {
+ span_lint_hir(
+ cx,
+ NO_EFFECT_UNDERSCORE_BINDING,
+ init.hir_id,
+ stmt.span,
+ "binding to `_` prefixed variable with no side-effect"
+ );
+ return true;
+ }
+ }
+ }
+ false
+}
+
+fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if expr.span.from_expansion() {
+ return false;
+ }
+ match expr.kind {
+ ExprKind::Lit(..) | ExprKind::Closure(..) => true,
+ ExprKind::Path(..) => !has_drop(cx, cx.typeck_results().expr_ty(expr)),
+ ExprKind::Index(a, b) | ExprKind::Binary(_, a, b) => has_no_effect(cx, a) && has_no_effect(cx, b),
+ ExprKind::Array(v) | ExprKind::Tup(v) => v.iter().all(|val| has_no_effect(cx, val)),
+ ExprKind::Repeat(inner, _)
+ | ExprKind::Cast(inner, _)
+ | ExprKind::Type(inner, _)
+ | ExprKind::Unary(_, inner)
+ | ExprKind::Field(inner, _)
+ | ExprKind::AddrOf(_, _, inner)
+ | ExprKind::Box(inner) => has_no_effect(cx, inner),
+ ExprKind::Struct(_, fields, ref base) => {
+ !has_drop(cx, cx.typeck_results().expr_ty(expr))
+ && fields.iter().all(|field| has_no_effect(cx, field.expr))
+ && base.as_ref().map_or(true, |base| has_no_effect(cx, base))
+ },
+ ExprKind::Call(callee, args) => {
+ if let ExprKind::Path(ref qpath) = callee.kind {
- res,
++ if cx.typeck_results().type_dependent_def(expr.hir_id).is_some() {
++ // type-dependent function call like `impl FnOnce for X`
++ return false;
++ }
+ let def_matched = matches!(
- let snippet;
- if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) {
- snippet = format!("assert!({}.len() > {});", &arr, &func);
++ cx.qpath_res(qpath, callee.hir_id),
+ Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..)
+ );
+ if def_matched || is_range_literal(expr) {
+ !has_drop(cx, cx.typeck_results().expr_ty(expr)) && args.iter().all(|arg| has_no_effect(cx, arg))
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ },
+ ExprKind::Block(block, _) => {
+ block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| has_no_effect(cx, expr))
+ },
+ _ => false,
+ }
+}
+
+fn check_unnecessary_operation(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if_chain! {
+ if let StmtKind::Semi(expr) = stmt.kind;
+ if let Some(reduced) = reduce_expression(cx, expr);
+ if !&reduced.iter().any(|e| e.span.from_expansion());
+ then {
+ if let ExprKind::Index(..) = &expr.kind {
- }
++ let snippet = if let (Some(arr), Some(func)) =
++ (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span))
++ {
++ format!("assert!({}.len() > {});", &arr, &func)
+ } else {
+ return;
++ };
+ span_lint_hir_and_then(
+ cx,
+ UNNECESSARY_OPERATION,
+ expr.hir_id,
+ stmt.span,
+ "unnecessary operation",
+ |diag| {
+ diag.span_suggestion(
+ stmt.span,
+ "statement can be written as",
+ snippet,
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ } else {
+ let mut snippet = String::new();
+ for e in reduced {
+ if let Some(snip) = snippet_opt(cx, e.span) {
+ snippet.push_str(&snip);
+ snippet.push(';');
+ } else {
+ return;
+ }
+ }
+ span_lint_hir_and_then(
+ cx,
+ UNNECESSARY_OPERATION,
+ expr.hir_id,
+ stmt.span,
+ "unnecessary operation",
+ |diag| {
+ diag.span_suggestion(
+ stmt.span,
+ "statement can be reduced to",
+ snippet,
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ }
+}
+
+fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec<&'a Expr<'a>>> {
+ if expr.span.from_expansion() {
+ return None;
+ }
+ match expr.kind {
+ ExprKind::Index(a, b) => Some(vec![a, b]),
+ ExprKind::Binary(ref binop, a, b) if binop.node != BinOpKind::And && binop.node != BinOpKind::Or => {
+ Some(vec![a, b])
+ },
+ ExprKind::Array(v) | ExprKind::Tup(v) => Some(v.iter().collect()),
+ ExprKind::Repeat(inner, _)
+ | ExprKind::Cast(inner, _)
+ | ExprKind::Type(inner, _)
+ | ExprKind::Unary(_, inner)
+ | ExprKind::Field(inner, _)
+ | ExprKind::AddrOf(_, _, inner)
+ | ExprKind::Box(inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])),
+ ExprKind::Struct(_, fields, ref base) => {
+ if has_drop(cx, cx.typeck_results().expr_ty(expr)) {
+ None
+ } else {
+ Some(fields.iter().map(|f| &f.expr).chain(base).map(Deref::deref).collect())
+ }
+ },
+ ExprKind::Call(callee, args) => {
+ if let ExprKind::Path(ref qpath) = callee.kind {
++ if cx.typeck_results().type_dependent_def(expr.hir_id).is_some() {
++ // type-dependent function call like `impl FnOnce for X`
++ return None;
++ }
+ let res = cx.qpath_res(qpath, callee.hir_id);
+ match res {
+ Res::Def(DefKind::Struct | DefKind::Variant | DefKind::Ctor(..), ..)
+ if !has_drop(cx, cx.typeck_results().expr_ty(expr)) =>
+ {
+ Some(args.iter().collect())
+ },
+ _ => None,
+ }
+ } else {
+ None
+ }
+ },
+ ExprKind::Block(block, _) => {
+ if block.stmts.is_empty() {
+ block.expr.as_ref().and_then(|e| {
+ match block.rules {
+ BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None,
+ BlockCheckMode::DefaultBlock => Some(vec![&**e]),
+ // in case of compiler-inserted signaling blocks
+ BlockCheckMode::UnsafeBlock(_) => reduce_expression(cx, e),
+ }
+ })
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
--- /dev/null
+//! Checks for uses of const which the type is not `Freeze` (`Cell`-free).
+//!
+//! This lint is **warn** by default.
+
+use std::ptr;
+
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::in_constant;
+use if_chain::if_chain;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::{
+ BodyId, Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind, UnOp,
+};
+use rustc_infer::traits::specialization_graph;
+use rustc_lint::{LateContext, LateLintPass, Lint};
+use rustc_middle::mir::interpret::{ConstValue, ErrorHandled};
+use rustc_middle::ty::adjustment::Adjust;
+use rustc_middle::ty::{self, AssocKind, Const, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{InnerSpan, Span, DUMMY_SP};
+use rustc_typeck::hir_ty_to_ty;
+
+// FIXME: this is a correctness problem but there's no suitable
+// warn-by-default category.
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for declaration of `const` items which is interior
+ /// mutable (e.g., contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.).
+ ///
+ /// ### Why is this bad?
+ /// Consts are copied everywhere they are referenced, i.e.,
+ /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
+ /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
+ /// these types in the first place.
+ ///
+ /// The `const` should better be replaced by a `static` item if a global
+ /// variable is wanted, or replaced by a `const fn` if a constructor is wanted.
+ ///
+ /// ### Known problems
+ /// A "non-constant" const item is a legacy way to supply an
+ /// initialized value to downstream `static` items (e.g., the
+ /// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit,
+ /// and this lint should be suppressed.
+ ///
+ /// Even though the lint avoids triggering on a constant whose type has enums that have variants
+ /// with interior mutability, and its value uses non interior mutable variants (see
+ /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962) and
+ /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825) for examples);
+ /// it complains about associated constants without default values only based on its types;
+ /// which might not be preferable.
+ /// There're other enums plus associated constants cases that the lint cannot handle.
+ ///
+ /// Types that have underlying or potential interior mutability trigger the lint whether
+ /// the interior mutable field is used or not. See issues
+ /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ ///
+ /// // Bad.
+ /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
+ /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
+ /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
+ ///
+ /// // Good.
+ /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15);
+ /// STATIC_ATOM.store(9, SeqCst);
+ /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DECLARE_INTERIOR_MUTABLE_CONST,
+ style,
+ "declaring `const` with interior mutability"
+}
+
+// FIXME: this is a correctness problem but there's no suitable
+// warn-by-default category.
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks if `const` items which is interior mutable (e.g.,
+ /// contains a `Cell`, `Mutex`, `AtomicXxxx`, etc.) has been borrowed directly.
+ ///
+ /// ### Why is this bad?
+ /// Consts are copied everywhere they are referenced, i.e.,
+ /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
+ /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
+ /// these types in the first place.
+ ///
+ /// The `const` value should be stored inside a `static` item.
+ ///
+ /// ### Known problems
+ /// When an enum has variants with interior mutability, use of its non
+ /// interior mutable variants can generate false positives. See issue
+ /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962)
+ ///
+ /// Types that have underlying or potential interior mutability trigger the lint whether
+ /// the interior mutable field is used or not. See issues
+ /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and
+ /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825)
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+ /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
+ ///
+ /// // Bad.
+ /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
+ /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
+ ///
+ /// // Good.
+ /// static STATIC_ATOM: AtomicUsize = CONST_ATOM;
+ /// STATIC_ATOM.store(9, SeqCst);
+ /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub BORROW_INTERIOR_MUTABLE_CONST,
+ style,
+ "referencing `const` with interior mutability"
+}
+
+fn is_unfrozen<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ // Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
+ // making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
+ // 'unfrozen'. However, this code causes a false negative in which
+ // a type contains a layout-unknown type, but also an unsafe cell like `const CELL: Cell<T>`.
+ // Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
+ // since it works when a pointer indirection involves (`Cell<*const T>`).
+ // Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
+ // but I'm not sure whether it's a decent way, if possible.
+ cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx.at(DUMMY_SP), cx.param_env)
+}
+
+fn is_value_unfrozen_raw<'tcx>(
+ cx: &LateContext<'tcx>,
+ result: Result<ConstValue<'tcx>, ErrorHandled>,
+ ty: Ty<'tcx>,
+) -> bool {
+ fn inner<'tcx>(cx: &LateContext<'tcx>, val: &'tcx Const<'tcx>) -> bool {
+ match val.ty.kind() {
+ // the fact that we have to dig into every structs to search enums
+ // leads us to the point checking `UnsafeCell` directly is the only option.
+ ty::Adt(ty_def, ..) if Some(ty_def.did) == cx.tcx.lang_items().unsafe_cell_type() => true,
+ ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => {
+ let val = cx.tcx.destructure_const(cx.param_env.and(val));
+ val.fields.iter().any(|field| inner(cx, field))
+ },
+ _ => false,
+ }
+ }
+
+ result.map_or_else(
+ |err| {
+ // Consider `TooGeneric` cases as being unfrozen.
+ // This causes a false positive where an assoc const whose type is unfrozen
+ // have a value that is a frozen variant with a generic param (an example is
+ // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
+ // However, it prevents a number of false negatives that is, I think, important:
+ // 1. assoc consts in trait defs referring to consts of themselves
+ // (an example is `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
+ // 2. a path expr referring to assoc consts whose type is doesn't have
+ // any frozen variants in trait defs (i.e. without substitute for `Self`).
+ // (e.g. borrowing `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
+ // 3. similar to the false positive above;
+ // but the value is an unfrozen variant, or the type has no enums. (An example is
+ // `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT`
+ // and `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
+ // One might be able to prevent these FNs correctly, and replace this with `false`;
+ // e.g. implementing `has_frozen_variant` described above, and not running this function
+ // when the type doesn't have any frozen variants would be the 'correct' way for the 2nd
+ // case (that actually removes another suboptimal behavior (I won't say 'false positive') where,
+ // similar to 2., but with the a frozen variant) (e.g. borrowing
+ // `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
+ // I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
+ err == ErrorHandled::TooGeneric
+ },
+ |val| inner(cx, Const::from_value(cx.tcx, val, ty)),
+ )
+}
+
+fn is_value_unfrozen_poly<'tcx>(cx: &LateContext<'tcx>, body_id: BodyId, ty: Ty<'tcx>) -> bool {
+ let result = cx.tcx.const_eval_poly(body_id.hir_id.owner.to_def_id());
+ is_value_unfrozen_raw(cx, result, ty)
+}
+
+fn is_value_unfrozen_expr<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
+ let substs = cx.typeck_results().node_substs(hir_id);
+
+ let result = cx.tcx.const_eval_resolve(
+ cx.param_env,
+ ty::Unevaluated::new(ty::WithOptConstParam::unknown(def_id), substs),
+ None,
+ );
+ is_value_unfrozen_raw(cx, result, ty)
+}
+
+#[derive(Copy, Clone)]
+enum Source {
+ Item { item: Span },
+ Assoc { item: Span },
+ Expr { expr: Span },
+}
+
+impl Source {
+ #[must_use]
+ fn lint(&self) -> (&'static Lint, &'static str, Span) {
+ match self {
+ Self::Item { item } | Self::Assoc { item, .. } => (
+ DECLARE_INTERIOR_MUTABLE_CONST,
+ "a `const` item should never be interior mutable",
+ *item,
+ ),
+ Self::Expr { expr } => (
+ BORROW_INTERIOR_MUTABLE_CONST,
+ "a `const` item with interior mutability should not be borrowed",
+ *expr,
+ ),
+ }
+ }
+}
+
+fn lint(cx: &LateContext<'_>, source: Source) {
+ let (lint, msg, span) = source.lint();
+ span_lint_and_then(cx, lint, span, msg, |diag| {
+ if span.from_expansion() {
+ return; // Don't give suggestions into macros.
+ }
+ match source {
+ Source::Item { .. } => {
+ let const_kw_span = span.from_inner(InnerSpan::new(0, 5));
+ diag.span_label(const_kw_span, "make this a static item (maybe with lazy_static)");
+ },
+ Source::Assoc { .. } => (),
+ Source::Expr { .. } => {
+ diag.help("assign this const to a local or static variable, and use the variable here");
+ },
+ }
+ });
+}
+
+declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]);
+
+impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
+ if let ItemKind::Const(hir_ty, body_id) = it.kind {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+
+ if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
+ lint(cx, Source::Item { item: it.span });
+ }
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Const(hir_ty, body_id_opt) = &trait_item.kind {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+
+ // Normalize assoc types because ones originated from generic params
+ // bounded other traits could have their bound.
+ let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
+ if is_unfrozen(cx, normalized)
+ // When there's no default value, lint it only according to its type;
+ // in other words, lint consts whose value *could* be unfrozen, not definitely is.
+ // This feels inconsistent with how the lint treats generic types,
+ // which avoids linting types which potentially become unfrozen.
+ // One could check whether an unfrozen type have a *frozen variant*
+ // (like `body_id_opt.map_or_else(|| !has_frozen_variant(...), ...)`),
+ // and do the same as the case of generic types at impl items.
+ // Note that it isn't sufficient to check if it has an enum
+ // since all of that enum's variants can be unfrozen:
+ // i.e. having an enum doesn't necessary mean a type has a frozen variant.
+ // And, implementing it isn't a trivial task; it'll probably end up
+ // re-implementing the trait predicate evaluation specific to `Freeze`.
+ && body_id_opt.map_or(true, |body_id| is_value_unfrozen_poly(cx, body_id, normalized))
+ {
+ lint(cx, Source::Assoc { item: trait_item.span });
+ }
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
+ if let ImplItemKind::Const(hir_ty, body_id) = &impl_item.kind {
+ let item_def_id = cx.tcx.hir().get_parent_did(impl_item.hir_id());
+ let item = cx.tcx.hir().expect_item(item_def_id);
+
+ match &item.kind {
+ ItemKind::Impl(Impl {
+ of_trait: Some(of_trait_ref),
+ ..
+ }) => {
+ if_chain! {
+ // Lint a trait impl item only when the definition is a generic type,
+ // assuming an assoc const is not meant to be an interior mutable type.
+ if let Some(of_trait_def_id) = of_trait_ref.trait_def_id();
+ if let Some(of_assoc_item) = specialization_graph::Node::Trait(of_trait_def_id)
+ .item(cx.tcx, impl_item.ident, AssocKind::Const, of_trait_def_id);
+ if cx
+ .tcx
+ .layout_of(cx.tcx.param_env(of_trait_def_id).and(
+ // Normalize assoc types because ones originated from generic params
+ // bounded other traits could have their bound at the trait defs;
+ // and, in that case, the definition is *not* generic.
+ cx.tcx.normalize_erasing_regions(
+ cx.tcx.param_env(of_trait_def_id),
+ cx.tcx.type_of(of_assoc_item.def_id),
+ ),
+ ))
+ .is_err();
+ // If there were a function like `has_frozen_variant` described above,
+ // we should use here as a frozen variant is a potential to be frozen
+ // similar to unknown layouts.
+ // e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
+ if is_unfrozen(cx, normalized);
+ if is_value_unfrozen_poly(cx, *body_id, normalized);
+ then {
+ lint(
+ cx,
+ Source::Assoc {
+ item: impl_item.span,
+ },
+ );
+ }
+ }
+ },
+ ItemKind::Impl(Impl { of_trait: None, .. }) => {
+ let ty = hir_ty_to_ty(cx.tcx, hir_ty);
+ // Normalize assoc types originated from generic params.
+ let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
+
+ if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, *body_id, normalized) {
+ lint(cx, Source::Assoc { item: impl_item.span });
+ }
+ },
+ _ => (),
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Path(qpath) = &expr.kind {
+ // Only lint if we use the const item inside a function.
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ // Make sure it is a const item.
+ let item_def_id = match cx.qpath_res(qpath, expr.hir_id) {
+ Res::Def(DefKind::Const | DefKind::AssocConst, did) => did,
+ _ => return,
+ };
+
+ // Climb up to resolve any field access and explicit referencing.
+ let mut cur_expr = expr;
+ let mut dereferenced_expr = expr;
+ let mut needs_check_adjustment = true;
+ loop {
+ let parent_id = cx.tcx.hir().get_parent_node(cur_expr.hir_id);
+ if parent_id == cur_expr.hir_id {
+ break;
+ }
+ if let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find(parent_id) {
+ match &parent_expr.kind {
+ ExprKind::AddrOf(..) => {
+ // `&e` => `e` must be referenced.
+ needs_check_adjustment = false;
+ },
+ ExprKind::Field(..) => {
+ needs_check_adjustment = true;
+
+ // Check whether implicit dereferences happened;
+ // if so, no need to go further up
+ // because of the same reason as the `ExprKind::Unary` case.
+ if cx
+ .typeck_results()
+ .expr_adjustments(dereferenced_expr)
+ .iter()
+ .any(|adj| matches!(adj.kind, Adjust::Deref(_)))
+ {
+ break;
+ }
+
+ dereferenced_expr = parent_expr;
+ },
+ ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => {
+ // `e[i]` => desugared to `*Index::index(&e, i)`,
+ // meaning `e` must be referenced.
+ // no need to go further up since a method call is involved now.
+ needs_check_adjustment = false;
+ break;
+ },
+ ExprKind::Unary(UnOp::Deref, _) => {
+ // `*e` => desugared to `*Deref::deref(&e)`,
+ // meaning `e` must be referenced.
+ // no need to go further up since a method call is involved now.
+ needs_check_adjustment = false;
+ break;
+ },
+ _ => break,
+ }
+ cur_expr = parent_expr;
+ } else {
+ break;
+ }
+ }
+
+ let ty = if needs_check_adjustment {
+ let adjustments = cx.typeck_results().expr_adjustments(dereferenced_expr);
+ if let Some(i) = adjustments
+ .iter()
+ .position(|adj| matches!(adj.kind, Adjust::Borrow(_) | Adjust::Deref(_)))
+ {
+ if i == 0 {
+ cx.typeck_results().expr_ty(dereferenced_expr)
+ } else {
+ adjustments[i - 1].target
+ }
+ } else {
+ // No borrow adjustments means the entire const is moved.
+ return;
+ }
+ } else {
+ cx.typeck_results().expr_ty(dereferenced_expr)
+ };
+
+ if is_unfrozen(cx, ty) && is_value_unfrozen_expr(cx, expr.hir_id, item_def_id, ty) {
+ lint(cx, Source::Expr { expr: expr.span });
+ }
+ }
+ }
+}
--- /dev/null
- if let ItemKind::Fn(box ast::Fn { ref sig, body: Some(ref blk), .. }) = item.kind {
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use rustc_ast::ast::{
+ self, Arm, AssocItem, AssocItemKind, Attribute, Block, FnDecl, Item, ItemKind, Local, Pat, PatKind,
+};
+use rustc_ast::visit::{walk_block, walk_expr, walk_pat, Visitor};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+use rustc_span::symbol::{Ident, Symbol};
+use std::cmp::Ordering;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for names that are very similar and thus confusing.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to distinguish between names that differ only
+ /// by a single character.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let checked_exp = something;
+ /// let checked_expr = something_else;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub SIMILAR_NAMES,
+ pedantic,
+ "similarly named items and bindings"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for too many variables whose name consists of a
+ /// single character.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to memorize what a variable means without a
+ /// descriptive name.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let (a, b, c, d, e, f, g) = (...);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MANY_SINGLE_CHAR_NAMES,
+ pedantic,
+ "too many single character bindings"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks if you have variables whose name consists of just
+ /// underscores and digits.
+ ///
+ /// ### Why is this bad?
+ /// It's hard to memorize what a variable means without a
+ /// descriptive name.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _1 = 1;
+ /// let ___1 = 1;
+ /// let __1___2 = 11;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub JUST_UNDERSCORES_AND_DIGITS,
+ style,
+ "unclear name"
+}
+
+#[derive(Copy, Clone)]
+pub struct NonExpressiveNames {
+ pub single_char_binding_names_threshold: u64,
+}
+
+impl_lint_pass!(NonExpressiveNames => [SIMILAR_NAMES, MANY_SINGLE_CHAR_NAMES, JUST_UNDERSCORES_AND_DIGITS]);
+
+struct ExistingName {
+ interned: Symbol,
+ span: Span,
+ len: usize,
+ exemptions: &'static [&'static str],
+}
+
+struct SimilarNamesLocalVisitor<'a, 'tcx> {
+ names: Vec<ExistingName>,
+ cx: &'a EarlyContext<'tcx>,
+ lint: &'a NonExpressiveNames,
+
+ /// A stack of scopes containing the single-character bindings in each scope.
+ single_char_names: Vec<Vec<Ident>>,
+}
+
+impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
+ fn check_single_char_names(&self) {
+ let num_single_char_names = self.single_char_names.iter().flatten().count();
+ let threshold = self.lint.single_char_binding_names_threshold;
+ if num_single_char_names as u64 > threshold {
+ let span = self
+ .single_char_names
+ .iter()
+ .flatten()
+ .map(|ident| ident.span)
+ .collect::<Vec<_>>();
+ span_lint(
+ self.cx,
+ MANY_SINGLE_CHAR_NAMES,
+ span,
+ &format!(
+ "{} bindings with single-character names in scope",
+ num_single_char_names
+ ),
+ );
+ }
+ }
+}
+
+// this list contains lists of names that are allowed to be similar
+// the assumption is that no name is ever contained in multiple lists.
+#[rustfmt::skip]
+const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
+ &["parsed", "parser"],
+ &["lhs", "rhs"],
+ &["tx", "rx"],
+ &["set", "get"],
+ &["args", "arms"],
+ &["qpath", "path"],
+ &["lit", "lint"],
+ &["wparam", "lparam"],
+ &["iter", "item"],
+];
+
+struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>);
+
+impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> {
+ fn visit_pat(&mut self, pat: &'tcx Pat) {
+ match pat.kind {
+ PatKind::Ident(_, ident, _) => {
+ if !pat.span.from_expansion() {
+ self.check_ident(ident);
+ }
+ },
+ PatKind::Struct(_, _, ref fields, _) => {
+ for field in fields {
+ if !field.is_shorthand {
+ self.visit_pat(&field.pat);
+ }
+ }
+ },
+ // just go through the first pattern, as either all patterns
+ // bind the same bindings or rustc would have errored much earlier
+ PatKind::Or(ref pats) => self.visit_pat(&pats[0]),
+ _ => walk_pat(self, pat),
+ }
+ }
+}
+
+#[must_use]
+fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> {
+ for &list in ALLOWED_TO_BE_SIMILAR {
+ if allowed_to_be_similar(interned_name, list) {
+ return Some(list);
+ }
+ }
+ None
+}
+
+#[must_use]
+fn allowed_to_be_similar(interned_name: &str, list: &[&str]) -> bool {
+ list.iter()
+ .any(|&name| interned_name.starts_with(name) || interned_name.ends_with(name))
+}
+
+impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
+ fn check_short_ident(&mut self, ident: Ident) {
+ // Ignore shadowing
+ if self
+ .0
+ .single_char_names
+ .iter()
+ .flatten()
+ .any(|id| id.name == ident.name)
+ {
+ return;
+ }
+
+ if let Some(scope) = &mut self.0.single_char_names.last_mut() {
+ scope.push(ident);
+ }
+ }
+
+ #[allow(clippy::too_many_lines)]
+ fn check_ident(&mut self, ident: Ident) {
+ let interned_name = ident.name.as_str();
+ if interned_name.chars().any(char::is_uppercase) {
+ return;
+ }
+ if interned_name.chars().all(|c| c.is_digit(10) || c == '_') {
+ span_lint(
+ self.0.cx,
+ JUST_UNDERSCORES_AND_DIGITS,
+ ident.span,
+ "consider choosing a more descriptive name",
+ );
+ return;
+ }
+ if interned_name.starts_with('_') {
+ // these bindings are typically unused or represent an ignored portion of a destructuring pattern
+ return;
+ }
+ let count = interned_name.chars().count();
+ if count < 3 {
+ if count == 1 {
+ self.check_short_ident(ident);
+ }
+ return;
+ }
+ for existing_name in &self.0.names {
+ if allowed_to_be_similar(&interned_name, existing_name.exemptions) {
+ continue;
+ }
+ match existing_name.len.cmp(&count) {
+ Ordering::Greater => {
+ if existing_name.len - count != 1
+ || levenstein_not_1(&interned_name, &existing_name.interned.as_str())
+ {
+ continue;
+ }
+ },
+ Ordering::Less => {
+ if count - existing_name.len != 1
+ || levenstein_not_1(&existing_name.interned.as_str(), &interned_name)
+ {
+ continue;
+ }
+ },
+ Ordering::Equal => {
+ let mut interned_chars = interned_name.chars();
+ let interned_str = existing_name.interned.as_str();
+ let mut existing_chars = interned_str.chars();
+ let first_i = interned_chars.next().expect("we know we have at least one char");
+ let first_e = existing_chars.next().expect("we know we have at least one char");
+ let eq_or_numeric = |(a, b): (char, char)| a == b || a.is_numeric() && b.is_numeric();
+
+ if eq_or_numeric((first_i, first_e)) {
+ let last_i = interned_chars.next_back().expect("we know we have at least two chars");
+ let last_e = existing_chars.next_back().expect("we know we have at least two chars");
+ if eq_or_numeric((last_i, last_e)) {
+ if interned_chars
+ .zip(existing_chars)
+ .filter(|&ie| !eq_or_numeric(ie))
+ .count()
+ != 1
+ {
+ continue;
+ }
+ } else {
+ let second_last_i = interned_chars
+ .next_back()
+ .expect("we know we have at least three chars");
+ let second_last_e = existing_chars
+ .next_back()
+ .expect("we know we have at least three chars");
+ if !eq_or_numeric((second_last_i, second_last_e))
+ || second_last_i == '_'
+ || !interned_chars.zip(existing_chars).all(eq_or_numeric)
+ {
+ // allowed similarity foo_x, foo_y
+ // or too many chars differ (foo_x, boo_y) or (foox, booy)
+ continue;
+ }
+ }
+ } else {
+ let second_i = interned_chars.next().expect("we know we have at least two chars");
+ let second_e = existing_chars.next().expect("we know we have at least two chars");
+ if !eq_or_numeric((second_i, second_e))
+ || second_i == '_'
+ || !interned_chars.zip(existing_chars).all(eq_or_numeric)
+ {
+ // allowed similarity x_foo, y_foo
+ // or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
+ continue;
+ }
+ }
+ },
+ }
+ span_lint_and_then(
+ self.0.cx,
+ SIMILAR_NAMES,
+ ident.span,
+ "binding's name is too similar to existing binding",
+ |diag| {
+ diag.span_note(existing_name.span, "existing binding defined here");
+ },
+ );
+ return;
+ }
+ self.0.names.push(ExistingName {
+ exemptions: get_exemptions(&interned_name).unwrap_or(&[]),
+ interned: ident.name,
+ span: ident.span,
+ len: count,
+ });
+ }
+}
+
+impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> {
+ /// ensure scoping rules work
+ fn apply<F: for<'c> Fn(&'c mut Self)>(&mut self, f: F) {
+ let n = self.names.len();
+ let single_char_count = self.single_char_names.len();
+ f(self);
+ self.names.truncate(n);
+ self.single_char_names.truncate(single_char_count);
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> {
+ fn visit_local(&mut self, local: &'tcx Local) {
+ if let Some((init, els)) = &local.kind.init_else_opt() {
+ self.apply(|this| walk_expr(this, init));
+ if let Some(els) = els {
+ self.apply(|this| walk_block(this, els));
+ }
+ }
+ // add the pattern after the expression because the bindings aren't available
+ // yet in the init
+ // expression
+ SimilarNamesNameVisitor(self).visit_pat(&*local.pat);
+ }
+ fn visit_block(&mut self, blk: &'tcx Block) {
+ self.single_char_names.push(vec![]);
+
+ self.apply(|this| walk_block(this, blk));
+
+ self.check_single_char_names();
+ self.single_char_names.pop();
+ }
+ fn visit_arm(&mut self, arm: &'tcx Arm) {
+ self.single_char_names.push(vec![]);
+
+ self.apply(|this| {
+ SimilarNamesNameVisitor(this).visit_pat(&arm.pat);
+ this.apply(|this| walk_expr(this, &arm.body));
+ });
+
+ self.check_single_char_names();
+ self.single_char_names.pop();
+ }
+ fn visit_item(&mut self, _: &Item) {
+ // do not recurse into inner items
+ }
+}
+
+impl EarlyLintPass for NonExpressiveNames {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if in_external_macro(cx.sess, item.span) {
+ return;
+ }
+
- if let AssocItemKind::Fn(box ast::Fn { ref sig, body: Some(ref blk), .. }) = item.kind {
++ if let ItemKind::Fn(box ast::Fn {
++ ref sig,
++ body: Some(ref blk),
++ ..
++ }) = item.kind
++ {
+ do_check(self, cx, &item.attrs, &sig.decl, blk);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &EarlyContext<'_>, item: &AssocItem) {
+ if in_external_macro(cx.sess, item.span) {
+ return;
+ }
+
++ if let AssocItemKind::Fn(box ast::Fn {
++ ref sig,
++ body: Some(ref blk),
++ ..
++ }) = item.kind
++ {
+ do_check(self, cx, &item.attrs, &sig.decl, blk);
+ }
+ }
+}
+
+fn do_check(lint: &mut NonExpressiveNames, cx: &EarlyContext<'_>, attrs: &[Attribute], decl: &FnDecl, blk: &Block) {
+ if !attrs.iter().any(|attr| attr.has_name(sym::test)) {
+ let mut visitor = SimilarNamesLocalVisitor {
+ names: Vec::new(),
+ cx,
+ lint,
+ single_char_names: vec![vec![]],
+ };
+
+ // initialize with function arguments
+ for arg in &decl.inputs {
+ SimilarNamesNameVisitor(&mut visitor).visit_pat(&arg.pat);
+ }
+ // walk all other bindings
+ walk_block(&mut visitor, blk);
+
+ visitor.check_single_char_names();
+ }
+}
+
+/// Precondition: `a_name.chars().count() < b_name.chars().count()`.
+#[must_use]
+fn levenstein_not_1(a_name: &str, b_name: &str) -> bool {
+ debug_assert!(a_name.chars().count() < b_name.chars().count());
+ let mut a_chars = a_name.chars();
+ let mut b_chars = b_name.chars();
+ while let (Some(a), Some(b)) = (a_chars.next(), b_chars.next()) {
+ if a == b {
+ continue;
+ }
+ if let Some(b2) = b_chars.next() {
+ // check if there's just one character inserted
+ return a != b2 || a_chars.ne(b_chars);
+ }
+ // tuple
+ // ntuple
+ return true;
+ }
+ // for item in items
+ true
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use clippy_utils::ty::match_type;
+use clippy_utils::{match_def_path, paths};
+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 non-octal values used to set Unix file permissions.
+ ///
+ /// ### Why is this bad?
+ /// They will be converted into octal, creating potentially
+ /// unintended file permissions.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::fs::OpenOptions;
+ /// use std::os::unix::fs::OpenOptionsExt;
+ ///
+ /// let mut options = OpenOptions::new();
+ /// options.mode(644);
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// use std::fs::OpenOptions;
+ /// use std::os::unix::fs::OpenOptionsExt;
+ ///
+ /// let mut options = OpenOptions::new();
+ /// options.mode(0o644);
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub NON_OCTAL_UNIX_PERMISSIONS,
+ correctness,
+ "use of non-octal value to set unix file permissions, which will be translated into octal"
+}
+
+declare_lint_pass!(NonOctalUnixPermissions => [NON_OCTAL_UNIX_PERMISSIONS]);
+
+impl LateLintPass<'_> for NonOctalUnixPermissions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ match &expr.kind {
+ ExprKind::MethodCall(path, _, [func, param], _) => {
+ let obj_ty = cx.typeck_results().expr_ty(func).peel_refs();
+
+ if_chain! {
+ if (path.ident.name == sym!(mode)
+ && (match_type(cx, obj_ty, &paths::OPEN_OPTIONS)
+ || match_type(cx, obj_ty, &paths::DIR_BUILDER)))
+ || (path.ident.name == sym!(set_mode) && match_type(cx, obj_ty, &paths::PERMISSIONS));
+ if let ExprKind::Lit(_) = param.kind;
+
+ then {
+ let snip = match snippet_opt(cx, param.span) {
+ Some(s) => s,
+ _ => return,
+ };
+
+ if !snip.starts_with("0o") {
+ show_error(cx, param);
+ }
+ }
+ }
+ },
+ ExprKind::Call(func, [param]) => {
+ if_chain! {
+ if let ExprKind::Path(ref path) = func.kind;
+ if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE);
+ if let ExprKind::Lit(_) = param.kind;
+
+ then {
+ let snip = match snippet_opt(cx, param.span) {
+ Some(s) => s,
+ _ => return,
+ };
+
+ if !snip.starts_with("0o") {
+ show_error(cx, param);
+ }
+ }
+ }
+ },
+ _ => {},
+ };
+ }
+}
+
+fn show_error(cx: &LateContext<'_>, param: &Expr<'_>) {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ NON_OCTAL_UNIX_PERMISSIONS,
+ param.span,
+ "using a non-octal value to set unix file permissions",
+ "consider using an octal literal instead",
+ format!(
+ "0o{}",
+ snippet_with_applicability(cx, param.span, "0o..", &mut applicability,),
+ ),
+ applicability,
+ );
+}
--- /dev/null
- use clippy_utils::is_lint_allowed;
+use clippy_utils::diagnostics::span_lint_and_then;
- if is_copy(cx, ty) && !contains_raw_pointer(cx, ty) {
+use clippy_utils::source::snippet;
+use clippy_utils::ty::{implements_trait, is_copy};
++use clippy_utils::{is_lint_allowed, match_def_path, paths};
+use rustc_ast::ImplPolarity;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{FieldDef, Item, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
++use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns about fields in struct implementing `Send` that are neither `Send` nor `Copy`.
+ ///
+ /// ### Why is this bad?
+ /// Sending the struct to another thread will transfer the ownership to
+ /// the new thread by dropping in the current thread during the transfer.
+ /// This causes soundness issues for non-`Send` fields, as they are also
+ /// dropped and might not be set up to handle this.
+ ///
+ /// See:
+ /// * [*The Rustonomicon* about *Send and Sync*](https://doc.rust-lang.org/nomicon/send-and-sync.html)
+ /// * [The documentation of `Send`](https://doc.rust-lang.org/std/marker/trait.Send.html)
+ ///
+ /// ### Known Problems
+ /// Data structures that contain raw pointers may cause false positives.
+ /// They are sometimes safe to be sent across threads but do not implement
+ /// the `Send` trait. This lint has a heuristic to filter out basic cases
+ /// such as `Vec<*const T>`, but it's not perfect. Feel free to create an
+ /// issue if you have a suggestion on how this heuristic can be improved.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// struct ExampleStruct<T> {
+ /// rc_is_not_send: Rc<String>,
+ /// unbounded_generic_field: T,
+ /// }
+ ///
+ /// // This impl is unsound because it allows sending `!Send` types through `ExampleStruct`
+ /// unsafe impl<T> Send for ExampleStruct<T> {}
+ /// ```
+ /// Use thread-safe types like [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html)
+ /// or specify correct bounds on generic type parameters (`T: Send`).
++ #[clippy::version = "1.57.0"]
+ pub NON_SEND_FIELDS_IN_SEND_TY,
+ suspicious,
+ "there is field that does not implement `Send` in a `Send` struct"
+}
+
+#[derive(Copy, Clone)]
+pub struct NonSendFieldInSendTy {
+ enable_raw_pointer_heuristic: bool,
+}
+
+impl NonSendFieldInSendTy {
+ pub fn new(enable_raw_pointer_heuristic: bool) -> Self {
+ Self {
+ enable_raw_pointer_heuristic,
+ }
+ }
+}
+
+impl_lint_pass!(NonSendFieldInSendTy => [NON_SEND_FIELDS_IN_SEND_TY]);
+
+impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let ty_allowed_in_send = if self.enable_raw_pointer_heuristic {
+ ty_allowed_with_raw_pointer_heuristic
+ } else {
+ ty_allowed_without_raw_pointer_heuristic
+ };
+
+ // Checks if we are in `Send` impl item.
+ // We start from `Send` impl instead of `check_field_def()` because
+ // single `AdtDef` may have multiple `Send` impls due to generic
+ // parameters, and the lint is much easier to implement in this way.
+ if_chain! {
++ if !in_external_macro(cx.tcx.sess, item.span);
+ if let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send);
+ if let ItemKind::Impl(hir_impl) = &item.kind;
+ if let Some(trait_ref) = &hir_impl.of_trait;
+ if let Some(trait_id) = trait_ref.trait_def_id();
+ if send_trait == trait_id;
+ if hir_impl.polarity == ImplPolarity::Positive;
+ if let Some(ty_trait_ref) = cx.tcx.impl_trait_ref(item.def_id);
+ if let self_ty = ty_trait_ref.self_ty();
+ if let ty::Adt(adt_def, impl_trait_substs) = self_ty.kind();
+ then {
+ let mut non_send_fields = Vec::new();
+
+ let hir_map = cx.tcx.hir();
+ for variant in &adt_def.variants {
+ for field in &variant.fields {
+ if_chain! {
+ if let Some(field_hir_id) = field
+ .did
+ .as_local()
+ .map(|local_def_id| hir_map.local_def_id_to_hir_id(local_def_id));
+ if !is_lint_allowed(cx, NON_SEND_FIELDS_IN_SEND_TY, field_hir_id);
+ if let field_ty = field.ty(cx.tcx, impl_trait_substs);
+ if !ty_allowed_in_send(cx, field_ty, send_trait);
+ if let Node::Field(field_def) = hir_map.get(field_hir_id);
+ then {
+ non_send_fields.push(NonSendField {
+ def: field_def,
+ ty: field_ty,
+ generic_params: collect_generic_params(cx, field_ty),
+ })
+ }
+ }
+ }
+ }
+
+ if !non_send_fields.is_empty() {
+ span_lint_and_then(
+ cx,
+ NON_SEND_FIELDS_IN_SEND_TY,
+ item.span,
+ &format!(
+ "this implementation is unsound, as some fields in `{}` are `!Send`",
+ snippet(cx, hir_impl.self_ty.span, "Unknown")
+ ),
+ |diag| {
+ for field in non_send_fields {
+ diag.span_note(
+ field.def.span,
+ &format!("the type of field `{}` is `!Send`", field.def.ident.name),
+ );
+
+ match field.generic_params.len() {
+ 0 => diag.help("use a thread-safe type that implements `Send`"),
+ 1 if is_ty_param(field.ty) => diag.help(&format!("add `{}: Send` bound in `Send` impl", field.ty)),
+ _ => diag.help(&format!(
+ "add bounds on type parameter{} `{}` that satisfy `{}: Send`",
+ if field.generic_params.len() > 1 { "s" } else { "" },
+ field.generic_params_string(),
+ snippet(cx, field.def.ty.span, "Unknown"),
+ )),
+ };
+ }
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+struct NonSendField<'tcx> {
+ def: &'tcx FieldDef<'tcx>,
+ ty: Ty<'tcx>,
+ generic_params: Vec<Ty<'tcx>>,
+}
+
+impl<'tcx> NonSendField<'tcx> {
+ fn generic_params_string(&self) -> String {
+ self.generic_params
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>()
+ .join(", ")
+ }
+}
+
+/// Given a type, collect all of its generic parameters.
+/// Example: `MyStruct<P, Box<Q, R>>` => `vec![P, Q, R]`
+fn collect_generic_params<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Vec<Ty<'tcx>> {
+ ty.walk(cx.tcx)
+ .filter_map(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => Some(inner_ty),
+ _ => None,
+ })
+ .filter(|&inner_ty| is_ty_param(inner_ty))
+ .collect()
+}
+
+/// Be more strict when the heuristic is disabled
+fn ty_allowed_without_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, send_trait: DefId) -> bool {
+ if implements_trait(cx, ty, send_trait, &[]) {
+ return true;
+ }
+
- if contains_raw_pointer(cx, ty) {
++ if is_copy(cx, ty) && !contains_pointer_like(cx, ty) {
+ return true;
+ }
+
+ false
+}
+
+/// Heuristic to allow cases like `Vec<*const u8>`
+fn ty_allowed_with_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, send_trait: DefId) -> bool {
+ if implements_trait(cx, ty, send_trait, &[]) || is_copy(cx, ty) {
+ return true;
+ }
+
+ // The type is known to be `!Send` and `!Copy`
+ match ty.kind() {
+ ty::Tuple(_) => ty
+ .tuple_fields()
+ .all(|ty| ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait)),
+ ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait),
+ ty::Adt(_, substs) => {
- /// Checks if the type contains any raw pointers in substs (including nested ones).
- fn contains_raw_pointer<'tcx>(cx: &LateContext<'tcx>, target_ty: Ty<'tcx>) -> bool {
++ if contains_pointer_like(cx, ty) {
+ // descends only if ADT contains any raw pointers
+ substs.iter().all(|generic_arg| match generic_arg.unpack() {
+ GenericArgKind::Type(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait),
+ // Lifetimes and const generics are not solid part of ADT and ignored
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => true,
+ })
+ } else {
+ false
+ }
+ },
+ // Raw pointers are `!Send` but allowed by the heuristic
+ ty::RawPtr(_) => true,
+ _ => false,
+ }
+}
+
- if_chain! {
- if let GenericArgKind::Type(inner_ty) = ty_node.unpack();
- if let ty::RawPtr(_) = inner_ty.kind();
- then {
- return true;
++/// Checks if the type contains any pointer-like types in substs (including nested ones)
++fn contains_pointer_like<'tcx>(cx: &LateContext<'tcx>, target_ty: Ty<'tcx>) -> bool {
+ for ty_node in target_ty.walk(cx.tcx) {
++ if let GenericArgKind::Type(inner_ty) = ty_node.unpack() {
++ match inner_ty.kind() {
++ ty::RawPtr(_) => {
++ return true;
++ },
++ ty::Adt(adt_def, _) => {
++ if match_def_path(cx, adt_def.did, &paths::PTR_NON_NULL) {
++ return true;
++ }
++ },
++ _ => (),
+ }
+ }
+ }
+
+ false
+}
+
+/// Returns `true` if the type is a type parameter such as `T`.
+fn is_ty_param(target_ty: Ty<'_>) -> bool {
+ matches!(target_ty.kind(), ty::Param(_))
+}
--- /dev/null
- use clippy_utils::{diagnostics::span_lint_and_help, in_macro, is_direct_expn_of, source::snippet_opt};
+use std::{
+ fmt,
+ hash::{Hash, Hasher},
+};
+
- use rustc_span::Span;
++use clippy_utils::diagnostics::span_lint_and_help;
++use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_hir::def_id::DefId;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
- type MacroInfo<'a> = (&'a str, &'a (String, String), String);
++use rustc_span::hygiene::{ExpnKind, MacroKind};
++use rustc_span::{Span, Symbol};
+use serde::{de, Deserialize};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks that common macros are used with consistent bracing.
+ ///
+ /// ### Why is this bad?
+ /// This is mostly a consistency lint although using () or []
+ /// doesn't give you a semicolon in item position, which can be unexpected.
+ ///
+ /// ### Example
+ /// ```rust
+ /// vec!{1, 2, 3};
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// vec![1, 2, 3];
+ /// ```
++ #[clippy::version = "1.55.0"]
+ pub NONSTANDARD_MACRO_BRACES,
+ nursery,
+ "check consistent use of braces in macro"
+}
+
+const BRACES: &[(&str, &str)] = &[("(", ")"), ("{", "}"), ("[", "]")];
+
+/// The (name, (open brace, close brace), source snippet)
- let nested = in_macro(span.ctxt().outer_expn_data().call_site);
- !nested
++type MacroInfo<'a> = (Symbol, &'a (String, String), String);
+
+#[derive(Clone, Debug, Default)]
+pub struct MacroBraces {
+ macro_braces: FxHashMap<String, (String, String)>,
+ done: FxHashSet<Span>,
+}
+
+impl MacroBraces {
+ pub fn new(conf: &FxHashSet<MacroMatcher>) -> Self {
+ let macro_braces = macro_braces(conf.clone());
+ Self {
+ macro_braces,
+ done: FxHashSet::default(),
+ }
+ }
+}
+
+impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]);
+
+impl EarlyLintPass for MacroBraces {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ if let Some((name, braces, snip)) = is_offending_macro(cx, item.span, self) {
+ let span = item.span.ctxt().outer_expn_data().call_site;
+ emit_help(cx, snip, braces, name, span);
+ self.done.insert(span);
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
+ if let Some((name, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
+ let span = stmt.span.ctxt().outer_expn_data().call_site;
+ emit_help(cx, snip, braces, name, span);
+ self.done.insert(span);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if let Some((name, braces, snip)) = is_offending_macro(cx, expr.span, self) {
+ let span = expr.span.ctxt().outer_expn_data().call_site;
+ emit_help(cx, snip, braces, name, span);
+ self.done.insert(span);
+ }
+ }
+
+ fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
+ if let Some((name, braces, snip)) = is_offending_macro(cx, ty.span, self) {
+ let span = ty.span.ctxt().outer_expn_data().call_site;
+ emit_help(cx, snip, braces, name, span);
+ self.done.insert(span);
+ }
+ }
+}
+
+fn is_offending_macro<'a>(cx: &EarlyContext<'_>, span: Span, mac_braces: &'a MacroBraces) -> Option<MacroInfo<'a>> {
+ let unnested_or_local = || {
- // Make sure we are only one level deep otherwise there are to many FP's
- if in_macro(span);
- if let Some((name, braces)) = find_matching_macro(span, &mac_braces.macro_braces);
++ !span.ctxt().outer_expn_data().call_site.from_expansion()
+ || span
+ .macro_backtrace()
+ .last()
+ .map_or(false, |e| e.macro_def_id.map_or(false, DefId::is_local))
+ };
+ if_chain! {
- let c = snip.replace(" ", "");
++ if let ExpnKind::Macro(MacroKind::Bang, mac_name) = span.ctxt().outer_expn_data().kind;
++ let name = &*mac_name.as_str();
++ if let Some(braces) = mac_braces.macro_braces.get(name);
+ if let Some(snip) = snippet_opt(cx, span.ctxt().outer_expn_data().call_site);
+ // we must check only invocation sites
+ // https://github.com/rust-lang/rust-clippy/issues/7422
+ if snip.starts_with(&format!("{}!", name));
+ if unnested_or_local();
+ // make formatting consistent
- Some((name, braces, snip))
++ let c = snip.replace(' ', "");
+ if !c.starts_with(&format!("{}!{}", name, braces.0));
+ if !mac_braces.done.contains(&span.ctxt().outer_expn_data().call_site);
+ then {
- fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), name: &str, span: Span) {
++ Some((mac_name, braces, snip))
+ } else {
+ None
+ }
+ }
+}
+
- fn find_matching_macro(
- span: Span,
- braces: &FxHashMap<String, (String, String)>,
- ) -> Option<(&String, &(String, String))> {
- braces
- .iter()
- .find(|(macro_name, _)| is_direct_expn_of(span, macro_name).is_some())
- }
-
++fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), name: Symbol, span: Span) {
+ let with_space = &format!("! {}", braces.0);
+ let without_space = &format!("!{}", braces.0);
+ let mut help = snip;
+ for b in BRACES.iter().filter(|b| b.0 != braces.0) {
+ help = help.replace(b.0, &braces.0).replace(b.1, &braces.1);
+ // Only `{` traditionally has space before the brace
+ if braces.0 != "{" && help.contains(with_space) {
+ help = help.replace(with_space, without_space);
+ } else if braces.0 == "{" && help.contains(without_space) {
+ help = help.replace(without_space, with_space);
+ }
+ }
+ span_lint_and_help(
+ cx,
+ NONSTANDARD_MACRO_BRACES,
+ span,
+ &format!("use of irregular braces for `{}!` macro", name),
+ Some(span),
+ &format!("consider writing `{}`", help),
+ );
+}
+
+fn macro_braces(conf: FxHashSet<MacroMatcher>) -> FxHashMap<String, (String, String)> {
+ let mut braces = vec![
+ macro_matcher!(
+ name: "print",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "println",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "eprint",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "eprintln",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "write",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "writeln",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "format",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "format_args",
+ braces: ("(", ")"),
+ ),
+ macro_matcher!(
+ name: "vec",
+ braces: ("[", "]"),
+ ),
+ ]
+ .into_iter()
+ .collect::<FxHashMap<_, _>>();
+ // We want users items to override any existing items
+ for it in conf {
+ braces.insert(it.name, it.braces);
+ }
+ braces
+}
+
+macro_rules! macro_matcher {
+ (name: $name:expr, braces: ($open:expr, $close:expr) $(,)?) => {
+ ($name.to_owned(), ($open.to_owned(), $close.to_owned()))
+ };
+}
+pub(crate) use macro_matcher;
+
+#[derive(Clone, Debug)]
+pub struct MacroMatcher {
+ name: String,
+ braces: (String, String),
+}
+
+impl Hash for MacroMatcher {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.name.hash(state);
+ }
+}
+
+impl PartialEq for MacroMatcher {
+ fn eq(&self, other: &Self) -> bool {
+ self.name == other.name
+ }
+}
+impl Eq for MacroMatcher {}
+
+impl<'de> Deserialize<'de> for MacroMatcher {
+ fn deserialize<D>(deser: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ #[derive(Deserialize)]
+ #[serde(field_identifier, rename_all = "lowercase")]
+ enum Field {
+ Name,
+ Brace,
+ }
+ struct MacVisitor;
+ impl<'de> de::Visitor<'de> for MacVisitor {
+ type Value = MacroMatcher;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("struct MacroMatcher")
+ }
+
+ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
+ where
+ V: de::MapAccess<'de>,
+ {
+ let mut name = None;
+ let mut brace: Option<&str> = None;
+ while let Some(key) = map.next_key()? {
+ match key {
+ Field::Name => {
+ if name.is_some() {
+ return Err(de::Error::duplicate_field("name"));
+ }
+ name = Some(map.next_value()?);
+ },
+ Field::Brace => {
+ if brace.is_some() {
+ return Err(de::Error::duplicate_field("brace"));
+ }
+ brace = Some(map.next_value()?);
+ },
+ }
+ }
+ let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
+ let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
+ Ok(MacroMatcher {
+ name,
+ braces: BRACES
+ .iter()
+ .find(|b| b.0 == brace)
+ .map(|(o, c)| ((*o).to_owned(), (*c).to_owned()))
+ .ok_or_else(|| {
+ de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{}`", brace))
+ })?,
+ })
+ }
+ }
+
+ const FIELDS: &[&str] = &["name", "brace"];
+ deser.deserialize_struct("MacroMatcher", FIELDS, MacVisitor)
+ }
+}
--- /dev/null
--- /dev/null
++use clippy_utils::diagnostics::span_lint_and_then;
++use rustc_ast::ast::{Expr, ExprKind};
++use rustc_ast::token::{Lit, LitKind};
++use rustc_errors::Applicability;
++use rustc_lint::{EarlyContext, EarlyLintPass};
++use rustc_middle::lint::in_external_macro;
++use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_span::Span;
++use std::fmt::Write;
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for `\0` escapes in string and byte literals that look like octal
++ /// character escapes in C.
++ ///
++ /// ### Why is this bad?
++ ///
++ /// C and other languages support octal character escapes in strings, where
++ /// a backslash is followed by up to three octal digits. For example, `\033`
++ /// stands for the ASCII character 27 (ESC). Rust does not support this
++ /// notation, but has the escape code `\0` which stands for a null
++ /// byte/character, and any following digits do not form part of the escape
++ /// sequence. Therefore, `\033` is not a compiler error but the result may
++ /// be surprising.
++ ///
++ /// ### Known problems
++ /// The actual meaning can be the intended one. `\x00` can be used in these
++ /// cases to be unambigious.
++ ///
++ /// The lint does not trigger for format strings in `print!()`, `write!()`
++ /// and friends since the string is already preprocessed when Clippy lints
++ /// can see it.
++ ///
++ /// # Example
++ /// ```rust
++ /// // Bad
++ /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
++ /// let two = "\033\0"; // \033 intended as null-3-3
++ ///
++ /// // Good
++ /// let one = "\x1b[1mWill this be bold?\x1b[0m";
++ /// let two = "\x0033\x00";
++ /// ```
++ #[clippy::version = "1.58.0"]
++ pub OCTAL_ESCAPES,
++ suspicious,
++ "string escape sequences looking like octal characters"
++}
++
++declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]);
++
++impl EarlyLintPass for OctalEscapes {
++ fn check_expr(&mut self, cx: &EarlyContext<'tcx>, expr: &Expr) {
++ if in_external_macro(cx.sess, expr.span) {
++ return;
++ }
++
++ if let ExprKind::Lit(lit) = &expr.kind {
++ if matches!(lit.token.kind, LitKind::Str) {
++ check_lit(cx, &lit.token, lit.span, true);
++ } else if matches!(lit.token.kind, LitKind::ByteStr) {
++ check_lit(cx, &lit.token, lit.span, false);
++ }
++ }
++ }
++}
++
++fn check_lit(cx: &EarlyContext<'tcx>, lit: &Lit, span: Span, is_string: bool) {
++ let contents = lit.symbol.as_str();
++ let mut iter = contents.char_indices().peekable();
++ let mut found = vec![];
++
++ // go through the string, looking for \0[0-7][0-7]?
++ while let Some((from, ch)) = iter.next() {
++ if ch == '\\' {
++ if let Some((_, '0')) = iter.next() {
++ // collect up to two further octal digits
++ if let Some((mut to, '0'..='7')) = iter.next() {
++ if let Some((_, '0'..='7')) = iter.peek() {
++ to += 1;
++ }
++ found.push((from, to + 1));
++ }
++ }
++ }
++ }
++
++ if found.is_empty() {
++ return;
++ }
++
++ // construct two suggestion strings, one with \x escapes with octal meaning
++ // as in C, and one with \x00 for null bytes.
++ let mut suggest_1 = if is_string { "\"" } else { "b\"" }.to_string();
++ let mut suggest_2 = suggest_1.clone();
++ let mut index = 0;
++ for (from, to) in found {
++ suggest_1.push_str(&contents[index..from]);
++ suggest_2.push_str(&contents[index..from]);
++
++ // construct a replacement escape
++ // the maximum value is \077, or \x3f, so u8 is sufficient here
++ if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) {
++ write!(&mut suggest_1, "\\x{:02x}", n).unwrap();
++ }
++
++ // append the null byte as \x00 and the following digits literally
++ suggest_2.push_str("\\x00");
++ suggest_2.push_str(&contents[from + 2..to]);
++
++ index = to;
++ }
++ suggest_1.push_str(&contents[index..]);
++ suggest_1.push('"');
++ suggest_2.push_str(&contents[index..]);
++ suggest_2.push('"');
++
++ span_lint_and_then(
++ cx,
++ OCTAL_ESCAPES,
++ span,
++ &format!(
++ "octal-looking escape in {} literal",
++ if is_string { "string" } else { "byte string" }
++ ),
++ |diag| {
++ diag.help(&format!(
++ "octal escapes are not supported, `\\0` is always a null {}",
++ if is_string { "character" } else { "byte" }
++ ));
++ // suggestion 1: equivalent hex escape
++ diag.span_suggestion(
++ span,
++ "if an octal escape was intended, use the hexadecimal representation instead",
++ suggest_1,
++ Applicability::MaybeIncorrect,
++ );
++ // suggestion 2: unambiguous null byte
++ diag.span_suggestion(
++ span,
++ &format!(
++ "if the null {} is intended, disambiguate using",
++ if is_string { "character" } else { "byte" }
++ ),
++ suggest_2,
++ Applicability::MaybeIncorrect,
++ );
++ },
++ );
++}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::paths;
+use clippy_utils::ty::match_type;
+use rustc_ast::ast::LitKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{Span, Spanned};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for duplicate open options as well as combinations
+ /// that make no sense.
+ ///
+ /// ### Why is this bad?
+ /// In the best case, the code will be harder to read than
+ /// necessary. I don't know the worst case.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::fs::OpenOptions;
+ ///
+ /// OpenOptions::new().read(true).truncate(true);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NONSENSICAL_OPEN_OPTIONS,
+ correctness,
+ "nonsensical combination of options for opening a file"
+}
+
+declare_lint_pass!(OpenOptions => [NONSENSICAL_OPEN_OPTIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for OpenOptions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &e.kind {
+ let obj_ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
+ if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) {
+ let mut options = Vec::new();
+ get_open_options(cx, self_arg, &mut options);
+ check_open_options(cx, &options, e.span);
+ }
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum Argument {
+ True,
+ False,
+ Unknown,
+}
+
+#[derive(Debug)]
+enum OpenOption {
+ Write,
+ Read,
+ Truncate,
+ Create,
+ Append,
+}
+
+fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec<(OpenOption, Argument)>) {
+ if let ExprKind::MethodCall(path, _, arguments, _) = argument.kind {
+ let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs();
+
+ // Only proceed if this is a call on some object of type std::fs::OpenOptions
+ if match_type(cx, obj_ty, &paths::OPEN_OPTIONS) && arguments.len() >= 2 {
+ let argument_option = match arguments[1].kind {
+ ExprKind::Lit(ref span) => {
+ if let Spanned {
+ node: LitKind::Bool(lit),
+ ..
+ } = *span
+ {
+ if lit { Argument::True } else { Argument::False }
+ } else {
+ // The function is called with a literal which is not a boolean literal.
+ // This is theoretically possible, but not very likely.
+ return;
+ }
+ },
+ _ => Argument::Unknown,
+ };
+
+ match &*path.ident.as_str() {
+ "create" => {
+ options.push((OpenOption::Create, argument_option));
+ },
+ "append" => {
+ options.push((OpenOption::Append, argument_option));
+ },
+ "truncate" => {
+ options.push((OpenOption::Truncate, argument_option));
+ },
+ "read" => {
+ options.push((OpenOption::Read, argument_option));
+ },
+ "write" => {
+ options.push((OpenOption::Write, argument_option));
+ },
+ _ => (),
+ }
+
+ get_open_options(cx, &arguments[0], options);
+ }
+ }
+}
+
+fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)], span: Span) {
+ let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
+ let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) =
+ (false, false, false, false, false);
+ // This code is almost duplicated (oh, the irony), but I haven't found a way to
+ // unify it.
+
+ for option in options {
+ match *option {
+ (OpenOption::Create, arg) => {
+ if create {
+ span_lint(
+ cx,
+ NONSENSICAL_OPEN_OPTIONS,
+ span,
+ "the method `create` is called more than once",
+ );
+ } else {
+ create = true;
+ }
+ create_arg = create_arg || (arg == Argument::True);
+ },
+ (OpenOption::Append, arg) => {
+ if append {
+ span_lint(
+ cx,
+ NONSENSICAL_OPEN_OPTIONS,
+ span,
+ "the method `append` is called more than once",
+ );
+ } else {
+ append = true;
+ }
+ append_arg = append_arg || (arg == Argument::True);
+ },
+ (OpenOption::Truncate, arg) => {
+ if truncate {
+ span_lint(
+ cx,
+ NONSENSICAL_OPEN_OPTIONS,
+ span,
+ "the method `truncate` is called more than once",
+ );
+ } else {
+ truncate = true;
+ }
+ truncate_arg = truncate_arg || (arg == Argument::True);
+ },
+ (OpenOption::Read, arg) => {
+ if read {
+ span_lint(
+ cx,
+ NONSENSICAL_OPEN_OPTIONS,
+ span,
+ "the method `read` is called more than once",
+ );
+ } else {
+ read = true;
+ }
+ read_arg = read_arg || (arg == Argument::True);
+ },
+ (OpenOption::Write, arg) => {
+ if write {
+ span_lint(
+ cx,
+ NONSENSICAL_OPEN_OPTIONS,
+ span,
+ "the method `write` is called more than once",
+ );
+ } else {
+ write = true;
+ }
+ write_arg = write_arg || (arg == Argument::True);
+ },
+ }
+ }
+
+ if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
+ span_lint(
+ cx,
+ NONSENSICAL_OPEN_OPTIONS,
+ span,
+ "file opened with `truncate` and `read`",
+ );
+ }
+ if append && truncate && append_arg && truncate_arg {
+ span_lint(
+ cx,
+ NONSENSICAL_OPEN_OPTIONS,
+ span,
+ "file opened with `append` and `truncate`",
+ );
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::is_direct_expn_of;
+use if_chain::if_chain;
+use rustc_ast::ast::{Expr, ExprKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `option_env!(...).unwrap()` and
+ /// suggests usage of the `env!` macro.
+ ///
+ /// ### Why is this bad?
+ /// Unwrapping the result of `option_env!` will panic
+ /// at run-time if the environment variable doesn't exist, whereas `env!`
+ /// catches it at compile-time.
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// let _ = option_env!("HOME").unwrap();
+ /// ```
+ ///
+ /// Is better expressed as:
+ ///
+ /// ```rust,no_run
+ /// let _ = env!("HOME");
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub OPTION_ENV_UNWRAP,
+ correctness,
+ "using `option_env!(...).unwrap()` to get environment variable"
+}
+
+declare_lint_pass!(OptionEnvUnwrap => [OPTION_ENV_UNWRAP]);
+
+impl EarlyLintPass for OptionEnvUnwrap {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if_chain! {
+ if let ExprKind::MethodCall(path_segment, args, _) = &expr.kind;
+ if matches!(path_segment.ident.name, sym::expect | sym::unwrap);
+ if let ExprKind::Call(caller, _) = &args[0].kind;
+ if is_direct_expn_of(caller.span, "option_env").is_some();
+ then {
+ span_lint_and_help(
+ cx,
+ OPTION_ENV_UNWRAP,
+ expr.span,
+ "this will panic at run-time if the environment variable doesn't exist at compile-time",
+ None,
+ "consider using the `env!` macro instead"
+ );
+ }
+ }
+ }
+}
--- /dev/null
- can_move_expr_to_closure, eager_or_lazy, in_constant, in_macro, is_else_clause, is_lang_ctor, peel_hir_expr_while,
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{
- Sugg::hir(cx, cond_expr, "..").maybe_par(),
++ can_move_expr_to_closure, eager_or_lazy, in_constant, is_else_clause, is_lang_ctor, peel_hir_expr_while,
+ CaptureKind,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::OptionSome;
+use rustc_hir::{def::Res, BindingAnnotation, Block, Expr, ExprKind, Mutability, PatKind, Path, QPath, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
+ /// idiomatically done with `Option::map_or` (if the else bit is a pure
+ /// expression) or `Option::map_or_else` (if the else bit is an impure
+ /// expression).
+ ///
+ /// ### Why is this bad?
+ /// Using the dedicated functions of the `Option` type is clearer and
+ /// more concise than an `if let` expression.
+ ///
+ /// ### Known problems
+ /// This lint uses a deliberately conservative metric for checking
+ /// if the inside of either body contains breaks or continues which will
+ /// cause it to not suggest a fix if either block contains a loop with
+ /// continues or breaks contained within the loop.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let optional: Option<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);
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub OPTION_IF_LET_ELSE,
+ nursery,
+ "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(path, _, &[ref receiver], _) = &expr.kind {
+ path.ident.name.as_str() == "ok"
+ && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver), sym::Result)
+ } else {
+ false
+ }
+}
+
+/// A struct containing information about occurrences of the
+/// `if let Some(..) = .. else` construct that this lint detects.
+struct OptionIfLetElseOccurence {
+ option: String,
+ method_sugg: String,
+ some_expr: String,
+ none_expr: String,
+}
+
+/// 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_expr<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
+ if let ExprKind::Block(
+ Block {
+ stmts: block_stmts,
+ expr: Some(block_expr),
+ ..
+ },
+ _,
+ ) = expr.kind
+ {
+ if let [] = block_stmts {
+ Some(block_expr)
+ } else {
+ Some(expr)
+ }
+ } else {
+ None
+ }
+}
+
+fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String {
+ format!(
+ "{}{}",
- if !in_macro(expr.span); // Don't lint macros, because it behaves weirdly
++ Sugg::hir_with_macro_callsite(cx, cond_expr, "..").maybe_par(),
+ if as_mut {
+ ".as_mut()"
+ } else if as_ref {
+ ".as_ref()"
+ } else {
+ ""
+ }
+ )
+}
+
+/// If this expression is the option if let/else construct we're detecting, then
+/// this function returns an `OptionIfLetElseOccurence` struct with details if
+/// this construct is found, or None if this construct is not found.
+fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurence> {
+ if_chain! {
- let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) {
- "map_or"
- } else {
- "map_or_else"
- };
++ if !expr.span.from_expansion(); // Don't lint macros, because it behaves weirdly
+ if !in_constant(cx, expr.hir_id);
+ if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
+ = higher::IfLet::hir(cx, expr);
+ if !is_else_clause(cx.tcx, expr);
+ if !is_result_ok(cx, let_expr); // Don't lint on Result::ok because a different lint does it already
+ if let PatKind::TupleStruct(struct_qpath, [inner_pat], _) = &let_pat.kind;
+ if is_lang_ctor(cx, struct_qpath, OptionSome);
+ if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind;
+ if let Some(some_captures) = can_move_expr_to_closure(cx, if_then);
+ if let Some(none_captures) = can_move_expr_to_closure(cx, if_else);
+ if some_captures
+ .iter()
+ .filter_map(|(id, &c)| none_captures.get(id).map(|&c2| (c, c2)))
+ .all(|(x, y)| x.is_imm_ref() && y.is_imm_ref());
+
+ then {
+ let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" };
+ let some_body = extract_body_from_expr(if_then)?;
+ let none_body = extract_body_from_expr(if_else)?;
- 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, "..")),
++ let method_sugg = if eager_or_lazy::switch_to_eager_eval(cx, none_body) { "map_or" } else { "map_or_else" };
+ let capture_name = id.name.to_ident_string();
+ let (as_ref, as_mut) = match &let_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 let_expr.kind {
+ // Pointer dereferencing happens automatically, so we can omit it in the suggestion
+ ExprKind::Unary(UnOp::Deref, expr) | ExprKind::AddrOf(_, _, expr) => expr,
+ _ => let_expr,
+ };
+ // Check if captures the closure will need conflict with borrows made in the scrutinee.
+ // TODO: check all the references made in the scrutinee expression. This will require interacting
+ // with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
+ if as_ref || as_mut {
+ let e = peel_hir_expr_while(cond_expr, |e| match e.kind {
+ ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
+ _ => None,
+ });
+ if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind {
+ match some_captures.get(local_id)
+ .or_else(|| (method_sugg == "map_or_else").then(|| ()).and_then(|_| none_captures.get(local_id)))
+ {
+ Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
+ Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None,
+ Some(CaptureKind::Ref(Mutability::Not)) | None => (),
+ }
+ }
+ }
+ 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_with_macro_callsite(cx, some_body, "..")),
++ none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir_with_macro_callsite(cx, none_body, "..")),
+ })
+ } else {
+ None
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for OptionIfLetElse {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
+ if let Some(detection) = detect_option_if_let_else(cx, expr) {
+ span_lint_and_sugg(
+ cx,
+ OPTION_IF_LET_ELSE,
+ expr.span,
+ format!("use Option::{} instead of an if let/else", detection.method_sugg).as_str(),
+ "try",
+ format!(
+ "{}.{}({}, {})",
+ detection.option, detection.method_sugg, detection.none_expr, detection.some_expr,
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::SpanlessEq;
+use if_chain::if_chain;
+use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects classic underflow/overflow checks.
+ ///
+ /// ### Why is this bad?
+ /// Most classic C underflow/overflow checks will fail in
+ /// Rust. Users can use functions like `overflowing_*` and `wrapping_*` instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let a = 1;
+ /// # let b = 2;
+ /// a + b < a;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub OVERFLOW_CHECK_CONDITIONAL,
+ complexity,
+ "overflow checks inspired by C which are likely to panic"
+}
+
+declare_lint_pass!(OverflowCheckConditional => [OVERFLOW_CHECK_CONDITIONAL]);
+
+const OVERFLOW_MSG: &str = "you are trying to use classic C overflow conditions that will fail in Rust";
+const UNDERFLOW_MSG: &str = "you are trying to use classic C underflow conditions that will fail in Rust";
+
+impl<'tcx> LateLintPass<'tcx> for OverflowCheckConditional {
+ // a + b < a, a > a + b, a < a - b, a - b > a
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let eq = |l, r| SpanlessEq::new(cx).eq_path_segment(l, r);
+ if_chain! {
+ if let ExprKind::Binary(ref op, first, second) = expr.kind;
+ if let ExprKind::Binary(ref op2, ident1, ident2) = first.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path1)) = ident1.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path2)) = ident2.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path3)) = second.kind;
+ if eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]);
+ if cx.typeck_results().expr_ty(ident1).is_integral();
+ if cx.typeck_results().expr_ty(ident2).is_integral();
+ then {
+ if op.node == BinOpKind::Lt && op2.node == BinOpKind::Add {
+ span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, OVERFLOW_MSG);
+ }
+ if op.node == BinOpKind::Gt && op2.node == BinOpKind::Sub {
+ span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, UNDERFLOW_MSG);
+ }
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(ref op, first, second) = expr.kind;
+ if let ExprKind::Binary(ref op2, ident1, ident2) = second.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path1)) = ident1.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path2)) = ident2.kind;
+ if let ExprKind::Path(QPath::Resolved(_, path3)) = first.kind;
+ if eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0]);
+ if cx.typeck_results().expr_ty(ident1).is_integral();
+ if cx.typeck_results().expr_ty(ident2).is_integral();
+ then {
+ if op.node == BinOpKind::Gt && op2.node == BinOpKind::Add {
+ span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, OVERFLOW_MSG);
+ }
+ if op.node == BinOpKind::Lt && op2.node == BinOpKind::Sub {
+ span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, UNDERFLOW_MSG);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{find_macro_calls, is_expn_of, return_ty};
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `panic!`, `unimplemented!`, `todo!`, `unreachable!` or assertions in a function of type result.
+ ///
+ /// ### Why is this bad?
+ /// For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided.
+ ///
+ /// ### Known problems
+ /// Functions called from a function returning a `Result` may invoke a panicking macro. This is not checked.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn result_with_panic() -> Result<bool, String>
+ /// {
+ /// panic!("error");
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn result_without_panic() -> Result<bool, String> {
+ /// Err(String::from("error"))
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub PANIC_IN_RESULT_FN,
+ restriction,
+ "functions of type `Result<..>` that contain `panic!()`, `todo!()`, `unreachable()`, `unimplemented()` or assertion"
+}
+
+declare_lint_pass!(PanicInResultFn => [PANIC_IN_RESULT_FN]);
+
+impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ fn_kind: FnKind<'tcx>,
+ _: &'tcx hir::FnDecl<'tcx>,
+ body: &'tcx hir::Body<'tcx>,
+ span: Span,
+ hir_id: hir::HirId,
+ ) {
+ if !matches!(fn_kind, FnKind::Closure) && is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) {
+ lint_impl_body(cx, span, body);
+ }
+ }
+}
+
+fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
+ let mut panics = find_macro_calls(
+ &[
+ "unimplemented",
+ "unreachable",
+ "panic",
+ "todo",
+ "assert",
+ "assert_eq",
+ "assert_ne",
+ ],
+ body,
+ );
+ panics.retain(|span| is_expn_of(*span, "debug_assert").is_none());
+ if !panics.is_empty() {
+ span_lint_and_then(
+ cx,
+ PANIC_IN_RESULT_FN,
+ impl_span,
+ "used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`",
+ move |diag| {
+ diag.help(
+ "`unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing",
+ );
+ diag.span_note(panics, "return Err() instead of panicking");
+ },
+ );
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{is_expn_of, match_panic_call};
+use if_chain::if_chain;
+use rustc_hir::Expr;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `panic!`.
+ ///
+ /// ### Why is this bad?
+ /// `panic!` will stop the execution of the executable
+ ///
+ /// ### Example
+ /// ```no_run
+ /// panic!("even with a good reason");
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub PANIC,
+ restriction,
+ "usage of the `panic!` macro"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `unimplemented!`.
+ ///
+ /// ### Why is this bad?
+ /// This macro should not be present in production code
+ ///
+ /// ### Example
+ /// ```no_run
+ /// unimplemented!();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNIMPLEMENTED,
+ restriction,
+ "`unimplemented!` should not be present in production code"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `todo!`.
+ ///
+ /// ### Why is this bad?
+ /// This macro should not be present in production code
+ ///
+ /// ### Example
+ /// ```no_run
+ /// todo!();
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub TODO,
+ restriction,
+ "`todo!` should not be present in production code"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `unreachable!`.
+ ///
+ /// ### Why is this bad?
+ /// This macro can cause code to panic
+ ///
+ /// ### Example
+ /// ```no_run
+ /// unreachable!();
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub UNREACHABLE,
+ restriction,
+ "usage of the `unreachable!` macro"
+}
+
+declare_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANIC]);
+
+impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if match_panic_call(cx, expr).is_some()
+ && (is_expn_of(expr.span, "debug_assert").is_none() && is_expn_of(expr.span, "assert").is_none())
+ {
+ let span = get_outer_span(expr);
+ if is_expn_of(expr.span, "unimplemented").is_some() {
+ span_lint(
+ cx,
+ UNIMPLEMENTED,
+ span,
+ "`unimplemented` should not be present in production code",
+ );
+ } else if is_expn_of(expr.span, "todo").is_some() {
+ span_lint(cx, TODO, span, "`todo` should not be present in production code");
+ } else if is_expn_of(expr.span, "unreachable").is_some() {
+ span_lint(cx, UNREACHABLE, span, "usage of the `unreachable!` macro");
+ } else if is_expn_of(expr.span, "panic").is_some() {
+ span_lint(cx, PANIC, span, "`panic` should not be present in production code");
+ }
+ }
+ }
+}
+
+fn get_outer_span(expr: &Expr<'_>) -> Span {
+ if_chain! {
+ if expr.span.from_expansion();
+ let first = expr.span.ctxt().outer_expn_data().call_site;
+ if first.from_expansion();
+ then {
+ first.ctxt().outer_expn_data().call_site
+ } else {
+ expr.span
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_hir;
+use clippy_utils::is_automatically_derived;
+use if_chain::if_chain;
+use rustc_hir::{Impl, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual re-implementations of `PartialEq::ne`.
+ ///
+ /// ### Why is this bad?
+ /// `PartialEq::ne` is required to always return the
+ /// negated result of `PartialEq::eq`, which is exactly what the default
+ /// implementation does. Therefore, there should never be any need to
+ /// re-implement it.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo;
+ ///
+ /// impl PartialEq for Foo {
+ /// fn eq(&self, other: &Foo) -> bool { true }
+ /// fn ne(&self, other: &Foo) -> bool { !(self == other) }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub PARTIALEQ_NE_IMPL,
+ complexity,
+ "re-implementing `PartialEq::ne`"
+}
+
+declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]);
+
+impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if_chain! {
+ if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), items: impl_items, .. }) = item.kind;
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ if !is_automatically_derived(attrs);
+ if let Some(eq_trait) = cx.tcx.lang_items().eq_trait();
+ if trait_ref.path.res.def_id() == eq_trait;
+ then {
+ for impl_item in impl_items {
+ if impl_item.ident.name == sym::ne {
+ span_lint_hir(
+ cx,
+ PARTIALEQ_NE_IMPL,
+ impl_item.id.hir_id(),
+ impl_item.span,
+ "re-implementing `PartialEq::ne` is unnecessary",
+ );
+ }
+ }
+ }
+ };
+ }
+}
--- /dev/null
+use std::cmp;
+use std::iter;
+
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_copy;
+use clippy_utils::{is_self, is_self_ty};
+use if_chain::if_chain;
+use rustc_ast::attr;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::def_id::LocalDefId;
+use rustc_span::{sym, Span};
+use rustc_target::spec::abi::Abi;
+use rustc_target::spec::Target;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by reference, where
+ /// the argument type is `Copy` and small enough to be more efficient to always
+ /// pass by value.
+ ///
+ /// ### Why is this bad?
+ /// In many calling conventions instances of structs will
+ /// be passed through registers if they fit into two or less general purpose
+ /// registers.
+ ///
+ /// ### Known problems
+ /// This lint is target register size dependent, it is
+ /// limited to 32-bit to try and reduce portability problems between 32 and
+ /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit
+ /// will be different.
+ ///
+ /// The configuration option `trivial_copy_size_limit` can be set to override
+ /// this limit for a project.
+ ///
+ /// This lint attempts to allow passing arguments by reference if a reference
+ /// to that argument is returned. This is implemented by comparing the lifetime
+ /// of the argument and return value for equality. However, this can cause
+ /// false positives in cases involving multiple lifetimes that are bounded by
+ /// each other.
+ ///
+ /// Also, it does not take account of other similar cases where getting memory addresses
+ /// matters; namely, returning the pointer to the argument in question,
+ /// and passing the argument, as both references and pointers,
+ /// to a function that needs the memory address. For further details, refer to
+ /// [this issue](https://github.com/rust-lang/rust-clippy/issues/5953)
+ /// that explains a real case in which this false positive
+ /// led to an **undefined behaviour** introduced with unsafe code.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// // Bad
+ /// fn foo(v: &u32) {}
+ /// ```
+ ///
+ /// ```rust
+ /// // Better
+ /// fn foo(v: u32) {}
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRIVIALLY_COPY_PASS_BY_REF,
+ pedantic,
+ "functions taking small copyable arguments by reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions taking arguments by value, where
+ /// the argument type is `Copy` and large enough to be worth considering
+ /// passing by reference. Does not trigger if the function is being exported,
+ /// because that might induce API breakage, if the parameter is declared as mutable,
+ /// or if the argument is a `self`.
+ ///
+ /// ### Why is this bad?
+ /// Arguments passed by value might result in an unnecessary
+ /// shallow copy, taking up more space in the stack and requiring a call to
+ /// `memcpy`, which can be expensive.
+ ///
+ /// ### Example
+ /// ```rust
+ /// #[derive(Clone, Copy)]
+ /// struct TooLarge([u8; 2048]);
+ ///
+ /// // Bad
+ /// fn foo(v: TooLarge) {}
+ /// ```
+ /// ```rust
+ /// #[derive(Clone, Copy)]
+ /// struct TooLarge([u8; 2048]);
+ ///
+ /// // Good
+ /// fn foo(v: &TooLarge) {}
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub LARGE_TYPES_PASSED_BY_VALUE,
+ pedantic,
+ "functions taking large arguments by value"
+}
+
+#[derive(Copy, Clone)]
+pub struct PassByRefOrValue {
+ ref_min_size: u64,
+ value_max_size: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl<'tcx> PassByRefOrValue {
+ pub fn new(
+ ref_min_size: Option<u64>,
+ value_max_size: u64,
+ avoid_breaking_exported_api: bool,
+ target: &Target,
+ ) -> Self {
+ let ref_min_size = ref_min_size.unwrap_or_else(|| {
+ let bit_width = u64::from(target.pointer_width);
+ // Cap the calculated bit width at 32-bits to reduce
+ // portability problems between 32 and 64-bit targets
+ let bit_width = cmp::min(bit_width, 32);
+ #[allow(clippy::integer_division)]
+ let byte_width = bit_width / 8;
+ // Use a limit of 2 times the register byte width
+ byte_width * 2
+ });
+
+ Self {
+ ref_min_size,
+ value_max_size,
+ avoid_breaking_exported_api,
+ }
+ }
+
+ fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option<Span>) {
+ if self.avoid_breaking_exported_api && cx.access_levels.is_exported(def_id) {
+ return;
+ }
+
+ let fn_sig = cx.tcx.fn_sig(def_id);
+ let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
+
+ let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
+
+ for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() {
+ // All spans generated from a proc-macro invocation are the same...
+ match span {
+ Some(s) if s == input.span => return,
+ _ => (),
+ }
+
+ match ty.kind() {
+ ty::Ref(input_lt, ty, Mutability::Not) => {
+ // Use lifetimes to determine if we're returning a reference to the
+ // argument. In that case we can't switch to pass-by-value as the
+ // argument will not live long enough.
+ let output_lts = match *fn_sig.output().kind() {
+ ty::Ref(output_lt, _, _) => vec![output_lt],
+ ty::Adt(_, substs) => substs.regions().collect(),
+ _ => vec![],
+ };
+
+ if_chain! {
+ if !output_lts.contains(input_lt);
+ if is_copy(cx, ty);
+ if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
+ if size <= self.ref_min_size;
+ if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind;
+ then {
+ let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
+ "self".into()
+ } else {
+ snippet(cx, decl_ty.span, "_").into()
+ };
+ span_lint_and_sugg(
+ cx,
+ TRIVIALLY_COPY_PASS_BY_REF,
+ input.span,
+ &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
+ "consider passing by value instead",
+ value_type,
+ Applicability::Unspecified,
+ );
+ }
+ }
+ },
+
+ ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => {
+ // if function has a body and parameter is annotated with mut, ignore
+ if let Some(param) = fn_body.and_then(|body| body.params.get(index)) {
+ match param.pat.kind {
+ PatKind::Binding(BindingAnnotation::Unannotated, _, _, _) => {},
+ _ => continue,
+ }
+ }
+
+ if_chain! {
+ if is_copy(cx, ty);
+ if !is_self_ty(input);
+ if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes());
+ if size > self.value_max_size;
+ then {
+ span_lint_and_sugg(
+ cx,
+ LARGE_TYPES_PASSED_BY_VALUE,
+ input.span,
+ &format!("this argument ({} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", size, self.value_max_size),
+ "consider passing by reference instead",
+ format!("&{}", snippet(cx, input.span, "_")),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ },
+
+ _ => {},
+ }
+ }
+ }
+}
+
+impl_lint_pass!(PassByRefOrValue => [TRIVIALLY_COPY_PASS_BY_REF, LARGE_TYPES_PASSED_BY_VALUE]);
+
+impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue {
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
+ if item.span.from_expansion() {
+ return;
+ }
+
+ if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind {
+ self.check_poly_fn(cx, item.def_id, &*method_sig.decl, None);
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ _body: &'tcx Body<'_>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if span.from_expansion() {
+ return;
+ }
+
+ match kind {
+ FnKind::ItemFn(.., header, _) => {
+ if header.abi != Abi::Rust {
+ return;
+ }
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ for a in attrs {
+ if let Some(meta_items) = a.meta_item_list() {
+ if a.has_name(sym::proc_macro_derive)
+ || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always))
+ {
+ return;
+ }
+ }
+ }
+ },
+ FnKind::Method(..) => (),
+ FnKind::Closure => return,
+ }
+
+ // Exclude non-inherent impls
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ if matches!(
+ item.kind,
+ ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
+ ) {
+ return;
+ }
+ }
+
+ self.check_poly_fn(cx, cx.tcx.hir().local_def_id(hir_id), decl, Some(span));
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+use std::path::{Component, Path};
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push)
+ /// calls on `PathBuf` that can cause overwrites.
+ ///
+ /// ### Why is this bad?
+ /// Calling `push` with a root path at the start can overwrite the
+ /// previous defined path.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::path::PathBuf;
+ ///
+ /// let mut x = PathBuf::from("/foo");
+ /// x.push("/bar");
+ /// assert_eq!(x, PathBuf::from("/bar"));
+ /// ```
+ /// Could be written:
+ ///
+ /// ```rust
+ /// use std::path::PathBuf;
+ ///
+ /// let mut x = PathBuf::from("/foo");
+ /// x.push("bar");
+ /// assert_eq!(x, PathBuf::from("/foo/bar"));
+ /// ```
++ #[clippy::version = "1.36.0"]
+ pub PATH_BUF_PUSH_OVERWRITE,
+ nursery,
+ "calling `push` with file system root on `PathBuf` can overwrite it"
+}
+
+declare_lint_pass!(PathBufPushOverwrite => [PATH_BUF_PUSH_OVERWRITE]);
+
+impl<'tcx> LateLintPass<'tcx> for PathBufPushOverwrite {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
+ if path.ident.name == sym!(push);
+ if args.len() == 2;
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::PathBuf);
+ if let Some(get_index_arg) = args.get(1);
+ if let ExprKind::Lit(ref lit) = get_index_arg.kind;
+ if let LitKind::Str(ref path_lit, _) = lit.node;
+ if let pushed_path = Path::new(&*path_lit.as_str());
+ if let Some(pushed_path_lit) = pushed_path.to_str();
+ if pushed_path.has_root();
+ if let Some(root) = pushed_path.components().next();
+ if root == Component::RootDir;
+ then {
+ span_lint_and_sugg(
+ cx,
+ PATH_BUF_PUSH_OVERWRITE,
+ lit.span,
+ "calling `push` with '/' or '\\' (file system root) will overwrite the previous path definition",
+ "try",
+ format!("\"{}\"", pushed_path_lit.trim_start_matches(|c| c == '/' || c == '\\')),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{
+ intravisit, Body, Expr, ExprKind, FnDecl, HirId, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind,
+};
+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 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.
+ ///
+ /// ### 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;
+ /// }
+ /// ```
++ #[clippy::version = "1.47.0"]
+ 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(local) = stmt.kind {
+ if in_external_macro(cx.sess(), local.pat.span) {
+ return;
+ }
+ let deref_possible = match local.source {
+ LocalSource::Normal => DerefPossible::Possible,
+ _ => DerefPossible::Impossible,
+ };
+ apply_lint(cx, local.pat, deref_possible);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Match(_, arms, _) = expr.kind {
+ for arm in arms {
+ let pat = &arm.pat;
+ if apply_lint(cx, pat, DerefPossible::Possible) {
+ break;
+ }
+ }
+ }
+ if let ExprKind::Let(let_pat, ..) = expr.kind {
+ apply_lint(cx, let_pat, DerefPossible::Possible);
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: intravisit::FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ for param in body.params {
+ apply_lint(cx, param.pat, DerefPossible::Impossible);
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+enum DerefPossible {
+ Possible,
+ Impossible,
+}
+
+fn apply_lint<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, deref_possible: DerefPossible) -> bool {
+ let maybe_mismatch = find_first_mismatch(cx, pat);
+ 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<'_>) -> Option<(Span, Mutability, Level)> {
+ let mut result = None;
+ pat.walk(|p| {
+ if result.is_some() {
+ return false;
+ }
+ if in_external_macro(cx.sess(), p.span) {
+ return true;
+ }
+ let adjust_pat = match p.kind {
+ PatKind::Or([p, ..]) => p,
+ _ => p,
+ };
+ if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
+ if let [first, ..] = **adjustments {
+ if let ty::Ref(.., mutability) = *first.kind() {
+ let level = if p.hir_id == pat.hir_id {
+ Level::Top
+ } else {
+ Level::Lower
+ };
+ result = Some((p.span, mutability, level));
+ }
+ }
+ }
+ result.is_none()
+ });
+ result
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+const ALLOWED_ODD_FUNCTIONS: [&str; 14] = [
+ "asin",
+ "asinh",
+ "atan",
+ "atanh",
+ "cbrt",
+ "fract",
+ "round",
+ "signum",
+ "sin",
+ "sinh",
+ "tan",
+ "tanh",
+ "to_degrees",
+ "to_radians",
+];
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for operations where precedence may be unclear
+ /// and suggests to add parentheses. Currently it catches the following:
+ /// * mixed usage of arithmetic and bit shifting/combining operators without
+ /// parentheses
+ /// * a "negative" numeric literal (which is really a unary `-` followed by a
+ /// numeric literal)
+ /// followed by a method call
+ ///
+ /// ### Why is this bad?
+ /// Not everyone knows the precedence of those operators by
+ /// heart, so expressions like these may trip others trying to reason about the
+ /// code.
+ ///
+ /// ### Example
+ /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7
+ /// * `-1i32.abs()` equals -1, while `(-1i32).abs()` equals 1
++ #[clippy::version = "pre 1.29.0"]
+ pub PRECEDENCE,
+ complexity,
+ "operations where precedence may be unclear"
+}
+
+declare_lint_pass!(Precedence => [PRECEDENCE]);
+
+impl EarlyLintPass for Precedence {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind {
+ let span_sugg = |expr: &Expr, sugg, appl| {
+ span_lint_and_sugg(
+ cx,
+ PRECEDENCE,
+ expr.span,
+ "operator precedence can trip the unwary",
+ "consider parenthesizing your expression",
+ sugg,
+ appl,
+ );
+ };
+
+ if !is_bit_op(op) {
+ return;
+ }
+ let mut applicability = Applicability::MachineApplicable;
+ match (is_arith_expr(left), is_arith_expr(right)) {
+ (true, true) => {
+ let sugg = format!(
+ "({}) {} ({})",
+ snippet_with_applicability(cx, left.span, "..", &mut applicability),
+ op.to_string(),
+ snippet_with_applicability(cx, right.span, "..", &mut applicability)
+ );
+ span_sugg(expr, sugg, applicability);
+ },
+ (true, false) => {
+ let sugg = format!(
+ "({}) {} {}",
+ snippet_with_applicability(cx, left.span, "..", &mut applicability),
+ op.to_string(),
+ snippet_with_applicability(cx, right.span, "..", &mut applicability)
+ );
+ span_sugg(expr, sugg, applicability);
+ },
+ (false, true) => {
+ let sugg = format!(
+ "{} {} ({})",
+ snippet_with_applicability(cx, left.span, "..", &mut applicability),
+ op.to_string(),
+ snippet_with_applicability(cx, right.span, "..", &mut applicability)
+ );
+ span_sugg(expr, sugg, applicability);
+ },
+ (false, false) => (),
+ }
+ }
+
+ if let ExprKind::Unary(UnOp::Neg, operand) = &expr.kind {
+ let mut arg = operand;
+
+ let mut all_odd = true;
+ while let ExprKind::MethodCall(path_segment, args, _) = &arg.kind {
+ let path_segment_str = path_segment.ident.name.as_str();
+ all_odd &= ALLOWED_ODD_FUNCTIONS
+ .iter()
+ .any(|odd_function| **odd_function == *path_segment_str);
+ arg = args.first().expect("A method always has a receiver.");
+ }
+
+ if_chain! {
+ if !all_odd;
+ if let ExprKind::Lit(lit) = &arg.kind;
+ if let LitKind::Int(..) | LitKind::Float(..) = &lit.kind;
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ PRECEDENCE,
+ expr.span,
+ "unary minus has lower precedence than method call",
+ "consider adding parentheses to clarify your intent",
+ format!(
+ "-({})",
+ snippet_with_applicability(cx, operand.span, "..", &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
+
+fn is_arith_expr(expr: &Expr) -> bool {
+ match expr.kind {
+ ExprKind::Binary(Spanned { node: op, .. }, _, _) => is_arith_op(op),
+ _ => false,
+ }
+}
+
+#[must_use]
+fn is_bit_op(op: BinOpKind) -> bool {
+ use rustc_ast::ast::BinOpKind::{BitAnd, BitOr, BitXor, Shl, Shr};
+ matches!(op, BitXor | BitAnd | BitOr | Shl | Shr)
+}
+
+#[must_use]
+fn is_arith_op(op: BinOpKind) -> bool {
+ use rustc_ast::ast::BinOpKind::{Add, Div, Mul, Rem, Sub};
+ matches!(op, Add | Sub | Mul | Div | Rem)
+}
--- /dev/null
+//! Checks for usage of `&Vec[_]` and `&String`.
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::ptr::get_spans;
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::walk_ptrs_hir_ty;
+use clippy_utils::{expr_path_res, is_lint_allowed, match_any_diagnostic_items, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::def::Res;
+use rustc_hir::{
+ BinOpKind, BodyId, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, Impl, ImplItem, ImplItemKind, Item, ItemKind,
+ Lifetime, MutTy, Mutability, Node, PathSegment, QPath, TraitFn, TraitItem, TraitItemKind, Ty, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Symbol;
+use rustc_span::{sym, MultiSpan};
+use std::borrow::Cow;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for function arguments of type `&String`
+ /// or `&Vec` unless the references are mutable. It will also suggest you
+ /// replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()`
+ /// calls.
+ ///
+ /// ### Why is this bad?
+ /// Requiring the argument to be of the specific size
+ /// makes the function less useful for no benefit; slices in the form of `&[T]`
+ /// or `&str` usually suffice and can be obtained from other types, too.
+ ///
+ /// ### Known problems
+ /// The lint does not follow data. So if you have an
+ /// argument `x` and write `let y = x; y.clone()` the lint will not suggest
+ /// changing that `.clone()` to `.to_owned()`.
+ ///
+ /// Other functions called from this function taking a `&String` or `&Vec`
+ /// argument may also fail to compile if you change the argument. Applying
+ /// this lint on them will fix the problem, but they may be in other crates.
+ ///
+ /// One notable example of a function that may cause issues, and which cannot
+ /// easily be changed due to being in the standard library is `Vec::contains`.
+ /// when called on a `Vec<Vec<T>>`. If a `&Vec` is passed to that method then
+ /// it will compile, but if a `&[T]` is passed then it will not compile.
+ ///
+ /// ```ignore
+ /// fn cannot_take_a_slice(v: &Vec<u8>) -> bool {
+ /// let vec_of_vecs: Vec<Vec<u8>> = some_other_fn();
+ ///
+ /// vec_of_vecs.contains(v)
+ /// }
+ /// ```
+ ///
+ /// Also there may be `fn(&Vec)`-typed references pointing to your function.
+ /// If you have them, you will get a compiler error after applying this lint's
+ /// suggestions. You then have the choice to undo your changes or change the
+ /// type of the reference.
+ ///
+ /// Note that if the function is part of your public interface, there may be
+ /// other crates referencing it, of which you may not be aware. Carefully
+ /// deprecate the function before applying the lint suggestions in this case.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// fn foo(&Vec<u32>) { .. }
+ ///
+ /// // Good
+ /// fn foo(&[u32]) { .. }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub PTR_ARG,
+ style,
+ "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for equality comparisons with `ptr::null`
+ ///
+ /// ### Why is this bad?
+ /// It's easier and more readable to use the inherent
+ /// `.is_null()`
+ /// method instead
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad
+ /// if x == ptr::null {
+ /// ..
+ /// }
+ ///
+ /// // Good
+ /// if x.is_null() {
+ /// ..
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub CMP_NULL,
+ style,
+ "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for functions that take immutable
+ /// references and return mutable ones.
+ ///
+ /// ### Why is this bad?
+ /// This is trivially unsound, as one can create two
+ /// mutable references from the same (immutable!) source.
+ /// This [error](https://github.com/rust-lang/rust/issues/39465)
+ /// actually lead to an interim Rust release 1.15.1.
+ ///
+ /// ### Known problems
+ /// To be on the conservative side, if there's at least one
+ /// mutable reference with the output lifetime, this lint will not trigger.
+ /// In practice, this case is unlikely anyway.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// fn foo(&Foo) -> &mut Bar { .. }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MUT_FROM_REF,
+ correctness,
+ "fns that create mutable refs from immutable ref args"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for invalid usages of `ptr::null`.
+ ///
+ /// ### Why is this bad?
+ /// This causes undefined behavior.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// // Bad. Undefined behavior
+ /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
+ /// ```
+ ///
+ /// ```ignore
+ /// // Good
+ /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub INVALID_NULL_PTR_USAGE,
+ correctness,
+ "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
+}
+
+declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
+
+impl<'tcx> LateLintPass<'tcx> for Ptr {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if let ItemKind::Fn(ref sig, _, body_id) = item.kind {
+ check_fn(cx, sig.decl, Some(body_id));
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ if let ImplItemKind::Fn(ref sig, body_id) = item.kind {
+ let parent_item = cx.tcx.hir().get_parent_item(item.hir_id());
+ if let Some(Node::Item(it)) = cx.tcx.hir().find(parent_item) {
+ if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = it.kind {
+ return; // ignore trait impls
+ }
+ }
+ check_fn(cx, sig.decl, Some(body_id));
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+ if let TraitItemKind::Fn(ref sig, ref trait_method) = item.kind {
+ let body_id = if let TraitFn::Provided(b) = *trait_method {
+ Some(b)
+ } else {
+ None
+ };
+ check_fn(cx, sig.decl, body_id);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Binary(ref op, l, r) = expr.kind {
+ if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
+ span_lint(
+ cx,
+ CMP_NULL,
+ expr.span,
+ "comparing with null is better expressed by the `.is_null()` method",
+ );
+ }
+ } else {
+ check_invalid_ptr_usage(cx, expr);
+ }
+ }
+}
+
+fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
+ const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
+ (&paths::SLICE_FROM_RAW_PARTS, &[0]),
+ (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
+ (&paths::PTR_COPY, &[0, 1]),
+ (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
+ (&paths::PTR_READ, &[0]),
+ (&paths::PTR_READ_UNALIGNED, &[0]),
+ (&paths::PTR_READ_VOLATILE, &[0]),
+ (&paths::PTR_REPLACE, &[0]),
+ (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
+ (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
+ (&paths::PTR_SWAP, &[0, 1]),
+ (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
+ (&paths::PTR_WRITE, &[0]),
+ (&paths::PTR_WRITE_UNALIGNED, &[0]),
+ (&paths::PTR_WRITE_VOLATILE, &[0]),
+ (&paths::PTR_WRITE_BYTES, &[0]),
+ ];
+
+ if_chain! {
+ if let ExprKind::Call(fun, 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();
+ let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
+ if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
+ .iter()
+ .find(|&&(fn_path, _)| fn_path == fun_def_path);
+ then {
+ for &arg_idx in arg_indices {
+ if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
+ span_lint_and_sugg(
+ cx,
+ INVALID_NULL_PTR_USAGE,
+ arg.span,
+ "pointer must be non-null",
+ "change this to",
+ "core::ptr::NonNull::dangling().as_ptr()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
+
+#[allow(clippy::too_many_lines)]
+fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, opt_body_id: Option<BodyId>) {
+ let body = opt_body_id.map(|id| cx.tcx.hir().body(id));
+
+ for (idx, arg) in decl.inputs.iter().enumerate() {
+ // Honor the allow attribute on parameters. See issue 5644.
+ if let Some(body) = &body {
+ if is_lint_allowed(cx, PTR_ARG, body.params[idx].hir_id) {
+ continue;
+ }
+ }
+
+ let (item_name, path) = if_chain! {
+ if let TyKind::Rptr(_, MutTy { ty, mutbl: Mutability::Not }) = arg.kind;
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
+ if let Res::Def(_, did) = path.res;
+ if let Some(item_name) = cx.tcx.get_diagnostic_name(did);
+ then {
+ (item_name, path)
+ } else {
+ continue
+ }
+ };
+
+ match item_name {
+ sym::Vec => {
+ if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) {
+ span_lint_and_then(
+ cx,
+ PTR_ARG,
+ arg.span,
+ "writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \
+ with non-Vec-based slices",
+ |diag| {
+ if let Some(ref snippet) = get_only_generic_arg_snippet(cx, arg) {
+ diag.span_suggestion(
+ arg.span,
+ "change this to",
+ format!("&[{}]", snippet),
+ Applicability::Unspecified,
+ );
+ }
+ for (clonespan, suggestion) in spans {
+ diag.span_suggestion(
+ clonespan,
+ &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| {
+ Cow::Owned(format!("change `{}` to", x))
+ }),
+ suggestion.into(),
+ Applicability::Unspecified,
+ );
+ }
+ },
+ );
+ }
+ },
+ sym::String => {
+ if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) {
+ span_lint_and_then(
+ cx,
+ PTR_ARG,
+ arg.span,
+ "writing `&String` instead of `&str` involves a new object where a slice will do",
+ |diag| {
+ diag.span_suggestion(arg.span, "change this to", "&str".into(), Applicability::Unspecified);
+ for (clonespan, suggestion) in spans {
+ diag.span_suggestion_short(
+ clonespan,
+ &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| {
+ Cow::Owned(format!("change `{}` to", x))
+ }),
+ suggestion.into(),
+ Applicability::Unspecified,
+ );
+ }
+ },
+ );
+ }
+ },
+ sym::PathBuf => {
+ if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_path_buf()"), ("as_path", "")]) {
+ span_lint_and_then(
+ cx,
+ PTR_ARG,
+ arg.span,
+ "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do",
+ |diag| {
+ diag.span_suggestion(
+ arg.span,
+ "change this to",
+ "&Path".into(),
+ Applicability::Unspecified,
+ );
+ for (clonespan, suggestion) in spans {
+ diag.span_suggestion_short(
+ clonespan,
+ &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| {
+ Cow::Owned(format!("change `{}` to", x))
+ }),
+ suggestion.into(),
+ Applicability::Unspecified,
+ );
+ }
+ },
+ );
+ }
+ },
+ sym::Cow => {
+ if_chain! {
+ if let [ref bx] = *path.segments;
+ if let Some(params) = bx.args;
+ if !params.parenthesized;
+ if let Some(inner) = params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ });
+ let replacement = snippet_opt(cx, inner.span);
+ if let Some(r) = replacement;
+ then {
+ span_lint_and_sugg(
+ cx,
+ PTR_ARG,
+ arg.span,
+ "using a reference to `Cow` is not recommended",
+ "change this to",
+ "&".to_owned() + &r,
+ Applicability::Unspecified,
+ );
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+
+ if let FnRetTy::Return(ty) = decl.output {
+ if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) {
+ let mut immutables = vec![];
+ for (_, ref mutbl, ref argspan) in decl
+ .inputs
+ .iter()
+ .filter_map(get_rptr_lm)
+ .filter(|&(lt, _, _)| lt.name == out.name)
+ {
+ if *mutbl == Mutability::Mut {
+ return;
+ }
+ immutables.push(*argspan);
+ }
+ if immutables.is_empty() {
+ return;
+ }
+ span_lint_and_then(
+ cx,
+ MUT_FROM_REF,
+ ty.span,
+ "mutable borrow from immutable input(s)",
+ |diag| {
+ let ms = MultiSpan::from_spans(immutables);
+ diag.span_note(ms, "immutable borrow here");
+ },
+ );
+ }
+ }
+}
+
+fn get_only_generic_arg_snippet(cx: &LateContext<'_>, arg: &Ty<'_>) -> Option<String> {
+ if_chain! {
+ if let TyKind::Path(QPath::Resolved(_, path)) = walk_ptrs_hir_ty(arg).kind;
+ if let Some(&PathSegment{args: Some(parameters), ..}) = path.segments.last();
+ let types: Vec<_> = parameters.args.iter().filter_map(|arg| match arg {
+ GenericArg::Type(ty) => Some(ty),
+ _ => None,
+ }).collect();
+ if types.len() == 1;
+ then {
+ snippet_opt(cx, types[0].span)
+ } else {
+ None
+ }
+ }
+}
+
+fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
+ if let TyKind::Rptr(ref lt, ref m) = ty.kind {
+ Some((lt, m.mutbl, ty.span))
+ } else {
+ None
+ }
+}
+
+fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let ExprKind::Call(pathexp, []) = expr.kind {
+ expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| {
+ match_any_diagnostic_items(cx, id, &[sym::ptr_null, sym::ptr_null_mut]).is_some()
+ })
+ } else {
+ false
+ }
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if in_macro(expr.span) {
+use clippy_utils::source::snippet_opt;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Use `std::ptr::eq` when applicable
+ ///
+ /// ### Why is this bad?
+ /// `ptr::eq` can be used to compare `&T` references
+ /// (which coerce to `*const T` implicitly) by their address rather than
+ /// comparing the values they point to.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let a = &[1, 2, 3];
+ /// let b = &[1, 2, 3];
+ ///
+ /// assert!(a as *const _ as usize == b as *const _ as usize);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let a = &[1, 2, 3];
+ /// let b = &[1, 2, 3];
+ ///
+ /// assert!(std::ptr::eq(a, b));
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub PTR_EQ,
+ style,
+ "use `std::ptr::eq` when comparing raw pointers"
+}
+
+declare_lint_pass!(PtrEq => [PTR_EQ]);
+
+static LINT_MSG: &str = "use `std::ptr::eq` when comparing raw pointers";
+
+impl LateLintPass<'_> for PtrEq {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let ExprKind::Binary(ref op, left, right) = expr.kind {
+ if BinOpKind::Eq == op.node {
+ let (left, right) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) {
+ (Some(lhs), Some(rhs)) => (lhs, rhs),
+ _ => (left, right),
+ };
+
+ if_chain! {
+ if let Some(left_var) = expr_as_cast_to_raw_pointer(cx, left);
+ if let Some(right_var) = expr_as_cast_to_raw_pointer(cx, right);
+ if let Some(left_snip) = snippet_opt(cx, left_var.span);
+ if let Some(right_snip) = snippet_opt(cx, right_var.span);
+ then {
+ span_lint_and_sugg(
+ cx,
+ PTR_EQ,
+ expr.span,
+ LINT_MSG,
+ "try",
+ format!("std::ptr::eq({}, {})", left_snip, right_snip),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+ }
+}
+
+// If the given expression is a cast to a usize, return the lhs of the cast
+// E.g., `foo as *const _ as usize` returns `foo as *const _`.
+fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize {
+ if let ExprKind::Cast(expr, _) = cast_expr.kind {
+ return Some(expr);
+ }
+ }
+ None
+}
+
+// If the given expression is a cast to a `*const` pointer, return the lhs of the cast
+// E.g., `foo as *const _` returns `foo`.
+fn expr_as_cast_to_raw_pointer<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if cx.typeck_results().expr_ty(cast_expr).is_unsafe_ptr() {
+ if let ExprKind::Cast(expr, _) = cast_expr.kind {
+ return Some(expr);
+ }
+ }
+ None
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
+use clippy_utils::source::snippet_opt;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+use std::fmt;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of the `offset` pointer method with a `usize` casted to an
+ /// `isize`.
+ ///
+ /// ### Why is this bad?
+ /// If we’re always increasing the pointer address, we can avoid the numeric
+ /// cast by using the `add` method instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vec = vec![b'a', b'b', b'c'];
+ /// let ptr = vec.as_ptr();
+ /// let offset = 1_usize;
+ ///
+ /// unsafe {
+ /// ptr.offset(offset as isize);
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// let vec = vec![b'a', b'b', b'c'];
+ /// let ptr = vec.as_ptr();
+ /// let offset = 1_usize;
+ ///
+ /// unsafe {
+ /// ptr.add(offset);
+ /// }
+ /// ```
++ #[clippy::version = "1.30.0"]
+ pub PTR_OFFSET_WITH_CAST,
+ complexity,
+ "unneeded pointer offset cast"
+}
+
+declare_lint_pass!(PtrOffsetWithCast => [PTR_OFFSET_WITH_CAST]);
+
+impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Check if the expressions is a ptr.offset or ptr.wrapping_offset method call
+ let (receiver_expr, arg_expr, method) = match expr_as_ptr_offset_call(cx, expr) {
+ Some(call_arg) => call_arg,
+ None => return,
+ };
+
+ // Check if the argument to the method call is a cast from usize
+ let cast_lhs_expr = match expr_as_cast_from_usize(cx, arg_expr) {
+ Some(cast_lhs_expr) => cast_lhs_expr,
+ None => return,
+ };
+
+ let msg = format!("use of `{}` with a `usize` casted to an `isize`", method);
+ if let Some(sugg) = build_suggestion(cx, method, receiver_expr, cast_lhs_expr) {
+ span_lint_and_sugg(
+ cx,
+ PTR_OFFSET_WITH_CAST,
+ expr.span,
+ &msg,
+ "try",
+ sugg,
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint(cx, PTR_OFFSET_WITH_CAST, expr.span, &msg);
+ }
+ }
+}
+
+// If the given expression is a cast from a usize, return the lhs of the cast
+fn expr_as_cast_from_usize<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Cast(cast_lhs_expr, _) = expr.kind {
+ if is_expr_ty_usize(cx, cast_lhs_expr) {
+ return Some(cast_lhs_expr);
+ }
+ }
+ None
+}
+
+// If the given expression is a ptr::offset or ptr::wrapping_offset method call, return the
+// receiver, the arg of the method call, and the method.
+fn expr_as_ptr_offset_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> {
+ if let ExprKind::MethodCall(path_segment, _, [arg_0, arg_1, ..], _) = &expr.kind {
+ if is_expr_ty_raw_ptr(cx, arg_0) {
+ if path_segment.ident.name == sym::offset {
+ return Some((arg_0, arg_1, Method::Offset));
+ }
+ if path_segment.ident.name == sym!(wrapping_offset) {
+ return Some((arg_0, arg_1, Method::WrappingOffset));
+ }
+ }
+ }
+ None
+}
+
+// Is the type of the expression a usize?
+fn is_expr_ty_usize<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+ cx.typeck_results().expr_ty(expr) == cx.tcx.types.usize
+}
+
+// Is the type of the expression a raw pointer?
+fn is_expr_ty_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> bool {
+ cx.typeck_results().expr_ty(expr).is_unsafe_ptr()
+}
+
+fn build_suggestion<'tcx>(
+ cx: &LateContext<'tcx>,
+ method: Method,
+ receiver_expr: &Expr<'_>,
+ cast_lhs_expr: &Expr<'_>,
+) -> Option<String> {
+ let receiver = snippet_opt(cx, receiver_expr.span)?;
+ let cast_lhs = snippet_opt(cx, cast_lhs_expr.span)?;
+ Some(format!("{}.{}({})", receiver, method.suggestion(), cast_lhs))
+}
+
+#[derive(Copy, Clone)]
+enum Method {
+ Offset,
+ WrappingOffset,
+}
+
+impl Method {
+ #[must_use]
+ fn suggestion(self) -> &'static str {
+ match self {
+ Self::Offset => "add",
+ Self::WrappingOffset => "wrapping_add",
+ }
+ }
+}
+
+impl fmt::Display for Method {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Offset => write!(f, "offset"),
+ Self::WrappingOffset => write!(f, "wrapping_offset"),
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::is_lang_ctor;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{eq_expr_value, path_to_local, path_to_local_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk};
+use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, PatKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions that could be replaced by the question mark operator.
+ ///
+ /// ### Why is this bad?
+ /// Question mark usage is more idiomatic.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// if option.is_none() {
+ /// return None;
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```ignore
+ /// option?;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub QUESTION_MARK,
+ style,
+ "checks for expressions that could be replaced by the question mark operator"
+}
+
+declare_lint_pass!(QuestionMark => [QUESTION_MARK]);
+
+impl QuestionMark {
+ /// Checks if the given expression on the given context matches the following structure:
+ ///
+ /// ```ignore
+ /// if option.is_none() {
+ /// return None;
+ /// }
+ /// ```
+ ///
+ /// ```ignore
+ /// if result.is_err() {
+ /// return result;
+ /// }
+ /// ```
+ ///
+ /// If it matches, it will suggest to use the question mark operator instead
+ fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
+ if let ExprKind::MethodCall(segment, _, args, _) = &cond.kind;
+ if let Some(subject) = args.get(0);
+ if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) ||
+ (Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err));
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
+ let mut replacement: Option<String> = None;
+ if let Some(else_inner) = r#else {
+ if_chain! {
+ if let ExprKind::Block(block, None) = &else_inner.kind;
+ if block.stmts.is_empty();
+ if let Some(block_expr) = &block.expr;
+ if eq_expr_value(cx, subject, block_expr);
+ then {
+ replacement = Some(format!("Some({}?)", receiver_str));
+ }
+ }
+ } else if Self::moves_by_default(cx, subject)
+ && !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
+ {
+ replacement = Some(format!("{}.as_ref()?;", receiver_str));
+ } else {
+ replacement = Some(format!("{}?;", receiver_str));
+ }
+
+ if let Some(replacement_str) = replacement {
+ span_lint_and_sugg(
+ cx,
+ QUESTION_MARK,
+ expr.span,
+ "this block may be rewritten with the `?` operator",
+ "replace it with",
+ replacement_str,
+ applicability,
+ );
+ }
+ }
+ }
+ }
+
+ fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
+ = higher::IfLet::hir(cx, expr);
+ if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
+ if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) ||
+ (Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk));
+
+ if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
+ let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
+ if let ExprKind::Block(block, None) = if_then.kind;
+ if block.stmts.is_empty();
+ if let Some(trailing_expr) = &block.expr;
+ if path_to_local_id(trailing_expr, bind_id);
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
+ let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
+
+ span_lint_and_sugg(
+ cx,
+ QUESTION_MARK,
+ expr.span,
+ "this if-let-else may be rewritten with the `?` operator",
+ "replace it with",
+ replacement,
+ applicability,
+ );
+ }
+ }
+ }
+
+ fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
+ Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
+ }
+
+ fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
+ Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr)
+ }
+
+ fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
+ let expr_ty = cx.typeck_results().expr_ty(expression);
+
+ !expr_ty.is_copy_modulo_regions(cx.tcx.at(expression.span), cx.param_env)
+ }
+
+ fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
+ let expr_ty = cx.typeck_results().expr_ty(expression);
+
+ is_type_diagnostic_item(cx, expr_ty, sym::Option)
+ }
+
+ fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
+ let expr_ty = cx.typeck_results().expr_ty(expression);
+
+ is_type_diagnostic_item(cx, expr_ty, sym::Result)
+ }
+
+ fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
+ match expression.kind {
+ ExprKind::Block(block, _) => {
+ if let Some(return_expression) = Self::return_expression(block) {
+ return Self::expression_returns_none(cx, return_expression);
+ }
+
+ false
+ },
+ ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr),
+ ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
+ _ => false,
+ }
+ }
+
+ fn expression_returns_unmodified_err(cx: &LateContext<'_>, expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
+ match expr.kind {
+ ExprKind::Block(block, _) => {
+ if let Some(return_expression) = Self::return_expression(block) {
+ return Self::expression_returns_unmodified_err(cx, return_expression, cond_expr);
+ }
+
+ false
+ },
+ ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(cx, ret_expr, cond_expr),
+ ExprKind::Path(_) => path_to_local(expr) == path_to_local(cond_expr),
+ _ => false,
+ }
+ }
+
+ fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ // Check if last expression is a return statement. Then, return the expression
+ if_chain! {
+ if block.stmts.len() == 1;
+ if let Some(expr) = block.stmts.iter().last();
+ if let StmtKind::Semi(expr) = expr.kind;
+ if let ExprKind::Ret(Some(ret_expr)) = expr.kind;
+
+ then {
+ return Some(ret_expr);
+ }
+ }
+
+ // Check for `return` without a semicolon.
+ if_chain! {
+ if block.stmts.is_empty();
+ if let Some(ExprKind::Ret(Some(ret_expr))) = block.expr.as_ref().map(|e| &e.kind);
+ then {
+ return Some(ret_expr);
+ }
+ }
+
+ None
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for QuestionMark {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ Self::check_is_none_or_err_and_early_return(cx, expr);
+ Self::check_if_let_some_or_err_and_early_return(cx, expr);
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, single_segment_path};
+use clippy_utils::{higher, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast::RangeLimits;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, QPath};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::ty;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::{Span, Spanned};
+use rustc_span::sym;
+use rustc_span::symbol::Ident;
+use std::cmp::Ordering;
+
+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()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = vec![1];
+ /// x.iter().zip(0..x.len());
+ /// ```
+ /// Could be written as
+ /// ```rust
+ /// # let x = vec![1];
+ /// x.iter().enumerate();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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 an 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 { .. }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub RANGE_PLUS_ONE,
+ pedantic,
+ "`x..(y+1)` reads better as `x..=y`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for inclusive ranges where 1 is subtracted from
+ /// the upper bound, e.g., `x..=(y-1)`.
+ ///
+ /// ### Why is this bad?
+ /// The code is more readable with an exclusive range
+ /// like `x..y`.
+ ///
+ /// ### Known problems
+ /// This will cause a warning that cannot be fixed if
+ /// the consumer of the range only accepts a specific range type, instead of
+ /// the generic `RangeBounds` trait
+ /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)).
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// for x..=(y-1) { .. }
+ /// ```
+ /// Could be written as
+ /// ```rust,ignore
+ /// for x..y { .. }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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];
+ /// }
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub REVERSED_EMPTY_RANGES,
+ correctness,
+ "reversing the limits of range expressions, resulting in empty ranges"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for expressions like `x >= 3 && x < 8` that could
+ /// be more readably expressed as `(3..8).contains(x)`.
+ ///
+ /// ### Why is this bad?
+ /// `contains` expresses the intent better and has less
+ /// failure modes (such as fencepost errors or using `||` instead of `&&`).
+ ///
+ /// ### Example
+ /// ```rust
+ /// // given
+ /// let x = 6;
+ ///
+ /// assert!(x >= 3 && x < 8);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ ///# let x = 6;
+ /// assert!((3..8).contains(&x));
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub MANUAL_RANGE_CONTAINS,
+ style,
+ "manually reimplementing {`Range`, `RangeInclusive`}`::contains`"
+}
+
+pub struct Ranges {
+ msrv: Option<RustcVersion>,
+}
+
+impl Ranges {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(Ranges => [
+ RANGE_ZIP_WITH_LEN,
+ RANGE_PLUS_ONE,
+ RANGE_MINUS_ONE,
+ REVERSED_EMPTY_RANGES,
+ MANUAL_RANGE_CONTAINS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Ranges {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ match expr.kind {
+ ExprKind::MethodCall(path, _, args, _) => {
+ check_range_zip_with_len(cx, path, args, expr.span);
+ },
+ ExprKind::Binary(ref op, l, r) => {
+ if meets_msrv(self.msrv.as_ref(), &msrvs::RANGE_CONTAINS) {
+ check_possible_range_contains(cx, op.node, l, r, expr);
+ }
+ },
+ _ => {},
+ }
+
+ check_exclusive_range_plus_one(cx, expr);
+ check_inclusive_range_minus_one(cx, expr);
+ check_reversed_empty_range(cx, expr);
+ }
+ extract_msrv_attr!(LateContext);
+}
+
+fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'_>, r: &Expr<'_>, expr: &Expr<'_>) {
+ if in_constant(cx, expr.hir_id) {
+ return;
+ }
+
+ let span = expr.span;
+ let combine_and = match op {
+ BinOpKind::And | BinOpKind::BitAnd => true,
+ BinOpKind::Or | BinOpKind::BitOr => false,
+ _ => return,
+ };
+ // value, name, order (higher/lower), inclusiveness
+ if let (Some((lval, lname, name_span, lval_span, lord, linc)), Some((rval, rname, _, rval_span, rord, rinc))) =
+ (check_range_bounds(cx, l), check_range_bounds(cx, r))
+ {
+ // we only lint comparisons on the same name and with different
+ // direction
+ if lname != rname || lord == rord {
+ return;
+ }
+ let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval);
+ if combine_and && ord == Some(rord) {
+ // order lower bound and upper bound
+ let (l_span, u_span, l_inc, u_inc) = if rord == Ordering::Less {
+ (lval_span, rval_span, linc, rinc)
+ } else {
+ (rval_span, lval_span, rinc, linc)
+ };
+ // we only lint inclusive lower bounds
+ if !l_inc {
+ return;
+ }
+ let (range_type, range_op) = if u_inc {
+ ("RangeInclusive", "..=")
+ } else {
+ ("Range", "..")
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let name = snippet_with_applicability(cx, name_span, "_", &mut applicability);
+ let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability);
+ let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability);
+ let space = if lo.ends_with('.') { " " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ MANUAL_RANGE_CONTAINS,
+ span,
+ &format!("manual `{}::contains` implementation", range_type),
+ "use",
+ format!("({}{}{}{}).contains(&{})", lo, space, range_op, hi, name),
+ applicability,
+ );
+ } else if !combine_and && ord == Some(lord) {
+ // `!_.contains(_)`
+ // order lower bound and upper bound
+ let (l_span, u_span, l_inc, u_inc) = if lord == Ordering::Less {
+ (lval_span, rval_span, linc, rinc)
+ } else {
+ (rval_span, lval_span, rinc, linc)
+ };
+ if l_inc {
+ return;
+ }
+ let (range_type, range_op) = if u_inc {
+ ("Range", "..")
+ } else {
+ ("RangeInclusive", "..=")
+ };
+ let mut applicability = Applicability::MachineApplicable;
+ let name = snippet_with_applicability(cx, name_span, "_", &mut applicability);
+ let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability);
+ let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability);
+ let space = if lo.ends_with('.') { " " } else { "" };
+ span_lint_and_sugg(
+ cx,
+ MANUAL_RANGE_CONTAINS,
+ span,
+ &format!("manual `!{}::contains` implementation", range_type),
+ "use",
+ format!("!({}{}{}{}).contains(&{})", lo, space, range_op, hi, name),
+ applicability,
+ );
+ }
+ }
+}
+
+fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, Ident, Span, Span, Ordering, bool)> {
+ if let ExprKind::Binary(ref op, l, r) = ex.kind {
+ let (inclusive, ordering) = match op.node {
+ BinOpKind::Gt => (false, Ordering::Greater),
+ BinOpKind::Ge => (true, Ordering::Greater),
+ BinOpKind::Lt => (false, Ordering::Less),
+ BinOpKind::Le => (true, Ordering::Less),
+ _ => return None,
+ };
+ if let Some(id) = match_ident(l) {
+ if let Some((c, _)) = constant(cx, cx.typeck_results(), r) {
+ return Some((c, id, l.span, r.span, ordering, inclusive));
+ }
+ } else if let Some(id) = match_ident(r) {
+ if let Some((c, _)) = constant(cx, cx.typeck_results(), l) {
+ return Some((c, id, r.span, l.span, ordering.reverse(), inclusive));
+ }
+ }
+ }
+ None
+}
+
+fn match_ident(e: &Expr<'_>) -> Option<Ident> {
+ if let ExprKind::Path(ref qpath) = e.kind {
+ if let Some(seg) = single_segment_path(qpath) {
+ if seg.args.is_none() {
+ return Some(seg.ident);
+ }
+ }
+ }
+ None
+}
+
+fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) {
+ if_chain! {
+ if path.ident.as_str() == "zip";
+ if let [iter, zip_arg] = args;
+ // `.iter()` call
+ if let ExprKind::MethodCall(iter_path, _, iter_args, _) = iter.kind;
+ 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::hir(zip_arg);
+ if is_integer_const(cx, start, 0);
+ // `.len()` call
+ if let ExprKind::MethodCall(len_path, _, 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(_, iter_path)) = iter_args[0].kind;
+ if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
+ if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
+ then {
+ span_lint(cx,
+ RANGE_ZIP_WITH_LEN,
+ span,
+ &format!("it is more idiomatic to use `{}.iter().enumerate()`",
+ snippet(cx, iter_args[0].span, "_"))
+ );
+ }
+ }
+}
+
+// exclusive range plus one: `x..(y+1)`
+fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if let Some(higher::Range {
+ start,
+ end: Some(end),
+ limits: RangeLimits::HalfOpen
+ }) = higher::Range::hir(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::hir(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::ForLoop::hir(parent_expr) {
+ Some(higher::ForLoop { arg, .. }) if arg.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::hir(expr);
+ let ty = cx.typeck_results().expr_ty(start);
+ if let ty::Int(_) | ty::Uint(_) = ty.kind();
+ if let Some((start_idx, _)) = constant(cx, cx.typeck_results(), start);
+ if let Some((end_idx, _)) = constant(cx, cx.typeck_results(), end);
+ if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx);
+ if is_empty_range(limits, ordering);
+ then {
+ if inside_indexing_expr(cx, expr) {
+ // Avoid linting `N..N` as it has proven to be useful, see #5689 and #5628 ...
+ if ordering != Ordering::Equal {
+ span_lint(
+ cx,
+ REVERSED_EMPTY_RANGES,
+ expr.span,
+ "this range is reversed and using it to index a slice will panic at run-time",
+ );
+ }
+ // ... except in for loop arguments for backwards compatibility with `reverse_range_loop`
+ } else if ordering != Ordering::Equal || is_for_loop_arg(cx, expr) {
+ span_lint_and_then(
+ cx,
+ REVERSED_EMPTY_RANGES,
+ expr.span,
+ "this range is empty so it will yield no values",
+ |diag| {
+ if ordering != Ordering::Equal {
+ let start_snippet = snippet(cx, start.span, "_");
+ let end_snippet = snippet(cx, end.span, "_");
+ let dots = match limits {
+ RangeLimits::HalfOpen => "..",
+ RangeLimits::Closed => "..="
+ };
+
+ diag.span_suggestion(
+ expr.span,
+ "consider using the following if you are attempting to iterate over this \
+ range in reverse",
+ format!("({}{}{}).rev()", end_snippet, dots, start_snippet),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ },
+ );
+ }
+ }
+ }
+}
+
+fn y_plus_one<'t>(cx: &LateContext<'_>, expr: &'t Expr<'_>) -> Option<&'t Expr<'t>> {
+ match expr.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ lhs,
+ 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, ..
+ },
+ lhs,
+ rhs,
+ ) if is_integer_const(cx, rhs, 1) => Some(lhs),
+ _ => None,
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
+use clippy_utils::source::snippet_opt;
+use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, walk_ptrs_ty_depth};
+use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation};
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{def_id, Body, FnDecl, HirId};
+use rustc_index::bit_set::{BitSet, HybridBitSet};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::mir::{
+ self, traversal,
+ visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _},
+ Mutability,
+};
+use rustc_middle::ty::{self, fold::TypeVisitor, Ty, TyCtxt};
+use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis, ResultsCursor};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{BytePos, Span};
+use rustc_span::sym;
+use std::convert::TryFrom;
+use std::ops::ControlFlow;
+
+macro_rules! unwrap_or_continue {
+ ($x:expr) => {
+ match $x {
+ Some(x) => x,
+ None => continue,
+ }
+ };
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for a redundant `clone()` (and its relatives) which clones an owned
+ /// value that is going to be dropped without further use.
+ ///
+ /// ### Why is this bad?
+ /// It is not always possible for the compiler to eliminate useless
+ /// allocations and deallocations generated by redundant `clone()`s.
+ ///
+ /// ### Known problems
+ /// False-negatives: analysis performed by this lint is conservative and limited.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::path::Path;
+ /// # #[derive(Clone)]
+ /// # struct Foo;
+ /// # impl Foo {
+ /// # fn new() -> Self { Foo {} }
+ /// # }
+ /// # fn call(x: Foo) {}
+ /// {
+ /// let x = Foo::new();
+ /// call(x.clone());
+ /// call(x.clone()); // this can just pass `x`
+ /// }
+ ///
+ /// ["lorem", "ipsum"].join(" ").to_string();
+ ///
+ /// Path::new("/a/b").join("c").to_path_buf();
+ /// ```
++ #[clippy::version = "1.32.0"]
+ pub REDUNDANT_CLONE,
+ perf,
+ "`clone()` of an owned value that is going to be dropped immediately"
+}
+
+declare_lint_pass!(RedundantClone => [REDUNDANT_CLONE]);
+
+impl<'tcx> LateLintPass<'tcx> for RedundantClone {
+ #[allow(clippy::too_many_lines)]
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ _: FnKind<'tcx>,
+ _: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ _: Span,
+ _: HirId,
+ ) {
+ let def_id = cx.tcx.hir().body_owner_def_id(body.id());
+
+ // Building MIR for `fn`s with unsatisfiable preds results in ICE.
+ if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) {
+ return;
+ }
+
+ let mir = cx.tcx.optimized_mir(def_id.to_def_id());
+
+ let possible_origin = {
+ let mut vis = PossibleOriginVisitor::new(mir);
+ vis.visit_body(mir);
+ vis.into_map(cx)
+ };
+ let maybe_storage_live_result = MaybeStorageLive
+ .into_engine(cx.tcx, mir)
+ .pass_name("redundant_clone")
+ .iterate_to_fixpoint()
+ .into_results_cursor(mir);
+ let mut possible_borrower = {
+ let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
+ vis.visit_body(mir);
+ vis.into_map(cx, maybe_storage_live_result)
+ };
+
+ for (bb, bbdata) in mir.basic_blocks().iter_enumerated() {
+ let terminator = bbdata.terminator();
+
+ if terminator.source_info.span.from_expansion() {
+ continue;
+ }
+
+ // Give up on loops
+ if terminator.successors().any(|s| *s == bb) {
+ continue;
+ }
+
+ let (fn_def_id, arg, arg_ty, clone_ret) =
+ unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind));
+
+ let from_borrow = match_def_path(cx, fn_def_id, &paths::CLONE_TRAIT_METHOD)
+ || match_def_path(cx, fn_def_id, &paths::TO_OWNED_METHOD)
+ || (match_def_path(cx, fn_def_id, &paths::TO_STRING_METHOD)
+ && is_type_diagnostic_item(cx, arg_ty, sym::String));
+
+ let from_deref = !from_borrow
+ && (match_def_path(cx, fn_def_id, &paths::PATH_TO_PATH_BUF)
+ || match_def_path(cx, fn_def_id, &paths::OS_STR_TO_OS_STRING));
+
+ if !from_borrow && !from_deref {
+ continue;
+ }
+
+ if let ty::Adt(def, _) = arg_ty.kind() {
+ if match_def_path(cx, def.did, &paths::MEM_MANUALLY_DROP) {
+ continue;
+ }
+ }
+
+ // `{ arg = &cloned; clone(move arg); }` or `{ arg = &cloned; to_path_buf(arg); }`
+ let (cloned, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(cx, mir, arg, from_borrow, bb));
+
+ let loc = mir::Location {
+ block: bb,
+ statement_index: bbdata.statements.len(),
+ };
+
+ // `Local` to be cloned, and a local of `clone` call's destination
+ let (local, ret_local) = if from_borrow {
+ // `res = clone(arg)` can be turned into `res = move arg;`
+ // if `arg` is the only borrow of `cloned` at this point.
+
+ if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) {
+ continue;
+ }
+
+ (cloned, clone_ret)
+ } else {
+ // `arg` is a reference as it is `.deref()`ed in the previous block.
+ // Look into the predecessor block and find out the source of deref.
+
+ let ps = &mir.predecessors()[bb];
+ if ps.len() != 1 {
+ continue;
+ }
+ let pred_terminator = mir[ps[0]].terminator();
+
+ // receiver of the `deref()` call
+ let (pred_arg, deref_clone_ret) = if_chain! {
+ if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) =
+ is_call_with_ref_arg(cx, mir, &pred_terminator.kind);
+ if res == cloned;
+ if cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id);
+ if is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf)
+ || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString);
+ then {
+ (pred_arg, res)
+ } else {
+ continue;
+ }
+ };
+
+ let (local, cannot_move_out) =
+ unwrap_or_continue!(find_stmt_assigns_to(cx, mir, pred_arg, true, ps[0]));
+ let loc = mir::Location {
+ block: bb,
+ statement_index: mir.basic_blocks()[bb].statements.len(),
+ };
+
+ // This can be turned into `res = move local` if `arg` and `cloned` are not borrowed
+ // at the last statement:
+ //
+ // ```
+ // pred_arg = &local;
+ // cloned = deref(pred_arg);
+ // arg = &cloned;
+ // StorageDead(pred_arg);
+ // res = to_path_buf(cloned);
+ // ```
+ if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) {
+ continue;
+ }
+
+ (local, deref_clone_ret)
+ };
+
+ let clone_usage = if local == ret_local {
+ CloneUsage {
+ cloned_used: false,
+ cloned_consume_or_mutate_loc: None,
+ clone_consumed_or_mutated: true,
+ }
+ } else {
+ let clone_usage = visit_clone_usage(local, ret_local, mir, bb);
+ if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated {
+ // cloned value is used, and the clone is modified or moved
+ continue;
+ } else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc {
+ // cloned value is mutated, and the clone is alive.
+ if possible_borrower.is_alive_at(ret_local, loc) {
+ continue;
+ }
+ }
+ clone_usage
+ };
+
+ let span = terminator.source_info.span;
+ let scope = terminator.source_info.scope;
+ let node = mir.source_scopes[scope]
+ .local_data
+ .as_ref()
+ .assert_crate_local()
+ .lint_root;
+
+ if_chain! {
+ if let Some(snip) = snippet_opt(cx, span);
+ if let Some(dot) = snip.rfind('.');
+ then {
+ let sugg_span = span.with_lo(
+ span.lo() + BytePos(u32::try_from(dot).unwrap())
+ );
+ let mut app = Applicability::MaybeIncorrect;
+
+ let call_snip = &snip[dot + 1..];
+ // Machine applicable when `call_snip` looks like `foobar()`
+ if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) {
+ if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') {
+ app = Applicability::MachineApplicable;
+ }
+ }
+
+ span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| {
+ diag.span_suggestion(
+ sugg_span,
+ "remove this",
+ String::new(),
+ app,
+ );
+ if clone_usage.cloned_used {
+ diag.span_note(
+ span,
+ "cloned value is neither consumed nor mutated",
+ );
+ } else {
+ diag.span_note(
+ span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
+ "this value is dropped without further use",
+ );
+ }
+ });
+ } else {
+ span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone");
+ }
+ }
+ }
+ }
+}
+
+/// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`.
+fn is_call_with_ref_arg<'tcx>(
+ cx: &LateContext<'tcx>,
+ mir: &'tcx mir::Body<'tcx>,
+ kind: &'tcx mir::TerminatorKind<'tcx>,
+) -> Option<(def_id::DefId, mir::Local, Ty<'tcx>, mir::Local)> {
+ if_chain! {
+ if let mir::TerminatorKind::Call { func, args, destination, .. } = kind;
+ if args.len() == 1;
+ if let mir::Operand::Move(mir::Place { local, .. }) = &args[0];
+ if let ty::FnDef(def_id, _) = *func.ty(&*mir, cx.tcx).kind();
+ if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx));
+ if !is_copy(cx, inner_ty);
+ then {
+ Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)?.as_local()?))
+ } else {
+ None
+ }
+ }
+}
+
+type CannotMoveOut = bool;
+
+/// Finds the first `to = (&)from`, and returns
+/// ``Some((from, whether `from` cannot be moved out))``.
+fn find_stmt_assigns_to<'tcx>(
+ cx: &LateContext<'tcx>,
+ mir: &mir::Body<'tcx>,
+ to_local: mir::Local,
+ by_ref: bool,
+ bb: mir::BasicBlock,
+) -> Option<(mir::Local, CannotMoveOut)> {
+ let rvalue = mir.basic_blocks()[bb].statements.iter().rev().find_map(|stmt| {
+ if let mir::StatementKind::Assign(box (mir::Place { local, .. }, v)) = &stmt.kind {
+ return if *local == to_local { Some(v) } else { None };
+ }
+
+ None
+ })?;
+
+ match (by_ref, &*rvalue) {
+ (true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place))) => {
+ Some(base_local_and_movability(cx, mir, *place))
+ },
+ (false, mir::Rvalue::Ref(_, _, place)) => {
+ if let [mir::ProjectionElem::Deref] = place.as_ref().projection {
+ Some(base_local_and_movability(cx, mir, *place))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
+
+/// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself
+/// if it is already a `Local`.
+///
+/// Also reports whether given `place` cannot be moved out.
+fn base_local_and_movability<'tcx>(
+ cx: &LateContext<'tcx>,
+ mir: &mir::Body<'tcx>,
+ place: mir::Place<'tcx>,
+) -> (mir::Local, CannotMoveOut) {
+ use rustc_middle::mir::PlaceRef;
+
+ // Dereference. You cannot move things out from a borrowed value.
+ let mut deref = false;
+ // Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509.
+ let mut field = false;
+ // If projection is a slice index then clone can be removed only if the
+ // underlying type implements Copy
+ let mut slice = false;
+
+ let PlaceRef { local, mut projection } = place.as_ref();
+ while let [base @ .., elem] = projection {
+ projection = base;
+ deref |= matches!(elem, mir::ProjectionElem::Deref);
+ field |= matches!(elem, mir::ProjectionElem::Field(..))
+ && has_drop(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty);
+ slice |= matches!(elem, mir::ProjectionElem::Index(..))
+ && !is_copy(cx, mir::Place::ty_from(local, projection, &mir.local_decls, cx.tcx).ty);
+ }
+
+ (local, deref || field || slice)
+}
+
+#[derive(Default)]
+struct CloneUsage {
+ /// Whether the cloned value is used after the clone.
+ cloned_used: bool,
+ /// The first location where the cloned value is consumed or mutated, if any.
+ cloned_consume_or_mutate_loc: Option<mir::Location>,
+ /// Whether the clone value is mutated.
+ clone_consumed_or_mutated: bool,
+}
+fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, bb: mir::BasicBlock) -> CloneUsage {
+ struct V {
+ cloned: mir::Local,
+ clone: mir::Local,
+ result: CloneUsage,
+ }
+ impl<'tcx> mir::visit::Visitor<'tcx> for V {
+ fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
+ let statements = &data.statements;
+ for (statement_index, statement) in statements.iter().enumerate() {
+ self.visit_statement(statement, mir::Location { block, statement_index });
+ }
+
+ self.visit_terminator(
+ data.terminator(),
+ mir::Location {
+ block,
+ statement_index: statements.len(),
+ },
+ );
+ }
+
+ fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, loc: mir::Location) {
+ let local = place.local;
+
+ if local == self.cloned
+ && !matches!(
+ ctx,
+ PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
+ )
+ {
+ self.result.cloned_used = true;
+ self.result.cloned_consume_or_mutate_loc = self.result.cloned_consume_or_mutate_loc.or_else(|| {
+ matches!(
+ ctx,
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
+ | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
+ )
+ .then(|| loc)
+ });
+ } else if local == self.clone {
+ match ctx {
+ PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
+ | PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
+ self.result.clone_consumed_or_mutated = true;
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+
+ let init = CloneUsage {
+ cloned_used: false,
+ cloned_consume_or_mutate_loc: None,
+ // Consider non-temporary clones consumed.
+ // TODO: Actually check for mutation of non-temporaries.
+ clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp,
+ };
+ traversal::ReversePostorder::new(mir, bb)
+ .skip(1)
+ .fold(init, |usage, (tbb, tdata)| {
+ // Short-circuit
+ if (usage.cloned_used && usage.clone_consumed_or_mutated) ||
+ // Give up on loops
+ tdata.terminator().successors().any(|s| *s == bb)
+ {
+ return CloneUsage {
+ cloned_used: true,
+ clone_consumed_or_mutated: true,
+ ..usage
+ };
+ }
+
+ let mut v = V {
+ cloned,
+ clone,
+ result: usage,
+ };
+ v.visit_basic_block_data(tbb, tdata);
+ v.result
+ })
+}
+
+/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
+#[derive(Copy, Clone)]
+struct MaybeStorageLive;
+
+impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
+ type Domain = BitSet<mir::Local>;
+ const NAME: &'static str = "maybe_storage_live";
+
+ fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+ // bottom = dead
+ BitSet::new_empty(body.local_decls.len())
+ }
+
+ fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
+ for arg in body.args_iter() {
+ state.insert(arg);
+ }
+ }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
+ type Idx = mir::Local;
+
+ fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
+ match stmt.kind {
+ mir::StatementKind::StorageLive(l) => trans.gen(l),
+ mir::StatementKind::StorageDead(l) => trans.kill(l),
+ _ => (),
+ }
+ }
+
+ fn terminator_effect(
+ &self,
+ _trans: &mut impl GenKill<Self::Idx>,
+ _terminator: &mir::Terminator<'tcx>,
+ _loc: mir::Location,
+ ) {
+ }
+
+ fn call_return_effect(
+ &self,
+ _trans: &mut impl GenKill<Self::Idx>,
+ _block: mir::BasicBlock,
+ _return_places: CallReturnPlaces<'_, 'tcx>,
+ ) {
+ // Nothing to do when a call returns successfully
+ }
+}
+
+/// Collects the possible borrowers of each local.
+/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
+/// possible borrowers of `a`.
+struct PossibleBorrowerVisitor<'a, 'tcx> {
+ possible_borrower: TransitiveRelation<mir::Local>,
+ body: &'a mir::Body<'tcx>,
+ cx: &'a LateContext<'tcx>,
+ possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
+}
+
+impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> {
+ fn new(
+ cx: &'a LateContext<'tcx>,
+ body: &'a mir::Body<'tcx>,
+ possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
+ ) -> Self {
+ Self {
+ possible_borrower: TransitiveRelation::default(),
+ cx,
+ body,
+ possible_origin,
+ }
+ }
+
+ fn into_map(
+ self,
+ cx: &LateContext<'tcx>,
+ maybe_live: ResultsCursor<'tcx, 'tcx, MaybeStorageLive>,
+ ) -> PossibleBorrowerMap<'a, 'tcx> {
+ let mut map = FxHashMap::default();
+ for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
+ if is_copy(cx, self.body.local_decls[row].ty) {
+ continue;
+ }
+
+ let borrowers = self.possible_borrower.reachable_from(&row);
+ if !borrowers.is_empty() {
+ let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
+ for &c in borrowers {
+ if c != mir::Local::from_usize(0) {
+ bs.insert(c);
+ }
+ }
+
+ if !bs.is_empty() {
+ map.insert(row, bs);
+ }
+ }
+ }
+
+ let bs = BitSet::new_empty(self.body.local_decls.len());
+ PossibleBorrowerMap {
+ map,
+ maybe_live,
+ bitset: (bs.clone(), bs),
+ }
+ }
+}
+
+impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
+ fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
+ let lhs = place.local;
+ match rvalue {
+ mir::Rvalue::Ref(_, _, borrowed) => {
+ self.possible_borrower.add(borrowed.local, lhs);
+ },
+ other => {
+ if ContainsRegion(self.cx.tcx)
+ .visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty)
+ .is_continue()
+ {
+ return;
+ }
+ rvalue_locals(other, |rhs| {
+ if lhs != rhs {
+ self.possible_borrower.add(rhs, lhs);
+ }
+ });
+ },
+ }
+ }
+
+ fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
+ if let mir::TerminatorKind::Call {
+ args,
+ destination: Some((mir::Place { local: dest, .. }, _)),
+ ..
+ } = &terminator.kind
+ {
+ // TODO add doc
+ // If the call returns something with lifetimes,
+ // let's conservatively assume the returned value contains lifetime of all the arguments.
+ // For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`.
+
+ let mut immutable_borrowers = vec![];
+ let mut mutable_borrowers = vec![];
+
+ for op in args {
+ match op {
+ mir::Operand::Copy(p) | mir::Operand::Move(p) => {
+ if let ty::Ref(_, _, Mutability::Mut) = self.body.local_decls[p.local].ty.kind() {
+ mutable_borrowers.push(p.local);
+ } else {
+ immutable_borrowers.push(p.local);
+ }
+ },
+ mir::Operand::Constant(..) => (),
+ }
+ }
+
+ let mut mutable_variables: Vec<mir::Local> = mutable_borrowers
+ .iter()
+ .filter_map(|r| self.possible_origin.get(r))
+ .flat_map(HybridBitSet::iter)
+ .collect();
+
+ if ContainsRegion(self.cx.tcx)
+ .visit_ty(self.body.local_decls[*dest].ty)
+ .is_break()
+ {
+ mutable_variables.push(*dest);
+ }
+
+ for y in mutable_variables {
+ for x in &immutable_borrowers {
+ self.possible_borrower.add(*x, y);
+ }
+ for x in &mutable_borrowers {
+ self.possible_borrower.add(*x, y);
+ }
+ }
+ }
+ }
+}
+
+/// Collect possible borrowed for every `&mut` local.
+/// For exampel, `_1 = &mut _2` generate _1: {_2,...}
+/// Known Problems: not sure all borrowed are tracked
+struct PossibleOriginVisitor<'a, 'tcx> {
+ possible_origin: TransitiveRelation<mir::Local>,
+ body: &'a mir::Body<'tcx>,
+}
+
+impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
+ fn new(body: &'a mir::Body<'tcx>) -> Self {
+ Self {
+ possible_origin: TransitiveRelation::default(),
+ body,
+ }
+ }
+
+ fn into_map(self, cx: &LateContext<'tcx>) -> FxHashMap<mir::Local, HybridBitSet<mir::Local>> {
+ let mut map = FxHashMap::default();
+ for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
+ if is_copy(cx, self.body.local_decls[row].ty) {
+ continue;
+ }
+
+ let borrowers = self.possible_origin.reachable_from(&row);
+ if !borrowers.is_empty() {
+ let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
+ for &c in borrowers {
+ if c != mir::Local::from_usize(0) {
+ bs.insert(c);
+ }
+ }
+
+ if !bs.is_empty() {
+ map.insert(row, bs);
+ }
+ }
+ }
+ map
+ }
+}
+
+impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
+ fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
+ let lhs = place.local;
+ match rvalue {
+ // Only consider `&mut`, which can modify origin place
+ mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
+ // _2: &mut _;
+ // _3 = move _2
+ mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
+ // _3 = move _2 as &mut _;
+ mir::Rvalue::Cast(_, mir::Operand::Move(borrowed), _)
+ => {
+ self.possible_origin.add(lhs, borrowed.local);
+ },
+ _ => {},
+ }
+ }
+}
+
+struct ContainsRegion<'tcx>(TyCtxt<'tcx>);
+
+impl<'tcx> TypeVisitor<'tcx> for ContainsRegion<'tcx> {
+ type BreakTy = ();
+ fn tcx_for_anon_const_substs(&self) -> Option<TyCtxt<'tcx>> {
+ Some(self.0)
+ }
+
+ fn visit_region(&mut self, _: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
+ ControlFlow::BREAK
+ }
+}
+
+fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
+ use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use};
+
+ let mut visit_op = |op: &mir::Operand<'_>| match op {
+ mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
+ mir::Operand::Constant(..) => (),
+ };
+
+ match rvalue {
+ Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op),
+ Aggregate(_, ops) => ops.iter().for_each(visit_op),
+ BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => {
+ visit_op(lhs);
+ visit_op(rhs);
+ },
+ _ => (),
+ }
+}
+
+/// Result of `PossibleBorrowerVisitor`.
+struct PossibleBorrowerMap<'a, 'tcx> {
+ /// Mapping `Local -> its possible borrowers`
+ map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
+ maybe_live: ResultsCursor<'a, 'tcx, MaybeStorageLive>,
+ // Caches to avoid allocation of `BitSet` on every query
+ bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
+}
+
+impl PossibleBorrowerMap<'_, '_> {
+ /// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`.
+ fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool {
+ self.maybe_live.seek_after_primary_effect(at);
+
+ self.bitset.0.clear();
+ let maybe_live = &mut self.maybe_live;
+ if let Some(bitset) = self.map.get(&borrowed) {
+ for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) {
+ self.bitset.0.insert(b);
+ }
+ } else {
+ return false;
+ }
+
+ self.bitset.1.clear();
+ for b in borrowers {
+ self.bitset.1.insert(*b);
+ }
+
+ self.bitset.0 == self.bitset.1
+ }
+
+ fn is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
+ self.maybe_live.seek_after_primary_effect(at);
+ self.maybe_live.contains(local)
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_ast::visit as ast_visit;
+use rustc_ast::visit::Visitor as AstVisitor;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::intravisit as hir_visit;
+use rustc_hir::intravisit::Visitor as HirVisitor;
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects closures called in the same expression where they
+ /// are defined.
+ ///
+ /// ### Why is this bad?
+ /// It is unnecessarily adding to the expression's
+ /// complexity.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// let a = (|| 42)()
+ ///
+ /// // Good
+ /// let a = 42
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub REDUNDANT_CLOSURE_CALL,
+ complexity,
+ "throwaway closures called in the expression they are defined"
+}
+
+declare_lint_pass!(RedundantClosureCall => [REDUNDANT_CLOSURE_CALL]);
+
+// Used to find `return` statements or equivalents e.g., `?`
+struct ReturnVisitor {
+ found_return: bool,
+}
+
+impl ReturnVisitor {
+ #[must_use]
+ fn new() -> Self {
+ Self { found_return: false }
+ }
+}
+
+impl<'ast> ast_visit::Visitor<'ast> for ReturnVisitor {
+ fn visit_expr(&mut self, ex: &'ast ast::Expr) {
+ if let ast::ExprKind::Ret(_) | ast::ExprKind::Try(_) = ex.kind {
+ self.found_return = true;
+ }
+
+ ast_visit::walk_expr(self, ex);
+ }
+}
+
+impl EarlyLintPass for RedundantClosureCall {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ if in_external_macro(cx.sess, expr.span) {
+ return;
+ }
+ if_chain! {
+ if let ast::ExprKind::Call(ref paren, _) = expr.kind;
+ if let ast::ExprKind::Paren(ref closure) = paren.kind;
+ if let ast::ExprKind::Closure(_, _, _, ref decl, ref block, _) = closure.kind;
+ then {
+ let mut visitor = ReturnVisitor::new();
+ visitor.visit_expr(block);
+ if !visitor.found_return {
+ span_lint_and_then(
+ cx,
+ REDUNDANT_CLOSURE_CALL,
+ expr.span,
+ "try not to call a closure in the expression where it is declared",
+ |diag| {
+ if decl.inputs.is_empty() {
+ let mut app = Applicability::MachineApplicable;
+ let hint =
+ snippet_with_applicability(cx, block.span, "..", &mut app).into_owned();
+ diag.span_suggestion(expr.span, "try doing something like", hint, app);
+ }
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ fn count_closure_usage<'a, 'tcx>(
+ cx: &'a LateContext<'tcx>,
+ block: &'tcx hir::Block<'_>,
+ path: &'tcx hir::Path<'tcx>,
+ ) -> usize {
+ struct ClosureUsageCount<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ path: &'tcx hir::Path<'tcx>,
+ count: usize,
+ }
+ impl<'a, 'tcx> hir_visit::Visitor<'tcx> for ClosureUsageCount<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if_chain! {
+ if let hir::ExprKind::Call(closure, _) = expr.kind;
+ if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = closure.kind;
+ if self.path.segments[0].ident == path.segments[0].ident;
+ if self.path.res == path.res;
+ then {
+ self.count += 1;
+ }
+ }
+ hir_visit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> hir_visit::NestedVisitorMap<Self::Map> {
+ hir_visit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+ }
+ let mut closure_usage_count = ClosureUsageCount { cx, path, count: 0 };
+ closure_usage_count.visit_block(block);
+ closure_usage_count.count
+ }
+
+ for w in block.stmts.windows(2) {
+ if_chain! {
+ if let hir::StmtKind::Local(local) = w[0].kind;
+ if let Option::Some(t) = local.init;
+ if let hir::ExprKind::Closure(..) = t.kind;
+ if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind;
+ if let hir::StmtKind::Semi(second) = w[1].kind;
+ if let hir::ExprKind::Assign(_, call, _) = second.kind;
+ if let hir::ExprKind::Call(closure, _) = call.kind;
+ if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = closure.kind;
+ if ident == path.segments[0].ident;
+ if count_closure_usage(cx, block, path) == 1;
+ then {
+ span_lint(
+ cx,
+ REDUNDANT_CLOSURE_CALL,
+ second.span,
+ "closure called just once immediately after it was declared",
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_ast::ast::{Block, Expr, ExprKind, Stmt, StmtKind};
+use rustc_ast::visit::{walk_expr, Visitor};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `else` blocks that can be removed without changing semantics.
+ ///
+ /// ### Why is this bad?
+ /// The `else` block adds unnecessary indentation and verbosity.
+ ///
+ /// ### Known problems
+ /// Some may prefer to keep the `else` block for clarity.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn my_func(count: u32) {
+ /// if count == 0 {
+ /// print!("Nothing to do");
+ /// return;
+ /// } else {
+ /// print!("Moving on...");
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn my_func(count: u32) {
+ /// if count == 0 {
+ /// print!("Nothing to do");
+ /// return;
+ /// }
+ /// print!("Moving on...");
+ /// }
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub REDUNDANT_ELSE,
+ pedantic,
+ "`else` branch that can be removed without changing semantics"
+}
+
+declare_lint_pass!(RedundantElse => [REDUNDANT_ELSE]);
+
+impl EarlyLintPass for RedundantElse {
+ fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &Stmt) {
+ if in_external_macro(cx.sess, stmt.span) {
+ return;
+ }
+ // Only look at expressions that are a whole statement
+ let expr: &Expr = match &stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => expr,
+ _ => return,
+ };
+ // if else
+ let (mut then, mut els): (&Block, &Expr) = match &expr.kind {
+ ExprKind::If(_, then, Some(els)) => (then, els),
+ _ => return,
+ };
+ loop {
+ if !BreakVisitor::default().check_block(then) {
+ // then block does not always break
+ return;
+ }
+ match &els.kind {
+ // else if else
+ ExprKind::If(_, next_then, Some(next_els)) => {
+ then = next_then;
+ els = next_els;
+ continue;
+ },
+ // else if without else
+ ExprKind::If(..) => return,
+ // done
+ _ => break,
+ }
+ }
+ span_lint_and_help(
+ cx,
+ REDUNDANT_ELSE,
+ els.span,
+ "redundant else block",
+ None,
+ "remove the `else` block and move the contents out",
+ );
+ }
+}
+
+/// Call `check` functions to check if an expression always breaks control flow
+#[derive(Default)]
+struct BreakVisitor {
+ is_break: bool,
+}
+
+impl<'ast> Visitor<'ast> for BreakVisitor {
+ fn visit_block(&mut self, block: &'ast Block) {
+ self.is_break = match block.stmts.as_slice() {
+ [.., last] => self.check_stmt(last),
+ _ => false,
+ };
+ }
+
+ fn visit_expr(&mut self, expr: &'ast Expr) {
+ self.is_break = match expr.kind {
+ ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) => true,
+ ExprKind::Match(_, ref arms) => arms.iter().all(|arm| self.check_expr(&arm.body)),
+ ExprKind::If(_, ref then, Some(ref els)) => self.check_block(then) && self.check_expr(els),
+ ExprKind::If(_, _, None)
+ // ignore loops for simplicity
+ | ExprKind::While(..) | ExprKind::ForLoop(..) | ExprKind::Loop(..) => false,
+ _ => {
+ walk_expr(self, expr);
+ return;
+ },
+ };
+ }
+}
+
+impl BreakVisitor {
+ fn check<T>(&mut self, item: T, visit: fn(&mut Self, T)) -> bool {
+ visit(self, item);
+ std::mem::replace(&mut self.is_break, false)
+ }
+
+ fn check_block(&mut self, block: &Block) -> bool {
+ self.check(block, Self::visit_block)
+ }
+
+ fn check_expr(&mut self, expr: &Expr) -> bool {
+ self.check(expr, Self::visit_expr)
+ }
+
+ fn check_stmt(&mut self, stmt: &Stmt) -> bool {
+ self.check(stmt, Self::visit_stmt)
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{Expr, ExprKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for fields in struct literals where shorthands
+ /// could be used.
+ ///
+ /// ### Why is this bad?
+ /// If the field and variable names are the same,
+ /// the field name is redundant.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let bar: u8 = 123;
+ ///
+ /// struct Foo {
+ /// bar: u8,
+ /// }
+ ///
+ /// let foo = Foo { bar: bar };
+ /// ```
+ /// the last line can be simplified to
+ /// ```ignore
+ /// let foo = Foo { bar };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub REDUNDANT_FIELD_NAMES,
+ style,
+ "checks for fields in struct literals where shorthands could be used"
+}
+
+pub struct RedundantFieldNames {
+ msrv: Option<RustcVersion>,
+}
+
+impl RedundantFieldNames {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]);
+
+impl EarlyLintPass for RedundantFieldNames {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::FIELD_INIT_SHORTHAND) {
+ return;
+ }
+
+ if in_external_macro(cx.sess, expr.span) {
+ return;
+ }
+ if let ExprKind::Struct(ref se) = expr.kind {
+ for field in &se.fields {
+ if field.is_shorthand {
+ continue;
+ }
+ if let ExprKind::Path(None, path) = &field.expr.kind {
+ if path.segments.len() == 1
+ && path.segments[0].ident == field.ident
+ && path.segments[0].args.is_none()
+ {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_FIELD_NAMES,
+ field.span,
+ "redundant field names in struct initialization",
+ "replace it with",
+ field.ident.to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+ }
+ extract_msrv_attr!(EarlyContext);
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind, VisibilityKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for items declared `pub(crate)` that are not crate visible because they
+ /// are inside a private module.
+ ///
+ /// ### Why is this bad?
+ /// Writing `pub(crate)` is misleading when it's redundant due to the parent
+ /// module's visibility.
+ ///
+ /// ### Example
+ /// ```rust
+ /// mod internal {
+ /// pub(crate) fn internal_fn() { }
+ /// }
+ /// ```
+ /// This function is not visible outside the module and it can be declared with `pub` or
+ /// private visibility
+ /// ```rust
+ /// mod internal {
+ /// pub fn internal_fn() { }
+ /// }
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub REDUNDANT_PUB_CRATE,
+ nursery,
+ "Using `pub(crate)` visibility on items that are not crate visible due to the visibility of the module that contains them."
+}
+
+#[derive(Default)]
+pub struct RedundantPubCrate {
+ is_exported: Vec<bool>,
+}
+
+impl_lint_pass!(RedundantPubCrate => [REDUNDANT_PUB_CRATE]);
+
+impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if let VisibilityKind::Crate { .. } = item.vis.node {
+ if !cx.access_levels.is_exported(item.def_id) && self.is_exported.last() == Some(&false) {
+ let span = item.span.with_hi(item.ident.span.hi());
+ let descr = cx.tcx.def_kind(item.def_id).descr(item.def_id.to_def_id());
+ span_lint_and_then(
+ cx,
+ REDUNDANT_PUB_CRATE,
+ span,
+ &format!("pub(crate) {} inside private module", descr),
+ |diag| {
+ diag.span_suggestion(
+ item.vis.span,
+ "consider using",
+ "pub".to_string(),
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+
+ if let ItemKind::Mod { .. } = item.kind {
+ self.is_exported.push(cx.access_levels.is_exported(item.def_id));
+ }
+ }
+
+ fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if let ItemKind::Mod { .. } = item.kind {
+ self.is_exported.pop().expect("unbalanced check_item/check_item_post");
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{get_parent_expr, in_macro};
+use clippy_utils::diagnostics::span_lint_and_sugg;
++use clippy_utils::get_parent_expr;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::ty::is_type_lang_item;
- if in_macro(expr.span) {
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::TyS;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for redundant slicing expressions which use the full range, and
+ /// do not change the type.
+ ///
+ /// ### Why is this bad?
+ /// It unnecessarily adds complexity to the expression.
+ ///
+ /// ### Known problems
+ /// If the type being sliced has an implementation of `Index<RangeFull>`
+ /// that actually changes anything then it can't be removed. However, this would be surprising
+ /// to people reading the code and should have a note with it.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// fn get_slice(x: &[u32]) -> &[u32] {
+ /// &x[..]
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```ignore
+ /// fn get_slice(x: &[u32]) -> &[u32] {
+ /// x
+ /// }
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub REDUNDANT_SLICING,
+ complexity,
+ "redundant slicing of the whole range of a type"
+}
+
+declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING]);
+
+impl LateLintPass<'_> for RedundantSlicing {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
++ if expr.span.from_expansion() {
+ return;
+ }
+
+ let ctxt = expr.span.ctxt();
+ if_chain! {
+ if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind;
+ if addressee.span.ctxt() == ctxt;
+ if let ExprKind::Index(indexed, range) = addressee.kind;
+ if is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull);
+ if TyS::same_type(cx.typeck_results().expr_ty(expr), cx.typeck_results().expr_ty(indexed));
+ then {
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
+
+ let (reborrow_str, help_str) = if mutability == Mutability::Mut {
+ // The slice was used to reborrow the mutable reference.
+ ("&mut *", "reborrow the original value instead")
+ } else if matches!(
+ get_parent_expr(cx, expr),
+ Some(Expr {
+ kind: ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _),
+ ..
+ })
+ ) {
+ // The slice was used to make a temporary reference.
+ ("&*", "reborrow the original value instead")
+ } else {
+ ("", "use the original value instead")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_SLICING,
+ expr.span,
+ "redundant slicing of the whole range",
+ help_str,
+ format!("{}{}", reborrow_str, snip),
+ app,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use clippy_utils::{meets_msrv, msrvs};
+use rustc_ast::ast::{Item, ItemKind, Ty, TyKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for constants and statics with an explicit `'static` lifetime.
+ ///
+ /// ### Why is this bad?
+ /// Adding `'static` to every reference can create very
+ /// complicated types.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// const FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =
+ /// &[...]
+ /// static FOO: &'static [(&'static str, &'static str, fn(&Bar) -> bool)] =
+ /// &[...]
+ /// ```
+ /// This code can be rewritten as
+ /// ```ignore
+ /// const FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...]
+ /// static FOO: &[(&str, &str, fn(&Bar) -> bool)] = &[...]
+ /// ```
++ #[clippy::version = "1.37.0"]
+ pub REDUNDANT_STATIC_LIFETIMES,
+ style,
+ "Using explicit `'static` lifetime for constants or statics when elision rules would allow omitting them."
+}
+
+pub struct RedundantStaticLifetimes {
+ msrv: Option<RustcVersion>,
+}
+
+impl RedundantStaticLifetimes {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(RedundantStaticLifetimes => [REDUNDANT_STATIC_LIFETIMES]);
+
+impl RedundantStaticLifetimes {
+ // Recursively visit types
+ fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) {
+ match ty.kind {
+ // Be careful of nested structures (arrays and tuples)
+ TyKind::Array(ref ty, _) => {
+ self.visit_type(&*ty, cx, reason);
+ },
+ TyKind::Tup(ref tup) => {
+ for tup_ty in tup {
+ self.visit_type(&*tup_ty, cx, reason);
+ }
+ },
+ // This is what we are looking for !
+ TyKind::Rptr(ref optional_lifetime, ref borrow_type) => {
+ // Match the 'static lifetime
+ if let Some(lifetime) = *optional_lifetime {
+ match borrow_type.ty.kind {
+ TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => {
+ if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime {
+ let snip = snippet(cx, borrow_type.ty.span, "<type>");
+ let sugg = format!("&{}", snip);
+ span_lint_and_then(
+ cx,
+ REDUNDANT_STATIC_LIFETIMES,
+ lifetime.ident.span,
+ reason,
+ |diag| {
+ diag.span_suggestion(
+ ty.span,
+ "consider removing `'static`",
+ sugg,
+ Applicability::MachineApplicable, //snippet
+ );
+ },
+ );
+ }
+ },
+ _ => {},
+ }
+ }
+ self.visit_type(&*borrow_type.ty, cx, reason);
+ },
+ TyKind::Slice(ref ty) => {
+ self.visit_type(ty, cx, reason);
+ },
+ _ => {},
+ }
+ }
+}
+
+impl EarlyLintPass for RedundantStaticLifetimes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if !meets_msrv(self.msrv.as_ref(), &msrvs::STATIC_IN_CONST) {
+ return;
+ }
+
+ if !item.span.from_expansion() {
+ if let ItemKind::Const(_, ref var_type, _) = item.kind {
+ self.visit_type(var_type, cx, "constants have by default a `'static` lifetime");
+ // Don't check associated consts because `'static` cannot be elided on those (issue
+ // #2438)
+ }
+
+ if let ItemKind::Static(ref var_type, _, _) = item.kind {
+ self.visit_type(var_type, cx, "statics have by default a `'static` lifetime");
+ }
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::last_path_segment;
+use clippy_utils::source::snippet;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{GenericArg, Mutability, Ty, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `&Option<&T>`.
+ ///
+ /// ### Why is this bad?
+ /// Since `&` is Copy, it's useless to have a
+ /// reference on `Option<&T>`.
+ ///
+ /// ### Known problems
+ /// It may be irrelevant to use this lint on
+ /// public API code as it will make a breaking change to apply it.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let x: &Option<&u32> = &Some(&0u32);
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// let x: Option<&u32> = Some(&0u32);
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub REF_OPTION_REF,
+ pedantic,
+ "use `Option<&T>` instead of `&Option<&T>`"
+}
+
+declare_lint_pass!(RefOptionRef => [REF_OPTION_REF]);
+
+impl<'tcx> LateLintPass<'tcx> for RefOptionRef {
+ fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) {
+ if_chain! {
+ if let TyKind::Rptr(_, ref mut_ty) = ty.kind;
+ if mut_ty.mutbl == Mutability::Not;
+ if let TyKind::Path(ref qpath) = &mut_ty.ty.kind;
+ let last = last_path_segment(qpath);
+ if let Some(res) = last.res;
+ if let Some(def_id) = res.opt_def_id();
+
+ if cx.tcx.is_diagnostic_item(sym::Option, def_id);
+ if let Some(params) = last_path_segment(qpath).args ;
+ if !params.parenthesized;
+ if let Some(inner_ty) = params.args.iter().find_map(|arg| match arg {
+ GenericArg::Type(inner_ty) => Some(inner_ty),
+ _ => None,
+ });
+ if let TyKind::Rptr(_, _) = inner_ty.kind;
+
+ then {
+ span_lint_and_sugg(
+ cx,
+ REF_OPTION_REF,
+ ty.span,
+ "since `&` implements the `Copy` trait, `&Option<&T>` can be simplified to `Option<&T>`",
+ "try",
+ format!("Option<{}>", &snippet(cx, inner_ty.span, "..")),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if !in_macro(addrof_target.span);
+use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
+use if_chain::if_chain;
+use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::BytePos;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `*&` and `*&mut` in expressions.
+ ///
+ /// ### Why is this bad?
+ /// Immediately dereferencing a reference is no-op and
+ /// makes the code less clear.
+ ///
+ /// ### Known problems
+ /// Multiple dereference/addrof pairs are not handled so
+ /// the suggested fix for `x = **&&y` is `x = *&y`, which is still incorrect.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// let a = f(*&mut b);
+ /// let c = *&d;
+ ///
+ /// // Good
+ /// let a = f(b);
+ /// let c = d;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub DEREF_ADDROF,
+ complexity,
+ "use of `*&` or `*&mut` in an expression"
+}
+
+declare_lint_pass!(DerefAddrOf => [DEREF_ADDROF]);
+
+fn without_parens(mut e: &Expr) -> &Expr {
+ while let ExprKind::Paren(ref child_e) = e.kind {
+ e = child_e;
+ }
+ e
+}
+
+impl EarlyLintPass for DerefAddrOf {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
+ if_chain! {
+ if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind;
+ if let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind;
++ if !addrof_target.span.from_expansion();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let sugg = if e.span.from_expansion() {
+ #[allow(clippy::option_if_let_else)]
+ if let Some(macro_source) = snippet_opt(cx, e.span) {
+ // Remove leading whitespace from the given span
+ // e.g: ` $visitor` turns into `$visitor`
+ let trim_leading_whitespaces = |span| {
+ snippet_opt(cx, span).and_then(|snip| {
+ #[allow(clippy::cast_possible_truncation)]
+ snip.find(|c: char| !c.is_whitespace()).map(|pos| {
+ span.lo() + BytePos(pos as u32)
+ })
+ }).map_or(span, |start_no_whitespace| e.span.with_lo(start_no_whitespace))
+ };
+
+ let mut generate_snippet = |pattern: &str| {
+ #[allow(clippy::cast_possible_truncation)]
+ macro_source.rfind(pattern).map(|pattern_pos| {
+ let rpos = pattern_pos + pattern.len();
+ let span_after_ref = e.span.with_lo(BytePos(e.span.lo().0 + rpos as u32));
+ let span = trim_leading_whitespaces(span_after_ref);
+ snippet_with_applicability(cx, span, "_", &mut applicability)
+ })
+ };
+
+ if *mutability == Mutability::Mut {
+ generate_snippet("mut")
+ } else {
+ generate_snippet("&")
+ }
+ } else {
+ Some(snippet_with_applicability(cx, e.span, "_", &mut applicability))
+ }
+ } else {
+ Some(snippet_with_applicability(cx, addrof_target.span, "_", &mut applicability))
+ };
+ if let Some(sugg) = sugg {
+ span_lint_and_sugg(
+ cx,
+ DEREF_ADDROF,
+ e.span,
+ "immediately dereferencing a reference",
+ "try this",
+ sugg.to_string(),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for references in expressions that use
+ /// auto dereference.
+ ///
+ /// ### Why is this bad?
+ /// The reference is a no-op and is automatically
+ /// dereferenced by the compiler and makes the code less clear.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Point(u32, u32);
+ /// let point = Point(30, 20);
+ /// let x = (&point).0;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # struct Point(u32, u32);
+ /// # let point = Point(30, 20);
+ /// let x = point.0;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub REF_IN_DEREF,
+ complexity,
+ "Use of reference in auto dereference expression."
+}
+
+declare_lint_pass!(RefInDeref => [REF_IN_DEREF]);
+
+impl EarlyLintPass for RefInDeref {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
+ if_chain! {
+ if let ExprKind::Field(ref object, _) = e.kind;
+ if let ExprKind::Paren(ref parened) = object.kind;
+ if let ExprKind::AddrOf(_, _, ref inner) = parened.kind;
+ then {
+ let applicability = if inner.span.from_expansion() {
+ Applicability::MaybeIncorrect
+ } else {
+ Applicability::MachineApplicable
+ };
+ let sugg = Sugg::ast(cx, inner, "_").maybe_par();
+ span_lint_and_sugg(
+ cx,
+ REF_IN_DEREF,
+ object.span,
+ "creating a reference that is immediately dereferenced",
+ "try this",
+ sugg.to_string(),
+ applicability,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast::ast::{LitKind, StrStyle};
+use rustc_hir::{BorrowKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{BytePos, Span};
+use std::convert::TryFrom;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks [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.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// Regex::new("|")
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub INVALID_REGEX,
+ correctness,
+ "invalid regular expressions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for trivial [regex](https://crates.io/crates/regex)
+ /// creation (with `Regex::new`, `RegexBuilder::new`, or `RegexSet::new`).
+ ///
+ /// ### Why is this bad?
+ /// Matching the regex can likely be replaced by `==` or
+ /// `str::starts_with`, `str::ends_with` or `std::contains` or other `str`
+ /// methods.
+ ///
+ /// ### Known problems
+ /// If the same regex is going to be applied to multiple
+ /// inputs, the precomputations done by `Regex` construction can give
+ /// significantly better performance than any of the `str`-based methods.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// Regex::new("^foobar")
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRIVIAL_REGEX,
+ nursery,
+ "trivial regular expressions"
+}
+
+declare_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]);
+
+impl<'tcx> LateLintPass<'tcx> for Regex {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(fun, 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(), base.parent())
+}
+
+fn const_str<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<String> {
+ constant(cx, cx.typeck_results(), e).and_then(|(c, _)| match c {
+ Constant::Str(s) => Some(s),
+ _ => None,
+ })
+}
+
+fn is_trivial_regex(s: ®ex_syntax::hir::Hir) -> Option<&'static str> {
+ use regex_syntax::hir::Anchor::{EndText, StartText};
+ use regex_syntax::hir::HirKind::{Alternation, Anchor, Concat, Empty, Literal};
+
+ let is_literal = |e: &[regex_syntax::hir::Hir]| e.iter().all(|e| matches!(*e.kind(), Literal(_)));
+
+ match *s.kind() {
+ Empty | Anchor(_) => Some("the regex is unlikely to be useful as it is"),
+ Literal(_) => Some("consider using `str::contains`"),
+ Alternation(ref exprs) => {
+ if exprs.iter().all(|e| e.kind().is_empty()) {
+ Some("the regex is unlikely to be useful as it is")
+ } else {
+ None
+ }
+ },
+ Concat(ref exprs) => match (exprs[0].kind(), exprs[exprs.len() - 1].kind()) {
+ (&Anchor(StartText), &Anchor(EndText)) if exprs[1..(exprs.len() - 1)].is_empty() => {
+ Some("consider using `str::is_empty`")
+ },
+ (&Anchor(StartText), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
+ Some("consider using `==` on `str`s")
+ },
+ (&Anchor(StartText), &Literal(_)) if is_literal(&exprs[1..]) => Some("consider using `str::starts_with`"),
+ (&Literal(_), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
+ Some("consider using `str::ends_with`")
+ },
+ _ if is_literal(exprs) => Some("consider using `str::contains`"),
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+fn check_set<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
+ if_chain! {
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
+ if let ExprKind::Array(exprs) = expr.kind;
+ then {
+ for expr in exprs {
+ check_regex(cx, expr, utf8);
+ }
+ }
+ }
+}
+
+fn check_regex<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
+ let mut parser = regex_syntax::ParserBuilder::new()
+ .unicode(true)
+ .allow_invalid_utf8(!utf8)
+ .build();
+
+ if let ExprKind::Lit(ref lit) = expr.kind {
+ if let LitKind::Str(ref r, style) = lit.node {
+ let r = &r.as_str();
+ let offset = if let StrStyle::Raw(n) = style { 2 + n } else { 1 };
+ match parser.parse(r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ str_span(expr.span, *e.span(), offset),
+ &format!("regex syntax error: {}", e.kind()),
+ );
+ },
+ Err(e) => {
+ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
+ },
+ }
+ }
+ } else if let Some(r) = const_str(cx, expr) {
+ match parser.parse(&r) {
+ Ok(r) => {
+ if let Some(repl) = is_trivial_regex(&r) {
+ span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl);
+ }
+ },
+ Err(regex_syntax::Error::Parse(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(regex_syntax::Error::Translate(e)) => {
+ span_lint(
+ cx,
+ INVALID_REGEX,
+ expr.span,
+ &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()),
+ );
+ },
+ Err(e) => {
+ span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {}", e));
+ },
+ }
+ }
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::consts::{constant_context, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
- if !in_macro(receiver.span);
+use clippy_utils::source::snippet;
+use clippy_utils::ty::is_type_diagnostic_item;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.repeat(1)` and suggest the following method for each types.
+ /// - `.to_string()` for `str`
+ /// - `.clone()` for `String`
+ /// - `.to_vec()` for `slice`
+ ///
+ /// The lint will evaluate constant expressions and values as arguments of `.repeat(..)` and emit a message if
+ /// they are equivalent to `1`. (Related discussion in [rust-clippy#7306](https://github.com/rust-lang/rust-clippy/issues/7306))
+ ///
+ /// ### 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// let x = String::from("hello world").repeat(1);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// let x = String::from("hello world").clone();
+ /// }
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub REPEAT_ONCE,
+ complexity,
+ "using `.repeat(1)` instead of `String.clone()`, `str.to_string()` or `slice.to_vec()` "
+}
+
+declare_lint_pass!(RepeatOnce => [REPEAT_ONCE]);
+
+impl<'tcx> LateLintPass<'tcx> for RepeatOnce {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, [receiver, count], _) = &expr.kind;
+ if path.ident.name == sym!(repeat);
+ if constant_context(cx, cx.typeck_results()).expr(count) == Some(Constant::Int(1));
++ if !receiver.span.from_expansion();
+ then {
+ let ty = cx.typeck_results().expr_ty(receiver).peel_refs();
+ if ty.is_str() {
+ span_lint_and_sugg(
+ cx,
+ REPEAT_ONCE,
+ expr.span,
+ "calling `repeat(1)` on str",
+ "consider using `.to_string()` instead",
+ format!("{}.to_string()", snippet(cx, receiver.span, r#""...""#)),
+ Applicability::MachineApplicable,
+ );
+ } else if ty.builtin_index().is_some() {
+ span_lint_and_sugg(
+ cx,
+ REPEAT_ONCE,
+ expr.span,
+ "calling `repeat(1)` on slice",
+ "consider using `.to_vec()` instead",
+ format!("{}.to_vec()", snippet(cx, receiver.span, r#""...""#)),
+ Applicability::MachineApplicable,
+ );
+ } else if is_type_diagnostic_item(cx, ty, sym::String) {
+ span_lint_and_sugg(
+ cx,
+ REPEAT_ONCE,
+ expr.span,
+ "calling `repeat(1)` on a string literal",
+ "consider using `.clone()` instead",
+ format!("{}.clone()", snippet(cx, receiver.span, r#""...""#)),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{fn_def_id, in_macro, path_to_local_id};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_opt;
- if !in_external_macro(cx.sess(), local.span);
- if !in_macro(local.span);
++use clippy_utils::{fn_def_id, path_to_local_id};
+use if_chain::if_chain;
+use rustc_ast::ast::Attribute;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::hygiene::DesugaringKind;
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo() -> String {
+ /// let x = String::new();
+ /// x
+ /// }
+ /// ```
+ /// instead, use
+ /// ```
+ /// fn foo() -> String {
+ /// String::new()
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub LET_AND_RETURN,
+ style,
+ "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for return statements at the end of a block.
+ ///
+ /// ### Why is this bad?
+ /// Removing the `return` and semicolon will make the code
+ /// more rusty.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// return x;
+ /// }
+ /// ```
+ /// simplify to
+ /// ```rust
+ /// fn foo(x: usize) -> usize {
+ /// x
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NEEDLESS_RETURN,
+ style,
+ "using a return statement like `return expr;` where an expression would suffice"
+}
+
+#[derive(PartialEq, Eq, Copy, Clone)]
+enum RetReplacement {
+ Empty,
+ Block,
+}
+
+declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]);
+
+impl<'tcx> LateLintPass<'tcx> for Return {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ // we need both a let-binding stmt and an expr
+ if_chain! {
+ if let Some(retexpr) = block.expr;
+ if let Some(stmt) = block.stmts.iter().last();
+ if let StmtKind::Local(local) = &stmt.kind;
+ if local.ty.is_none();
+ if cx.tcx.hir().attrs(local.hir_id).is_empty();
+ if let Some(initexpr) = &local.init;
+ if let PatKind::Binding(_, local_id, _, _) = local.pat.kind;
+ if path_to_local_id(retexpr, local_id);
+ if !last_statement_borrows(cx, initexpr);
+ if !in_external_macro(cx.sess(), initexpr.span);
+ if !in_external_macro(cx.sess(), retexpr.span);
++ if !local.span.from_expansion();
+ then {
+ span_lint_and_then(
+ cx,
+ LET_AND_RETURN,
+ retexpr.span,
+ "returning the result of a `let` binding from a block",
+ |err| {
+ err.span_label(local.span, "unnecessary `let` binding");
+
+ if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
+ if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
+ snippet.push_str(" as _");
+ }
+ err.multipart_suggestion(
+ "return the expression directly",
+ vec![
+ (local.span, String::new()),
+ (retexpr.span, snippet),
+ ],
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.span_help(initexpr.span, "this expression can be directly returned");
+ }
+ },
+ );
+ }
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ _: &'tcx FnDecl<'tcx>,
+ body: &'tcx Body<'tcx>,
+ _: Span,
+ _: HirId,
+ ) {
+ match kind {
+ FnKind::Closure => {
+ // when returning without value in closure, replace this `return`
+ // with an empty block to prevent invalid suggestion (see #6501)
+ let replacement = if let ExprKind::Ret(None) = &body.value.kind {
+ RetReplacement::Block
+ } else {
+ RetReplacement::Empty
+ };
+ check_final_expr(cx, &body.value, Some(body.value.span), replacement);
+ },
+ FnKind::ItemFn(..) | FnKind::Method(..) => {
+ if let ExprKind::Block(block, _) = body.value.kind {
+ check_block_return(cx, block);
+ }
+ },
+ }
+ }
+}
+
+fn attr_is_cfg(attr: &Attribute) -> bool {
+ attr.meta_item_list().is_some() && attr.has_name(sym::cfg)
+}
+
+fn check_block_return<'tcx>(cx: &LateContext<'tcx>, block: &Block<'tcx>) {
+ if let Some(expr) = block.expr {
+ check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty);
+ } else if let Some(stmt) = block.stmts.iter().last() {
+ match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty);
+ },
+ _ => (),
+ }
+ }
+}
+
+fn check_final_expr<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ span: Option<Span>,
+ replacement: RetReplacement,
+) {
+ match expr.kind {
+ // simple return is always "bad"
+ ExprKind::Ret(ref inner) => {
+ // allow `#[cfg(a)] return a; #[cfg(b)] return b;`
+ let attrs = cx.tcx.hir().attrs(expr.hir_id);
+ if !attrs.iter().any(attr_is_cfg) {
+ let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
+ if !borrows {
+ emit_return_lint(
+ cx,
+ span.expect("`else return` is not possible"),
+ inner.as_ref().map(|i| i.span),
+ replacement,
+ );
+ }
+ }
+ },
+ // a whole block? check it!
+ ExprKind::Block(block, _) => {
+ check_block_return(cx, block);
+ },
+ ExprKind::If(_, then, else_clause_opt) => {
+ if let ExprKind::Block(ifblock, _) = then.kind {
+ check_block_return(cx, ifblock);
+ }
+ if let Some(else_clause) = else_clause_opt {
+ if expr.span.desugaring_kind() != Some(DesugaringKind::LetElse) {
+ check_final_expr(cx, else_clause, None, RetReplacement::Empty);
+ }
+ }
+ },
+ // a match expr, check all arms
+ // an if/if let expr, check both exprs
+ // note, if without else is going to be a type checking error anyways
+ // (except for unit type functions) so we don't match it
+ ExprKind::Match(_, arms, MatchSource::Normal) => {
+ for arm in arms.iter() {
+ check_final_expr(cx, arm.body, Some(arm.body.span), RetReplacement::Block);
+ }
+ },
+ ExprKind::DropTemps(expr) => check_final_expr(cx, expr, None, RetReplacement::Empty),
+ _ => (),
+ }
+}
+
+fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
+ if ret_span.from_expansion() {
+ return;
+ }
+ match inner_span {
+ Some(inner_span) => {
+ if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() {
+ return;
+ }
+
+ span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
+ if let Some(snippet) = snippet_opt(cx, inner_span) {
+ diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable);
+ }
+ });
+ },
+ None => match replacement {
+ RetReplacement::Empty => {
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_RETURN,
+ ret_span,
+ "unneeded `return` statement",
+ "remove `return`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ },
+ RetReplacement::Block => {
+ span_lint_and_sugg(
+ cx,
+ NEEDLESS_RETURN,
+ ret_span,
+ "unneeded `return` statement",
+ "replace `return` with an empty block",
+ "{}".to_string(),
+ Applicability::MachineApplicable,
+ );
+ },
+ },
+ }
+}
+
+fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ let mut visitor = BorrowVisitor { cx, borrows: false };
+ walk_expr(&mut visitor, expr);
+ visitor.borrows
+}
+
+struct BorrowVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ borrows: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ if self.borrows {
+ return;
+ }
+
+ if let Some(def_id) = fn_def_id(self.cx, expr) {
+ self.borrows = self
+ .cx
+ .tcx
+ .fn_sig(def_id)
+ .output()
+ .skip_binder()
+ .walk(self.cx.tcx)
+ .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- /// It lints if a struct has two method with same time:
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::AssocKind;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::Symbol;
+use rustc_span::Span;
+use std::collections::{BTreeMap, BTreeSet};
+
+declare_clippy_lint! {
+ /// ### What it does
- "method's name is same to an existing method in a trait",
++ /// It lints if a struct has two methods with the same name:
+ /// one from a trait, another not from trait.
+ ///
+ /// ### Why is this bad?
+ /// Confusing.
+ ///
+ /// ### Example
+ /// ```rust
+ /// trait T {
+ /// fn foo(&self) {}
+ /// }
+ ///
+ /// struct S;
+ ///
+ /// impl T for S {
+ /// fn foo(&self) {}
+ /// }
+ ///
+ /// impl S {
+ /// fn foo(&self) {}
+ /// }
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub SAME_NAME_METHOD,
+ restriction,
+ "two method with same name"
+}
+
+declare_lint_pass!(SameNameMethod => [SAME_NAME_METHOD]);
+
+struct ExistingName {
+ impl_methods: BTreeMap<Symbol, Span>,
+ trait_methods: BTreeMap<Symbol, Vec<Span>>,
+}
+
+impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ let mut map = FxHashMap::<Res, ExistingName>::default();
+
+ for item in cx.tcx.hir().items() {
+ if let ItemKind::Impl(Impl {
+ items,
+ of_trait,
+ self_ty,
+ ..
+ }) = &item.kind
+ {
+ if let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind {
+ if !map.contains_key(res) {
+ map.insert(
+ *res,
+ ExistingName {
+ impl_methods: BTreeMap::new(),
+ trait_methods: BTreeMap::new(),
+ },
+ );
+ }
+ let existing_name = map.get_mut(res).unwrap();
+
+ match of_trait {
+ Some(trait_ref) => {
+ let mut methods_in_trait: BTreeSet<Symbol> = if_chain! {
+ if let Some(Node::TraitRef(TraitRef { path, .. })) =
+ cx.tcx.hir().find(trait_ref.hir_ref_id);
+ if let Res::Def(DefKind::Trait, did) = path.res;
+ then{
+ // FIXME: if
+ // `rustc_middle::ty::assoc::AssocItems::items` is public,
+ // we can iterate its keys instead of `in_definition_order`,
+ // which's more efficient
+ cx.tcx
+ .associated_items(did)
+ .in_definition_order()
+ .filter(|assoc_item| {
+ matches!(assoc_item.kind, AssocKind::Fn)
+ })
+ .map(|assoc_item| assoc_item.ident.name)
+ .collect()
+ }else{
+ BTreeSet::new()
+ }
+ };
+
+ let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| {
+ if let Some(impl_span) = existing_name.impl_methods.get(&method_name) {
+ span_lint_and_then(
+ cx,
+ SAME_NAME_METHOD,
+ *impl_span,
- "method's name is same to an existing method in a trait",
++ "method's name is the same as an existing method in a trait",
+ |diag| {
+ diag.span_note(
+ trait_method_span,
+ &format!("existing `{}` defined here", method_name),
+ );
+ },
+ );
+ }
+ if let Some(v) = existing_name.trait_methods.get_mut(&method_name) {
+ v.push(trait_method_span);
+ } else {
+ existing_name.trait_methods.insert(method_name, vec![trait_method_span]);
+ }
+ };
+
+ for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
+ matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
+ }) {
+ let method_name = impl_item_ref.ident.name;
+ methods_in_trait.remove(&method_name);
+ check_trait_method(method_name, impl_item_ref.span);
+ }
+
+ for method_name in methods_in_trait {
+ check_trait_method(method_name, item.span);
+ }
+ },
+ None => {
+ for impl_item_ref in (*items).iter().filter(|impl_item_ref| {
+ matches!(impl_item_ref.kind, rustc_hir::AssocItemKind::Fn { .. })
+ }) {
+ let method_name = impl_item_ref.ident.name;
+ let impl_span = impl_item_ref.span;
+ if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) {
+ span_lint_and_then(
+ cx,
+ SAME_NAME_METHOD,
+ impl_span,
++ "method's name is the same as an existing method in a trait",
+ |diag| {
+ // TODO should we `span_note` on every trait?
+ // iterate on trait_spans?
+ diag.span_note(
+ trait_spans[0],
+ &format!("existing `{}` defined here", method_name),
+ );
+ },
+ );
+ }
+ existing_name.impl_methods.insert(method_name, impl_span);
+ }
+ },
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::eq_expr_value;
+use clippy_utils::source::snippet;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for explicit self-assignments.
+ ///
+ /// ### Why is this bad?
+ /// Self-assignments are redundant and unlikely to be
+ /// intentional.
+ ///
+ /// ### Known problems
+ /// If expression contains any deref coercions or
+ /// indexing operations they are assumed not to have any side effects.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Event {
+ /// id: usize,
+ /// x: i32,
+ /// y: i32,
+ /// }
+ ///
+ /// fn copy_position(a: &mut Event, b: &Event) {
+ /// a.x = b.x;
+ /// a.y = a.y;
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub SELF_ASSIGNMENT,
+ correctness,
+ "explicit self-assignment"
+}
+
+declare_lint_pass!(SelfAssignment => [SELF_ASSIGNMENT]);
+
+impl<'tcx> LateLintPass<'tcx> for SelfAssignment {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Assign(lhs, rhs, _) = &expr.kind {
+ if eq_expr_value(cx, lhs, rhs) {
+ let lhs = snippet(cx, lhs.span, "<lhs>");
+ let rhs = snippet(cx, rhs.span, "<rhs>");
+ span_lint(
+ cx,
+ SELF_ASSIGNMENT,
+ expr.span,
+ &format!("self-assignment of `{}` to `{}`", rhs, lhs),
+ );
+ }
+ }
+ }
+}
--- /dev/null
- if impl_item.ident.name.as_str() == type_name || impl_item.ident.name.as_str().replace("_", "") == type_name;
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::return_ty;
+use clippy_utils::ty::{contains_adt_constructor, contains_ty};
+use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Warns when constructors have the same name as their types.
+ ///
+ /// ### Why is this bad?
+ /// Repeating the name of the type is redundant.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// struct Foo {}
+ ///
+ /// impl Foo {
+ /// pub fn foo() -> Foo {
+ /// Foo {}
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// struct Foo {}
+ ///
+ /// impl Foo {
+ /// pub fn new() -> Foo {
+ /// Foo {}
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.55.0"]
+ pub SELF_NAMED_CONSTRUCTORS,
+ style,
+ "method should not have the same name as the type it is implemented for"
+}
+
+declare_lint_pass!(SelfNamedConstructors => [SELF_NAMED_CONSTRUCTORS]);
+
+impl<'tcx> LateLintPass<'tcx> for SelfNamedConstructors {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
+ match impl_item.kind {
+ ImplItemKind::Fn(ref sig, _) => {
+ if sig.decl.implicit_self.has_implicit_self() {
+ return;
+ }
+ },
+ _ => return,
+ }
+
+ let parent = cx.tcx.hir().get_parent_did(impl_item.hir_id());
+ let item = cx.tcx.hir().expect_item(parent);
+ let self_ty = cx.tcx.type_of(item.def_id);
+ let ret_ty = return_ty(cx, impl_item.hir_id());
+
+ // Do not check trait impls
+ if matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. })) {
+ return;
+ }
+
+ // Ensure method is constructor-like
+ if let Some(self_adt) = self_ty.ty_adt_def() {
+ if !contains_adt_constructor(cx.tcx, ret_ty, self_adt) {
+ return;
+ }
+ } else if !contains_ty(cx.tcx, ret_ty, self_ty) {
+ return;
+ }
+
+ if_chain! {
+ if let Some(self_def) = self_ty.ty_adt_def();
+ if let Some(self_local_did) = self_def.did.as_local();
+ let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
+ if let Some(Node::Item(x)) = cx.tcx.hir().find(self_id);
+ let type_name = x.ident.name.as_str().to_lowercase();
++ if impl_item.ident.name.as_str() == type_name || impl_item.ident.name.as_str().replace('_', "") == type_name;
+
+ then {
+ span_lint(
+ cx,
+ SELF_NAMED_CONSTRUCTORS,
+ impl_item.span,
+ &format!("constructor `{}` has the same name as the type", impl_item.ident.name),
+ );
+ }
+ }
+ }
+}
--- /dev/null
- if !snippet.ends_with('}');
+use crate::rustc_lint::LintContext;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::sugg;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Block, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Looks for blocks of expressions and fires if the last expression returns
+ /// `()` but is not followed by a semicolon.
+ ///
+ /// ### Why is this bad?
+ /// The semicolon might be optional but when extending the block with new
+ /// code, it doesn't require a change in previous last line.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// println!("Hello world")
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// println!("Hello world");
+ /// }
+ /// ```
++ #[clippy::version = "1.52.0"]
+ pub SEMICOLON_IF_NOTHING_RETURNED,
+ pedantic,
+ "add a semicolon if nothing is returned"
+}
+
+declare_lint_pass!(SemicolonIfNothingReturned => [SEMICOLON_IF_NOTHING_RETURNED]);
+
+impl LateLintPass<'_> for SemicolonIfNothingReturned {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
+ if_chain! {
+ if !block.span.from_expansion();
+ if let Some(expr) = block.expr;
+ let t_expr = cx.typeck_results().expr_ty(expr);
+ if t_expr.is_unit();
+ if let snippet = snippet_with_macro_callsite(cx, expr.span, "}");
++ if !snippet.ends_with('}') && !snippet.ends_with(';');
+ if cx.sess().source_map().is_multiline(block.span);
+ then {
+ // filter out the desugared `for` loop
+ if let ExprKind::DropTemps(..) = &expr.kind {
+ return;
+ }
+
+ let sugg = sugg::Sugg::hir_with_macro_callsite(cx, expr, "..");
+ let suggestion = format!("{0};", sugg);
+ span_lint_and_sugg(
+ cx,
+ SEMICOLON_IF_NOTHING_RETURNED,
+ expr.span.source_callsite(),
+ "consider adding a `;` to the last statement for consistent formatting",
+ "add a `;` here",
+ suggestion,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{get_trait_def_id, paths};
+use rustc_hir::{Impl, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for mis-uses of the serde API.
+ ///
+ /// ### Why is this bad?
+ /// Serde is very finnicky about how its API should be
+ /// used, but the type system can't be used to enforce it (yet?).
+ ///
+ /// ### Example
+ /// Implementing `Visitor::visit_string` but not
+ /// `Visitor::visit_str`.
++ #[clippy::version = "pre 1.29.0"]
+ pub SERDE_API_MISUSE,
+ correctness,
+ "various things that will negatively affect your serde experience"
+}
+
+declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]);
+
+impl<'tcx> LateLintPass<'tcx> for SerdeApi {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if let ItemKind::Impl(Impl {
+ of_trait: Some(ref trait_ref),
+ items,
+ ..
+ }) = item.kind
+ {
+ let did = trait_ref.path.res.def_id();
+ if let Some(visit_did) = get_trait_def_id(cx, &paths::SERDE_DE_VISITOR) {
+ if did == visit_did {
+ let mut seen_str = None;
+ let mut seen_string = None;
+ for item in items {
+ match &*item.ident.as_str() {
+ "visit_str" => seen_str = Some(item.span),
+ "visit_string" => seen_string = Some(item.span),
+ _ => {},
+ }
+ }
+ if let Some(span) = seen_string {
+ if seen_str.is_none() {
+ span_lint(
+ cx,
+ SERDE_API_MISUSE,
+ span,
+ "you should not implement `visit_string` without also implementing `visit_str`",
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
- let HirId { owner, local_id } = id;
+use clippy_utils::diagnostics::span_lint_and_note;
+use clippy_utils::source::snippet;
+use clippy_utils::visitors::is_local_used;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::def::Res;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::hir_id::ItemLocalId;
+use rustc_hir::{Block, Body, BodyOwnerKind, Expr, ExprKind, HirId, Node, Pat, PatKind, QPath, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{Span, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for bindings that shadow other bindings already in
+ /// scope, while just changing reference level or mutability.
+ ///
+ /// ### Why is this bad?
+ /// Not much, in fact it's a very common pattern in Rust
+ /// code. Still, some may opt to avoid it in their code base, they can set this
+ /// lint to `Warn`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let x = 1;
+ /// // Bad
+ /// let x = &x;
+ ///
+ /// // Good
+ /// let y = &x; // use different variable name
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 2;
+ /// let x = x + 1;
+ /// ```
+ /// use different variable name:
+ /// ```rust
+ /// let x = 2;
+ /// let y = x + 1;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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 an 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.
+ ///
+ /// ### 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
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub SHADOW_UNRELATED,
+ restriction,
+ "rebinding a name without even using the original value"
+}
+
+#[derive(Default)]
+pub(crate) struct Shadow {
+ bindings: Vec<FxHashMap<Symbol, Vec<ItemLocalId>>>,
+}
+
+impl_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]);
+
+impl<'tcx> LateLintPass<'tcx> for Shadow {
+ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
+ let (id, ident) = match pat.kind {
+ PatKind::Binding(_, hir_id, ident, _) => (hir_id, ident),
+ _ => return,
+ };
++
++ if pat.span.desugaring_kind().is_some() {
++ return;
++ }
++
+ if ident.span.from_expansion() || ident.span.is_dummy() {
+ return;
+ }
+
++ let HirId { owner, local_id } = id;
+ // get (or insert) the list of items for this owner and symbol
+ let data = self.bindings.last_mut().unwrap();
+ let items_with_name = data.entry(ident.name).or_default();
+
+ // check other bindings with the same name, most recently seen first
+ for &prev in items_with_name.iter().rev() {
+ if prev == local_id {
+ // repeated binding in an `Or` pattern
+ return;
+ }
+
+ if is_shadow(cx, owner, prev, local_id) {
+ let prev_hir_id = HirId { owner, local_id: prev };
+ lint_shadow(cx, pat, prev_hir_id, ident.span);
+ // only lint against the "nearest" shadowed binding
+ break;
+ }
+ }
+ // store the binding
+ items_with_name.push(local_id);
+ }
+
+ fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
+ let hir = cx.tcx.hir();
+ if !matches!(hir.body_owner_kind(hir.body_owner(body.id())), BodyOwnerKind::Closure) {
+ self.bindings.push(FxHashMap::default());
+ }
+ }
+
+ fn check_body_post(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
+ let hir = cx.tcx.hir();
+ if !matches!(hir.body_owner_kind(hir.body_owner(body.id())), BodyOwnerKind::Closure) {
+ self.bindings.pop();
+ }
+ }
+}
+
+fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second: ItemLocalId) -> bool {
+ let scope_tree = cx.tcx.region_scope_tree(owner.to_def_id());
+ let first_scope = scope_tree.var_scope(first);
+ let second_scope = scope_tree.var_scope(second);
+ scope_tree.is_subscope_of(second_scope, first_scope)
+}
+
+fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span) {
+ let (lint, msg) = match find_init(cx, pat.hir_id) {
+ Some(expr) if is_self_shadow(cx, pat, expr, shadowed) => {
+ let msg = format!(
+ "`{}` is shadowed by itself in `{}`",
+ snippet(cx, pat.span, "_"),
+ snippet(cx, expr.span, "..")
+ );
+ (SHADOW_SAME, msg)
+ },
+ Some(expr) if is_local_used(cx, expr, shadowed) => {
+ let msg = format!("`{}` is shadowed", snippet(cx, pat.span, "_"));
+ (SHADOW_REUSE, msg)
+ },
+ _ => {
+ let msg = format!("`{}` shadows a previous, unrelated binding", snippet(cx, pat.span, "_"));
+ (SHADOW_UNRELATED, msg)
+ },
+ };
+ span_lint_and_note(
+ cx,
+ lint,
+ span,
+ &msg,
+ Some(cx.tcx.hir().span(shadowed)),
+ "previous binding is here",
+ );
+}
+
+/// Returns true if the expression is a simple transformation of a local binding such as `&x`
+fn is_self_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, mut expr: &Expr<'_>, hir_id: HirId) -> bool {
+ let hir = cx.tcx.hir();
+ let is_direct_binding = hir
+ .parent_iter(pat.hir_id)
+ .map_while(|(_id, node)| match node {
+ Node::Pat(pat) => Some(pat),
+ _ => None,
+ })
+ .all(|pat| matches!(pat.kind, PatKind::Ref(..) | PatKind::Or(_)));
+ if !is_direct_binding {
+ return false;
+ }
+ loop {
+ expr = match expr.kind {
+ ExprKind::Box(e)
+ | ExprKind::AddrOf(_, _, e)
+ | ExprKind::Block(
+ &Block {
+ stmts: [],
+ expr: Some(e),
+ ..
+ },
+ _,
+ )
+ | ExprKind::Unary(UnOp::Deref, e) => e,
+ ExprKind::Path(QPath::Resolved(None, path)) => break path.res == Res::Local(hir_id),
+ _ => break false,
+ }
+ }
+}
+
+/// Finds the "init" expression for a pattern: `let <pat> = <init>;` or
+/// `match <init> { .., <pat> => .., .. }`
+fn find_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
+ for (_, node) in cx.tcx.hir().parent_iter(hir_id) {
+ let init = match node {
+ Node::Arm(_) | Node::Pat(_) => continue,
+ Node::Expr(expr) => match expr.kind {
+ ExprKind::Match(e, _, _) => Some(e),
+ _ => None,
+ },
+ Node::Local(local) => local.init,
+ _ => None,
+ };
+ return init;
+ }
+ None
+}
--- /dev/null
- use clippy_utils::in_macro;
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
- if in_macro(item.span) || item.vis.kind.is_pub() {
+use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind, VisibilityKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{edition::Edition, symbol::kw, Span, Symbol};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checking for imports with single component use path.
+ ///
+ /// ### Why is this bad?
+ /// Import with single component use path such as `use cratename;`
+ /// is not necessary, and thus should be removed.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use regex;
+ ///
+ /// fn main() {
+ /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
+ /// }
+ /// ```
+ /// Better as
+ /// ```rust,ignore
+ /// fn main() {
+ /// regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
+ /// }
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub SINGLE_COMPONENT_PATH_IMPORTS,
+ style,
+ "imports with single component path are redundant"
+}
+
+declare_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]);
+
+impl EarlyLintPass for SingleComponentPathImports {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
+ if cx.sess.opts.edition < Edition::Edition2018 {
+ return;
+ }
+ check_mod(cx, &krate.items);
+ }
+}
+
+fn check_mod(cx: &EarlyContext<'_>, items: &[P<Item>]) {
+ // keep track of imports reused with `self` keyword,
+ // such as `self::crypto_hash` in the example below
+ // ```rust,ignore
+ // use self::crypto_hash::{Algorithm, Hasher};
+ // ```
+ let mut imports_reused_with_self = Vec::new();
+
+ // keep track of single use statements
+ // such as `crypto_hash` in the example below
+ // ```rust,ignore
+ // use crypto_hash;
+ // ```
+ let mut single_use_usages = Vec::new();
+
+ // keep track of macros defined in the module as we don't want it to trigger on this (#7106)
+ // ```rust,ignore
+ // macro_rules! foo { () => {} };
+ // pub(crate) use foo;
+ // ```
+ let mut macros = Vec::new();
+
+ for item in items {
+ track_uses(
+ cx,
+ item,
+ &mut imports_reused_with_self,
+ &mut single_use_usages,
+ &mut macros,
+ );
+ }
+
+ for single_use in &single_use_usages {
+ if !imports_reused_with_self.contains(&single_use.0) {
+ let can_suggest = single_use.2;
+ if can_suggest {
+ span_lint_and_sugg(
+ cx,
+ SINGLE_COMPONENT_PATH_IMPORTS,
+ single_use.1,
+ "this import is redundant",
+ "remove it entirely",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ span_lint_and_help(
+ cx,
+ SINGLE_COMPONENT_PATH_IMPORTS,
+ single_use.1,
+ "this import is redundant",
+ None,
+ "remove this import",
+ );
+ }
+ }
+ }
+}
+
+fn track_uses(
+ cx: &EarlyContext<'_>,
+ item: &Item,
+ imports_reused_with_self: &mut Vec<Symbol>,
+ single_use_usages: &mut Vec<(Symbol, Span, bool)>,
+ macros: &mut Vec<Symbol>,
+) {
++ if item.span.from_expansion() || item.vis.kind.is_pub() {
+ return;
+ }
+
+ match &item.kind {
+ ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
+ check_mod(cx, items);
+ },
+ ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {
+ macros.push(item.ident.name);
+ },
+ ItemKind::Use(use_tree) => {
+ let segments = &use_tree.prefix.segments;
+
+ let should_report =
+ |name: &Symbol| !macros.contains(name) || matches!(item.vis.kind, VisibilityKind::Inherited);
+
+ // keep track of `use some_module;` usages
+ if segments.len() == 1 {
+ if let UseTreeKind::Simple(None, _, _) = use_tree.kind {
+ let name = segments[0].ident.name;
+ if should_report(&name) {
+ single_use_usages.push((name, item.span, true));
+ }
+ }
+ return;
+ }
+
+ if segments.is_empty() {
+ // keep track of `use {some_module, some_other_module};` usages
+ if let UseTreeKind::Nested(trees) = &use_tree.kind {
+ for tree in trees {
+ let segments = &tree.0.prefix.segments;
+ if segments.len() == 1 {
+ if let UseTreeKind::Simple(None, _, _) = tree.0.kind {
+ let name = segments[0].ident.name;
+ if should_report(&name) {
+ single_use_usages.push((name, tree.0.span, false));
+ }
+ }
+ }
+ }
+ }
+ } else {
+ // keep track of `use self::some_module` usages
+ if segments[0].ident.name == kw::SelfLower {
+ // simple case such as `use self::module::SomeStruct`
+ if segments.len() > 1 {
+ imports_reused_with_self.push(segments[1].ident.name);
+ return;
+ }
+
+ // nested case such as `use self::{module1::Struct1, module2::Struct2}`
+ if let UseTreeKind::Nested(trees) = &use_tree.kind {
+ for tree in trees {
+ let segments = &tree.0.prefix.segments;
+ if !segments.is_empty() {
+ imports_reused_with_self.push(segments[0].ident.name);
+ }
+ }
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+}
--- /dev/null
+//! Lint on use of `size_of` or `size_of_val` of T in an expression
+//! expecting a count of T
+
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_hir::BinOpKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, Ty, TyS, TypeAndMut};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects expressions where
+ /// `size_of::<T>` or `size_of_val::<T>` is used as a
+ /// count of elements of type `T`
+ ///
+ /// ### Why is this bad?
+ /// These functions expect a count
+ /// of `T` and not a number of bytes
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # use std::ptr::copy_nonoverlapping;
+ /// # use std::mem::size_of;
+ /// const SIZE: usize = 128;
+ /// let x = [2u8; SIZE];
+ /// let mut y = [2u8; SIZE];
+ /// unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>() * SIZE) };
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub SIZE_OF_IN_ELEMENT_COUNT,
+ correctness,
+ "using `size_of::<T>` or `size_of_val::<T>` where a count of elements of `T` is expected"
+}
+
+declare_lint_pass!(SizeOfInElementCount => [SIZE_OF_IN_ELEMENT_COUNT]);
+
+fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: bool) -> Option<Ty<'tcx>> {
+ match expr.kind {
+ ExprKind::Call(count_func, _func_args) => {
+ if_chain! {
+ if !inverted;
+ if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
+ if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::MEM_SIZE_OF)
+ || match_def_path(cx, def_id, &paths::MEM_SIZE_OF_VAL);
+ then {
+ cx.typeck_results().node_substs(count_func.hir_id).types().next()
+ } else {
+ None
+ }
+ }
+ },
+ ExprKind::Binary(op, left, right) if BinOpKind::Mul == op.node => {
+ get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, inverted))
+ },
+ ExprKind::Binary(op, left, right) if BinOpKind::Div == op.node => {
+ get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, !inverted))
+ },
+ ExprKind::Cast(expr, _) => get_size_of_ty(cx, expr, inverted),
+ _ => None,
+ }
+}
+
+fn get_pointee_ty_and_count_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> {
+ const FUNCTIONS: [&[&str]; 8] = [
+ &paths::PTR_COPY_NONOVERLAPPING,
+ &paths::PTR_COPY,
+ &paths::PTR_WRITE_BYTES,
+ &paths::PTR_SWAP_NONOVERLAPPING,
+ &paths::PTR_SLICE_FROM_RAW_PARTS,
+ &paths::PTR_SLICE_FROM_RAW_PARTS_MUT,
+ &paths::SLICE_FROM_RAW_PARTS,
+ &paths::SLICE_FROM_RAW_PARTS_MUT,
+ ];
+ const METHODS: [&str; 11] = [
+ "write_bytes",
+ "copy_to",
+ "copy_from",
+ "copy_to_nonoverlapping",
+ "copy_from_nonoverlapping",
+ "add",
+ "wrapping_add",
+ "sub",
+ "wrapping_sub",
+ "offset",
+ "wrapping_offset",
+ ];
+
+ if_chain! {
+ // Find calls to ptr::{copy, copy_nonoverlapping}
+ // and ptr::{swap_nonoverlapping, write_bytes},
+ if let ExprKind::Call(func, [.., count]) = expr.kind;
+ if let ExprKind::Path(ref func_qpath) = func.kind;
+ if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
+ if FUNCTIONS.iter().any(|func_path| match_def_path(cx, def_id, func_path));
+
+ // Get the pointee type
+ if let Some(pointee_ty) = cx.typeck_results().node_substs(func.hir_id).types().next();
+ then {
+ return Some((pointee_ty, count));
+ }
+ };
+ if_chain! {
+ // Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods
+ if let ExprKind::MethodCall(method_path, _, [ptr_self, .., count], _) = expr.kind;
+ let method_ident = method_path.ident.as_str();
+ if METHODS.iter().any(|m| *m == &*method_ident);
+
+ // Get the pointee type
+ if let ty::RawPtr(TypeAndMut { ty: pointee_ty, .. }) =
+ cx.typeck_results().expr_ty(ptr_self).kind();
+ then {
+ return Some((pointee_ty, count));
+ }
+ };
+ None
+}
+
+impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ const HELP_MSG: &str = "use a count of elements instead of a count of bytes\
+ , it already gets multiplied by the size of the type";
+
+ const LINT_MSG: &str = "found a count of bytes \
+ instead of a count of elements of `T`";
+
+ if_chain! {
+ // Find calls to functions with an element count parameter and get
+ // the pointee type and count parameter expression
+ if let Some((pointee_ty, count_expr)) = get_pointee_ty_and_count_expr(cx, expr);
+
+ // Find a size_of call in the count parameter expression and
+ // check that it's the same type
+ if let Some(ty_used_for_size_of) = get_size_of_ty(cx, count_expr, false);
+ if TyS::same_type(pointee_ty, ty_used_for_size_of);
+ then {
+ span_lint_and_help(
+ cx,
+ SIZE_OF_IN_ELEMENT_COUNT,
+ count_expr.span,
+ LINT_MSG,
+ None,
+ HELP_MSG
+ );
+ }
+ };
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{get_enclosing_block, is_expr_path_def_path, path_to_local, path_to_local_id, paths, SpanlessEq};
+use if_chain::if_chain;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, NestedVisitorMap, Visitor};
+use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks slow zero-filled vector initialization
+ ///
+ /// ### Why is this bad?
+ /// These structures are non-idiomatic and less efficient than simply using
+ /// `vec![0; len]`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use core::iter::repeat;
+ /// # let len = 4;
+ ///
+ /// // Bad
+ /// let mut vec1 = Vec::with_capacity(len);
+ /// vec1.resize(len, 0);
+ ///
+ /// let mut vec2 = Vec::with_capacity(len);
+ /// vec2.extend(repeat(0).take(len));
+ ///
+ /// // Good
+ /// let mut vec1 = vec![0; len];
+ /// let mut vec2 = vec![0; len];
+ /// ```
++ #[clippy::version = "1.32.0"]
+ pub SLOW_VECTOR_INITIALIZATION,
+ perf,
+ "slow vector initialization"
+}
+
+declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]);
+
+/// `VecAllocation` contains data regarding a vector allocated with `with_capacity` and then
+/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or
+/// `vec = Vec::with_capacity(0)`
+struct VecAllocation<'tcx> {
+ /// HirId of the variable
+ local_id: HirId,
+
+ /// Reference to the expression which allocates the vector
+ allocation_expr: &'tcx Expr<'tcx>,
+
+ /// Reference to the expression used as argument on `with_capacity` call. This is used
+ /// to only match slow zero-filling idioms of the same length than vector initialization.
+ len_expr: &'tcx Expr<'tcx>,
+}
+
+/// Type of slow initialization
+enum InitializationType<'tcx> {
+ /// Extend is a slow initialization with the form `vec.extend(repeat(0).take(..))`
+ Extend(&'tcx Expr<'tcx>),
+
+ /// Resize is a slow initialization with the form `vec.resize(.., 0)`
+ Resize(&'tcx Expr<'tcx>),
+}
+
+impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Matches initialization on reassignements. For example: `vec = Vec::with_capacity(100)`
+ if_chain! {
+ if let ExprKind::Assign(left, right, _) = expr.kind;
+
+ // Extract variable
+ if let Some(local_id) = path_to_local(left);
+
+ // Extract len argument
+ if let Some(len_arg) = Self::is_vec_with_capacity(cx, right);
+
+ then {
+ let vi = VecAllocation {
+ local_id,
+ allocation_expr: right,
+ len_expr: len_arg,
+ };
+
+ Self::search_initialization(cx, vi, expr.hir_id);
+ }
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
+ if_chain! {
+ if let StmtKind::Local(local) = stmt.kind;
+ if let PatKind::Binding(BindingAnnotation::Mutable, local_id, _, None) = local.pat.kind;
+ if let Some(init) = local.init;
+ if let Some(len_arg) = Self::is_vec_with_capacity(cx, init);
+
+ then {
+ let vi = VecAllocation {
+ local_id,
+ allocation_expr: init,
+ len_expr: len_arg,
+ };
+
+ Self::search_initialization(cx, vi, stmt.hir_id);
+ }
+ }
+ }
+}
+
+impl SlowVectorInit {
+ /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
+ /// of the first argument of `with_capacity` call if it matches or `None` if it does not.
+ fn is_vec_with_capacity<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ if_chain! {
+ if let ExprKind::Call(func, [arg]) = expr.kind;
+ if let ExprKind::Path(QPath::TypeRelative(ty, name)) = func.kind;
+ if name.ident.as_str() == "with_capacity";
+ if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec);
+ then {
+ Some(arg)
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Search initialization for the given vector
+ fn search_initialization<'tcx>(cx: &LateContext<'tcx>, vec_alloc: VecAllocation<'tcx>, parent_node: HirId) {
+ let enclosing_body = get_enclosing_block(cx, parent_node);
+
+ if enclosing_body.is_none() {
+ return;
+ }
+
+ let mut v = VectorInitializationVisitor {
+ cx,
+ vec_alloc,
+ slow_expression: None,
+ initialization_found: false,
+ };
+
+ v.visit_block(enclosing_body.unwrap());
+
+ if let Some(ref allocation_expr) = v.slow_expression {
+ Self::lint_initialization(cx, allocation_expr, &v.vec_alloc);
+ }
+ }
+
+ fn lint_initialization<'tcx>(
+ cx: &LateContext<'tcx>,
+ initialization: &InitializationType<'tcx>,
+ vec_alloc: &VecAllocation<'_>,
+ ) {
+ match initialization {
+ InitializationType::Extend(e) | InitializationType::Resize(e) => {
+ Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization");
+ },
+ };
+ }
+
+ fn emit_lint<'tcx>(cx: &LateContext<'tcx>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
+ let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len");
+
+ span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
+ diag.span_suggestion(
+ vec_alloc.allocation_expr.span,
+ "consider replace allocation with",
+ format!("vec![0; {}]", len_expr),
+ Applicability::Unspecified,
+ );
+ });
+ }
+}
+
+/// `VectorInitializationVisitor` searches for unsafe or slow vector initializations for the given
+/// vector.
+struct VectorInitializationVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+
+ /// Contains the information.
+ vec_alloc: VecAllocation<'tcx>,
+
+ /// Contains the slow initialization expression, if one was found.
+ slow_expression: Option<InitializationType<'tcx>>,
+
+ /// `true` if the initialization of the vector has been found on the visited block.
+ initialization_found: bool,
+}
+
+impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
+ /// Checks if the given expression is extending a vector with `repeat(0).take(..)`
+ fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if self.initialization_found;
+ if let ExprKind::MethodCall(path, _, [self_arg, extend_arg], _) = expr.kind;
+ if path_to_local_id(self_arg, self.vec_alloc.local_id);
+ if path.ident.name == sym!(extend);
+ if self.is_repeat_take(extend_arg);
+
+ then {
+ self.slow_expression = Some(InitializationType::Extend(expr));
+ }
+ }
+ }
+
+ /// Checks if the given expression is resizing a vector with 0
+ fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if self.initialization_found;
+ if let ExprKind::MethodCall(path, _, [self_arg, len_arg, fill_arg], _) = expr.kind;
+ if path_to_local_id(self_arg, self.vec_alloc.local_id);
+ if path.ident.name == sym!(resize);
+
+ // Check that is filled with 0
+ if let ExprKind::Lit(ref lit) = fill_arg.kind;
+ if let LitKind::Int(0, _) = lit.node;
+
+ // Check that len expression is equals to `with_capacity` expression
+ if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
+
+ then {
+ self.slow_expression = Some(InitializationType::Resize(expr));
+ }
+ }
+ }
+
+ /// Returns `true` if give expression is `repeat(0).take(...)`
+ fn is_repeat_take(&self, expr: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(take_path, _, take_args, _) = expr.kind;
+ if take_path.ident.name == sym!(take);
+
+ // Check that take is applied to `repeat(0)`
+ if let Some(repeat_expr) = take_args.get(0);
+ if self.is_repeat_zero(repeat_expr);
+
+ // Check that len expression is equals to `with_capacity` expression
+ if let Some(len_arg) = take_args.get(1);
+ if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr);
+
+ then {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ /// Returns `true` if given expression is `repeat(0)`
+ fn is_repeat_zero(&self, expr: &Expr<'_>) -> bool {
+ if_chain! {
+ if let ExprKind::Call(fn_expr, [repeat_arg]) = expr.kind;
+ if is_expr_path_def_path(self.cx, fn_expr, &paths::ITER_REPEAT);
+ if let ExprKind::Lit(ref lit) = repeat_arg.kind;
+ if let LitKind::Int(0, _) = lit.node;
+
+ then {
+ true
+ } else {
+ false
+ }
+ }
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
+ if self.initialization_found {
+ match stmt.kind {
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
+ self.search_slow_extend_filling(expr);
+ self.search_slow_resize_filling(expr);
+ },
+ _ => (),
+ }
+
+ self.initialization_found = false;
+ } else {
+ walk_stmt(self, stmt);
+ }
+ }
+
+ fn visit_block(&mut self, block: &'tcx Block<'_>) {
+ if self.initialization_found {
+ if let Some(s) = block.stmts.get(0) {
+ self.visit_stmt(s);
+ }
+
+ self.initialization_found = false;
+ } else {
+ walk_block(self, block);
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // Skip all the expressions previous to the vector initialization
+ if self.vec_alloc.allocation_expr.hir_id == expr.hir_id {
+ self.initialization_found = true;
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{is_slice_of_primitives, sugg::Sugg};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// When sorting primitive values (integers, bools, chars, as well
+ /// as arrays, slices, and tuples of such items), it is better to
+ /// use an unstable sort than a stable sort.
+ ///
+ /// ### Why is this bad?
+ /// Using a stable sort consumes more memory and cpu cycles. Because
+ /// values which compare equal are identical, preserving their
+ /// relative order (the guarantee that a stable sort provides) means
+ /// nothing, while the extra costs still apply.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut vec = vec![2, 1, 3];
+ /// vec.sort();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let mut vec = vec![2, 1, 3];
+ /// vec.sort_unstable();
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub STABLE_SORT_PRIMITIVE,
+ perf,
+ "use of sort() when sort_unstable() is equivalent"
+}
+
+declare_lint_pass!(StableSortPrimitive => [STABLE_SORT_PRIMITIVE]);
+
+/// The three "kinds" of sorts
+enum SortingKind {
+ Vanilla,
+ /* The other kinds of lint are currently commented out because they
+ * can map distinct values to equal ones. If the key function is
+ * provably one-to-one, or if the Cmp function conserves equality,
+ * then they could be linted on, but I don't know if we can check
+ * for that. */
+
+ /* ByKey,
+ * ByCmp, */
+}
+impl SortingKind {
+ /// The name of the stable version of this kind of sort
+ fn stable_name(&self) -> &str {
+ match self {
+ SortingKind::Vanilla => "sort",
+ /* SortingKind::ByKey => "sort_by_key",
+ * SortingKind::ByCmp => "sort_by", */
+ }
+ }
+ /// The name of the unstable version of this kind of sort
+ fn unstable_name(&self) -> &str {
+ match self {
+ SortingKind::Vanilla => "sort_unstable",
+ /* SortingKind::ByKey => "sort_unstable_by_key",
+ * SortingKind::ByCmp => "sort_unstable_by", */
+ }
+ }
+ /// Takes the name of a function call and returns the kind of sort
+ /// that corresponds to that function name (or None if it isn't)
+ fn from_stable_name(name: &str) -> Option<SortingKind> {
+ match name {
+ "sort" => Some(SortingKind::Vanilla),
+ // "sort_by" => Some(SortingKind::ByCmp),
+ // "sort_by_key" => Some(SortingKind::ByKey),
+ _ => None,
+ }
+ }
+}
+
+/// A detected instance of this lint
+struct LintDetection {
+ slice_name: String,
+ method: SortingKind,
+ method_args: String,
+ slice_type: String,
+}
+
+fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintDetection> {
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
+ if let Some(slice) = &args.get(0);
+ if let Some(method) = SortingKind::from_stable_name(&method_name.ident.name.as_str());
+ if let Some(slice_type) = is_slice_of_primitives(cx, slice);
+ then {
+ let args_str = args.iter().skip(1).map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::<Vec<String>>().join(", ");
+ Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str, slice_type })
+ } else {
+ None
+ }
+ }
+}
+
+impl LateLintPass<'_> for StableSortPrimitive {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let Some(detection) = detect_stable_sort_primitive(cx, expr) {
+ span_lint_and_then(
+ cx,
+ STABLE_SORT_PRIMITIVE,
+ expr.span,
+ format!(
+ "used `{}` on primitive type `{}`",
+ detection.method.stable_name(),
+ detection.slice_type,
+ )
+ .as_str(),
+ |diag| {
+ diag.span_suggestion(
+ expr.span,
+ "try",
+ format!(
+ "{}.{}({})",
+ detection.slice_name,
+ detection.method.unstable_name(),
+ detection.method_args,
+ ),
+ Applicability::MachineApplicable,
+ );
+ diag.note(
+ "an unstable sort would perform faster without any observable difference for this data type",
+ );
+ },
+ );
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::SpanlessEq;
+use clippy_utils::{get_parent_expr, is_lint_allowed, match_function_call, method_calls, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for string appends of the form `x = x + y` (without
+ /// `let`!).
+ ///
+ /// ### Why is this bad?
+ /// It's not really bad, but some people think that the
+ /// `.push_str(_)` method is more readable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut x = "Hello".to_owned();
+ /// x = x + ", World";
+ ///
+ /// // More readable
+ /// x += ", World";
+ /// x.push_str(", World");
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub STRING_ADD_ASSIGN,
+ pedantic,
+ "using `x = x + ..` where x is a `String` instead of `push_str()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for all instances of `x + _` where `x` is of type
+ /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
+ /// match.
+ ///
+ /// ### Why is this bad?
+ /// It's not bad in and of itself. However, this particular
+ /// `Add` implementation is asymmetric (the other operand need not be `String`,
+ /// but `x` does), while addition as mathematically defined is symmetric, also
+ /// the `String::push_str(_)` function is a perfectly good replacement.
+ /// Therefore, some dislike it and wish not to have it in their code.
+ ///
+ /// That said, other people think that string addition, having a long tradition
+ /// in other languages is actually fine, which is why we decided to make this
+ /// particular lint `allow` by default.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = "Hello".to_owned();
+ /// x + ", World";
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub STRING_ADD,
+ restriction,
+ "using `x + ..` where x is a `String` instead of `push_str()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for the `as_bytes` method called on string literals
+ /// that contain only ASCII characters.
+ ///
+ /// ### Why is this bad?
+ /// Byte string literals (e.g., `b"foo"`) can be used
+ /// instead. They are shorter but less discoverable than `as_bytes()`.
+ ///
+ /// ### Known problems
+ /// `"str".as_bytes()` and the suggested replacement of `b"str"` are not
+ /// equivalent because they have different types. The former is `&[u8]`
+ /// while the latter is `&[u8; 3]`. That means in general they will have a
+ /// different set of methods and different trait implementations.
+ ///
+ /// ```compile_fail
+ /// fn f(v: Vec<u8>) {}
+ ///
+ /// f("...".as_bytes().to_owned()); // works
+ /// f(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec<u8>
+ ///
+ /// fn g(r: impl std::io::Read) {}
+ ///
+ /// g("...".as_bytes()); // works
+ /// g(b"..."); // does not work
+ /// ```
+ ///
+ /// The actual equivalent of `"str".as_bytes()` with the same type is not
+ /// `b"str"` but `&b"str"[..]`, which is a great deal of punctuation and not
+ /// more readable than a function call.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let bs = "a byte string".as_bytes();
+ ///
+ /// // Good
+ /// let bs = b"a byte string";
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub STRING_LIT_AS_BYTES,
+ nursery,
+ "calling `as_bytes` on a string literal instead of using a byte string literal"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for slice operations on strings
+ ///
+ /// ### Why is this bad?
+ /// UTF-8 characters span multiple bytes, and it is easy to inadvertently confuse character
+ /// counts and string indices. This may lead to panics, and should warrant some test cases
+ /// containing wide UTF-8 characters. This lint is most useful in code that should avoid
+ /// panics at all costs.
+ ///
+ /// ### Known problems
+ /// Probably lots of false positives. If an index comes from a known valid position (e.g.
+ /// obtained via `char_indices` over the same string), it is totally OK.
+ ///
+ /// # Example
+ /// ```rust,should_panic
+ /// &"Ölkanne"[1..];
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub STRING_SLICE,
+ restriction,
+ "slicing a string"
+}
+
+declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN, STRING_SLICE]);
+
+impl<'tcx> LateLintPass<'tcx> for StringAdd {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if in_external_macro(cx.sess(), e.span) {
+ return;
+ }
+ match e.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ left,
+ _,
+ ) => {
+ if is_string(cx, left) {
+ if !is_lint_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
+ let parent = get_parent_expr(cx, e);
+ if let Some(p) = parent {
+ if let ExprKind::Assign(target, _, _) = p.kind {
+ // avoid duplicate matches
+ if SpanlessEq::new(cx).eq_expr(target, left) {
+ return;
+ }
+ }
+ }
+ }
+ span_lint(
+ cx,
+ STRING_ADD,
+ e.span,
+ "you added something to a string. Consider using `String::push_str()` instead",
+ );
+ }
+ },
+ ExprKind::Assign(target, src, _) => {
+ if is_string(cx, target) && is_add(cx, src, target) {
+ span_lint(
+ cx,
+ STRING_ADD_ASSIGN,
+ e.span,
+ "you assigned the result of adding something to this string. Consider using \
+ `String::push_str()` instead",
+ );
+ }
+ },
+ ExprKind::Index(target, _idx) => {
+ let e_ty = cx.typeck_results().expr_ty(target).peel_refs();
+ if matches!(e_ty.kind(), ty::Str) || is_type_diagnostic_item(cx, e_ty, sym::String) {
+ span_lint(
+ cx,
+ STRING_SLICE,
+ e.span,
+ "indexing into a string may panic if the index is within a UTF-8 character",
+ );
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::String)
+}
+
+fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
+ match src.kind {
+ ExprKind::Binary(
+ Spanned {
+ node: BinOpKind::Add, ..
+ },
+ left,
+ _,
+ ) => SpanlessEq::new(cx).eq_expr(target, left),
+ ExprKind::Block(block, _) => {
+ block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target))
+ },
+ _ => false,
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Check if the string is transformed to byte array and casted back to string.
+ ///
+ /// ### Why is this bad?
+ /// It's unnecessary, the string can be used directly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap();
+ /// ```
+ /// could be written as
+ /// ```rust
+ /// let _ = &"Hello World!"[6..11];
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub STRING_FROM_UTF8_AS_BYTES,
+ complexity,
+ "casting string slices to byte slices and back"
+}
+
+// Max length a b"foo" string can take
+const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;
+
+declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES, STRING_FROM_UTF8_AS_BYTES]);
+
+impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ use rustc_ast::LitKind;
+
+ if_chain! {
+ // Find std::str::converts::from_utf8
+ if let Some(args) = match_function_call(cx, e, &paths::STR_FROM_UTF8);
+
+ // Find string::as_bytes
+ if let ExprKind::AddrOf(BorrowKind::Ref, _, args) = args[0].kind;
+ if let ExprKind::Index(left, right) = args.kind;
+ let (method_names, expressions, _) = method_calls(left, 1);
+ if method_names.len() == 1;
+ if expressions.len() == 1;
+ if expressions[0].len() == 1;
+ if method_names[0] == sym!(as_bytes);
+
+ // Check for slicer
+ if let ExprKind::Struct(QPath::LangItem(LangItem::Range, _), _, _) = right.kind;
+
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let string_expression = &expressions[0][0];
+
+ let snippet_app = snippet_with_applicability(
+ cx,
+ string_expression.span, "..",
+ &mut applicability,
+ );
+
+ span_lint_and_sugg(
+ cx,
+ STRING_FROM_UTF8_AS_BYTES,
+ e.span,
+ "calling a slice of `as_bytes()` with `from_utf8` should be not necessary",
+ "try",
+ format!("Some(&{}[{}])", snippet_app, snippet(cx, right.span, "..")),
+ applicability
+ )
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, args, _) = &e.kind;
+ if path.ident.name == sym!(as_bytes);
+ if let ExprKind::Lit(lit) = &args[0].kind;
+ if let LitKind::Str(lit_content, _) = &lit.node;
+ then {
+ let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#);
+ let mut applicability = Applicability::MachineApplicable;
+ if callsite.starts_with("include_str!") {
+ span_lint_and_sugg(
+ cx,
+ STRING_LIT_AS_BYTES,
+ e.span,
+ "calling `as_bytes()` on `include_str!(..)`",
+ "consider using `include_bytes!(..)` instead",
+ snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen(
+ "include_str",
+ "include_bytes",
+ 1,
+ ),
+ applicability,
+ );
+ } else if lit_content.as_str().is_ascii()
+ && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
+ && !args[0].span.from_expansion()
+ {
+ span_lint_and_sugg(
+ cx,
+ STRING_LIT_AS_BYTES,
+ e.span,
+ "calling `as_bytes()` on a string literal",
+ "consider using a byte string literal instead",
+ format!(
+ "b{}",
+ snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, [recv], _) = &e.kind;
+ if path.ident.name == sym!(into_bytes);
+ if let ExprKind::MethodCall(path, _, [recv], _) = &recv.kind;
+ if matches!(&*path.ident.name.as_str(), "to_owned" | "to_string");
+ if let ExprKind::Lit(lit) = &recv.kind;
+ if let LitKind::Str(lit_content, _) = &lit.node;
+
+ if lit_content.as_str().is_ascii();
+ if lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT;
+ if !recv.span.from_expansion();
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+
+ span_lint_and_sugg(
+ cx,
+ STRING_LIT_AS_BYTES,
+ e.span,
+ "calling `into_bytes()` on a string literal",
+ "consider using a byte string literal instead",
+ format!(
+ "b{}.to_vec()",
+ snippet_with_applicability(cx, recv.span, r#""..""#, &mut applicability)
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for `.to_string()` method calls on values of type `&str`.
+ ///
+ /// ### Why is this bad?
+ /// The `to_string` method is also used on other types to convert them to a string.
+ /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better
+ /// expressed with `.to_owned()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let _ = "str".to_string();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code which does not raise clippy warning
+ /// let _ = "str".to_owned();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub STR_TO_STRING,
+ restriction,
+ "using `to_string()` on a `&str`, which should be `to_owned()`"
+}
+
+declare_lint_pass!(StrToString => [STR_TO_STRING]);
+
+impl LateLintPass<'_> for StrToString {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
+ if path.ident.name == sym!(to_string);
+ let ty = cx.typeck_results().expr_ty(self_arg);
+ if let ty::Ref(_, ty, ..) = ty.kind();
+ if *ty.kind() == ty::Str;
+ then {
+ span_lint_and_help(
+ cx,
+ STR_TO_STRING,
+ expr.span,
+ "`to_string()` called on a `&str`",
+ None,
+ "consider using `.to_owned()`",
+ );
+ }
+ }
+ }
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint checks for `.to_string()` method calls on values of type `String`.
+ ///
+ /// ### Why is this bad?
+ /// The `to_string` method is also used on other types to convert them to a string.
+ /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code where clippy issues a warning
+ /// let msg = String::from("Hello World");
+ /// let _ = msg.to_string();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// // example code which does not raise clippy warning
+ /// let msg = String::from("Hello World");
+ /// let _ = msg.clone();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub STRING_TO_STRING,
+ restriction,
+ "using `to_string()` on a `String`, which should be `clone()`"
+}
+
+declare_lint_pass!(StringToString => [STRING_TO_STRING]);
+
+impl LateLintPass<'_> for StringToString {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
+ if path.ident.name == sym!(to_string);
+ let ty = cx.typeck_results().expr_ty(self_arg);
+ if is_type_diagnostic_item(cx, ty, sym::String);
+ then {
+ span_lint_and_help(
+ cx,
+ STRING_TO_STRING,
+ expr.span,
+ "`to_string()` called on a `String`",
+ None,
+ "consider using `.clone()`",
+ );
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::in_macro;
- use clippy_utils::paths;
- use clippy_utils::source::snippet_with_macro_callsite;
- use clippy_utils::ty::{is_type_diagnostic_item, is_type_ref_to_diagnostic_item};
+use clippy_utils::diagnostics::span_lint_and_sugg;
- use rustc_hir as hir;
++use clippy_utils::source::snippet_with_context;
++use clippy_utils::ty::is_type_diagnostic_item;
++use clippy_utils::visitors::is_expr_unsafe;
++use clippy_utils::{get_parent_node, match_libc_symbol};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
- use rustc_span::symbol::{sym, Symbol};
++use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, UnsafeSource};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
- fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
- if in_macro(expr.span) {
- return;
- }
-
++use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `libc::strlen` on a `CString` or `CStr` value,
+ /// and suggest calling `as_bytes().len()` or `to_bytes().len()` respectively instead.
+ ///
+ /// ### Why is this bad?
+ /// This avoids calling an unsafe `libc` function.
+ /// Currently, it also avoids calculating the length.
+ ///
+ /// ### Example
+ /// ```rust, ignore
+ /// use std::ffi::CString;
+ /// let cstring = CString::new("foo").expect("CString::new failed");
+ /// let len = unsafe { libc::strlen(cstring.as_ptr()) };
+ /// ```
+ /// Use instead:
+ /// ```rust, no_run
+ /// use std::ffi::CString;
+ /// let cstring = CString::new("foo").expect("CString::new failed");
+ /// let len = cstring.as_bytes().len();
+ /// ```
++ #[clippy::version = "1.55.0"]
+ pub STRLEN_ON_C_STRINGS,
+ complexity,
+ "using `libc::strlen` on a `CString` or `CStr` value, while `as_bytes().len()` or `to_bytes().len()` respectively can be used instead"
+}
+
+declare_lint_pass!(StrlenOnCStrings => [STRLEN_ON_C_STRINGS]);
+
+impl LateLintPass<'tcx> for StrlenOnCStrings {
- if let hir::ExprKind::Call(func, [recv]) = expr.kind;
- if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = func.kind;
-
- if (&paths::LIBC_STRLEN).iter().map(|x| Symbol::intern(x)).eq(
- path.segments.iter().map(|seg| seg.ident.name));
- if let hir::ExprKind::MethodCall(path, _, args, _) = recv.kind;
- if args.len() == 1;
- if !args.iter().any(|e| e.span.from_expansion());
++ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
- let cstring = &args[0];
- let ty = cx.typeck_results().expr_ty(cstring);
- let val_name = snippet_with_macro_callsite(cx, cstring.span, "..");
- let sugg = if is_type_diagnostic_item(cx, ty, sym::cstring_type){
- format!("{}.as_bytes().len()", val_name)
- } else if is_type_ref_to_diagnostic_item(cx, ty, sym::CStr){
- format!("{}.to_bytes().len()", val_name)
++ if !expr.span.from_expansion();
++ if let ExprKind::Call(func, [recv]) = expr.kind;
++ if let ExprKind::Path(path) = &func.kind;
++ if let Some(did) = cx.qpath_res(path, func.hir_id).opt_def_id();
++ if match_libc_symbol(cx, did, "strlen");
++ if let ExprKind::MethodCall(path, _, [self_arg], _) = recv.kind;
++ if !recv.span.from_expansion();
+ if path.ident.name == sym::as_ptr;
+ then {
- expr.span,
++ let ctxt = expr.span.ctxt();
++ let span = match get_parent_node(cx.tcx, expr.hir_id) {
++ Some(Node::Block(&Block {
++ rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), span, ..
++ }))
++ if span.ctxt() == ctxt && !is_expr_unsafe(cx, self_arg) => {
++ span
++ }
++ _ => expr.span,
++ };
++
++ let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
++ let mut app = Applicability::MachineApplicable;
++ let val_name = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
++ let method_name = if is_type_diagnostic_item(cx, ty, sym::cstring_type) {
++ "as_bytes"
++ } else if is_type_diagnostic_item(cx, ty, sym::CStr) {
++ "to_bytes"
+ } else {
+ return;
+ };
+
+ span_lint_and_sugg(
+ cx,
+ STRLEN_ON_C_STRINGS,
- "try this (you might also need to get rid of `unsafe` block in some cases):",
- sugg,
- Applicability::Unspecified // Sometimes unnecessary `unsafe` block
++ span,
+ "using `libc::strlen` on a `CString` or `CStr` value",
++ "try this",
++ format!("{}.{}().len()", val_name, method_name),
++ app,
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::ast_utils::{eq_id, is_useless_with_eq_exprs, IdentIter};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use core::ops::{Add, AddAssign};
+use if_chain::if_chain;
+use rustc_ast::ast::{BinOpKind, Expr, ExprKind, StmtKind};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unlikely usages of binary operators that are almost
+ /// certainly typos and/or copy/paste errors, given the other usages
+ /// of binary operators nearby.
+ ///
+ /// ### Why is this bad?
+ /// They are probably bugs and if they aren't then they look like bugs
+ /// and you should add a comment explaining why you are doing such an
+ /// odd set of operations.
+ ///
+ /// ### Known problems
+ /// There may be some false positives if you are trying to do something
+ /// unusual that happens to look like a typo.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Vec3 {
+ /// x: f64,
+ /// y: f64,
+ /// z: f64,
+ /// }
+ ///
+ /// impl Eq for Vec3 {}
+ ///
+ /// impl PartialEq for Vec3 {
+ /// fn eq(&self, other: &Self) -> bool {
+ /// // This should trigger the lint because `self.x` is compared to `other.y`
+ /// self.x == other.y && self.y == other.y && self.z == other.z
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # struct Vec3 {
+ /// # x: f64,
+ /// # y: f64,
+ /// # z: f64,
+ /// # }
+ /// // same as above except:
+ /// impl PartialEq for Vec3 {
+ /// fn eq(&self, other: &Self) -> bool {
+ /// // Note we now compare other.x to self.x
+ /// self.x == other.x && self.y == other.y && self.z == other.z
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub SUSPICIOUS_OPERATION_GROUPINGS,
+ nursery,
+ "groupings of binary operations that look suspiciously like typos"
+}
+
+declare_lint_pass!(SuspiciousOperationGroupings => [SUSPICIOUS_OPERATION_GROUPINGS]);
+
+impl EarlyLintPass for SuspiciousOperationGroupings {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if expr.span.from_expansion() {
+ return;
+ }
+
+ if let Some(binops) = extract_related_binops(&expr.kind) {
+ check_binops(cx, &binops.iter().collect::<Vec<_>>());
+
+ let mut op_types = Vec::with_capacity(binops.len());
+ // We could use a hashmap, etc. to avoid being O(n*m) here, but
+ // we want the lints to be emitted in a consistent order. Besides,
+ // m, (the number of distinct `BinOpKind`s in `binops`)
+ // will often be small, and does have an upper limit.
+ binops.iter().map(|b| b.op).for_each(|op| {
+ if !op_types.contains(&op) {
+ op_types.push(op);
+ }
+ });
+
+ for op_type in op_types {
+ let ops: Vec<_> = binops.iter().filter(|b| b.op == op_type).collect();
+
+ check_binops(cx, &ops);
+ }
+ }
+ }
+}
+
+fn check_binops(cx: &EarlyContext<'_>, binops: &[&BinaryOp<'_>]) {
+ let binop_count = binops.len();
+ if binop_count < 2 {
+ // Single binary operation expressions would likely be false
+ // positives.
+ return;
+ }
+
+ let mut one_ident_difference_count = 0;
+ let mut no_difference_info = None;
+ let mut double_difference_info = None;
+ let mut expected_ident_loc = None;
+
+ let mut paired_identifiers = FxHashSet::default();
+
+ for (i, BinaryOp { left, right, op, .. }) in binops.iter().enumerate() {
+ match ident_difference_expr(left, right) {
+ IdentDifference::NoDifference => {
+ if is_useless_with_eq_exprs(*op) {
+ // The `eq_op` lint should catch this in this case.
+ return;
+ }
+
+ no_difference_info = Some(i);
+ },
+ IdentDifference::Single(ident_loc) => {
+ one_ident_difference_count += 1;
+ if let Some(previous_expected) = expected_ident_loc {
+ if previous_expected != ident_loc {
+ // This expression doesn't match the form we're
+ // looking for.
+ return;
+ }
+ } else {
+ expected_ident_loc = Some(ident_loc);
+ }
+
+ // If there was only a single difference, all other idents
+ // must have been the same, and thus were paired.
+ for id in skip_index(IdentIter::from(*left), ident_loc.index) {
+ paired_identifiers.insert(id);
+ }
+ },
+ IdentDifference::Double(ident_loc1, ident_loc2) => {
+ double_difference_info = Some((i, ident_loc1, ident_loc2));
+ },
+ IdentDifference::Multiple | IdentDifference::NonIdent => {
+ // It's too hard to know whether this is a bug or not.
+ return;
+ },
+ }
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+
+ if let Some(expected_loc) = expected_ident_loc {
+ match (no_difference_info, double_difference_info) {
+ (Some(i), None) => attempt_to_emit_no_difference_lint(cx, binops, i, expected_loc),
+ (None, Some((double_difference_index, ident_loc1, ident_loc2))) => {
+ if_chain! {
+ if one_ident_difference_count == binop_count - 1;
+ if let Some(binop) = binops.get(double_difference_index);
+ then {
+ let changed_loc = if ident_loc1 == expected_loc {
+ ident_loc2
+ } else if ident_loc2 == expected_loc {
+ ident_loc1
+ } else {
+ // This expression doesn't match the form we're
+ // looking for.
+ return;
+ };
+
+ if let Some(sugg) = ident_swap_sugg(
+ cx,
+ &paired_identifiers,
+ binop,
+ changed_loc,
+ &mut applicability,
+ ) {
+ emit_suggestion(
+ cx,
+ binop.span,
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+ }
+}
+
+fn attempt_to_emit_no_difference_lint(
+ cx: &EarlyContext<'_>,
+ binops: &[&BinaryOp<'_>],
+ i: usize,
+ expected_loc: IdentLocation,
+) {
+ if let Some(binop) = binops.get(i).copied() {
+ // We need to try and figure out which identifier we should
+ // suggest using instead. Since there could be multiple
+ // replacement candidates in a given expression, and we're
+ // just taking the first one, we may get some bad lint
+ // messages.
+ let mut applicability = Applicability::MaybeIncorrect;
+
+ // We assume that the correct ident is one used elsewhere in
+ // the other binops, in a place that there was a single
+ // difference between idents before.
+ let old_left_ident = get_ident(binop.left, expected_loc);
+ let old_right_ident = get_ident(binop.right, expected_loc);
+
+ for b in skip_index(binops.iter(), i) {
+ if_chain! {
+ if let (Some(old_ident), Some(new_ident)) =
+ (old_left_ident, get_ident(b.left, expected_loc));
+ if old_ident != new_ident;
+ if let Some(sugg) = suggestion_with_swapped_ident(
+ cx,
+ binop.left,
+ expected_loc,
+ new_ident,
+ &mut applicability,
+ );
+ then {
+ emit_suggestion(
+ cx,
+ binop.span,
+ replace_left_sugg(cx, binop, &sugg, &mut applicability),
+ applicability,
+ );
+ return;
+ }
+ }
+
+ if_chain! {
+ if let (Some(old_ident), Some(new_ident)) =
+ (old_right_ident, get_ident(b.right, expected_loc));
+ if old_ident != new_ident;
+ if let Some(sugg) = suggestion_with_swapped_ident(
+ cx,
+ binop.right,
+ expected_loc,
+ new_ident,
+ &mut applicability,
+ );
+ then {
+ emit_suggestion(
+ cx,
+ binop.span,
+ replace_right_sugg(cx, binop, &sugg, &mut applicability),
+ applicability,
+ );
+ return;
+ }
+ }
+ }
+ }
+}
+
+fn emit_suggestion(cx: &EarlyContext<'_>, span: Span, sugg: String, applicability: Applicability) {
+ span_lint_and_sugg(
+ cx,
+ SUSPICIOUS_OPERATION_GROUPINGS,
+ span,
+ "this sequence of operators looks suspiciously like a bug",
+ "did you mean",
+ sugg,
+ applicability,
+ );
+}
+
+fn ident_swap_sugg(
+ cx: &EarlyContext<'_>,
+ paired_identifiers: &FxHashSet<Ident>,
+ binop: &BinaryOp<'_>,
+ location: IdentLocation,
+ applicability: &mut Applicability,
+) -> Option<String> {
+ let left_ident = get_ident(binop.left, location)?;
+ let right_ident = get_ident(binop.right, location)?;
+
+ let sugg = match (
+ paired_identifiers.contains(&left_ident),
+ paired_identifiers.contains(&right_ident),
+ ) {
+ (true, true) | (false, false) => {
+ // We don't have a good guess of what ident should be
+ // used instead, in these cases.
+ *applicability = Applicability::MaybeIncorrect;
+
+ // We arbitraily choose one side to suggest changing,
+ // since we don't have a better guess. If the user
+ // ends up duplicating a clause, the `logic_bug` lint
+ // should catch it.
+
+ let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?;
+
+ replace_right_sugg(cx, binop, &right_suggestion, applicability)
+ },
+ (false, true) => {
+ // We haven't seen a pair involving the left one, so
+ // it's probably what is wanted.
+
+ let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?;
+
+ replace_right_sugg(cx, binop, &right_suggestion, applicability)
+ },
+ (true, false) => {
+ // We haven't seen a pair involving the right one, so
+ // it's probably what is wanted.
+ let left_suggestion = suggestion_with_swapped_ident(cx, binop.left, location, right_ident, applicability)?;
+
+ replace_left_sugg(cx, binop, &left_suggestion, applicability)
+ },
+ };
+
+ Some(sugg)
+}
+
+fn replace_left_sugg(
+ cx: &EarlyContext<'_>,
+ binop: &BinaryOp<'_>,
+ left_suggestion: &str,
+ applicability: &mut Applicability,
+) -> String {
+ format!(
+ "{} {} {}",
+ left_suggestion,
+ binop.op.to_string(),
+ snippet_with_applicability(cx, binop.right.span, "..", applicability),
+ )
+}
+
+fn replace_right_sugg(
+ cx: &EarlyContext<'_>,
+ binop: &BinaryOp<'_>,
+ right_suggestion: &str,
+ applicability: &mut Applicability,
+) -> String {
+ format!(
+ "{} {} {}",
+ snippet_with_applicability(cx, binop.left.span, "..", applicability),
+ binop.op.to_string(),
+ right_suggestion,
+ )
+}
+
+#[derive(Clone, Debug)]
+struct BinaryOp<'exprs> {
+ op: BinOpKind,
+ span: Span,
+ left: &'exprs Expr,
+ right: &'exprs Expr,
+}
+
+impl BinaryOp<'exprs> {
+ fn new(op: BinOpKind, span: Span, (left, right): (&'exprs Expr, &'exprs Expr)) -> Self {
+ Self { op, span, left, right }
+ }
+}
+
+fn strip_non_ident_wrappers(expr: &Expr) -> &Expr {
+ let mut output = expr;
+ loop {
+ output = match &output.kind {
+ ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner,
+ _ => {
+ return output;
+ },
+ };
+ }
+}
+
+fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+ append_opt_vecs(chained_binops(kind), if_statment_binops(kind))
+}
+
+fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+ match kind {
+ ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind),
+ ExprKind::Paren(ref e) => if_statment_binops(&e.kind),
+ ExprKind::Block(ref block, _) => {
+ let mut output = None;
+ for stmt in &block.stmts {
+ match stmt.kind {
+ StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => {
+ output = append_opt_vecs(output, if_statment_binops(&e.kind));
+ },
+ _ => {},
+ }
+ }
+ output
+ },
+ _ => None,
+ }
+}
+
+fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
+ match (target_opt, source_opt) {
+ (Some(mut target), Some(mut source)) => {
+ target.reserve(source.len());
+ for op in source.drain(..) {
+ target.push(op);
+ }
+ Some(target)
+ },
+ (Some(v), None) | (None, Some(v)) => Some(v),
+ (None, None) => None,
+ }
+}
+
+fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+ match kind {
+ ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer),
+ ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind),
+ _ => None,
+ }
+}
+
+fn chained_binops_helper(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option<Vec<BinaryOp<'expr>>> {
+ match (&left_outer.kind, &right_outer.kind) {
+ (
+ ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e),
+ ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e),
+ ) => chained_binops_helper(left_e, right_e),
+ (ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer),
+ (_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => {
+ chained_binops_helper(left_outer, right_e)
+ },
+ (
+ ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right),
+ ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right),
+ ) => match (
+ chained_binops_helper(left_left, left_right),
+ chained_binops_helper(right_left, right_right),
+ ) {
+ (Some(mut left_ops), Some(mut right_ops)) => {
+ left_ops.reserve(right_ops.len());
+ for op in right_ops.drain(..) {
+ left_ops.push(op);
+ }
+ Some(left_ops)
+ },
+ (Some(mut left_ops), _) => {
+ left_ops.push(BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)));
+ Some(left_ops)
+ },
+ (_, Some(mut right_ops)) => {
+ right_ops.insert(0, BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)));
+ Some(right_ops)
+ },
+ (None, None) => Some(vec![
+ BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)),
+ BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)),
+ ]),
+ },
+ _ => None,
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
+struct IdentLocation {
+ index: usize,
+}
+
+impl Add for IdentLocation {
+ type Output = IdentLocation;
+
+ fn add(self, other: Self) -> Self::Output {
+ Self {
+ index: self.index + other.index,
+ }
+ }
+}
+
+impl AddAssign for IdentLocation {
+ fn add_assign(&mut self, other: Self) {
+ *self = *self + other;
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+enum IdentDifference {
+ NoDifference,
+ Single(IdentLocation),
+ Double(IdentLocation, IdentLocation),
+ Multiple,
+ NonIdent,
+}
+
+impl Add for IdentDifference {
+ type Output = IdentDifference;
+
+ fn add(self, other: Self) -> Self::Output {
+ match (self, other) {
+ (Self::NoDifference, output) | (output, Self::NoDifference) => output,
+ (Self::Multiple, _)
+ | (_, Self::Multiple)
+ | (Self::Double(_, _), Self::Single(_))
+ | (Self::Single(_) | Self::Double(_, _), Self::Double(_, _)) => Self::Multiple,
+ (Self::NonIdent, _) | (_, Self::NonIdent) => Self::NonIdent,
+ (Self::Single(il1), Self::Single(il2)) => Self::Double(il1, il2),
+ }
+ }
+}
+
+impl AddAssign for IdentDifference {
+ fn add_assign(&mut self, other: Self) {
+ *self = *self + other;
+ }
+}
+
+impl IdentDifference {
+ /// Returns true if learning about more differences will not change the value
+ /// of this `IdentDifference`, and false otherwise.
+ fn is_complete(&self) -> bool {
+ match self {
+ Self::NoDifference | Self::Single(_) | Self::Double(_, _) => false,
+ Self::Multiple | Self::NonIdent => true,
+ }
+ }
+}
+
+fn ident_difference_expr(left: &Expr, right: &Expr) -> IdentDifference {
+ ident_difference_expr_with_base_location(left, right, IdentLocation::default()).0
+}
+
+fn ident_difference_expr_with_base_location(
+ left: &Expr,
+ right: &Expr,
+ mut base: IdentLocation,
+) -> (IdentDifference, IdentLocation) {
+ // Ideally, this function should not use IdentIter because it should return
+ // early if the expressions have any non-ident differences. We want that early
+ // return because if without that restriction the lint would lead to false
+ // positives.
+ //
+ // But, we cannot (easily?) use a `rustc_ast::visit::Visitor`, since we need
+ // the two expressions to be walked in lockstep. And without a `Visitor`, we'd
+ // have to do all the AST traversal ourselves, which is a lot of work, since to
+ // do it properly we'd need to be able to handle more or less every possible
+ // AST node since `Item`s can be written inside `Expr`s.
+ //
+ // In practice, it seems likely that expressions, above a certain size, that
+ // happen to use the exact same idents in the exact same order, and which are
+ // not structured the same, would be rare. Therefore it seems likely that if
+ // we do only the first layer of matching ourselves and eventually fallback on
+ // IdentIter, then the output of this function will be almost always be correct
+ // in practice.
+ //
+ // If it turns out that problematic cases are more prelavent than we assume,
+ // then we should be able to change this function to do the correct traversal,
+ // without needing to change the rest of the code.
+
+ #![allow(clippy::enum_glob_use)]
+ use ExprKind::*;
+
+ match (
+ &strip_non_ident_wrappers(left).kind,
+ &strip_non_ident_wrappers(right).kind,
+ ) {
+ (Yield(_), Yield(_))
+ | (Try(_), Try(_))
+ | (Paren(_), Paren(_))
+ | (Repeat(_, _), Repeat(_, _))
+ | (Struct(_), Struct(_))
+ | (MacCall(_), MacCall(_))
+ | (LlvmInlineAsm(_), LlvmInlineAsm(_))
+ | (InlineAsm(_), InlineAsm(_))
+ | (Ret(_), Ret(_))
+ | (Continue(_), Continue(_))
+ | (Break(_, _), Break(_, _))
+ | (AddrOf(_, _, _), AddrOf(_, _, _))
+ | (Path(_, _), Path(_, _))
+ | (Range(_, _, _), Range(_, _, _))
+ | (Index(_, _), Index(_, _))
+ | (Field(_, _), Field(_, _))
+ | (AssignOp(_, _, _), AssignOp(_, _, _))
+ | (Assign(_, _, _), Assign(_, _, _))
+ | (TryBlock(_), TryBlock(_))
+ | (Await(_), Await(_))
+ | (Async(_, _, _), Async(_, _, _))
+ | (Block(_, _), Block(_, _))
+ | (Closure(_, _, _, _, _, _), Closure(_, _, _, _, _, _))
+ | (Match(_, _), Match(_, _))
+ | (Loop(_, _), Loop(_, _))
+ | (ForLoop(_, _, _, _), ForLoop(_, _, _, _))
+ | (While(_, _, _), While(_, _, _))
+ | (If(_, _, _), If(_, _, _))
+ | (Let(_, _, _), Let(_, _, _))
+ | (Type(_, _), Type(_, _))
+ | (Cast(_, _), Cast(_, _))
+ | (Lit(_), Lit(_))
+ | (Unary(_, _), Unary(_, _))
+ | (Binary(_, _, _), Binary(_, _, _))
+ | (Tup(_), Tup(_))
+ | (MethodCall(_, _, _), MethodCall(_, _, _))
+ | (Call(_, _), Call(_, _))
+ | (ConstBlock(_), ConstBlock(_))
+ | (Array(_), Array(_))
+ | (Box(_), Box(_)) => {
+ // keep going
+ },
+ _ => {
+ return (IdentDifference::NonIdent, base);
+ },
+ }
+
+ let mut difference = IdentDifference::NoDifference;
+
+ for (left_attr, right_attr) in left.attrs.iter().zip(right.attrs.iter()) {
+ let (new_difference, new_base) =
+ ident_difference_via_ident_iter_with_base_location(left_attr, right_attr, base);
+ base = new_base;
+ difference += new_difference;
+ if difference.is_complete() {
+ return (difference, base);
+ }
+ }
+
+ let (new_difference, new_base) = ident_difference_via_ident_iter_with_base_location(left, right, base);
+ base = new_base;
+ difference += new_difference;
+
+ (difference, base)
+}
+
+fn ident_difference_via_ident_iter_with_base_location<Iterable: Into<IdentIter>>(
+ left: Iterable,
+ right: Iterable,
+ mut base: IdentLocation,
+) -> (IdentDifference, IdentLocation) {
+ // See the note in `ident_difference_expr_with_base_location` about `IdentIter`
+ let mut difference = IdentDifference::NoDifference;
+
+ let mut left_iterator = left.into();
+ let mut right_iterator = right.into();
+
+ loop {
+ match (left_iterator.next(), right_iterator.next()) {
+ (Some(left_ident), Some(right_ident)) => {
+ if !eq_id(left_ident, right_ident) {
+ difference += IdentDifference::Single(base);
+ if difference.is_complete() {
+ return (difference, base);
+ }
+ }
+ },
+ (Some(_), None) | (None, Some(_)) => {
+ return (IdentDifference::NonIdent, base);
+ },
+ (None, None) => {
+ return (difference, base);
+ },
+ }
+ base += IdentLocation { index: 1 };
+ }
+}
+
+fn get_ident(expr: &Expr, location: IdentLocation) -> Option<Ident> {
+ IdentIter::from(expr).nth(location.index)
+}
+
+fn suggestion_with_swapped_ident(
+ cx: &EarlyContext<'_>,
+ expr: &Expr,
+ location: IdentLocation,
+ new_ident: Ident,
+ applicability: &mut Applicability,
+) -> Option<String> {
+ get_ident(expr, location).and_then(|current_ident| {
+ if eq_id(current_ident, new_ident) {
+ // We never want to suggest a non-change
+ return None;
+ }
+
+ Some(format!(
+ "{}{}{}",
+ snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability),
+ new_ident,
+ snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability),
+ ))
+ })
+}
+
+fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A>
+where
+ Iter: Iterator<Item = A>,
+{
+ iter.enumerate()
+ .filter_map(move |(i, a)| if i == index { None } else { Some(a) })
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{binop_traits, trait_ref_of_method, BINOP_TRAITS, OP_ASSIGN_TRAITS};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lints for suspicious operations in impls of arithmetic operators, e.g.
+ /// subtracting elements in an Add impl.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a typo or copy-and-paste error and not intended.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// impl Add for Foo {
+ /// type Output = Foo;
+ ///
+ /// fn add(self, other: Foo) -> Foo {
+ /// Foo(self.0 - other.0)
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_ARITHMETIC_IMPL,
+ suspicious,
+ "suspicious use of operators in impl of arithmetic trait"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lints for suspicious operations in impls of OpAssign, e.g.
+ /// subtracting elements in an AddAssign impl.
+ ///
+ /// ### Why is this bad?
+ /// This is probably a typo or copy-and-paste error and not intended.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// impl AddAssign for Foo {
+ /// fn add_assign(&mut self, other: Foo) {
+ /// *self = *self - other;
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub SUSPICIOUS_OP_ASSIGN_IMPL,
+ suspicious,
+ "suspicious use of operators in impl of OpAssign trait"
+}
+
+declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_ASSIGN_IMPL]);
+
+impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::Binary(binop, _, _) | hir::ExprKind::AssignOp(binop, ..) = expr.kind;
+ if let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop.node);
+ if let Ok(binop_trait_id) = cx.tcx.lang_items().require(binop_trait_lang);
+ if let Ok(op_assign_trait_id) = cx.tcx.lang_items().require(op_assign_trait_lang);
+
+ // Check for more than one binary operation in the implemented function
+ // Linting when multiple operations are involved can result in false positives
+ let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
+ if let hir::Node::ImplItem(impl_item) = cx.tcx.hir().get(parent_fn);
+ if let hir::ImplItemKind::Fn(_, body_id) = impl_item.kind;
+ let body = cx.tcx.hir().body(body_id);
+ let parent_fn = cx.tcx.hir().get_parent_item(expr.hir_id);
+ if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn);
+ let trait_id = trait_ref.path.res.def_id();
+ if ![binop_trait_id, op_assign_trait_id].contains(&trait_id);
+ if let Some(&(_, lint)) = [
+ (&BINOP_TRAITS, SUSPICIOUS_ARITHMETIC_IMPL),
+ (&OP_ASSIGN_TRAITS, SUSPICIOUS_OP_ASSIGN_IMPL),
+ ]
+ .iter()
+ .find(|&(ts, _)| ts.iter().any(|&t| Ok(trait_id) == cx.tcx.lang_items().require(t)));
+ if count_binops(&body.value) == 1;
+ then {
+ span_lint(
+ cx,
+ lint,
+ binop.span,
+ &format!("suspicious use of `{}` in `{}` impl", binop.node.as_str(), cx.tcx.item_name(trait_id)),
+ );
+ }
+ }
+ }
+}
+
+fn count_binops(expr: &hir::Expr<'_>) -> u32 {
+ let mut visitor = BinaryExprVisitor::default();
+ visitor.visit_expr(expr);
+ visitor.nb_binops
+}
+
+#[derive(Default)]
+struct BinaryExprVisitor {
+ nb_binops: u32,
+}
+
+impl<'tcx> Visitor<'tcx> for BinaryExprVisitor {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+ match expr.kind {
+ hir::ExprKind::Binary(..)
+ | hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
+ | hir::ExprKind::AssignOp(..) => self.nb_binops += 1,
+ _ => {},
+ }
+
+ walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
--- /dev/null
- use clippy_utils::{can_mut_borrow_both, differing_macro_contexts, eq_expr_value};
+use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::is_type_diagnostic_item;
- format!("std::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
++use clippy_utils::{can_mut_borrow_both, differing_macro_contexts, eq_expr_value, std_or_core};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
+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_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for manual swapping.
+ ///
+ /// ### Why is this bad?
+ /// The `std::mem::swap` function exposes the intent better
+ /// without deinitializing or copying either variable.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut a = 42;
+ /// let mut b = 1337;
+ ///
+ /// let t = b;
+ /// b = a;
+ /// a = t;
+ /// ```
+ /// Use std::mem::swap():
+ /// ```rust
+ /// let mut a = 1;
+ /// let mut b = 2;
+ /// std::mem::swap(&mut a, &mut b);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub MANUAL_SWAP,
+ complexity,
+ "manual swap of two variables"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `foo = bar; bar = foo` sequences.
+ ///
+ /// ### Why is this bad?
+ /// This looks like a failed attempt to swap.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let mut a = 1;
+ /// # let mut b = 2;
+ /// a = b;
+ /// b = a;
+ /// ```
+ /// If swapping is intended, use `swap()` instead:
+ /// ```rust
+ /// # let mut a = 1;
+ /// # let mut b = 2;
+ /// std::mem::swap(&mut a, &mut b);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ALMOST_SWAPPED,
+ correctness,
+ "`foo = bar; bar = foo` sequence"
+}
+
+declare_lint_pass!(Swap => [MANUAL_SWAP, ALMOST_SWAPPED]);
+
+impl<'tcx> LateLintPass<'tcx> for Swap {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ check_manual_swap(cx, block);
+ check_suspicious_swap(cx, block);
+ check_xor_swap(cx, block);
+ }
+}
+
+fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, span: Span, is_xor_based: bool) {
+ let mut applicability = Applicability::MachineApplicable;
+
+ if !can_mut_borrow_both(cx, e1, e2) {
+ if let ExprKind::Index(lhs1, idx1) = e1.kind {
+ if let ExprKind::Index(lhs2, idx2) = e2.kind {
+ if eq_expr_value(cx, lhs1, lhs2) {
+ let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
+
+ if matches!(ty.kind(), ty::Slice(_))
+ || matches!(ty.kind(), ty::Array(_, _))
+ || is_type_diagnostic_item(cx, ty, sym::Vec)
+ || is_type_diagnostic_item(cx, ty, sym::VecDeque)
+ {
+ let slice = Sugg::hir_with_applicability(cx, lhs1, "<slice>", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ MANUAL_SWAP,
+ span,
+ &format!("this looks like you are swapping elements of `{}` manually", slice),
+ "try",
+ format!(
+ "{}.swap({}, {})",
+ slice.maybe_par(),
+ snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
+ snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
+ ),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ let first = Sugg::hir_with_applicability(cx, e1, "..", &mut applicability);
+ let second = Sugg::hir_with_applicability(cx, e2, "..", &mut applicability);
++ let Some(sugg) = std_or_core(cx) else { return };
++
+ span_lint_and_then(
+ cx,
+ MANUAL_SWAP,
+ span,
+ &format!("this looks like you are swapping `{}` and `{}` manually", first, second),
+ |diag| {
+ diag.span_suggestion(
+ span,
+ "try",
- diag.note("or maybe you should use `std::mem::replace`?");
++ format!("{}::mem::swap({}, {})", sugg, first.mut_addr(), second.mut_addr()),
+ applicability,
+ );
+ if !is_xor_based {
- ALMOST_SWAPPED,
- span,
- &format!("this looks like you are trying to swap{}", what),
- |diag| {
- if !what.is_empty() {
- diag.span_suggestion(
- span,
- "try",
- format!(
- "std::mem::swap({}, {})",
- lhs,
- rhs,
- ),
- Applicability::MaybeIncorrect,
- );
- diag.note("or maybe you should use `std::mem::replace`?");
- }
- });
++ diag.note(&format!("or maybe you should use `{}::mem::replace`?", sugg));
+ }
+ },
+ );
+}
+
+/// Implementation of the `MANUAL_SWAP` lint.
+fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
+ for w in block.stmts.windows(3) {
+ if_chain! {
+ // let t = foo();
+ if let StmtKind::Local(tmp) = w[0].kind;
+ if let Some(tmp_init) = tmp.init;
+ if let PatKind::Binding(.., ident, None) = tmp.pat.kind;
+
+ // foo() = bar();
+ if let StmtKind::Semi(first) = w[1].kind;
+ if let ExprKind::Assign(lhs1, rhs1, _) = first.kind;
+
+ // bar() = t;
+ if let StmtKind::Semi(second) = w[2].kind;
+ if let ExprKind::Assign(lhs2, rhs2, _) = second.kind;
+ if let ExprKind::Path(QPath::Resolved(None, rhs2)) = rhs2.kind;
+ if rhs2.segments.len() == 1;
+
+ if ident.name == rhs2.segments[0].ident.name;
+ if eq_expr_value(cx, tmp_init, lhs1);
+ if eq_expr_value(cx, rhs1, lhs2);
+ then {
+ let span = w[0].span.to(second.span);
+ generate_swap_warning(cx, lhs1, lhs2, span, false);
+ }
+ }
+ }
+}
+
+/// Implementation of the `ALMOST_SWAPPED` lint.
+fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
+ for w in block.stmts.windows(2) {
+ if_chain! {
+ if let StmtKind::Semi(first) = w[0].kind;
+ if let StmtKind::Semi(second) = w[1].kind;
+ if !differing_macro_contexts(first.span, second.span);
+ if let ExprKind::Assign(lhs0, rhs0, _) = first.kind;
+ if let ExprKind::Assign(lhs1, rhs1, _) = second.kind;
+ if eq_expr_value(cx, lhs0, rhs1);
+ if eq_expr_value(cx, lhs1, rhs0);
+ then {
+ let lhs0 = Sugg::hir_opt(cx, lhs0);
+ let rhs0 = Sugg::hir_opt(cx, rhs0);
+ let (what, lhs, rhs) = if let (Some(first), Some(second)) = (lhs0, rhs0) {
+ (
+ format!(" `{}` and `{}`", first, second),
+ first.mut_addr().to_string(),
+ second.mut_addr().to_string(),
+ )
+ } else {
+ (String::new(), String::new(), String::new())
+ };
+
+ let span = first.span.to(second.span);
++ let Some(sugg) = std_or_core(cx) else { return };
+
+ span_lint_and_then(cx,
++ ALMOST_SWAPPED,
++ span,
++ &format!("this looks like you are trying to swap{}", what),
++ |diag| {
++ if !what.is_empty() {
++ diag.span_suggestion(
++ span,
++ "try",
++ format!(
++ "{}::mem::swap({}, {})",
++ sugg,
++ lhs,
++ rhs,
++ ),
++ Applicability::MaybeIncorrect,
++ );
++ diag.note(
++ &format!("or maybe you should use `{}::mem::replace`?", sugg)
++ );
++ }
++ });
+ }
+ }
+ }
+}
+
+/// Implementation of the xor case for `MANUAL_SWAP` lint.
+fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
+ for window in block.stmts.windows(3) {
+ if_chain! {
+ if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(&window[0]);
+ if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(&window[1]);
+ if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(&window[2]);
+ if eq_expr_value(cx, lhs0, rhs1);
+ if eq_expr_value(cx, lhs2, rhs1);
+ if eq_expr_value(cx, lhs1, rhs0);
+ if eq_expr_value(cx, lhs1, rhs2);
+ then {
+ let span = window[0].span.to(window[2].span);
+ generate_swap_warning(cx, lhs0, rhs0, span, true);
+ }
+ };
+ }
+}
+
+/// Returns the lhs and rhs of an xor assignment statement.
+fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> {
+ if let StmtKind::Semi(expr) = stmt.kind {
+ if let ExprKind::AssignOp(
+ Spanned {
+ node: BinOpKind::BitXor,
+ ..
+ },
+ lhs,
+ rhs,
+ ) = expr.kind
+ {
+ return Some((lhs, rhs));
+ }
+ }
+ None
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_ast::ast;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::{BytePos, Span};
+use std::convert::TryFrom;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks doc comments for usage of tab characters.
+ ///
+ /// ### Why is this bad?
+ /// The rust style-guide promotes spaces instead of tabs for indentation.
+ /// To keep a consistent view on the source, also doc comments should not have tabs.
+ /// Also, explaining ascii-diagrams containing tabs can get displayed incorrectly when the
+ /// display settings of the author and reader differ.
+ ///
+ /// ### Example
+ /// ```rust
+ /// ///
+ /// /// Struct to hold two strings:
+ /// /// - first one
+ /// /// - second one
+ /// pub struct DoubleString {
+ /// ///
+ /// /// - First String:
+ /// /// - needs to be inside here
+ /// first_string: String,
+ /// ///
+ /// /// - Second String:
+ /// /// - needs to be inside here
+ /// second_string: String,
+ ///}
+ /// ```
+ ///
+ /// Will be converted to:
+ /// ```rust
+ /// ///
+ /// /// Struct to hold two strings:
+ /// /// - first one
+ /// /// - second one
+ /// pub struct DoubleString {
+ /// ///
+ /// /// - First String:
+ /// /// - needs to be inside here
+ /// first_string: String,
+ /// ///
+ /// /// - Second String:
+ /// /// - needs to be inside here
+ /// second_string: String,
+ ///}
+ /// ```
++ #[clippy::version = "1.41.0"]
+ pub TABS_IN_DOC_COMMENTS,
+ style,
+ "using tabs in doc comments is not recommended"
+}
+
+declare_lint_pass!(TabsInDocComments => [TABS_IN_DOC_COMMENTS]);
+
+impl TabsInDocComments {
+ fn warn_if_tabs_in_doc(cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+ if let ast::AttrKind::DocComment(_, comment) = attr.kind {
+ let comment = comment.as_str();
+
+ for (lo, hi) in get_chunks_of_tabs(&comment) {
+ // +3 skips the opening delimiter
+ let new_span = Span::new(
+ attr.span.lo() + BytePos(3 + lo),
+ attr.span.lo() + BytePos(3 + hi),
+ attr.span.ctxt(),
+ attr.span.parent(),
+ );
+ span_lint_and_sugg(
+ cx,
+ TABS_IN_DOC_COMMENTS,
+ new_span,
+ "using tabs in doc comments is not recommended",
+ "consider using four spaces per tab",
+ " ".repeat((hi - lo) as usize),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
+
+impl EarlyLintPass for TabsInDocComments {
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attribute: &ast::Attribute) {
+ Self::warn_if_tabs_in_doc(cx, attribute);
+ }
+}
+
+///
+/// scans the string for groups of tabs and returns the start(inclusive) and end positions
+/// (exclusive) of all groups
+/// e.g. "sd\tasd\t\taa" will be converted to [(2, 3), (6, 8)] as
+/// 012 3456 7 89
+/// ^-^ ^---^
+fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> {
+ let line_length_way_to_long = "doc comment longer than 2^32 chars";
+ let mut spans: Vec<(u32, u32)> = vec![];
+ let mut current_start: u32 = 0;
+
+ // tracker to decide if the last group of tabs is not closed by a non-tab character
+ let mut is_active = false;
+
+ // Note that we specifically need the char _byte_ indices here, not the positional indexes
+ // within the char array to deal with multi-byte characters properly. `char_indices` does
+ // exactly that. It provides an iterator over tuples of the form `(byte position, char)`.
+ let char_indices: Vec<_> = the_str.char_indices().collect();
+
+ if let [(_, '\t')] = char_indices.as_slice() {
+ return vec![(0, 1)];
+ }
+
+ for entry in char_indices.windows(2) {
+ match entry {
+ [(_, '\t'), (_, '\t')] => {
+ // either string starts with double tab, then we have to set it active,
+ // otherwise is_active is true anyway
+ is_active = true;
+ },
+ [(_, _), (index_b, '\t')] => {
+ // as ['\t', '\t'] is excluded, this has to be a start of a tab group,
+ // set indices accordingly
+ is_active = true;
+ current_start = u32::try_from(*index_b).unwrap();
+ },
+ [(_, '\t'), (index_b, _)] => {
+ // this now has to be an end of the group, hence we have to push a new tuple
+ is_active = false;
+ spans.push((current_start, u32::try_from(*index_b).unwrap()));
+ },
+ _ => {},
+ }
+ }
+
+ // only possible when tabs are at the end, insert last group
+ if is_active {
+ spans.push((
+ current_start,
+ u32::try_from(char_indices.last().unwrap().0 + 1).expect(line_length_way_to_long),
+ ));
+ }
+
+ spans
+}
+
+#[cfg(test)]
+mod tests_for_get_chunks_of_tabs {
+ use super::get_chunks_of_tabs;
+
+ #[test]
+ fn test_unicode_han_string() {
+ let res = get_chunks_of_tabs(" \u{4f4d}\t");
+
+ assert_eq!(res, vec![(4, 5)]);
+ }
+
+ #[test]
+ fn test_empty_string() {
+ let res = get_chunks_of_tabs("");
+
+ assert_eq!(res, vec![]);
+ }
+
+ #[test]
+ fn test_simple() {
+ let res = get_chunks_of_tabs("sd\t\t\taa");
+
+ assert_eq!(res, vec![(2, 5)]);
+ }
+
+ #[test]
+ fn test_only_t() {
+ let res = get_chunks_of_tabs("\t\t");
+
+ assert_eq!(res, vec![(0, 2)]);
+ }
+
+ #[test]
+ fn test_only_one_t() {
+ let res = get_chunks_of_tabs("\t");
+
+ assert_eq!(res, vec![(0, 1)]);
+ }
+
+ #[test]
+ fn test_double() {
+ let res = get_chunks_of_tabs("sd\tasd\t\taa");
+
+ assert_eq!(res, vec![(2, 3), (6, 8)]);
+ }
+
+ #[test]
+ fn test_start() {
+ let res = get_chunks_of_tabs("\t\taa");
+
+ assert_eq!(res, vec![(0, 2)]);
+ }
+
+ #[test]
+ fn test_end() {
+ let res = get_chunks_of_tabs("aa\t\t");
+
+ assert_eq!(res, vec![(2, 4)]);
+ }
+
+ #[test]
+ fn test_start_single() {
+ let res = get_chunks_of_tabs("\taa");
+
+ assert_eq!(res, vec![(0, 1)]);
+ }
+
+ #[test]
+ fn test_end_single() {
+ let res = get_chunks_of_tabs("aa\t");
+
+ assert_eq!(res, vec![(2, 3)]);
+ }
+
+ #[test]
+ fn test_no_tabs() {
+ let res = get_chunks_of_tabs("dsfs");
+
+ assert_eq!(res, vec![]);
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::is_adjusted;
+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?
+ ///
+ /// ### Example
+ /// ```rust
+ /// (0, 0).0 = 1
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TEMPORARY_ASSIGNMENT,
+ complexity,
+ "assignments to temporaries"
+}
+
+fn is_temporary(expr: &Expr<'_>) -> bool {
+ matches!(&expr.kind, ExprKind::Struct(..) | ExprKind::Tup(..))
+}
+
+declare_lint_pass!(TemporaryAssignment => [TEMPORARY_ASSIGNMENT]);
+
+impl<'tcx> LateLintPass<'tcx> for TemporaryAssignment {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let ExprKind::Assign(target, ..) = &expr.kind {
+ let mut base = target;
+ while let ExprKind::Field(f, _) | ExprKind::Index(f, _) = &base.kind {
+ base = f;
+ }
+ if is_temporary(base) && !is_adjusted(cx, base) {
+ span_lint(cx, TEMPORARY_ASSIGNMENT, expr.span, "assignment to temporary");
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::match_def_path;
+use clippy_utils::source::snippet_with_applicability;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.to_digit(..).is_some()` on `char`s.
+ ///
+ /// ### Why is this bad?
+ /// This is a convoluted way of checking if a `char` is a digit. It's
+ /// more straight forward to use the dedicated `is_digit` method.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let c = 'c';
+ /// # let radix = 10;
+ /// let is_digit = c.to_digit(radix).is_some();
+ /// ```
+ /// can be written as:
+ /// ```
+ /// # let c = 'c';
+ /// # let radix = 10;
+ /// let is_digit = c.is_digit(radix);
+ /// ```
++ #[clippy::version = "1.41.0"]
+ pub TO_DIGIT_IS_SOME,
+ style,
+ "`char.is_digit()` is clearer"
+}
+
+declare_lint_pass!(ToDigitIsSome => [TO_DIGIT_IS_SOME]);
+
+impl<'tcx> LateLintPass<'tcx> for ToDigitIsSome {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::MethodCall(is_some_path, _, is_some_args, _) = &expr.kind;
+ if is_some_path.ident.name.as_str() == "is_some";
+ if let [to_digit_expr] = &**is_some_args;
+ then {
+ let match_result = match &to_digit_expr.kind {
+ hir::ExprKind::MethodCall(to_digits_path, _, to_digit_args, _) => {
+ if_chain! {
+ if let [char_arg, radix_arg] = &**to_digit_args;
+ if to_digits_path.ident.name.as_str() == "to_digit";
+ let char_arg_ty = cx.typeck_results().expr_ty_adjusted(char_arg);
+ if *char_arg_ty.kind() == ty::Char;
+ then {
+ Some((true, char_arg, radix_arg))
+ } else {
+ None
+ }
+ }
+ }
+ hir::ExprKind::Call(to_digits_call, to_digit_args) => {
+ if_chain! {
+ if let [char_arg, radix_arg] = &**to_digit_args;
+ if let hir::ExprKind::Path(to_digits_path) = &to_digits_call.kind;
+ if let to_digits_call_res = cx.qpath_res(to_digits_path, to_digits_call.hir_id);
+ if let Some(to_digits_def_id) = to_digits_call_res.opt_def_id();
+ if match_def_path(cx, to_digits_def_id, &["core", "char", "methods", "<impl char>", "to_digit"]);
+ then {
+ Some((false, char_arg, radix_arg))
+ } else {
+ None
+ }
+ }
+ }
+ _ => None
+ };
+
+ if let Some((is_method_call, char_arg, radix_arg)) = match_result {
+ let mut applicability = Applicability::MachineApplicable;
+ let char_arg_snip = snippet_with_applicability(cx, char_arg.span, "_", &mut applicability);
+ let radix_snip = snippet_with_applicability(cx, radix_arg.span, "_", &mut applicability);
+
+ span_lint_and_sugg(
+ cx,
+ TO_DIGIT_IS_SOME,
+ expr.span,
+ "use of `.to_digit(..).is_some()`",
+ "try this",
+ if is_method_call {
+ format!("{}.is_digit({})", char_arg_snip, radix_snip)
+ } else {
+ format!("char::is_digit({}, {})", char_arg_snip, radix_snip)
+ },
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{is_diag_trait_item, match_def_path, path_to_local_id, paths};
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for uses of `to_string()` in `Display` traits.
+ ///
+ /// ### Why is this bad?
+ /// Usually `to_string` is implemented indirectly
+ /// via `Display`. Hence using it while implementing `Display` would
+ /// lead to infinite recursion.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// struct Structure(i32);
+ /// impl fmt::Display for Structure {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "{}", self.to_string())
+ /// }
+ /// }
+ ///
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::fmt;
+ ///
+ /// struct Structure(i32);
+ /// impl fmt::Display for Structure {
+ /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ /// write!(f, "{}", self.0)
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub TO_STRING_IN_DISPLAY,
+ correctness,
+ "`to_string` method used while implementing `Display` trait"
+}
+
+#[derive(Default)]
+pub struct ToStringInDisplay {
+ in_display_impl: bool,
+ self_hir_id: Option<HirId>,
+}
+
+impl ToStringInDisplay {
+ pub fn new() -> Self {
+ Self {
+ in_display_impl: false,
+ self_hir_id: None,
+ }
+ }
+}
+
+impl_lint_pass!(ToStringInDisplay => [TO_STRING_IN_DISPLAY]);
+
+impl LateLintPass<'_> for ToStringInDisplay {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_display_impl(cx, item) {
+ self.in_display_impl = true;
+ }
+ }
+
+ fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_display_impl(cx, item) {
+ self.in_display_impl = false;
+ self.self_hir_id = None;
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
+ if_chain! {
+ if self.in_display_impl;
+ if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
+ let body = cx.tcx.hir().body(*body_id);
+ if !body.params.is_empty();
+ then {
+ let self_param = &body.params[0];
+ self.self_hir_id = Some(self_param.pat.hir_id);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
+ if self.in_display_impl;
+ if let Some(self_hir_id) = self.self_hir_id;
+ if let ExprKind::MethodCall(path, _, [ref self_arg, ..], _) = expr.kind;
+ if path.ident.name == sym!(to_string);
+ if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if is_diag_trait_item(cx, expr_def_id, sym::ToString);
+ if path_to_local_id(self_arg, self_hir_id);
+ then {
+ span_lint(
+ cx,
+ TO_STRING_IN_DISPLAY,
+ expr.span,
+ "using `to_string` in `fmt::Display` implementation might lead to infinite recursion",
+ );
+ }
+ }
+ }
+}
+
+fn is_display_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
+ if_chain! {
+ if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind;
+ if let Some(did) = trait_ref.trait_def_id();
+ then {
+ match_def_path(cx, did, &paths::DISPLAY_TRAIT)
+ } else {
+ false
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{HirId, Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Const;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute.
+ ///
+ /// ### Why is this bad?
+ /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct RarelyUseful {
+ /// some_field: u32,
+ /// last: [u32; 0],
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// #[repr(C)]
+ /// struct MoreOftenUseful {
+ /// some_field: usize,
+ /// last: [u32; 0],
+ /// }
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub TRAILING_EMPTY_ARRAY,
+ nursery,
+ "struct with a trailing zero-sized array but without `#[repr(C)]` or another `repr` attribute"
+}
+declare_lint_pass!(TrailingEmptyArray => [TRAILING_EMPTY_ARRAY]);
+
+impl<'tcx> LateLintPass<'tcx> for TrailingEmptyArray {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
+ if is_struct_with_trailing_zero_sized_array(cx, item) && !has_repr_attr(cx, item.hir_id()) {
+ span_lint_and_help(
+ cx,
+ TRAILING_EMPTY_ARRAY,
+ item.span,
+ "trailing zero-sized array in a struct which is not marked with a `repr` attribute",
+ None,
+ &format!(
+ "consider annotating `{}` with `#[repr(C)]` or another `repr` attribute",
+ cx.tcx.def_path_str(item.def_id.to_def_id())
+ ),
+ );
+ }
+ }
+}
+
+fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
+ if_chain! {
+ // First check if last field is an array
+ if let ItemKind::Struct(data, _) = &item.kind;
+ if let Some(last_field) = data.fields().last();
+ if let rustc_hir::TyKind::Array(_, length) = last_field.ty.kind;
+
+ // Then check if that that array zero-sized
+ let length_ldid = cx.tcx.hir().local_def_id(length.hir_id);
+ let length = Const::from_anon_const(cx.tcx, length_ldid);
+ let length = length.try_eval_usize(cx.tcx, cx.param_env);
+ if let Some(length) = length;
+ then {
+ length == 0
+ } else {
+ false
+ }
+ }
+}
+
+fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ cx.tcx.hir().attrs(hir_id).iter().any(|attr| attr.has_name(sym::repr))
+}
--- /dev/null
- use clippy_utils::{in_macro, SpanlessHash};
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::source::{snippet, snippet_with_applicability};
- if in_macro(gen.span) {
++use clippy_utils::SpanlessHash;
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::unhash::UnhashMap;
+use rustc_errors::Applicability;
+use rustc_hir::{def::Res, GenericBound, Generics, ParamName, Path, QPath, TyKind, WherePredicate};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns about unnecessary type repetitions in trait bounds
+ ///
+ /// ### Why is this bad?
+ /// Repeating the type for every bound makes the code
+ /// less readable than combining the bounds
+ ///
+ /// ### 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 {}
+ /// ```
++ #[clippy::version = "1.38.0"]
+ pub TYPE_REPETITION_IN_BOUNDS,
+ pedantic,
+ "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for cases where generics are being used and multiple
+ /// syntax specifications for trait bounds are used simultaneously.
+ ///
+ /// ### Why is this bad?
+ /// Duplicate bounds makes the code
+ /// less readable than specifing them only once.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn func<T: Clone + Default>(arg: T) where T: Clone + Default {}
+ /// ```
+ ///
+ /// Could be written as:
+ ///
+ /// ```rust
+ /// fn func<T: Clone + Default>(arg: T) {}
+ /// ```
+ /// or
+ ///
+ /// ```rust
+ /// fn func<T>(arg: T) where T: Clone + Default {}
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub TRAIT_DUPLICATION_IN_BOUNDS,
+ pedantic,
+ "Check if the same trait bounds are specified twice during a function declaration"
+}
+
+#[derive(Copy, Clone)]
+pub struct TraitBounds {
+ max_trait_bounds: u64,
+}
+
+impl TraitBounds {
+ #[must_use]
+ pub fn new(max_trait_bounds: u64) -> Self {
+ Self { max_trait_bounds }
+ }
+}
+
+impl_lint_pass!(TraitBounds => [TYPE_REPETITION_IN_BOUNDS, TRAIT_DUPLICATION_IN_BOUNDS]);
+
+impl<'tcx> LateLintPass<'tcx> for TraitBounds {
+ fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
+ self.check_type_repetition(cx, gen);
+ check_trait_bound_duplication(cx, gen);
+ }
+}
+
+fn get_trait_res_span_from_bound(bound: &GenericBound<'_>) -> Option<(Res, Span)> {
+ if let GenericBound::Trait(t, _) = bound {
+ Some((t.trait_ref.path.res, t.span))
+ } else {
+ None
+ }
+}
+
+impl TraitBounds {
+ fn check_type_repetition(self, cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
- if !in_macro(p.span);
++ if gen.span.from_expansion() {
+ return;
+ }
+ let hash = |ty| -> u64 {
+ let mut hasher = SpanlessHash::new(cx);
+ hasher.hash_ty(ty);
+ hasher.finish()
+ };
+ let mut map: UnhashMap<u64, Vec<&GenericBound<'_>>> = UnhashMap::default();
+ let mut applicability = Applicability::MaybeIncorrect;
+ for bound in gen.where_clause.predicates {
+ if_chain! {
+ if let WherePredicate::BoundPredicate(ref p) = bound;
+ if p.bounds.len() as u64 <= self.max_trait_bounds;
- if in_macro(gen.span) || gen.params.is_empty() || gen.where_clause.predicates.is_empty() {
++ if !p.span.from_expansion();
+ 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,
+ );
+ }
+ }
+ }
+ }
+}
+
+fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
- if !in_macro(bound_predicate.span);
++ if gen.span.from_expansion() || gen.params.is_empty() || gen.where_clause.predicates.is_empty() {
+ return;
+ }
+
+ let mut map = FxHashMap::default();
+ for param in gen.params {
+ if let ParamName::Plain(ref ident) = param.name {
+ let res = param
+ .bounds
+ .iter()
+ .filter_map(get_trait_res_span_from_bound)
+ .collect::<Vec<_>>();
+ map.insert(*ident, res);
+ }
+ }
+
+ for predicate in gen.where_clause.predicates {
+ if_chain! {
+ if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate;
++ if !bound_predicate.span.from_expansion();
+ if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind;
+ if let Some(segment) = segments.first();
+ if let Some(trait_resolutions_direct) = map.get(&segment.ident);
+ then {
+ for (res_where, _) in bound_predicate.bounds.iter().filter_map(get_trait_res_span_from_bound) {
+ if let Some((_, span_direct)) = trait_resolutions_direct
+ .iter()
+ .find(|(res_direct, _)| *res_direct == res_where) {
+ span_lint_and_help(
+ cx,
+ TRAIT_DUPLICATION_IN_BOUNDS,
+ *span_direct,
+ "this trait bound is already specified in the where clause",
+ None,
+ "consider removing this trait bound",
+ );
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+mod crosspointer_transmute;
+mod transmute_float_to_int;
+mod transmute_int_to_bool;
+mod transmute_int_to_char;
+mod transmute_int_to_float;
+mod transmute_num_to_bytes;
+mod transmute_ptr_to_ptr;
+mod transmute_ptr_to_ref;
+mod transmute_ref_to_ref;
+mod transmutes_expressible_as_ptr_casts;
+mod unsound_collection_transmute;
+mod useless_transmute;
+mod utils;
+mod wrong_transmute;
+
+use clippy_utils::in_constant;
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes that can't ever be correct on any
+ /// architecture.
+ ///
+ /// ### Why is this bad?
+ /// It's basically guaranteed to be undefined behaviour.
+ ///
+ /// ### Known problems
+ /// When accessing C, users might want to store pointer
+ /// sized objects in `extradata` arguments to save an allocation.
+ ///
+ /// ### Example
+ /// ```ignore
+ /// let ptr: *const T = core::intrinsics::transmute('x')
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub WRONG_TRANSMUTE,
+ correctness,
+ "transmutes that are confusing at best, undefined behaviour at worst and always useless"
+}
+
+// FIXME: Move this to `complexity` again, after #5343 is fixed
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes to the original type of the object
+ /// and transmutes that could be a cast.
+ ///
+ /// ### Why is this bad?
+ /// Readability. The code tricks people into thinking that
+ /// something complex is going on.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// core::intrinsics::transmute(t); // where the result type is the same as `t`'s
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_TRANSMUTE,
+ nursery,
+ "transmutes that have the same to and from types or could be a cast/coercion"
+}
+
+// FIXME: Merge this lint with USELESS_TRANSMUTE once that is out of the nursery.
+declare_clippy_lint! {
+ /// ### What it does
+ ///Checks for transmutes that could be a pointer cast.
+ ///
+ /// ### Why is this bad?
+ /// Readability. The code tricks people into thinking that
+ /// something complex is going on.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// # let p: *const [i32] = &[];
+ /// unsafe { std::mem::transmute::<*const [i32], *const [u16]>(p) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let p: *const [i32] = &[];
+ /// p as *const [u16];
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+ complexity,
+ "transmutes that could be a pointer cast"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes between a type `T` and `*T`.
+ ///
+ /// ### Why is this bad?
+ /// It's easy to mistakenly transmute between a type and a
+ /// pointer to that type.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// core::intrinsics::transmute(t) // where the result type is the same as
+ /// // `*t` or `&t`'s
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub CROSSPOINTER_TRANSMUTE,
+ complexity,
+ "transmutes that have to or from types that are a pointer to the other"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a pointer to a reference.
+ ///
+ /// ### Why is this bad?
+ /// This can always be rewritten with `&` and `*`.
+ ///
+ /// ### Known problems
+ /// - `mem::transmute` in statics and constants is stable from Rust 1.46.0,
+ /// while dereferencing raw pointer is not stable yet.
+ /// If you need to do this in those places,
+ /// you would have to use `transmute` instead.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// unsafe {
+ /// let _: &T = std::mem::transmute(p); // where p: *const T
+ /// }
+ ///
+ /// // can be written:
+ /// let _: &T = &*p;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_PTR_TO_REF,
+ complexity,
+ "transmutes from a pointer to a reference type"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from an integer to a `char`.
+ ///
+ /// ### Why is this bad?
+ /// Not every integer is a Unicode scalar value.
+ ///
+ /// ### Known problems
+ /// - [`from_u32`] which this lint suggests using is slower than `transmute`
+ /// as it needs to validate the input.
+ /// If you are certain that the input is always a valid Unicode scalar value,
+ /// use [`from_u32_unchecked`] which is as fast as `transmute`
+ /// but has a semantically meaningful name.
+ /// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`.
+ ///
+ /// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html
+ /// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1_u32;
+ /// unsafe {
+ /// let _: char = std::mem::transmute(x); // where x: u32
+ /// }
+ ///
+ /// // should be:
+ /// let _ = std::char::from_u32(x).unwrap();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_INT_TO_CHAR,
+ complexity,
+ "transmutes from an integer to a `char`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a `&[u8]` to a `&str`.
+ ///
+ /// ### Why is this bad?
+ /// Not every byte slice is a valid UTF-8 string.
+ ///
+ /// ### Known problems
+ /// - [`from_utf8`] which this lint suggests using is slower than `transmute`
+ /// as it needs to validate the input.
+ /// If you are certain that the input is always a valid UTF-8,
+ /// use [`from_utf8_unchecked`] which is as fast as `transmute`
+ /// but has a semantically meaningful name.
+ /// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`.
+ ///
+ /// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html
+ /// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html
+ ///
+ /// ### Example
+ /// ```rust
+ /// let b: &[u8] = &[1_u8, 2_u8];
+ /// unsafe {
+ /// let _: &str = std::mem::transmute(b); // where b: &[u8]
+ /// }
+ ///
+ /// // should be:
+ /// let _ = std::str::from_utf8(b).unwrap();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_BYTES_TO_STR,
+ complexity,
+ "transmutes from a `&[u8]` to a `&str`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from an integer to a `bool`.
+ ///
+ /// ### Why is this bad?
+ /// This might result in an invalid in-memory representation of a `bool`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = 1_u8;
+ /// unsafe {
+ /// let _: bool = std::mem::transmute(x); // where x: u8
+ /// }
+ ///
+ /// // should be:
+ /// let _: bool = x != 0;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_INT_TO_BOOL,
+ complexity,
+ "transmutes from an integer to a `bool`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from an integer to a float.
+ ///
+ /// ### Why is this bad?
+ /// Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive
+ /// and safe.
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe {
+ /// let _: f32 = std::mem::transmute(1_u32); // where x: u32
+ /// }
+ ///
+ /// // should be:
+ /// let _: f32 = f32::from_bits(1_u32);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_INT_TO_FLOAT,
+ complexity,
+ "transmutes from an integer to a float"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a float to an integer.
+ ///
+ /// ### Why is this bad?
+ /// Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive
+ /// and safe.
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe {
+ /// let _: u32 = std::mem::transmute(1f32);
+ /// }
+ ///
+ /// // should be:
+ /// let _: u32 = 1f32.to_bits();
+ /// ```
++ #[clippy::version = "1.41.0"]
+ pub TRANSMUTE_FLOAT_TO_INT,
+ complexity,
+ "transmutes from a float to an integer"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a number to an array of `u8`
+ ///
+ /// ### Why this is bad?
+ /// Transmutes are dangerous and error-prone, whereas `to_ne_bytes`
+ /// is intuitive and safe.
+ ///
+ /// ### Example
+ /// ```rust
+ /// unsafe {
+ /// let x: [u8; 8] = std::mem::transmute(1i64);
+ /// }
+ ///
+ /// // should be
+ /// let x: [u8; 8] = 0i64.to_ne_bytes();
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub TRANSMUTE_NUM_TO_BYTES,
+ complexity,
+ "transmutes from a number to an array of `u8`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes from a pointer to a pointer, or
+ /// from a reference to a reference.
+ ///
+ /// ### Why is this bad?
+ /// Transmutes are dangerous, and these can instead be
+ /// written as casts.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let ptr = &1u32 as *const u32;
+ /// unsafe {
+ /// // pointer-to-pointer transmute
+ /// let _: *const f32 = std::mem::transmute(ptr);
+ /// // ref-ref transmute
+ /// let _: &f32 = std::mem::transmute(&1u32);
+ /// }
+ /// // These can be respectively written:
+ /// let _ = ptr as *const f32;
+ /// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TRANSMUTE_PTR_TO_PTR,
+ pedantic,
+ "transmutes from a pointer to a pointer / a reference to a reference"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmutes between collections whose
+ /// types have different ABI, size or alignment.
+ ///
+ /// ### Why is this bad?
+ /// This is undefined behavior.
+ ///
+ /// ### Known problems
+ /// Currently, we cannot know whether a type is a
+ /// collection, so we just lint the ones that come with `std`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // different size, therefore likely out-of-bounds memory access
+ /// // You absolutely do not want this in your code!
+ /// unsafe {
+ /// std::mem::transmute::<_, Vec<u32>>(vec![2_u16])
+ /// };
+ /// ```
+ ///
+ /// You must always iterate, map and collect the values:
+ ///
+ /// ```rust
+ /// vec![2_u16].into_iter().map(u32::from).collect::<Vec<_>>();
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub UNSOUND_COLLECTION_TRANSMUTE,
+ correctness,
+ "transmute between collections of layout-incompatible types"
+}
+
+declare_lint_pass!(Transmute => [
+ CROSSPOINTER_TRANSMUTE,
+ TRANSMUTE_PTR_TO_REF,
+ TRANSMUTE_PTR_TO_PTR,
+ USELESS_TRANSMUTE,
+ WRONG_TRANSMUTE,
+ TRANSMUTE_INT_TO_CHAR,
+ TRANSMUTE_BYTES_TO_STR,
+ TRANSMUTE_INT_TO_BOOL,
+ TRANSMUTE_INT_TO_FLOAT,
+ TRANSMUTE_FLOAT_TO_INT,
+ TRANSMUTE_NUM_TO_BYTES,
+ UNSOUND_COLLECTION_TRANSMUTE,
+ TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for Transmute {
+ #[allow(clippy::similar_names, clippy::too_many_lines)]
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(path_expr, args) = e.kind;
+ if let ExprKind::Path(ref qpath) = path_expr.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
+ if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
+ then {
+ // Avoid suggesting from/to bits and dereferencing raw pointers in const contexts.
+ // See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`.
+ // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
+ let const_context = in_constant(cx, e.hir_id);
+
+ let from_ty = cx.typeck_results().expr_ty(&args[0]);
+ let to_ty = cx.typeck_results().expr_ty(e);
+
+ // If useless_transmute is triggered, the other lints can be skipped.
+ if useless_transmute::check(cx, e, from_ty, to_ty, args) {
+ return;
+ }
+
+ let mut linted = wrong_transmute::check(cx, e, from_ty, to_ty);
+ linted |= crosspointer_transmute::check(cx, e, from_ty, to_ty);
+ linted |= transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, args, qpath);
+ linted |= transmute_int_to_char::check(cx, e, from_ty, to_ty, args);
+ linted |= transmute_ref_to_ref::check(cx, e, from_ty, to_ty, args, const_context);
+ linted |= transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, args);
+ linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args);
+ linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context);
+ linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context);
+ linted |= transmute_num_to_bytes::check(cx, e, from_ty, to_ty, args, const_context);
+ linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty);
+
+ if !linted {
+ transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, args);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant_context, Constant};
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::is_expr_diagnostic_item;
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for transmute calls which would receive a null pointer.
+ ///
+ /// ### Why is this bad?
+ /// Transmuting a null pointer is undefined behavior.
+ ///
+ /// ### Known problems
+ /// Not all cases can be detected at the moment of this writing.
+ /// For example, variables which hold a null pointer and are then fed to a `transmute`
+ /// call, aren't detectable yet.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let null_ref: &u64 = unsafe { std::mem::transmute(0 as *const u64) };
+ /// ```
++ #[clippy::version = "1.35.0"]
+ pub TRANSMUTING_NULL,
+ correctness,
+ "transmutes from a null pointer to a reference, which is undefined behavior"
+}
+
+declare_lint_pass!(TransmutingNull => [TRANSMUTING_NULL]);
+
+const LINT_MSG: &str = "transmuting a known null pointer into a reference";
+
+impl<'tcx> LateLintPass<'tcx> for TransmutingNull {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if in_external_macro(cx.sess(), expr.span) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Call(func, [arg]) = expr.kind;
+ if is_expr_diagnostic_item(cx, func, sym::transmute);
+
+ then {
+ // Catching transmute over constants that resolve to `null`.
+ let mut const_eval_context = constant_context(cx, cx.typeck_results());
+ if_chain! {
+ if let ExprKind::Path(ref _qpath) = arg.kind;
+ if let Some(Constant::RawPtr(x)) = const_eval_context.expr(arg);
+ if x == 0;
+ then {
+ span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG)
+ }
+ }
+
+ // Catching:
+ // `std::mem::transmute(0 as *const i32)`
+ if_chain! {
+ if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind;
+ if let ExprKind::Lit(ref lit) = inner_expr.kind;
+ if let LitKind::Int(0, _) = lit.node;
+ then {
+ span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG)
+ }
+ }
+
+ // Catching:
+ // `std::mem::transmute(std::ptr::null::<i32>())`
+ if_chain! {
+ if let ExprKind::Call(func1, []) = arg.kind;
+ if is_expr_diagnostic_item(cx, func1, sym::ptr_null);
+ then {
+ span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG)
+ }
+ }
+
+ // FIXME:
+ // Also catch transmutations of variables which are known nulls.
+ // To do this, MIR const propagation seems to be the better tool.
+ // Whenever MIR const prop routines are more developed, this will
+ // become available. As of this writing (25/03/19) it is not yet.
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::diagnostics::span_lint_and_sugg;
- use clippy_utils::{differing_macro_contexts, get_parent_expr, in_macro, is_lang_ctor, match_def_path, paths};
++use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
- use rustc_span::sym;
++use clippy_utils::{get_parent_expr, is_lang_ctor, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::LangItem::ResultErr;
+use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
- let differing_contexts = differing_macro_contexts(expr.span, err_arg.span);
-
- let origin_snippet = if in_macro(expr.span) && in_macro(err_arg.span) && differing_contexts {
- snippet(cx, err_arg.span.ctxt().outer_expn_data().call_site, "_")
- } else if err_arg.span.from_expansion() && !in_macro(expr.span) {
- snippet_with_macro_callsite(cx, err_arg.span, "_")
- } else {
- snippet(cx, err_arg.span, "_")
- };
++use rustc_span::{hygiene, sym};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usages of `Err(x)?`.
+ ///
+ /// ### Why is this bad?
+ /// The `?` operator is designed to allow calls that
+ /// can fail to be easily chained. For example, `foo()?.bar()` or
+ /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will
+ /// always return), it is more clear to write `return Err(x)`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo(fail: bool) -> Result<i32, String> {
+ /// if fail {
+ /// Err("failed")?;
+ /// }
+ /// Ok(0)
+ /// }
+ /// ```
+ /// Could be written:
+ ///
+ /// ```rust
+ /// fn foo(fail: bool) -> Result<i32, String> {
+ /// if fail {
+ /// return Err("failed".into());
+ /// }
+ /// Ok(0)
+ /// }
+ /// ```
++ #[clippy::version = "1.38.0"]
+ pub TRY_ERR,
+ style,
+ "return errors explicitly rather than hiding them behind a `?`"
+}
+
+declare_lint_pass!(TryErr => [TRY_ERR]);
+
+impl<'tcx> LateLintPass<'tcx> for TryErr {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // Looks for a structure like this:
+ // match ::std::ops::Try::into_result(Err(5)) {
+ // ::std::result::Result::Err(err) =>
+ // #[allow(unreachable_code)]
+ // return ::std::ops::Try::from_error(::std::convert::From::from(err)),
+ // ::std::result::Result::Ok(val) =>
+ // #[allow(unreachable_code)]
+ // val,
+ // };
+ if_chain! {
+ if !in_external_macro(cx.tcx.sess, expr.span);
+ if let ExprKind::Match(match_arg, _, MatchSource::TryDesugar) = expr.kind;
+ if let ExprKind::Call(match_fun, try_args) = match_arg.kind;
+ if let ExprKind::Path(ref match_fun_path) = match_fun.kind;
+ if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, _));
+ if let Some(try_arg) = try_args.get(0);
+ if let ExprKind::Call(err_fun, err_args) = try_arg.kind;
+ if let Some(err_arg) = err_args.get(0);
+ if let ExprKind::Path(ref err_fun_path) = err_fun.kind;
+ if is_lang_ctor(cx, err_fun_path, ResultErr);
+ if let Some(return_ty) = find_return_type(cx, &expr.kind);
+ then {
+ let prefix;
+ let suffix;
+ let err_ty;
+
+ if let Some(ty) = result_error_type(cx, return_ty) {
+ prefix = "Err(";
+ suffix = ")";
+ err_ty = ty;
+ } else if let Some(ty) = poll_result_error_type(cx, return_ty) {
+ prefix = "Poll::Ready(Err(";
+ suffix = "))";
+ err_ty = ty;
+ } else if let Some(ty) = poll_option_result_error_type(cx, return_ty) {
+ prefix = "Poll::Ready(Some(Err(";
+ suffix = ")))";
+ err_ty = ty;
+ } else {
+ return;
+ };
+
+ let expr_err_ty = cx.typeck_results().expr_ty(err_arg);
- Applicability::MachineApplicable
++ let span = hygiene::walk_chain(err_arg.span, try_arg.span.ctxt());
++ let mut applicability = Applicability::MachineApplicable;
++ let origin_snippet = snippet_with_applicability(cx, span, "_", &mut applicability);
+ let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) {
+ "" // already returns
+ } else {
+ "return "
+ };
+ let suggestion = if err_ty == expr_err_ty {
+ format!("{}{}{}{}", ret_prefix, prefix, origin_snippet, suffix)
+ } else {
+ format!("{}{}{}.into(){}", ret_prefix, prefix, origin_snippet, suffix)
+ };
+
+ span_lint_and_sugg(
+ cx,
+ TRY_ERR,
+ expr.span,
+ "returning an `Err(_)` with the `?` operator",
+ "try this",
+ suggestion,
++ applicability,
+ );
+ }
+ }
+ }
+}
+
+/// Finds function return type by examining return expressions in match arms.
+fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option<Ty<'tcx>> {
+ if let ExprKind::Match(_, arms, MatchSource::TryDesugar) = expr {
+ for arm in arms.iter() {
+ if let ExprKind::Ret(Some(ret)) = arm.body.kind {
+ return Some(cx.typeck_results().expr_ty(ret));
+ }
+ }
+ }
+ None
+}
+
+/// Extracts the error type from Result<T, E>.
+fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ if_chain! {
+ if let ty::Adt(_, subst) = ty.kind();
+ if is_type_diagnostic_item(cx, ty, sym::Result);
+ then {
+ Some(subst.type_at(1))
+ } else {
+ None
+ }
+ }
+}
+
+/// Extracts the error type from Poll<Result<T, E>>.
+fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ if_chain! {
+ if let ty::Adt(def, subst) = ty.kind();
+ if match_def_path(cx, def.did, &paths::POLL);
+ let ready_ty = subst.type_at(0);
+
+ if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
+ if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did);
+ then {
+ Some(ready_subst.type_at(1))
+ } else {
+ None
+ }
+ }
+}
+
+/// Extracts the error type from Poll<Option<Result<T, E>>>.
+fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ if_chain! {
+ if let ty::Adt(def, subst) = ty.kind();
+ if match_def_path(cx, def.did, &paths::POLL);
+ let ready_ty = subst.type_at(0);
+
+ if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
+ if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did);
+ let some_ty = ready_subst.type_at(0);
+
+ if let ty::Adt(some_def, some_subst) = some_ty.kind();
+ if cx.tcx.is_diagnostic_item(sym::Result, some_def.did);
+ then {
+ Some(some_subst.type_at(1))
+ } else {
+ None
+ }
+ }
+}
--- /dev/null
+mod borrowed_box;
+mod box_collection;
+mod linked_list;
+mod option_option;
+mod rc_buffer;
+mod rc_mutex;
+mod redundant_allocation;
+mod type_complexity;
+mod utils;
+mod vec_box;
+
+use rustc_hir as hir;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{
+ Body, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem,
+ TraitItemKind, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `Box<T>` where T is a collection such as Vec anywhere in the code.
+ /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
+ ///
+ /// ### Why is this bad?
+ /// Collections already keeps their contents in a separate area on
+ /// the heap. So if you `Box` them, you just add another level of indirection
+ /// without any benefit whatsoever.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// struct X {
+ /// values: Box<Vec<Foo>>,
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// struct X {
+ /// values: Vec<Foo>,
+ /// }
+ /// ```
++ #[clippy::version = "1.57.0"]
+ pub BOX_COLLECTION,
+ 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.
+ /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
+ ///
+ /// ### Why is this bad?
+ /// `Vec` already keeps its contents in a separate area on
+ /// the heap. So if you `Box` its contents, you just add another level of indirection.
+ ///
+ /// ### Known problems
+ /// Vec<Box<T: Sized>> makes sense if T is a large type (see [#3530](https://github.com/rust-lang/rust-clippy/issues/3530),
+ /// 1st comment).
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct X {
+ /// values: Vec<Box<i32>>,
+ /// }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// struct X {
+ /// values: Vec<i32>,
+ /// }
+ /// ```
++ #[clippy::version = "1.33.0"]
+ 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.
+ ///
+ /// ### 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
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ /// Check the [Box documentation](https://doc.rust-lang.org/std/boxed/index.html) for more information.
+ ///
+ /// ### Why is this bad?
+ /// Any `&Box<T>` can also be a `&T`, which is more
+ /// general.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// fn foo(bar: &Box<T>) { ... }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// fn foo(bar: &T) { ... }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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<Arc<T>>`, `Rc<Box<T>>`, `Arc<&T>`, `Arc<Rc<T>>`,
+ /// `Arc<Arc<T>>`, `Arc<Box<T>>`, `Box<&T>`, `Box<Rc<T>>`, `Box<Arc<T>>`, `Box<Box<T>>`, add an unnecessary level of indirection.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// fn foo(bar: Rc<&usize>) {}
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust
+ /// fn foo(bar: &usize) {}
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub REDUNDANT_ALLOCATION,
+ perf,
+ "redundant allocation"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `Rc<T>` and `Arc<T>` when `T` is a mutable buffer type such as `String` or `Vec`.
+ ///
+ /// ### Why is this bad?
+ /// Expressions such as `Rc<String>` usually have no advantage over `Rc<str>`, since
+ /// it is larger and involves an extra level of indirection, and doesn't implement `Borrow<str>`.
+ ///
+ /// While mutating a buffer type would still be possible with `Rc::get_mut()`, it only
+ /// works if there are no additional references yet, which usually defeats the purpose of
+ /// enclosing it in a shared ownership type. Instead, additionally wrapping the inner
+ /// type with an interior mutable container (such as `RefCell` or `Mutex`) would normally
+ /// be used.
+ ///
+ /// ### Known problems
+ /// This pattern can be desirable to avoid the overhead of a `RefCell` or `Mutex` for
+ /// cases where mutation only happens before there are any additional references.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// # use std::rc::Rc;
+ /// fn foo(interned: Rc<String>) { ... }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// fn foo(interned: Rc<str>) { ... }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub RC_BUFFER,
+ restriction,
+ "shared ownership of a buffer type"
+}
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::rc::Rc;
+ /// struct Foo {
+ /// inner: Rc<Vec<Vec<Box<(u32, u32, u32, u32)>>>>,
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub TYPE_COMPLEXITY,
+ complexity,
+ "usage of very complex types that might be better factored into `type` definitions"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `Rc<Mutex<T>>`.
+ ///
+ /// ### Why is this bad?
+ /// `Rc` is used in single thread and `Mutex` is used in multi thread.
+ /// Consider using `Rc<RefCell<T>>` in single thread or `Arc<Mutex<T>>` in multi thread.
+ ///
+ /// ### Known problems
+ /// Sometimes combining generic types can lead to the requirement that a
+ /// type use Rc in conjunction with Mutex. We must consider those cases false positives, but
+ /// alas they are quite hard to rule out. Luckily they are also rare.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::rc::Rc;
+ /// use std::sync::Mutex;
+ /// fn foo(interned: Rc<Mutex<i32>>) { ... }
+ /// ```
+ ///
+ /// Better:
+ ///
+ /// ```rust,ignore
+ /// use std::rc::Rc;
+ /// use std::cell::RefCell
+ /// fn foo(interned: Rc<RefCell<i32>>) { ... }
+ /// ```
++ #[clippy::version = "1.55.0"]
+ pub RC_MUTEX,
+ restriction,
+ "usage of `Rc<Mutex<T>>`"
+}
+
+pub struct Types {
+ vec_box_size_threshold: u64,
+ type_complexity_threshold: u64,
+ avoid_breaking_exported_api: bool,
+}
+
+impl_lint_pass!(Types => [BOX_COLLECTION, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER, RC_MUTEX, TYPE_COMPLEXITY]);
+
+impl<'tcx> LateLintPass<'tcx> for Types {
+ fn check_fn(&mut self, cx: &LateContext<'_>, _: FnKind<'_>, decl: &FnDecl<'_>, _: &Body<'_>, _: Span, id: HirId) {
+ let is_in_trait_impl = if let Some(hir::Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(id))
+ {
+ matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }))
+ } else {
+ false
+ };
+
+ let is_exported = cx.access_levels.is_exported(cx.tcx.hir().local_def_id(id));
+
+ self.check_fn_decl(
+ cx,
+ decl,
+ CheckTyContext {
+ is_in_trait_impl,
+ is_exported,
+ ..CheckTyContext::default()
+ },
+ );
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let is_exported = cx.access_levels.is_exported(item.def_id);
+
+ match item.kind {
+ ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _) => self.check_ty(
+ cx,
+ ty,
+ CheckTyContext {
+ is_exported,
+ ..CheckTyContext::default()
+ },
+ ),
+ // functions, enums, structs, impls and traits are covered
+ _ => (),
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
+ match item.kind {
+ ImplItemKind::Const(ty, _) | ImplItemKind::TyAlias(ty) => self.check_ty(
+ cx,
+ ty,
+ CheckTyContext {
+ is_in_trait_impl: true,
+ ..CheckTyContext::default()
+ },
+ ),
+ // methods are covered by check_fn
+ ImplItemKind::Fn(..) => (),
+ }
+ }
+
+ fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
+ let is_exported = cx.access_levels.is_exported(cx.tcx.hir().local_def_id(field.hir_id));
+
+ self.check_ty(
+ cx,
+ field.ty,
+ CheckTyContext {
+ is_exported,
+ ..CheckTyContext::default()
+ },
+ );
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'_>) {
+ let is_exported = cx.access_levels.is_exported(item.def_id);
+
+ let context = CheckTyContext {
+ is_exported,
+ ..CheckTyContext::default()
+ };
+
+ match item.kind {
+ TraitItemKind::Const(ty, _) | TraitItemKind::Type(_, Some(ty)) => {
+ self.check_ty(cx, ty, context);
+ },
+ TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, sig.decl, context),
+ TraitItemKind::Type(..) => (),
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
+ if let Some(ty) = local.ty {
+ self.check_ty(
+ cx,
+ ty,
+ CheckTyContext {
+ is_local: true,
+ ..CheckTyContext::default()
+ },
+ );
+ }
+ }
+}
+
+impl Types {
+ pub fn new(vec_box_size_threshold: u64, type_complexity_threshold: u64, avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ vec_box_size_threshold,
+ type_complexity_threshold,
+ avoid_breaking_exported_api,
+ }
+ }
+
+ fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, context: CheckTyContext) {
+ for input in decl.inputs {
+ self.check_ty(cx, input, context);
+ }
+
+ if let FnRetTy::Return(ty) = decl.output {
+ self.check_ty(cx, ty, context);
+ }
+ }
+
+ /// Recursively check for `TypePass` lints in the given type. Stop at the first
+ /// lint found.
+ ///
+ /// The parameter `is_local` distinguishes the context of the type.
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, mut context: CheckTyContext) {
+ if hir_ty.span.from_expansion() {
+ return;
+ }
+
+ if !context.is_nested_call && type_complexity::check(cx, hir_ty, self.type_complexity_threshold) {
+ return;
+ }
+
+ // Skip trait implementations; see issue #605.
+ if context.is_in_trait_impl {
+ return;
+ }
+
+ match hir_ty.kind {
+ TyKind::Path(ref qpath) if !context.is_local => {
+ let hir_id = hir_ty.hir_id;
+ let res = cx.qpath_res(qpath, hir_id);
+ if let Some(def_id) = res.opt_def_id() {
+ if self.is_type_change_allowed(context) {
+ // All lints that are being checked in this block are guarded by
+ // the `avoid_breaking_exported_api` configuration. When adding a
+ // new lint, please also add the name to the configuration documentation
+ // in `clippy_lints::utils::conf.rs`
+
+ let mut triggered = false;
+ triggered |= box_collection::check(cx, hir_ty, qpath, def_id);
+ triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id);
+ triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id);
+ triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold);
+ triggered |= option_option::check(cx, hir_ty, qpath, def_id);
+ triggered |= linked_list::check(cx, hir_ty, def_id);
+ triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id);
+
+ if triggered {
+ return;
+ }
+ }
+ }
+ match *qpath {
+ QPath::Resolved(Some(ty), p) => {
+ context.is_nested_call = true;
+ self.check_ty(cx, ty, context);
+ 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, context);
+ }
+ },
+ QPath::Resolved(None, p) => {
+ context.is_nested_call = true;
+ 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, context);
+ }
+ },
+ QPath::TypeRelative(ty, seg) => {
+ context.is_nested_call = true;
+ self.check_ty(cx, ty, context);
+ if let Some(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, context);
+ }
+ }
+ },
+ QPath::LangItem(..) => {},
+ }
+ },
+ TyKind::Rptr(ref lt, ref mut_ty) => {
+ context.is_nested_call = true;
+ if !borrowed_box::check(cx, hir_ty, lt, mut_ty) {
+ self.check_ty(cx, mut_ty.ty, context);
+ }
+ },
+ TyKind::Slice(ty) | TyKind::Array(ty, _) | TyKind::Ptr(MutTy { ty, .. }) => {
+ context.is_nested_call = true;
+ self.check_ty(cx, ty, context);
+ },
+ TyKind::Tup(tys) => {
+ context.is_nested_call = true;
+ for ty in tys {
+ self.check_ty(cx, ty, context);
+ }
+ },
+ _ => {},
+ }
+ }
+
+ /// This function checks if the type is allowed to change in the current context
+ /// based on the `avoid_breaking_exported_api` configuration
+ fn is_type_change_allowed(&self, context: CheckTyContext) -> bool {
+ !(context.is_exported && self.avoid_breaking_exported_api)
+ }
+}
+
+#[allow(clippy::struct_excessive_bools)]
+#[derive(Clone, Copy, Default)]
+struct CheckTyContext {
+ is_in_trait_impl: bool,
+ /// `true` for types on local variables.
+ is_local: bool,
+ /// `true` for types that are part of the public API.
+ is_exported: bool,
+ is_nested_call: bool,
+}
--- /dev/null
- use clippy_utils::{in_macro, is_lint_allowed};
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
++use clippy_utils::is_lint_allowed;
+use clippy_utils::source::{indent_of, reindent_multiline, snippet};
- let between_span = if in_macro(block_span) {
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, UnsafeSource};
+use rustc_lexer::TokenKind;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{BytePos, Span};
+use std::borrow::Cow;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `unsafe` blocks without a `// Safety: ` comment
+ /// explaining why the unsafe operations performed inside
+ /// the block are safe.
+ ///
+ /// ### Why is this bad?
+ /// Undocumented unsafe blocks can make it difficult to
+ /// read and maintain code, as well as uncover unsoundness
+ /// and bugs.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::ptr::NonNull;
+ /// let a = &mut 42;
+ ///
+ /// let ptr = unsafe { NonNull::new_unchecked(a) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::ptr::NonNull;
+ /// let a = &mut 42;
+ ///
+ /// // Safety: references are guaranteed to be non-null.
+ /// let ptr = unsafe { NonNull::new_unchecked(a) };
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub UNDOCUMENTED_UNSAFE_BLOCKS,
+ restriction,
+ "creating an unsafe block without explaining why it is safe"
+}
+
+impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
+
+#[derive(Default)]
+pub struct UndocumentedUnsafeBlocks {
+ pub local_level: u32,
+ pub local_span: Option<Span>,
+ // The local was already checked for an overall safety comment
+ // There is no need to continue checking the blocks in the local
+ pub local_checked: bool,
+ // Since we can only check the blocks from expanded macros
+ // We have to omit the suggestion due to the actual definition
+ // Not being available to us
+ pub macro_expansion: bool,
+}
+
+impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
+ fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
+ if_chain! {
+ if !self.local_checked;
+ if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id);
+ if !in_external_macro(cx.tcx.sess, block.span);
+ if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
+ if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
+ if self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, block.span) == Some(false);
+ then {
+ let mut span = block.span;
+
+ if let Some(local_span) = self.local_span {
+ span = local_span;
+
+ let result = self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, span);
+
+ if result.unwrap_or(true) {
+ self.local_checked = true;
+ return;
+ }
+ }
+
+ self.lint(cx, span);
+ }
+ }
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'_>, local: &'_ Local<'_>) {
+ if_chain! {
+ if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, local.hir_id);
+ if !in_external_macro(cx.tcx.sess, local.span);
+ if let Some(init) = local.init;
+ then {
+ self.visit_expr(init);
+
+ if self.local_level > 0 {
+ self.local_span = Some(local.span);
+ }
+ }
+ }
+ }
+
+ fn check_block_post(&mut self, _: &LateContext<'_>, _: &'_ Block<'_>) {
+ self.local_level = self.local_level.saturating_sub(1);
+
+ if self.local_level == 0 {
+ self.local_checked = false;
+ self.local_span = None;
+ }
+ }
+}
+
+impl<'hir> Visitor<'hir> for UndocumentedUnsafeBlocks {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, ex: &'v Expr<'v>) {
+ match ex.kind {
+ ExprKind::Block(_, _) => self.local_level = self.local_level.saturating_add(1),
+ _ => walk_expr(self, ex),
+ }
+ }
+}
+
+impl UndocumentedUnsafeBlocks {
+ fn block_has_safety_comment(&mut self, tcx: TyCtxt<'_>, enclosing_hir_id: HirId, block_span: Span) -> Option<bool> {
+ let map = tcx.hir();
+ let source_map = tcx.sess.source_map();
+
+ let enclosing_scope_span = map.opt_span(enclosing_hir_id)?;
+
- enclosing_scope_span.with_hi(block_span.hi())
++ let between_span = if block_span.from_expansion() {
+ self.macro_expansion = true;
- enclosing_scope_span.to(block_span)
++ enclosing_scope_span.with_hi(block_span.hi()).source_callsite()
+ } else {
+ self.macro_expansion = false;
- .lookup_file_pos_with_col_display(BytePos((lex_start + pos).try_into().unwrap()))
++ enclosing_scope_span.to(block_span).source_callsite()
+ };
+
+ let file_name = source_map.span_to_filename(between_span);
+ let source_file = source_map.get_source_file(&file_name)?;
+
+ let lex_start = (between_span.lo().0 - source_file.start_pos.0 + 1) as usize;
+ let lex_end = (between_span.hi().0 - source_file.start_pos.0) as usize;
+ let src_str = source_file.src.as_ref()?[lex_start..lex_end].to_string();
+
++ let source_start_pos = source_file.start_pos.0 as usize + lex_start;
++
+ let mut pos = 0;
+ let mut comment = false;
+
+ for token in rustc_lexer::tokenize(&src_str) {
+ match token.kind {
+ TokenKind::LineComment { doc_style: None }
+ | TokenKind::BlockComment {
+ doc_style: None,
+ terminated: true,
+ } => {
+ let comment_str = src_str[pos + 2..pos + token.len].to_ascii_uppercase();
+
+ if comment_str.contains("SAFETY:") {
+ comment = true;
+ }
+ },
+ // We need to add all whitespace to `pos` before checking the comment's line number
+ TokenKind::Whitespace => {},
+ _ => {
+ if comment {
+ // Get the line number of the "comment" (really wherever the trailing whitespace ended)
+ let comment_line_num = source_file
++ .lookup_file_pos(BytePos((source_start_pos + pos).try_into().unwrap()))
+ .0;
+ // Find the block/local's line number
+ let block_line_num = tcx.sess.source_map().lookup_char_pos(block_span.lo()).line;
+
+ // Check the comment is immediately followed by the block/local
+ if block_line_num == comment_line_num + 1 || block_line_num == comment_line_num {
+ return Some(true);
+ }
+
+ comment = false;
+ }
+ },
+ }
+
+ pos += token.len;
+ }
+
+ Some(false)
+ }
+
+ fn lint(&self, cx: &LateContext<'_>, mut span: Span) {
+ let source_map = cx.tcx.sess.source_map();
+
+ if source_map.is_multiline(span) {
+ span = source_map.span_until_char(span, '\n');
+ }
+
+ if self.macro_expansion {
+ span_lint_and_help(
+ cx,
+ UNDOCUMENTED_UNSAFE_BLOCKS,
+ span,
+ "unsafe block in macro expansion missing a safety comment",
+ None,
+ "consider adding a safety comment in the macro definition",
+ );
+ } else {
+ let block_indent = indent_of(cx, span);
+ let suggestion = format!("// Safety: ...\n{}", snippet(cx, span, ".."));
+
+ span_lint_and_sugg(
+ cx,
+ UNDOCUMENTED_UNSAFE_BLOCKS,
+ span,
+ "unsafe block missing a safety comment",
+ "consider adding a safety comment",
+ reindent_multiline(Cow::Borrowed(&suggestion), true, block_indent).to_string(),
+ Applicability::HasPlaceholders,
+ );
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::is_type_lang_item;
+use clippy_utils::{match_function_call, paths};
+use rustc_hir::{lang_items, Expr};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`.
+ ///
+ /// ### Why is this bad?
+ /// The safe `drop` function does not drop the inner value of a `ManuallyDrop`.
+ ///
+ /// ### Known problems
+ /// Does not catch cases if the user binds `std::mem::drop`
+ /// to a different name and calls it that way.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct S;
+ /// drop(std::mem::ManuallyDrop::new(S));
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct S;
+ /// unsafe {
+ /// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S));
+ /// }
+ /// ```
++ #[clippy::version = "1.49.0"]
+ pub UNDROPPED_MANUALLY_DROPS,
+ correctness,
+ "use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value"
+}
+
+declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]);
+
+impl LateLintPass<'tcx> for UndroppedManuallyDrops {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if let Some([arg_0, ..]) = match_function_call(cx, expr, &paths::DROP) {
+ let ty = cx.typeck_results().expr_ty(arg_0);
+ if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop) {
+ span_lint_and_help(
+ cx,
+ UNDROPPED_MANUALLY_DROPS,
+ expr.span,
+ "the inner value of this ManuallyDrop will not be dropped",
+ None,
+ "to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop",
+ );
+ }
+ }
+ }
+}
--- /dev/null
- /// Checks for non-ASCII characters in string literals.
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_lint_allowed;
+use clippy_utils::source::snippet;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, HirId};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use unicode_normalization::UnicodeNormalization;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for invisible Unicode characters in the code.
+ ///
+ /// ### Why is this bad?
+ /// Having an invisible character in the code makes for all
+ /// sorts of April fools, but otherwise is very much frowned upon.
+ ///
+ /// ### Example
+ /// You don't see it, but there may be a zero-width space or soft hyphen
+ /// somewhere in this text.
++ #[clippy::version = "1.49.0"]
+ pub INVISIBLE_CHARACTERS,
+ correctness,
+ "using an invisible character in a string literal, which is confusing"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
- if let LitKind::Str(_, _) = lit.node {
++ /// Checks for non-ASCII characters in string and char literals.
+ ///
+ /// ### Why is this bad?
+ /// Yeah, we know, the 90's called and wanted their charset
+ /// back. Even so, there still are editors and other programs out there that
+ /// don't work well with Unicode. So if the code is meant to be used
+ /// internationally, on multiple operating systems, or has other portability
+ /// requirements, activating this lint could be useful.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = String::from("€");
+ /// ```
+ /// Could be written as:
+ /// ```rust
+ /// let x = String::from("\u{20ac}");
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub NON_ASCII_LITERAL,
+ restriction,
+ "using any literal non-ASCII chars in a string literal instead of using the `\\u` escape"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for string literals that contain Unicode in a form
+ /// that is not equal to its
+ /// [NFC-recomposition](http://www.unicode.org/reports/tr15/#Norm_Forms).
+ ///
+ /// ### Why is this bad?
+ /// If such a string is compared to another, the results
+ /// may be surprising.
+ ///
+ /// ### Example
+ /// You may not see it, but "à"" and "à"" aren't the same string. The
+ /// former when escaped is actually `"a\u{300}"` while the latter is `"\u{e0}"`.
++ #[clippy::version = "pre 1.29.0"]
+ pub UNICODE_NOT_NFC,
+ pedantic,
+ "using a Unicode literal not in NFC normal form (see [Unicode tr15](http://www.unicode.org/reports/tr15/) for further information)"
+}
+
+declare_lint_pass!(Unicode => [INVISIBLE_CHARACTERS, NON_ASCII_LITERAL, UNICODE_NOT_NFC]);
+
+impl LateLintPass<'_> for Unicode {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
+ if let ExprKind::Lit(ref lit) = expr.kind {
- .replace("\u{200B}", "\\u{200B}")
- .replace("\u{ad}", "\\u{AD}")
- .replace("\u{2060}", "\\u{2060}"),
++ if let LitKind::Str(_, _) | LitKind::Char(_) = lit.node {
+ check_str(cx, lit.span, expr.hir_id);
+ }
+ }
+ }
+}
+
+fn escape<T: Iterator<Item = char>>(s: T) -> String {
+ let mut result = String::new();
+ for c in s {
+ if c as u32 > 0x7F {
+ for d in c.escape_unicode() {
+ result.push(d);
+ }
+ } else {
+ result.push(c);
+ }
+ }
+ result
+}
+
+fn check_str(cx: &LateContext<'_>, span: Span, id: HirId) {
+ let string = snippet(cx, span, "");
+ if string.chars().any(|c| ['\u{200B}', '\u{ad}', '\u{2060}'].contains(&c)) {
+ span_lint_and_sugg(
+ cx,
+ INVISIBLE_CHARACTERS,
+ span,
+ "invisible character detected",
+ "consider replacing the string with",
+ string
++ .replace('\u{200B}', "\\u{200B}")
++ .replace('\u{ad}', "\\u{AD}")
++ .replace('\u{2060}', "\\u{2060}"),
+ Applicability::MachineApplicable,
+ );
+ }
+ if string.chars().any(|c| c as u32 > 0x7F) {
+ span_lint_and_sugg(
+ cx,
+ NON_ASCII_LITERAL,
+ span,
+ "literal non-ASCII character detected",
+ "consider replacing the string with",
+ if is_lint_allowed(cx, UNICODE_NOT_NFC, id) {
+ escape(string.chars())
+ } else {
+ escape(string.nfc())
+ },
+ Applicability::MachineApplicable,
+ );
+ }
+ if is_lint_allowed(cx, NON_ASCII_LITERAL, id) && string.chars().zip(string.nfc()).any(|(a, b)| a != b) {
+ span_lint_and_sugg(
+ cx,
+ UNICODE_NOT_NFC,
+ span,
+ "non-NFC Unicode sequence detected",
+ "consider replacing the string with",
+ string.nfc().collect::<String>(),
+ Applicability::MachineApplicable,
+ );
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
+use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
+use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty};
+use clippy_utils::{is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq};
+use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
+ /// This is commonly caused by calling `set_len()` right after allocating or
+ /// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`.
+ ///
+ /// ### Why is this bad?
+ /// It creates a `Vec` with uninitialized data, which leads to
+ /// undefined behavior with most safe operations. Notably, uninitialized
+ /// `Vec<u8>` must not be used with generic `Read`.
+ ///
+ /// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()`
+ /// creates out-of-bound values that lead to heap memory corruption when used.
+ ///
+ /// ### Known Problems
+ /// This lint only checks directly adjacent statements.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ /// unsafe { vec.set_len(1000); }
+ /// reader.read(&mut vec); // undefined behavior!
+ /// ```
+ ///
+ /// ### How to fix?
+ /// 1. Use an initialized buffer:
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = vec![0; 1000];
+ /// reader.read(&mut vec);
+ /// ```
+ /// 2. Wrap the content in `MaybeUninit`:
+ /// ```rust,ignore
+ /// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
+ /// vec.set_len(1000); // `MaybeUninit` can be uninitialized
+ /// ```
+ /// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available:
+ /// ```rust,ignore
+ /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
+ /// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
+ /// // perform initialization with `remaining`
+ /// vec.set_len(...); // Safe to call `set_len()` on initialized part
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub UNINIT_VEC,
+ correctness,
+ "Vec with uninitialized data"
+}
+
+declare_lint_pass!(UninitVec => [UNINIT_VEC]);
+
+// FIXME: update to a visitor-based implementation.
+// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
+impl<'tcx> LateLintPass<'tcx> for UninitVec {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
+ if !in_external_macro(cx.tcx.sess, block.span) {
+ for w in block.stmts.windows(2) {
+ if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
+ handle_uninit_vec_pair(cx, &w[0], expr);
+ }
+ }
+
+ if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
+ handle_uninit_vec_pair(cx, stmt, expr);
+ }
+ }
+ }
+}
+
+fn handle_uninit_vec_pair(
+ cx: &LateContext<'tcx>,
+ maybe_init_or_reserve: &'tcx Stmt<'tcx>,
+ maybe_set_len: &'tcx Expr<'tcx>,
+) {
+ if_chain! {
+ if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
+ if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
+ if vec.location.eq_expr(cx, set_len_self);
+ if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
+ if let ty::Adt(_, substs) = vec_ty.kind();
+ // `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
+ if !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id);
+ then {
+ if vec.has_capacity() {
+ // with_capacity / reserve -> set_len
+
+ // Check T of Vec<T>
+ if !is_uninit_value_valid_for_ty(cx, substs.type_at(0)) {
+ // FIXME: #7698, false positive of the internal lints
+ #[allow(clippy::collapsible_span_lint_calls)]
+ span_lint_and_then(
+ cx,
+ UNINIT_VEC,
+ vec![call_span, maybe_init_or_reserve.span],
+ "calling `set_len()` immediately after reserving a buffer creates uninitialized values",
+ |diag| {
+ diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
+ },
+ );
+ }
+ } else {
+ // new / default -> set_len
+ span_lint(
+ cx,
+ UNINIT_VEC,
+ vec![call_span, maybe_init_or_reserve.span],
+ "calling `set_len()` on empty `Vec` creates out-of-bound values",
+ );
+ }
+ }
+ }
+}
+
+/// The target `Vec` that is initialized or reserved
+#[derive(Clone, Copy)]
+struct TargetVec<'tcx> {
+ location: VecLocation<'tcx>,
+ /// `None` if `reserve()`
+ init_kind: Option<VecInitKind>,
+}
+
+impl TargetVec<'_> {
+ pub fn has_capacity(self) -> bool {
+ !matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default))
+ }
+}
+
+#[derive(Clone, Copy)]
+enum VecLocation<'tcx> {
+ Local(HirId),
+ Expr(&'tcx Expr<'tcx>),
+}
+
+impl<'tcx> VecLocation<'tcx> {
+ pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ match self {
+ VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id),
+ VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
+ }
+ }
+}
+
+/// Finds the target location where the result of `Vec` initialization is stored
+/// or `self` expression for `Vec::reserve()`.
+fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> {
+ match stmt.kind {
+ StmtKind::Local(local) => {
+ if_chain! {
+ if let Some(init_expr) = local.init;
+ if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
+ if let Some(init_kind) = get_vec_init_kind(cx, init_expr);
+ then {
+ return Some(TargetVec {
+ location: VecLocation::Local(hir_id),
+ init_kind: Some(init_kind),
+ })
+ }
+ }
+ },
+ StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
+ ExprKind::Assign(lhs, rhs, _span) => {
+ if let Some(init_kind) = get_vec_init_kind(cx, rhs) {
+ return Some(TargetVec {
+ location: VecLocation::Expr(lhs),
+ init_kind: Some(init_kind),
+ });
+ }
+ },
+ ExprKind::MethodCall(path, _, [self_expr, _], _) if is_reserve(cx, path, self_expr) => {
+ return Some(TargetVec {
+ location: VecLocation::Expr(self_expr),
+ init_kind: None,
+ });
+ },
+ _ => (),
+ },
+ StmtKind::Item(_) => (),
+ }
+ None
+}
+
+fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
+ is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
+ && path.ident.name.as_str() == "reserve"
+}
+
+/// Returns self if the expression is `Vec::set_len()`
+fn extract_set_len_self(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> {
+ // peel unsafe blocks in `unsafe { vec.set_len() }`
+ let expr = peel_hir_expr_while(expr, |e| {
+ if let ExprKind::Block(block, _) = e.kind {
+ // Extract the first statement/expression
+ match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) {
+ (None, Some(expr)) => Some(expr),
+ (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ });
+ match expr.kind {
+ ExprKind::MethodCall(path, _, [self_expr, _], _) => {
+ let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
+ if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
+ Some((self_expr, expr.span))
+ } else {
+ None
+ }
+ },
+ _ => None,
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects `().hash(_)`.
+ ///
+ /// ### Why is this bad?
+ /// Hashing a unit value doesn't do anything as the implementation of `Hash` for `()` is a no-op.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::hash::Hash;
+ /// # use std::collections::hash_map::DefaultHasher;
+ /// # enum Foo { Empty, WithValue(u8) }
+ /// # use Foo::*;
+ /// # let mut state = DefaultHasher::new();
+ /// # let my_enum = Foo::Empty;
+ /// match my_enum {
+ /// Empty => ().hash(&mut state),
+ /// WithValue(x) => x.hash(&mut state),
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::hash::Hash;
+ /// # use std::collections::hash_map::DefaultHasher;
+ /// # enum Foo { Empty, WithValue(u8) }
+ /// # use Foo::*;
+ /// # let mut state = DefaultHasher::new();
+ /// # let my_enum = Foo::Empty;
+ /// match my_enum {
+ /// Empty => 0_u8.hash(&mut state),
+ /// WithValue(x) => x.hash(&mut state),
+ /// }
+ /// ```
++ #[clippy::version = "1.58.0"]
+ pub UNIT_HASH,
+ correctness,
+ "hashing a unit value, which does nothing"
+}
+declare_lint_pass!(UnitHash => [UNIT_HASH]);
+
+impl LateLintPass<'tcx> for UnitHash {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if_chain! {
+ if let ExprKind::MethodCall(name_ident, _, args, _) = &expr.kind;
+ if name_ident.ident.name == sym::hash;
+ if let [recv, state_param] = args;
+ if cx.typeck_results().expr_ty(recv).is_unit();
+ then {
+ span_lint_and_then(
+ cx,
+ UNIT_HASH,
+ expr.span,
+ "this call to `hash` on the unit type will do nothing",
+ |diag| {
+ diag.span_suggestion(
+ expr.span,
+ "remove the call to `hash` or consider using",
+ format!(
+ "0_u8.hash({})",
+ snippet(cx, state_param.span, ".."),
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ diag.note("the implementation of `Hash` for `()` is a no-op");
+ }
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::{get_trait_def_id, paths};
+use if_chain::if_chain;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Expr, ExprKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_middle::ty::{GenericPredicates, PredicateKind, ProjectionPredicate, TraitPredicate};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{BytePos, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions that expect closures of type
+ /// Fn(...) -> Ord where the implemented closure returns the unit type.
+ /// The lint also suggests to remove the semi-colon at the end of the statement if present.
+ ///
+ /// ### Why is this bad?
+ /// Likely, returning the unit type is unintentional, and
+ /// could simply be caused by an extra semi-colon. Since () implements Ord
+ /// it doesn't cause a compilation error.
+ /// This is the same reasoning behind the unit_cmp lint.
+ ///
+ /// ### Known problems
+ /// If returning unit is intentional, then there is no
+ /// way of specifying this without triggering needless_return lint
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut twins = vec!((1, 1), (2, 2));
+ /// twins.sort_by_key(|x| { x.1; });
+ /// ```
++ #[clippy::version = "1.47.0"]
+ pub UNIT_RETURN_EXPECTING_ORD,
+ correctness,
+ "fn arguments of type Fn(...) -> Ord returning the unit type ()."
+}
+
+declare_lint_pass!(UnitReturnExpectingOrd => [UNIT_RETURN_EXPECTING_ORD]);
+
+fn get_trait_predicates_for_trait_id<'tcx>(
+ cx: &LateContext<'tcx>,
+ generics: GenericPredicates<'tcx>,
+ trait_id: Option<DefId>,
+) -> Vec<TraitPredicate<'tcx>> {
+ let mut preds = Vec::new();
+ for (pred, _) in generics.predicates {
+ if_chain! {
+ if let PredicateKind::Trait(poly_trait_pred) = pred.kind().skip_binder();
+ let trait_pred = cx.tcx.erase_late_bound_regions(pred.kind().rebind(poly_trait_pred));
+ if let Some(trait_def_id) = trait_id;
+ if trait_def_id == trait_pred.trait_ref.def_id;
+ then {
+ preds.push(trait_pred);
+ }
+ }
+ }
+ preds
+}
+
+fn get_projection_pred<'tcx>(
+ cx: &LateContext<'tcx>,
+ generics: GenericPredicates<'tcx>,
+ trait_pred: TraitPredicate<'tcx>,
+) -> Option<ProjectionPredicate<'tcx>> {
+ generics.predicates.iter().find_map(|(proj_pred, _)| {
+ if let ty::PredicateKind::Projection(pred) = proj_pred.kind().skip_binder() {
+ let projection_pred = cx.tcx.erase_late_bound_regions(proj_pred.kind().rebind(pred));
+ if projection_pred.projection_ty.substs == trait_pred.trait_ref.substs {
+ return Some(projection_pred);
+ }
+ }
+ None
+ })
+}
+
+fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Vec<(usize, String)> {
+ let mut args_to_check = Vec::new();
+ if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
+ let fn_sig = cx.tcx.fn_sig(def_id);
+ let generics = cx.tcx.predicates_of(def_id);
+ let fn_mut_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().fn_mut_trait());
+ let ord_preds = get_trait_predicates_for_trait_id(cx, generics, get_trait_def_id(cx, &paths::ORD));
+ let partial_ord_preds =
+ get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().partial_ord_trait());
+ // Trying to call erase_late_bound_regions on fn_sig.inputs() gives the following error
+ // The trait `rustc::ty::TypeFoldable<'_>` is not implemented for `&[&rustc::ty::TyS<'_>]`
+ let inputs_output = cx.tcx.erase_late_bound_regions(fn_sig.inputs_and_output());
+ inputs_output
+ .iter()
+ .rev()
+ .skip(1)
+ .rev()
+ .enumerate()
+ .for_each(|(i, inp)| {
+ for trait_pred in &fn_mut_preds {
+ if_chain! {
+ if trait_pred.self_ty() == inp;
+ if let Some(return_ty_pred) = get_projection_pred(cx, generics, *trait_pred);
+ then {
+ if ord_preds.iter().any(|ord| ord.self_ty() == return_ty_pred.ty) {
+ args_to_check.push((i, "Ord".to_string()));
+ } else if partial_ord_preds.iter().any(|pord| pord.self_ty() == return_ty_pred.ty) {
+ args_to_check.push((i, "PartialOrd".to_string()));
+ }
+ }
+ }
+ }
+ });
+ }
+ args_to_check
+}
+
+fn check_arg<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Option<(Span, Option<Span>)> {
+ if_chain! {
+ if let ExprKind::Closure(_, _fn_decl, body_id, span, _) = arg.kind;
+ if let ty::Closure(_def_id, substs) = &cx.typeck_results().node_type(arg.hir_id).kind();
+ let ret_ty = substs.as_closure().sig().output();
+ let ty = cx.tcx.erase_late_bound_regions(ret_ty);
+ if ty.is_unit();
+ then {
+ let body = cx.tcx.hir().body(body_id);
+ if_chain! {
+ if let ExprKind::Block(block, _) = body.value.kind;
+ if block.expr.is_none();
+ if let Some(stmt) = block.stmts.last();
+ if let StmtKind::Semi(_) = stmt.kind;
+ then {
+ let data = stmt.span.data();
+ // Make a span out of the semicolon for the help message
+ Some((span, Some(data.with_lo(data.hi-BytePos(1)))))
+ } else {
+ Some((span, None))
+ }
+ }
+ } else {
+ None
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnitReturnExpectingOrd {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if let ExprKind::MethodCall(_, _, args, _) = expr.kind {
+ let arg_indices = get_args_to_check(cx, expr);
+ for (i, trait_name) in arg_indices {
+ if i < args.len() {
+ match check_arg(cx, &args[i]) {
+ Some((span, None)) => {
+ span_lint(
+ cx,
+ UNIT_RETURN_EXPECTING_ORD,
+ span,
+ &format!(
+ "this closure returns \
+ the unit type which also implements {}",
+ trait_name
+ ),
+ );
+ },
+ Some((span, Some(last_semi))) => {
+ span_lint_and_help(
+ cx,
+ UNIT_RETURN_EXPECTING_ORD,
+ span,
+ &format!(
+ "this closure returns \
+ the unit type which also implements {}",
+ trait_name
+ ),
+ Some(last_semi),
+ &"probably caused by this trailing semicolon".to_string(),
+ );
+ },
+ None => {},
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+mod let_unit_value;
+mod unit_arg;
+mod unit_cmp;
+mod utils;
+
+use rustc_hir::{Expr, Stmt};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let x = {
+ /// 1;
+ /// };
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub LET_UNIT_VALUE,
+ pedantic,
+ "creating a `let` binding to a value of unit type, which usually can't be used afterwards"
+}
+
+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.
+ ///
+ /// ### 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
++ #[clippy::version = "pre 1.29.0"]
+ pub UNIT_CMP,
+ correctness,
+ "comparing unit values"
+}
+
+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.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// foo({
+ /// let a = bar();
+ /// baz(a);
+ /// })
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNIT_ARG,
+ complexity,
+ "passing unit to a function"
+}
+
+declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]);
+
+impl LateLintPass<'_> for UnitTypes {
+ fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
+ let_unit_value::check(cx, stmt);
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ unit_cmp::check(cx, expr);
+ unit_arg::check(cx, expr);
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
+use clippy_utils::{match_def_path, paths};
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// type F = fn();
+ /// fn a() {}
+ /// let f: F = a;
+ /// if f == a {
+ /// // ...
+ /// }
+ /// ```
++ #[clippy::version = "1.44.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// let a: Rc<dyn Trait> = ...
+ /// let b: Rc<dyn Trait> = ...
+ /// if Rc::ptr_eq(&a, &b) {
+ /// ...
+ /// }
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub VTABLE_ADDRESS_COMPARISONS,
+ correctness,
+ "comparison with an address of a trait vtable"
+}
+
+declare_lint_pass!(UnnamedAddress => [FN_ADDRESS_COMPARISONS, VTABLE_ADDRESS_COMPARISONS]);
+
+impl LateLintPass<'_> for UnnamedAddress {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ fn is_comparison(binop: BinOpKind) -> bool {
+ matches!(
+ binop,
+ BinOpKind::Eq | BinOpKind::Lt | BinOpKind::Le | BinOpKind::Ne | BinOpKind::Ge | BinOpKind::Gt
+ )
+ }
+
+ fn is_trait_ptr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ match cx.typeck_results().expr_ty_adjusted(expr).kind() {
+ ty::RawPtr(ty::TypeAndMut { ty, .. }) => ty.is_trait(),
+ _ => false,
+ }
+ }
+
+ fn is_fn_def(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ matches!(cx.typeck_results().expr_ty(expr).kind(), ty::FnDef(..))
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(binop, left, 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(func, [ref _left, ref _right]) = expr.kind;
+ if let ExprKind::Path(ref func_qpath) = func.kind;
+ if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
+ if match_def_path(cx, def_id, &paths::PTR_EQ) ||
+ match_def_path(cx, def_id, &paths::RC_PTR_EQ) ||
+ match_def_path(cx, def_id, &paths::ARC_PTR_EQ);
+ let ty_param = cx.typeck_results().node_substs(func.hir_id).type_at(0);
+ if ty_param.is_trait();
+ then {
+ span_lint_and_help(
+ cx,
+ VTABLE_ADDRESS_COMPARISONS,
+ expr.span,
+ "comparing trait object pointers compares a non-unique vtable address",
+ None,
+ "consider extracting and comparing data pointers only",
+ );
+ }
+ }
+
+ if_chain! {
+ if let ExprKind::Binary(binop, left, right) = expr.kind;
+ if is_comparison(binop.node);
+ if cx.typeck_results().expr_ty_adjusted(left).is_fn_ptr();
+ if cx.typeck_results().expr_ty_adjusted(right).is_fn_ptr();
+ if is_fn_def(cx, left) || is_fn_def(cx, right);
+ then {
+ span_lint(
+ cx,
+ FN_ADDRESS_COMPARISONS,
+ expr.span,
+ "comparing with a non-unique address of a function item",
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use if_chain::if_chain;
+use rustc_ast::{Item, ItemKind, UseTreeKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::kw;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for imports ending in `::{self}`.
+ ///
+ /// ### Why is this bad?
+ /// In most cases, this can be written much more cleanly by omitting `::{self}`.
+ ///
+ /// ### Known problems
+ /// Removing `::{self}` will cause any non-module items at the same path to also be imported.
+ /// This might cause a naming conflict (https://github.com/rust-lang/rustfmt/issues/3568). This lint makes no attempt
+ /// to detect this scenario and that is why it is a restriction lint.
+ ///
+ /// ### Example
+ /// ```rust
+ /// use std::io::{self};
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// use std::io;
+ /// ```
++ #[clippy::version = "1.53.0"]
+ pub UNNECESSARY_SELF_IMPORTS,
+ restriction,
+ "imports ending in `::{self}`, which can be omitted"
+}
+
+declare_lint_pass!(UnnecessarySelfImports => [UNNECESSARY_SELF_IMPORTS]);
+
+impl EarlyLintPass for UnnecessarySelfImports {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if_chain! {
+ if let ItemKind::Use(use_tree) = &item.kind;
+ if let UseTreeKind::Nested(nodes) = &use_tree.kind;
+ if let [(self_tree, _)] = &**nodes;
+ if let [self_seg] = &*self_tree.prefix.segments;
+ if self_seg.ident.name == kw::SelfLower;
+ if let Some(last_segment) = use_tree.prefix.segments.last();
+
+ then {
+ span_lint_and_then(
+ cx,
+ UNNECESSARY_SELF_IMPORTS,
+ item.span,
+ "import ending with `::{self}`",
+ |diag| {
+ diag.span_suggestion(
+ last_segment.span().with_hi(item.span.hi()),
+ "consider omitting `::{self}`",
+ format!(
+ "{}{};",
+ last_segment.ident,
+ if let UseTreeKind::Simple(Some(alias), ..) = self_tree.kind { format!(" as {}", alias) } else { String::new() },
+ ),
+ Applicability::MaybeIncorrect,
+ );
+ diag.note("this will slightly change semantics; any non-module items at the same path will also be imported");
+ },
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::{self, subst::GenericArgKind};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+use rustc_span::symbol::Ident;
+use std::iter;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects uses of `Vec::sort_by` passing in a closure
+ /// which compares the two arguments, either directly or indirectly.
+ ///
+ /// ### Why is this bad?
+ /// It is more clear to use `Vec::sort_by_key` (or `Vec::sort` if
+ /// possible) than to use `Vec::sort_by` and a more complicated
+ /// closure.
+ ///
+ /// ### Known problems
+ /// If the suggested `Vec::sort_by_key` uses Reverse and it isn't already
+ /// imported by a use statement, then it will need to be added manually.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct A;
+ /// # impl A { fn foo(&self) {} }
+ /// # let mut vec: Vec<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());
+ /// ```
++ #[clippy::version = "1.46.0"]
+ 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)) => {
+ iter::zip(*left_exprs, *right_exprs).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)
+ && iter::zip(*left_args, *right_args)
+ .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
+ && iter::zip(*left_args, *right_args)
+ .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)) => {
+ iter::zip(*left_exprs, *right_exprs).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,
+ ..
+ },
+ )),
+ ) => {
+ (iter::zip(*left_segments, *right_segments).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 is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(vec), sym::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 == sym::cmp;
+ then {
+ let (closure_body, closure_arg, reverse) = if mirrored_exprs(
+ cx,
+ left_expr,
+ left_ident,
+ right_expr,
+ right_ident
+ ) {
+ (Sugg::hir(cx, left_expr, "..").to_string(), left_ident.name.to_string(), false)
+ } else if mirrored_exprs(cx, left_expr, right_ident, right_expr, left_ident) {
+ (Sugg::hir(cx, left_expr, "..").to_string(), right_ident.name.to_string(), true)
+ } else {
+ return None;
+ };
+ let vec_name = Sugg::hir(cx, &args[0], "..").to_string();
+ let unstable = name == "sort_unstable_by";
+
+ if_chain! {
+ if let ExprKind::Path(QPath::Resolved(_, Path {
+ segments: [PathSegment { ident: left_name, .. }], ..
+ })) = &left_expr.kind;
+ if left_name == left_ident;
+ if cx.tcx.get_diagnostic_item(sym::Ord).map_or(false, |id| {
+ implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[])
+ });
+ then {
+ return Some(LintTrigger::Sort(SortDetection { vec_name, unstable }));
+ }
+ }
+
+ if !expr_borrows(cx, left_expr) {
+ return Some(LintTrigger::SortByKey(SortByKeyDetection {
+ vec_name,
+ closure_arg,
+ closure_body,
+ reverse,
+ unstable,
+ }));
+ }
+ }
+ }
+
+ None
+}
+
+fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let ty = cx.typeck_results().expr_ty(expr);
+ matches!(ty.kind(), ty::Ref(..))
+ || ty
+ .walk(cx.tcx)
+ .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
+}
+
+impl LateLintPass<'_> for UnnecessarySortBy {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ match detect_lint(cx, expr) {
+ Some(LintTrigger::SortByKey(trigger)) => 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)) => 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
- use clippy_utils::{contains_return, in_macro, is_lang_ctor, return_ty, visitors::find_all_ret_expressions};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::source::snippet;
- if !in_macro(ret_expr.span);
++use clippy_utils::{contains_return, is_lang_ctor, return_ty, visitors::find_all_ret_expressions};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::LangItem::{OptionSome, ResultOk};
+use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for private functions that only return `Ok` or `Some`.
+ ///
+ /// ### Why is this bad?
+ /// It is not meaningful to wrap values when no `None` or `Err` is returned.
+ ///
+ /// ### Known problems
+ /// There can be false positives if the function signature is designed to
+ /// fit some external requirement.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn get_cool_number(a: bool, b: bool) -> Option<i32> {
+ /// if a && b {
+ /// return Some(50);
+ /// }
+ /// if a {
+ /// Some(0)
+ /// } else {
+ /// Some(10)
+ /// }
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn get_cool_number(a: bool, b: bool) -> i32 {
+ /// if a && b {
+ /// return 50;
+ /// }
+ /// if a {
+ /// 0
+ /// } else {
+ /// 10
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub UNNECESSARY_WRAPS,
+ pedantic,
+ "functions that only return `Ok` or `Some`"
+}
+
+pub struct UnnecessaryWraps {
+ avoid_breaking_exported_api: bool,
+}
+
+impl_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]);
+
+impl UnnecessaryWraps {
+ pub fn new(avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ fn_kind: FnKind<'tcx>,
+ fn_decl: &FnDecl<'tcx>,
+ body: &Body<'tcx>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ // Abort if public function/method or closure.
+ match fn_kind {
+ FnKind::ItemFn(..) | FnKind::Method(..) => {
+ let def_id = cx.tcx.hir().local_def_id(hir_id);
+ if self.avoid_breaking_exported_api && cx.access_levels.is_exported(def_id) {
+ return;
+ }
+ },
+ FnKind::Closure => return,
+ }
+
+ // Abort if the method is implementing a trait or of it a trait method.
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
+ if matches!(
+ item.kind,
+ ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
+ ) {
+ return;
+ }
+ }
+
+ // Get the wrapper and inner types, if can't, abort.
+ let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
+ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did) {
+ ("Option", OptionSome, subst.type_at(0))
+ } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did) {
+ ("Result", ResultOk, subst.type_at(0))
+ } else {
+ return;
+ }
+ } else {
+ return;
+ };
+
+ // Check if all return expression respect the following condition and collect them.
+ let mut suggs = Vec::new();
+ let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| {
+ if_chain! {
++ if !ret_expr.span.from_expansion();
+ // Check if a function call.
+ if let ExprKind::Call(func, [arg]) = ret_expr.kind;
+ // Check if OPTION_SOME or RESULT_OK, depending on return type.
+ if let ExprKind::Path(qpath) = &func.kind;
+ if is_lang_ctor(cx, qpath, lang_item);
+ // Make sure the function argument does not contain a return expression.
+ if !contains_return(arg);
+ then {
+ suggs.push(
+ (
+ ret_expr.span,
+ if inner_type.is_unit() {
+ "".to_string()
+ } else {
+ snippet(cx, arg.span.source_callsite(), "..").to_string()
+ }
+ )
+ );
+ true
+ } else {
+ false
+ }
+ }
+ });
+
+ if can_sugg && !suggs.is_empty() {
+ let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() {
+ (
+ "this function's return value is unnecessary".to_string(),
+ "remove the return type...".to_string(),
+ snippet(cx, fn_decl.output.span(), "..").to_string(),
+ "...and then remove returned values",
+ )
+ } else {
+ (
+ format!(
+ "this function's return value is unnecessarily wrapped by `{}`",
+ return_type_label
+ ),
+ format!("remove `{}` from the return type...", return_type_label),
+ inner_type.to_string(),
+ "...and then change returning expressions",
+ )
+ };
+
+ span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| {
+ diag.span_suggestion(
+ fn_decl.output.span(),
+ return_type_sugg_msg.as_str(),
+ return_type_sugg,
+ Applicability::MaybeIncorrect,
+ );
+ diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect);
+ });
+ }
+ }
+}
--- /dev/null
+#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
+
+use clippy_utils::ast_utils::{eq_field_pat, eq_id, eq_maybe_qself, eq_pat, eq_path};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{meets_msrv, msrvs, over};
+use rustc_ast::mut_visit::*;
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
+use rustc_ast_pretty::pprust;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn main() {
+ /// if let Some(0) | Some(2) = Some(0) {}
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn main() {
+ /// if let Some(0 | 2) = Some(0) {}
+ /// }
+ /// ```
++ #[clippy::version = "1.46.0"]
+ pub UNNESTED_OR_PATTERNS,
+ pedantic,
+ "unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`"
+}
+
+#[derive(Clone, Copy)]
+pub struct UnnestedOrPatterns {
+ msrv: Option<RustcVersion>,
+}
+
+impl UnnestedOrPatterns {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]);
+
+impl EarlyLintPass for UnnestedOrPatterns {
+ fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) {
+ if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
+ lint_unnested_or_patterns(cx, &a.pat);
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
+ if let ast::ExprKind::Let(pat, _, _) = &e.kind {
+ lint_unnested_or_patterns(cx, pat);
+ }
+ }
+ }
+
+ fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) {
+ if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
+ lint_unnested_or_patterns(cx, &p.pat);
+ }
+ }
+
+ fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) {
+ if meets_msrv(self.msrv.as_ref(), &msrvs::OR_PATTERNS) {
+ lint_unnested_or_patterns(cx, &l.pat);
+ }
+ }
+
+ extract_msrv_attr!(EarlyContext);
+}
+
+fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) {
+ 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(qself1, path1, ps1) => extend_with_matching_product(
+ ps1, start, alternatives,
+ |k, ps1, idx| matches!(
+ k,
+ TupleStruct(qself2, path2, ps2)
+ if eq_maybe_qself(qself1, qself2) && 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(qself1, path1, fps1, rest1) => extend_with_struct_pat(qself1, 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(
+ qself1: &Option<ast::QSelf>,
+ path1: &ast::Path,
+ fps1: &mut Vec<ast::PatField>,
+ 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(qself2, path2, fps2, rest2)
+ if rest1 == *rest2 // If one struct pattern has `..` so must the other.
+ && eq_maybe_qself(qself1, qself2)
+ && 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,
+ tokens: None,
+ };
+ mem::replace(from, dummy)
+}
+
+/// Extend `target` as an or-pattern with the alternatives
+/// in `tail_or` if there are any and return if there were.
+fn extend_with_tail_or(target: &mut Pat, tail_or: Vec<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
+use clippy_utils::diagnostics::span_lint;
+use rustc_ast::ast::{Item, ItemKind, UseTree, UseTreeKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::symbol::Ident;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for imports that remove "unsafe" from an item's
+ /// name.
+ ///
+ /// ### Why is this bad?
+ /// Renaming makes it less clear which traits and
+ /// structures are unsafe.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::cell::{UnsafeCell as TotallySafeCell};
+ ///
+ /// extern crate crossbeam;
+ /// use crossbeam::{spawn_unsafe as spawn};
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNSAFE_REMOVED_FROM_NAME,
+ style,
+ "`unsafe` removed from API names on import"
+}
+
+declare_lint_pass!(UnsafeNameRemoval => [UNSAFE_REMOVED_FROM_NAME]);
+
+impl EarlyLintPass for UnsafeNameRemoval {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ if let ItemKind::Use(ref use_tree) = item.kind {
+ check_use_tree(use_tree, cx, item.span);
+ }
+ }
+}
+
+fn check_use_tree(use_tree: &UseTree, cx: &EarlyContext<'_>, span: Span) {
+ match use_tree.kind {
+ UseTreeKind::Simple(Some(new_name), ..) => {
+ let old_name = use_tree
+ .prefix
+ .segments
+ .last()
+ .expect("use paths cannot be empty")
+ .ident;
+ unsafe_to_safe_check(old_name, new_name, cx, span);
+ },
+ UseTreeKind::Simple(None, ..) | UseTreeKind::Glob => {},
+ UseTreeKind::Nested(ref nested_use_tree) => {
+ for &(ref use_tree, _) in nested_use_tree {
+ check_use_tree(use_tree, cx, span);
+ }
+ },
+ }
+}
+
+fn unsafe_to_safe_check(old_name: Ident, new_name: Ident, cx: &EarlyContext<'_>, span: Span) {
+ let old_str = old_name.name.as_str();
+ let new_str = new_name.name.as_str();
+ if contains_unsafe(&old_str) && !contains_unsafe(&new_str) {
+ span_lint(
+ cx,
+ UNSAFE_REMOVED_FROM_NAME,
+ span,
+ &format!(
+ "removed `unsafe` from the name of `{}` in use as `{}`",
+ old_str, new_str
+ ),
+ );
+ }
+}
+
+#[must_use]
+fn contains_unsafe(name: &str) -> bool {
+ name.contains("Unsafe") || name.contains("unsafe")
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, YieldSource};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions that are declared `async` but have no `.await`s inside of them.
+ ///
+ /// ### Why is this bad?
+ /// Async functions with no async code create overhead, both mentally and computationally.
+ /// Callers of async methods either need to be calling from an async function themselves or run it on an executor, both of which
+ /// causes runtime overhead and hassle for the caller.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// async fn get_random_number() -> i64 {
+ /// 4 // Chosen by fair dice roll. Guaranteed to be random.
+ /// }
+ /// let number_future = get_random_number();
+ ///
+ /// // Good
+ /// fn get_random_number_improved() -> i64 {
+ /// 4 // Chosen by fair dice roll. Guaranteed to be random.
+ /// }
+ /// let number_future = async { get_random_number_improved() };
+ /// ```
++ #[clippy::version = "1.54.0"]
+ pub UNUSED_ASYNC,
+ pedantic,
+ "finds async functions with no await statements"
+}
+
+declare_lint_pass!(UnusedAsync => [UNUSED_ASYNC]);
+
+struct AsyncFnVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ found_await: bool,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
+ if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind {
+ self.found_await = true;
+ }
+ walk_expr(self, ex);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ fn_kind: FnKind<'tcx>,
+ fn_decl: &'tcx FnDecl<'tcx>,
+ body: &Body<'tcx>,
+ span: Span,
+ hir_id: HirId,
+ ) {
+ if let FnKind::ItemFn(_, _, FnHeader { asyncness, .. }, _) = &fn_kind {
+ if matches!(asyncness, IsAsync::Async) {
+ let mut visitor = AsyncFnVisitor { cx, found_await: false };
+ walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id);
+ if !visitor.found_await {
+ span_lint_and_help(
+ cx,
+ UNUSED_ASYNC,
+ span,
+ "unused `async` for function with no await statements",
+ None,
+ "consider removing the `async` from this function",
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::{is_try, match_trait_method, paths};
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unused written/read amount.
+ ///
+ /// ### Why is this bad?
+ /// `io::Write::write(_vectored)` and
+ /// `io::Read::read(_vectored)` are not guaranteed to
+ /// process the entire buffer. They return how many bytes were processed, which
+ /// might be smaller
+ /// than a given buffer's length. If you don't need to deal with
+ /// partial-write/read, use
+ /// `write_all`/`read_exact` instead.
+ ///
+ /// ### Known problems
+ /// Detects only common patterns.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// use std::io;
+ /// fn foo<W: io::Write>(w: &mut W) -> io::Result<()> {
+ /// // must be `w.write_all(b"foo")?;`
+ /// w.write(b"foo")?;
+ /// Ok(())
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNUSED_IO_AMOUNT,
+ correctness,
+ "unused written/read amount"
+}
+
+declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
+ fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+ let expr = match s.kind {
+ hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr,
+ _ => return,
+ };
+
+ match expr.kind {
+ hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
+ if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
+ if matches!(
+ func.kind,
+ hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, _))
+ ) {
+ check_map_error(cx, arg_0, expr);
+ }
+ } else {
+ check_map_error(cx, res, expr);
+ }
+ },
+ hir::ExprKind::MethodCall(path, _, [ref arg_0, ..], _) => match &*path.ident.as_str() {
+ "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => {
+ check_map_error(cx, arg_0, expr);
+ },
+ _ => (),
+ },
+ _ => (),
+ }
+ }
+}
+
+fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
+ let mut call = call;
+ while let hir::ExprKind::MethodCall(path, _, args, _) = call.kind {
+ if matches!(&*path.ident.as_str(), "or" | "or_else" | "ok") {
+ call = &args[0];
+ } else {
+ break;
+ }
+ }
+ check_method_call(cx, call, expr);
+}
+
+fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
+ if let hir::ExprKind::MethodCall(path, _, _, _) = call.kind {
+ let symbol = &*path.ident.as_str();
+ let read_trait = match_trait_method(cx, call, &paths::IO_READ);
+ let write_trait = match_trait_method(cx, call, &paths::IO_WRITE);
+
+ match (read_trait, write_trait, symbol) {
+ (true, _, "read") => span_lint(
+ cx,
+ UNUSED_IO_AMOUNT,
+ expr.span,
+ "read amount is not handled. Use `Read::read_exact` instead",
+ ),
+ (true, _, "read_vectored") => span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled"),
+ (_, true, "write") => span_lint(
+ cx,
+ UNUSED_IO_AMOUNT,
+ expr.span,
+ "written amount is not handled. Use `Write::write_all` instead",
+ ),
+ (_, true, "write_vectored") => span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled"),
+ _ => (),
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::visitors::is_local_used;
+use if_chain::if_chain;
+use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks methods that contain a `self` argument but don't use it
+ ///
+ /// ### Why is this bad?
+ /// It may be clearer to define the method as an associated function instead
+ /// of an instance method if it doesn't require `self`.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// struct A;
+ /// impl A {
+ /// fn method(&self) {}
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust,ignore
+ /// struct A;
+ /// impl A {
+ /// fn method() {}
+ /// }
+ /// ```
++ #[clippy::version = "1.40.0"]
+ pub UNUSED_SELF,
+ pedantic,
+ "methods that contain a `self` argument but don't use it"
+}
+
+declare_lint_pass!(UnusedSelf => [UNUSED_SELF]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'_>) {
+ if impl_item.span.from_expansion() {
+ return;
+ }
+ let parent = cx.tcx.hir().get_parent_did(impl_item.hir_id());
+ let parent_item = cx.tcx.hir().expect_item(parent);
+ let assoc_item = cx.tcx.associated_item(impl_item.def_id);
+ if_chain! {
+ if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind;
+ if assoc_item.fn_has_self_parameter;
+ if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
+ let body = cx.tcx.hir().body(*body_id);
+ if let [self_param, ..] = body.params;
+ if !is_local_used(cx, body, self_param.pat.hir_id);
+ then {
+ span_lint_and_help(
+ cx,
+ UNUSED_SELF,
+ self_param.span,
+ "unused `self` argument",
+ None,
+ "consider refactoring to a associated function",
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::{position_before_rarrow, snippet_opt};
+use if_chain::if_chain;
+use rustc_ast::ast;
+use rustc_ast::visit::FnKind;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::BytePos;
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn return_unit() -> () {
+ /// ()
+ /// }
+ /// ```
+ /// is equivalent to
+ /// ```rust
+ /// fn return_unit() {}
+ /// ```
++ #[clippy::version = "1.31.0"]
+ pub UNUSED_UNIT,
+ style,
+ "needless unit expression"
+}
+
+declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
+
+impl EarlyLintPass for UnusedUnit {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
+ if_chain! {
+ if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
+ if let ast::TyKind::Tup(ref vals) = ty.kind;
+ if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
+ then {
+ lint_unneeded_unit_return(cx, ty, span);
+ }
+ }
+ }
+
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+ if_chain! {
+ if let Some(stmt) = block.stmts.last();
+ if let ast::StmtKind::Expr(ref expr) = stmt.kind;
+ if is_unit_expr(expr);
+ let ctxt = block.span.ctxt();
+ if stmt.span.ctxt() == ctxt && expr.span.ctxt() == ctxt;
+ then {
+ let sp = expr.span;
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ sp,
+ "unneeded unit expression",
+ "remove the final `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ match e.kind {
+ ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
+ if is_unit_expr(expr) && !expr.span.from_expansion() {
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ expr.span,
+ "unneeded `()`",
+ "remove the `()`",
+ String::new(),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) {
+ let segments = &poly.trait_ref.path.segments;
+
+ if_chain! {
+ if segments.len() == 1;
+ if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str());
+ if let Some(args) = &segments[0].args;
+ if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
+ if let ast::FnRetTy::Ty(ty) = &generic_args.output;
+ if ty.kind.is_unit();
+ then {
+ lint_unneeded_unit_return(cx, ty, generic_args.span);
+ }
+ }
+ }
+}
+
+// get the def site
+#[must_use]
+fn get_def(span: Span) -> Option<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) =
+ snippet_opt(cx, span.with_hi(ty.span.hi())).map_or((ty.span, Applicability::MaybeIncorrect), |fn_source| {
+ position_before_rarrow(&fn_source).map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
+ (
+ #[allow(clippy::cast_possible_truncation)]
+ ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
+ Applicability::MachineApplicable,
+ )
+ })
+ });
+ span_lint_and_sugg(
+ cx,
+ UNUSED_UNIT,
+ ret_span,
+ "unneeded unit return type",
+ "remove the `-> ()`",
+ String::new(),
+ appl,
+ );
+}
--- /dev/null
- if let ExprKind::MethodCall(method_name, _, args, _) = expr.kind;
- if let Some(id) = path_to_local(&args[0]);
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::higher;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{differing_macro_contexts, path_to_local, usage::is_potentially_mutated};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, PathSegment, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::Ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Span;
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls of `unwrap[_err]()` that cannot fail.
+ ///
+ /// ### Why is this bad?
+ /// Using `if let` or `match` is more idiomatic.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let option = Some(0);
+ /// # fn do_something_with(_x: usize) {}
+ /// if option.is_some() {
+ /// do_something_with(option.unwrap())
+ /// }
+ /// ```
+ ///
+ /// Could be written:
+ ///
+ /// ```rust
+ /// # let option = Some(0);
+ /// # fn do_something_with(_x: usize) {}
+ /// if let Some(value) = option {
+ /// do_something_with(value)
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub UNNECESSARY_UNWRAP,
+ complexity,
+ "checks for calls of `unwrap[_err]()` that cannot fail"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls of `unwrap[_err]()` that will always fail.
+ ///
+ /// ### Why is this bad?
+ /// If panicking is desired, an explicit `panic!()` should be used.
+ ///
+ /// ### Known problems
+ /// This lint only checks `if` conditions not assignments.
+ /// So something like `let x: Option<()> = None; x.unwrap();` will not be recognized.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let option = Some(0);
+ /// # fn do_something_with(_x: usize) {}
+ /// if option.is_none() {
+ /// do_something_with(option.unwrap())
+ /// }
+ /// ```
+ ///
+ /// This code will always panic. The if condition should probably be inverted.
++ #[clippy::version = "pre 1.29.0"]
+ pub PANICKING_UNWRAP,
+ correctness,
+ "checks for calls of `unwrap[_err]()` that will always fail"
+}
+
+/// Visitor that keeps track of which variables are unwrappable.
+struct UnwrappableVariablesVisitor<'a, 'tcx> {
+ unwrappables: Vec<UnwrapInfo<'tcx>>,
+ cx: &'a LateContext<'tcx>,
+}
+
+/// What kind of unwrappable this is.
+#[derive(Copy, Clone, Debug)]
+enum UnwrappableKind {
+ Option,
+ Result,
+}
+
+impl UnwrappableKind {
+ fn success_variant_pattern(self) -> &'static str {
+ match self {
+ UnwrappableKind::Option => "Some(..)",
+ UnwrappableKind::Result => "Ok(..)",
+ }
+ }
+
+ fn error_variant_pattern(self) -> &'static str {
+ match self {
+ UnwrappableKind::Option => "None",
+ UnwrappableKind::Result => "Err(..)",
+ }
+ }
+}
+
+/// Contains information about whether a variable can be unwrapped.
+#[derive(Copy, Clone, Debug)]
+struct UnwrapInfo<'tcx> {
+ /// The variable that is checked
+ local_id: HirId,
+ /// The if itself
+ if_expr: &'tcx Expr<'tcx>,
+ /// The check, like `x.is_ok()`
+ check: &'tcx Expr<'tcx>,
+ /// The check's name, like `is_ok`
+ check_name: &'tcx PathSegment<'tcx>,
+ /// The branch where the check takes place, like `if x.is_ok() { .. }`
+ branch: &'tcx Expr<'tcx>,
+ /// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
+ safe_to_unwrap: bool,
+ /// What kind of unwrappable this is.
+ kind: UnwrappableKind,
+ /// If the check is the entire condition (`if x.is_ok()`) or only a part of it (`foo() &&
+ /// x.is_ok()`)
+ is_entire_condition: bool,
+}
+
+/// Collects the information about unwrappable variables from an if condition
+/// The `invert` argument tells us whether the condition is negated.
+fn collect_unwrap_info<'tcx>(
+ cx: &LateContext<'tcx>,
+ if_expr: &'tcx Expr<'_>,
+ expr: &'tcx Expr<'_>,
+ branch: &'tcx Expr<'_>,
+ invert: bool,
+ is_entire_condition: bool,
+) -> Vec<UnwrapInfo<'tcx>> {
+ fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
+ is_type_diagnostic_item(cx, ty, sym::Option) && ["is_some", "is_none"].contains(&method_name)
+ }
+
+ fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
+ is_type_diagnostic_item(cx, ty, sym::Result) && ["is_ok", "is_err"].contains(&method_name)
+ }
+
+ if let ExprKind::Binary(op, left, right) = &expr.kind {
+ match (invert, op.node) {
+ (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => {
+ let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
+ unwrap_info.append(&mut collect_unwrap_info(cx, if_expr, right, branch, invert, false));
+ return unwrap_info;
+ },
+ _ => (),
+ }
+ } else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind {
+ return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false);
+ } else {
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
+ if let Some(local_id) = path_to_local(&args[0]);
+ let ty = cx.typeck_results().expr_ty(&args[0]);
+ let name = method_name.ident.as_str();
+ if is_relevant_option_call(cx, ty, &name) || is_relevant_result_call(cx, ty, &name);
+ then {
+ assert!(args.len() == 1);
+ let unwrappable = match name.as_ref() {
+ "is_some" | "is_ok" => true,
+ "is_err" | "is_none" => false,
+ _ => unreachable!(),
+ };
+ let safe_to_unwrap = unwrappable != invert;
+ let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
+ UnwrappableKind::Option
+ } else {
+ UnwrappableKind::Result
+ };
+
+ return vec![
+ UnwrapInfo {
+ local_id,
+ if_expr,
+ check: expr,
+ check_name: method_name,
+ branch,
+ safe_to_unwrap,
+ kind,
+ is_entire_condition,
+ }
+ ]
+ }
+ }
+ }
+ Vec::new()
+}
+
+impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
+ fn visit_branch(
+ &mut self,
+ if_expr: &'tcx Expr<'_>,
+ cond: &'tcx Expr<'_>,
+ branch: &'tcx Expr<'_>,
+ else_branch: bool,
+ ) {
+ let prev_len = self.unwrappables.len();
+ for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
+ if is_potentially_mutated(unwrap_info.local_id, cond, self.cx)
+ || is_potentially_mutated(unwrap_info.local_id, branch, self.cx)
+ {
+ // if the variable is mutated, we don't know whether it can be unwrapped:
+ continue;
+ }
+ self.unwrappables.push(unwrap_info);
+ }
+ walk_expr(self, branch);
+ self.unwrappables.truncate(prev_len);
+ }
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // Shouldn't lint when `expr` is in macro.
+ if in_external_macro(self.cx.tcx.sess, expr.span) {
+ return;
+ }
+ if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr) {
+ walk_expr(self, cond);
+ self.visit_branch(expr, cond, then, false);
+ if let Some(else_inner) = r#else {
+ self.visit_branch(expr, cond, else_inner, true);
+ }
+ } else {
+ // find `unwrap[_err]()` calls:
+ if_chain! {
++ if let ExprKind::MethodCall(method_name, _, [self_arg, ..], _) = expr.kind;
++ if let Some(id) = path_to_local(self_arg);
+ if [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name);
+ let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name);
+ if let Some(unwrappable) = self.unwrappables.iter()
+ .find(|u| u.local_id == id);
+ // Span contexts should not differ with the conditional branch
+ if !differing_macro_contexts(unwrappable.branch.span, expr.span);
+ if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span);
+ then {
+ if call_to_unwrap == unwrappable.safe_to_unwrap {
+ let is_entire_condition = unwrappable.is_entire_condition;
+ let unwrappable_variable_name = self.cx.tcx.hir().name(unwrappable.local_id);
+ let suggested_pattern = if call_to_unwrap {
+ unwrappable.kind.success_variant_pattern()
+ } else {
+ unwrappable.kind.error_variant_pattern()
+ };
+
+ span_lint_and_then(
+ self.cx,
+ UNNECESSARY_UNWRAP,
+ expr.span,
+ &format!(
+ "called `{}` on `{}` after checking its variant with `{}`",
+ method_name.ident.name,
+ unwrappable_variable_name,
+ unwrappable.check_name.ident.as_str(),
+ ),
+ |diag| {
+ if is_entire_condition {
+ diag.span_suggestion(
+ unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()),
+ "try",
+ format!(
+ "if let {} = {}",
+ suggested_pattern,
+ unwrappable_variable_name,
+ ),
+ // We don't track how the unwrapped value is used inside the
+ // block or suggest deleting the unwrap, so we can't offer a
+ // fixable solution.
+ Applicability::Unspecified,
+ );
+ } else {
+ diag.span_label(unwrappable.check.span, "the check is happening here");
+ diag.help("try using `if let` or `match`");
+ }
+ },
+ );
+ } else {
+ span_lint_and_then(
+ self.cx,
+ PANICKING_UNWRAP,
+ expr.span,
+ &format!("this call to `{}()` will always panic",
+ method_name.ident.name),
+ |diag| { diag.span_label(unwrappable.check.span, "because of this check"); },
+ );
+ }
+ }
+ }
+ walk_expr(self, expr);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
+declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]);
+
+impl<'tcx> LateLintPass<'tcx> for Unwrap {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: FnKind<'tcx>,
+ decl: &'tcx FnDecl<'_>,
+ body: &'tcx Body<'_>,
+ span: Span,
+ fn_id: HirId,
+ ) {
+ if span.from_expansion() {
+ return;
+ }
+
+ let mut v = UnwrappableVariablesVisitor {
+ cx,
+ unwrappables: Vec::new(),
+ };
+
+ walk_fn(&mut v, kind, decl, body.id(), span, fn_id);
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{method_chain_args, return_ty};
+use if_chain::if_chain;
+use rustc_hir as hir;
+use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
+use rustc_hir::{Expr, ImplItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{sym, Span};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for functions of type `Result` that contain `expect()` or `unwrap()`
+ ///
+ /// ### Why is this bad?
+ /// These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics.
+ ///
+ /// ### Known problems
+ /// This can cause false positives in functions that handle both recoverable and non recoverable errors.
+ ///
+ /// ### Example
+ /// Before:
+ /// ```rust
+ /// fn divisible_by_3(i_str: String) -> Result<(), String> {
+ /// let i = i_str
+ /// .parse::<i32>()
+ /// .expect("cannot divide the input by three");
+ ///
+ /// if i % 3 != 0 {
+ /// Err("Number is not divisible by 3")?
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ ///
+ /// After:
+ /// ```rust
+ /// fn divisible_by_3(i_str: String) -> Result<(), String> {
+ /// let i = i_str
+ /// .parse::<i32>()
+ /// .map_err(|e| format!("cannot divide the input by three: {}", e))?;
+ ///
+ /// if i % 3 != 0 {
+ /// Err("Number is not divisible by 3")?
+ /// }
+ ///
+ /// Ok(())
+ /// }
+ /// ```
++ #[clippy::version = "1.48.0"]
+ pub UNWRAP_IN_RESULT,
+ restriction,
+ "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`"
+}
+
+declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]);
+
+impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
+ if_chain! {
+ // first check if it's a method or function
+ if let hir::ImplItemKind::Fn(ref _signature, _) = impl_item.kind;
+ // checking if its return type is `result` or `option`
+ if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Result)
+ || is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Option);
+ then {
+ lint_impl_body(cx, impl_item.span, impl_item);
+ }
+ }
+ }
+}
+
+struct FindExpectUnwrap<'a, 'tcx> {
+ lcx: &'a LateContext<'tcx>,
+ typeck_results: &'tcx ty::TypeckResults<'tcx>,
+ result: Vec<Span>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+ // check for `expect`
+ if let Some(arglists) = method_chain_args(expr, &["expect"]) {
+ let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
+ || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+ {
+ self.result.push(expr.span);
+ }
+ }
+
+ // check for `unwrap`
+ if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
+ let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
+ || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+ {
+ self.result.push(expr.span);
+ }
+ }
+
+ // and check sub-expressions
+ intravisit::walk_expr(self, expr);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
+ if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
+ let body = cx.tcx.hir().body(body_id);
+ let mut fpu = FindExpectUnwrap {
+ lcx: cx,
+ typeck_results: cx.tcx.typeck(impl_item.def_id),
+ result: Vec::new(),
+ };
+ fpu.visit_expr(&body.value);
+
+ // if we've found one, lint
+ if !fpu.result.is_empty() {
+ span_lint_and_then(
+ cx,
+ UNWRAP_IN_RESULT,
+ impl_span,
+ "used unwrap or expect in a function that returns result or option",
+ move |diag| {
+ diag.help("unwrap and expect should not be used in a function that returns result or option");
+ diag.span_note(fpu.result, "potential non-recoverable error(s)");
+ },
+ );
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use itertools::Itertools;
+use rustc_errors::Applicability;
+use rustc_hir::{Item, ItemKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::Ident;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for fully capitalized names and optionally names containing a capitalized acronym.
+ ///
+ /// ### Why is this bad?
+ /// In CamelCase, acronyms count as one word.
+ /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case)
+ /// for more.
+ ///
+ /// By default, the lint only triggers on fully-capitalized names.
+ /// You can use the `upper-case-acronyms-aggressive: true` config option to enable linting
+ /// on all camel case names
+ ///
+ /// ### Known problems
+ /// When two acronyms are contiguous, the lint can't tell where
+ /// the first acronym ends and the second starts, so it suggests to lowercase all of
+ /// the letters in the second acronym.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct HTTPResponse;
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct HttpResponse;
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub UPPER_CASE_ACRONYMS,
+ style,
+ "capitalized acronyms are against the naming convention"
+}
+
+#[derive(Default)]
+pub struct UpperCaseAcronyms {
+ avoid_breaking_exported_api: bool,
+ upper_case_acronyms_aggressive: bool,
+}
+
+impl UpperCaseAcronyms {
+ pub fn new(avoid_breaking_exported_api: bool, aggressive: bool) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ upper_case_acronyms_aggressive: aggressive,
+ }
+ }
+}
+
+impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]);
+
+fn correct_ident(ident: &str) -> String {
+ let ident = ident.chars().rev().collect::<String>();
+ let fragments = ident
+ .split_inclusive(|x: char| !x.is_ascii_lowercase())
+ .rev()
+ .map(|x| x.chars().rev().collect::<String>());
+
+ let mut ident = fragments.clone().next().unwrap();
+ for (ref prev, ref curr) in fragments.tuple_windows() {
+ if [prev, curr]
+ .iter()
+ .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase())
+ {
+ ident.push_str(&curr.to_ascii_lowercase());
+ } else {
+ ident.push_str(curr);
+ }
+ }
+ ident
+}
+
+fn check_ident(cx: &LateContext<'_>, ident: &Ident, be_aggressive: bool) {
+ let span = ident.span;
+ let ident = &ident.as_str();
+ let corrected = correct_ident(ident);
+ // warn if we have pure-uppercase idents
+ // assume that two-letter words are some kind of valid abbreviation like FP for false positive
+ // (and don't warn)
+ if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2)
+ // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive
+ // upper-case-acronyms-aggressive config option enabled
+ || (be_aggressive && ident != &corrected)
+ {
+ span_lint_and_sugg(
+ cx,
+ UPPER_CASE_ACRONYMS,
+ span,
+ &format!("name `{}` contains a capitalized acronym", ident),
+ "consider making the acronym lowercase, except the initial letter",
+ corrected,
+ Applicability::MaybeIncorrect,
+ );
+ }
+}
+
+impl LateLintPass<'_> for UpperCaseAcronyms {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &Item<'_>) {
+ // do not lint public items or in macros
+ if in_external_macro(cx.sess(), it.span)
+ || (self.avoid_breaking_exported_api && cx.access_levels.is_exported(it.def_id))
+ {
+ return;
+ }
+ match it.kind {
+ ItemKind::TyAlias(..) | ItemKind::Struct(..) | ItemKind::Trait(..) => {
+ check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
+ },
+ ItemKind::Enum(ref enumdef, _) => {
+ // check enum variants seperately because again we only want to lint on private enums and
+ // the fn check_variant does not know about the vis of the enum of its variants
+ enumdef
+ .variants
+ .iter()
+ .for_each(|variant| check_ident(cx, &variant.ident, self.upper_case_acronyms_aggressive));
+ },
+ _ => {},
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{in_macro, meets_msrv, msrvs};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::ty::same_type_and_consts;
- if !in_macro(hir_ty.span);
++use clippy_utils::{meets_msrv, msrvs};
+use if_chain::if_chain;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ self as hir,
+ def::{CtorOf, DefKind, Res},
+ def_id::LocalDefId,
+ intravisit::{walk_inf, walk_ty, NestedVisitorMap, Visitor},
+ Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Path, QPath, TyKind,
+};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::ty::AssocKind;
+use rustc_semver::RustcVersion;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary repetition of structure name when a
+ /// replacement with `Self` is applicable.
+ ///
+ /// ### Why is this bad?
+ /// Unnecessary repetition. Mixed use of `Self` and struct
+ /// name
+ /// feels inconsistent.
+ ///
+ /// ### Known problems
+ /// - Unaddressed false negative in fn bodies of trait implementations
+ /// - False positive with assotiated types in traits (#4140)
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct Foo {}
+ /// impl Foo {
+ /// fn new() -> Foo {
+ /// Foo {}
+ /// }
+ /// }
+ /// ```
+ /// could be
+ /// ```rust
+ /// struct Foo {}
+ /// impl Foo {
+ /// fn new() -> Self {
+ /// Self {}
+ /// }
+ /// }
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub USE_SELF,
+ nursery,
+ "unnecessary structure name repetition whereas `Self` is applicable"
+}
+
+#[derive(Default)]
+pub struct UseSelf {
+ msrv: Option<RustcVersion>,
+ stack: Vec<StackItem>,
+}
+
+impl UseSelf {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self {
+ msrv,
+ ..Self::default()
+ }
+ }
+}
+
+#[derive(Debug)]
+enum StackItem {
+ Check {
+ impl_id: LocalDefId,
+ in_body: u32,
+ types_to_skip: FxHashSet<HirId>,
+ },
+ NoCheck,
+}
+
+impl_lint_pass!(UseSelf => [USE_SELF]);
+
+const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
+
+impl<'tcx> LateLintPass<'tcx> for UseSelf {
+ fn check_item(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
+ if matches!(item.kind, ItemKind::OpaqueTy(_)) {
+ // skip over `ItemKind::OpaqueTy` in order to lint `foo() -> impl <..>`
+ return;
+ }
+ // We push the self types of `impl`s on a stack here. Only the top type on the stack is
+ // relevant for linting, since this is the self type of the `impl` we're currently in. To
+ // avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that
+ // we're in an `impl` or nested item, that we don't want to lint
+ let stack_item = if_chain! {
+ if let ItemKind::Impl(Impl { self_ty, .. }) = item.kind;
+ if let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind;
+ let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
+ if parameters.as_ref().map_or(true, |params| {
+ !params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
+ });
+ then {
+ StackItem::Check {
+ impl_id: item.def_id,
+ in_body: 0,
+ types_to_skip: std::iter::once(self_ty.hir_id).collect(),
+ }
+ } else {
+ StackItem::NoCheck
+ }
+ };
+ self.stack.push(stack_item);
+ }
+
+ fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
+ if !matches!(item.kind, ItemKind::OpaqueTy(_)) {
+ self.stack.pop();
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+ // We want to skip types in trait `impl`s that aren't declared as `Self` in the trait
+ // declaration. The collection of those types is all this method implementation does.
+ if_chain! {
+ if let ImplItemKind::Fn(FnSig { decl, .. }, ..) = impl_item.kind;
+ if let Some(&mut StackItem::Check {
+ impl_id,
+ ref mut types_to_skip,
+ ..
+ }) = self.stack.last_mut();
+ if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_id);
+ then {
+ // `self_ty` is the semantic self type of `impl <trait> for <type>`. This cannot be
+ // `Self`.
+ let self_ty = impl_trait_ref.self_ty();
+
+ // `trait_method_sig` is the signature of the function, how it is declared in the
+ // trait, not in the impl of the trait.
+ let trait_method = cx
+ .tcx
+ .associated_items(impl_trait_ref.def_id)
+ .find_by_name_and_kind(cx.tcx, impl_item.ident, AssocKind::Fn, impl_trait_ref.def_id)
+ .expect("impl method matches a trait method");
+ let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id);
+ let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig);
+
+ // `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the
+ // implementation of the trait.
+ let output_hir_ty = if let FnRetTy::Return(ty) = &decl.output {
+ Some(&**ty)
+ } else {
+ None
+ };
+ let impl_inputs_outputs = decl.inputs.iter().chain(output_hir_ty);
+
+ // `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
+ //
+ // `trait_sem_ty` (of type `ty::Ty`) is the semantic type for the signature in the
+ // trait declaration. This is used to check if `Self` was used in the trait
+ // declaration.
+ //
+ // If `any`where in the `trait_sem_ty` the `self_ty` was used verbatim (as opposed
+ // to `Self`), we want to skip linting that type and all subtypes of it. This
+ // avoids suggestions to e.g. replace `Vec<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.
+ for (impl_hir_ty, trait_sem_ty) in impl_inputs_outputs.zip(trait_method_sig.inputs_and_output) {
+ if trait_sem_ty.walk(cx.tcx).any(|inner| inner == self_ty.into()) {
+ let mut visitor = SkipTyCollector::default();
+ visitor.visit_ty(impl_hir_ty);
+ types_to_skip.extend(visitor.types_to_skip);
+ }
+ }
+ }
+ }
+ }
+
+ fn check_body(&mut self, _: &LateContext<'_>, _: &hir::Body<'_>) {
+ // `hir_ty_to_ty` cannot be called in `Body`s or it will panic (sometimes). But in bodies
+ // we can use `cx.typeck_results.node_type(..)` to get the `ty::Ty` from a `hir::Ty`.
+ // However the `node_type()` method can *only* be called in bodies.
+ if let Some(&mut StackItem::Check { ref mut in_body, .. }) = self.stack.last_mut() {
+ *in_body = in_body.saturating_add(1);
+ }
+ }
+
+ fn check_body_post(&mut self, _: &LateContext<'_>, _: &hir::Body<'_>) {
+ if let Some(&mut StackItem::Check { ref mut in_body, .. }) = self.stack.last_mut() {
+ *in_body = in_body.saturating_sub(1);
+ }
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
+ if_chain! {
- let id = hir.get_parent_node(hir_ty.hir_id);
- if !hir.opt_span(id).map_or(false, in_macro);
++ if !hir_ty.span.from_expansion();
+ if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check {
+ impl_id,
+ in_body,
+ ref types_to_skip,
+ }) = self.stack.last();
+ if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
+ if !matches!(path.res, Res::SelfTy(..) | Res::Def(DefKind::TyParam, _));
+ if !types_to_skip.contains(&hir_ty.hir_id);
+ let ty = if in_body > 0 {
+ cx.typeck_results().node_type(hir_ty.hir_id)
+ } else {
+ hir_ty_to_ty(cx.tcx, hir_ty)
+ };
+ if same_type_and_consts(ty, cx.tcx.type_of(impl_id));
+ let hir = cx.tcx.hir();
- if !in_macro(expr.span);
++ // prevents false positive on `#[derive(serde::Deserialize)]`
++ if !hir.span(hir.get_parent_node(hir_ty.hir_id)).in_derive_expansion();
+ then {
+ span_lint(cx, hir_ty.span);
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if_chain! {
++ if !expr.span.from_expansion();
+ if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
+ if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
+ if cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id);
+ then {} else { return; }
+ }
+ match expr.kind {
+ ExprKind::Struct(QPath::Resolved(_, path), ..) => match path.res {
+ Res::SelfTy(..) => (),
+ Res::Def(DefKind::Variant, _) => lint_path_to_variant(cx, path),
+ _ => span_lint(cx, path.span),
+ },
+ // tuple struct instantiation (`Foo(arg)` or `Enum::Foo(arg)`)
+ ExprKind::Call(fun, _) => {
+ if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind {
+ if let Res::Def(DefKind::Ctor(ctor_of, _), ..) = path.res {
+ match ctor_of {
+ CtorOf::Variant => lint_path_to_variant(cx, path),
+ CtorOf::Struct => span_lint(cx, path.span),
+ }
+ }
+ }
+ },
+ // unit enum variants (`Enum::A`)
+ ExprKind::Path(QPath::Resolved(_, path)) => lint_path_to_variant(cx, path),
+ _ => (),
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
+
+#[derive(Default)]
+struct SkipTyCollector {
+ types_to_skip: Vec<HirId>,
+}
+
+impl<'tcx> Visitor<'tcx> for SkipTyCollector {
+ type Map = Map<'tcx>;
+
+ fn visit_infer(&mut self, inf: &hir::InferArg) {
+ self.types_to_skip.push(inf.hir_id);
+
+ walk_inf(self, inf);
+ }
+ fn visit_ty(&mut self, hir_ty: &hir::Ty<'_>) {
+ self.types_to_skip.push(hir_ty.hir_id);
+
+ walk_ty(self, hir_ty);
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+}
+
+fn span_lint(cx: &LateContext<'_>, span: Span) {
+ span_lint_and_sugg(
+ cx,
+ USE_SELF,
+ span,
+ "unnecessary structure name repetition",
+ "use the applicable keyword",
+ "Self".to_owned(),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) {
+ if let [.., self_seg, _variant] = path.segments {
+ let span = path
+ .span
+ .with_hi(self_seg.args().span_ext().unwrap_or(self_seg.ident.span).hi());
+ span_lint(cx, span);
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
+use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::sugg::Sugg;
+use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
+use clippy_utils::{get_parent_expr, is_trait_method, match_def_path, paths};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, HirId, MatchSource};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` calls
+ /// which uselessly convert to the same type.
+ ///
+ /// ### Why is this bad?
+ /// Redundant code.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// // format!() returns a `String`
+ /// let s: String = format!("hello").into();
+ ///
+ /// // Good
+ /// let s: String = format!("hello");
+ /// ```
++ #[clippy::version = "1.45.0"]
+ pub USELESS_CONVERSION,
+ complexity,
+ "calls to `Into`, `TryInto`, `From`, `TryFrom`, or `IntoIter` which perform useless conversions to the same type"
+}
+
+#[derive(Default)]
+pub struct UselessConversion {
+ try_desugar_arm: Vec<HirId>,
+}
+
+impl_lint_pass!(UselessConversion => [USELESS_CONVERSION]);
+
+#[allow(clippy::too_many_lines)]
+impl<'tcx> LateLintPass<'tcx> for UselessConversion {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if e.span.from_expansion() {
+ return;
+ }
+
+ if Some(&e.hir_id) == self.try_desugar_arm.last() {
+ return;
+ }
+
+ match e.kind {
+ ExprKind::Match(_, arms, MatchSource::TryDesugar) => {
+ let e = match arms[0].body.kind {
+ ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e)) => e,
+ _ => return,
+ };
+ if let ExprKind::Call(_, args) = e.kind {
+ self.try_desugar_arm.push(args[0].hir_id);
+ }
+ },
+
+ ExprKind::MethodCall(name, .., args, _) => {
+ if is_trait_method(cx, e, sym::Into) && &*name.ident.as_str() == "into" {
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if same_type_and_consts(a, b) {
+ let sugg = snippet_with_macro_callsite(cx, args[0].span, "<expr>").to_string();
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ "consider removing `.into()`",
+ sugg,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ if is_trait_method(cx, e, sym::IntoIterator) && name.ident.name == sym::into_iter {
+ if let Some(parent_expr) = get_parent_expr(cx, e) {
+ if let ExprKind::MethodCall(parent_name, ..) = parent_expr.kind {
+ if parent_name.ident.name != sym::into_iter {
+ return;
+ }
+ }
+ }
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if same_type_and_consts(a, b) {
+ let sugg = snippet(cx, args[0].span, "<expr>").into_owned();
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ "consider removing `.into_iter()`",
+ sugg,
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ if_chain! {
+ if is_trait_method(cx, e, sym::TryInto) && name.ident.name == sym::try_into;
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if is_type_diagnostic_item(cx, a, sym::Result);
+ if let ty::Adt(_, substs) = a.kind();
+ if let Some(a_type) = substs.types().next();
+ if same_type_and_consts(a_type, b);
+
+ then {
+ span_lint_and_help(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ None,
+ "consider removing `.try_into()`",
+ );
+ }
+ }
+ },
+
+ ExprKind::Call(path, args) => {
+ if_chain! {
+ if args.len() == 1;
+ if let ExprKind::Path(ref qpath) = path.kind;
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
+ then {
+ let a = cx.typeck_results().expr_ty(e);
+ let b = cx.typeck_results().expr_ty(&args[0]);
+ if_chain! {
+ if match_def_path(cx, def_id, &paths::TRY_FROM);
+ if is_type_diagnostic_item(cx, a, sym::Result);
+ if let ty::Adt(_, substs) = a.kind();
+ if let Some(a_type) = substs.types().next();
+ if same_type_and_consts(a_type, b);
+
+ then {
+ let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from"));
+ span_lint_and_help(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ None,
+ &hint,
+ );
+ }
+ }
+
+ if_chain! {
+ if match_def_path(cx, def_id, &paths::FROM_FROM);
+ if same_type_and_consts(a, b);
+
+ then {
+ let sugg = Sugg::hir_with_macro_callsite(cx, &args[0], "<expr>").maybe_par();
+ let sugg_msg =
+ format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
+ span_lint_and_sugg(
+ cx,
+ USELESS_CONVERSION,
+ e.span,
+ &format!("useless conversion to the same type: `{}`", b),
+ &sugg_msg,
+ sugg.to_string(),
+ Applicability::MachineApplicable, // snippet
+ );
+ }
+ }
+ }
+ }
+ },
+
+ _ => {},
+ }
+ }
+
+ fn check_expr_post(&mut self, _: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
+ if Some(&e.hir_id) == self.try_desugar_arm.last() {
+ self.try_desugar_arm.pop();
+ }
+ }
+}
--- /dev/null
- use clippy_utils::get_attr;
+//! A group of attributes that can be attached to Rust code in order
+//! to generate a clippy lint detecting said code automatically.
+
- use rustc_ast::walk_list;
++use clippy_utils::{get_attr, higher};
+use rustc_ast::ast::{LitFloatType, LitKind};
- use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
- use rustc_hir::{Block, Expr, ExprKind, Pat, PatKind, QPath, Stmt, StmtKind, TyKind};
++use rustc_ast::LitIntType;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir as hir;
- use rustc_middle::hir::map::Map;
++use rustc_hir::{ExprKind, FnRetTy, HirId, Lit, PatKind, QPath, StmtKind, TyKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
- if !has_attr(cx, item.hir_id()) {
- return;
- }
- prelude();
- PrintVisitor::new("item").visit_item(item);
- done();
+use rustc_session::{declare_lint_pass, declare_tool_lint};
++use rustc_span::symbol::{Ident, Symbol};
++use std::fmt::{Display, Formatter, Write as _};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Generates clippy code that detects the offending pattern
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // ./tests/ui/my_lint.rs
+ /// fn foo() {
+ /// // detect the following pattern
+ /// #[clippy::author]
+ /// if x == 42 {
+ /// // but ignore everything from here on
+ /// #![clippy::author = "ignore"]
+ /// }
+ /// ()
+ /// }
+ /// ```
+ ///
+ /// Running `TESTNAME=ui/my_lint cargo uitest` will produce
+ /// a `./tests/ui/new_lint.stdout` file with the generated code:
+ ///
+ /// ```rust,ignore
+ /// // ./tests/ui/new_lint.stdout
+ /// if_chain! {
+ /// if let ExprKind::If(ref cond, ref then, None) = item.kind,
+ /// if let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind,
+ /// if let ExprKind::Path(ref path) = left.kind,
+ /// if let ExprKind::Lit(ref lit) = right.kind,
+ /// if let LitKind::Int(42, _) = lit.node,
+ /// then {
+ /// // report your lint here
+ /// }
+ /// }
+ /// ```
+ pub LINT_AUTHOR,
+ internal_warn,
+ "helper for writing lints"
+}
+
+declare_lint_pass!(Author => [LINT_AUTHOR]);
+
++/// Writes a line of output with indentation added
++macro_rules! out {
++ ($($t:tt)*) => {
++ println!(" {}", format_args!($($t)*))
++ };
++}
++
++/// The variables passed in are replaced with `&Binding`s where the `value` field is set
++/// to the original value of the variable. The `name` field is set to the name of the variable
++/// (using `stringify!`) and is adjusted to avoid duplicate names.
++/// Note that the `Binding` may be printed directly to output the `name`.
++macro_rules! bind {
++ ($self:ident $(, $name:ident)+) => {
++ $(let $name = & $self.bind(stringify!($name), $name);)+
++ };
++}
++
++/// Transforms the given `Option<T>` varibles into `OptionPat<Binding<T>>`.
++/// This displays as `Some($name)` or `None` when printed. The name of the inner binding
++/// is set to the name of the variable passed to the macro.
++macro_rules! opt_bind {
++ ($self:ident $(, $name:ident)+) => {
++ $(let $name = OptionPat::new($name.map(|o| $self.bind(stringify!($name), o)));)+
++ };
++}
++
++/// Creates a `Binding` that accesses the field of an existing `Binding`
++macro_rules! field {
++ ($binding:ident.$field:ident) => {
++ &Binding {
++ name: $binding.name.to_string() + stringify!(.$field),
++ value: $binding.value.$field,
++ }
++ };
++}
++
+fn prelude() {
+ println!("if_chain! {{");
+}
+
+fn done() {
+ println!(" then {{");
+ println!(" // report your lint here");
+ println!(" }}");
+ println!("}}");
+}
+
+impl<'tcx> LateLintPass<'tcx> for Author {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
- if !has_attr(cx, item.hir_id()) {
- return;
- }
- prelude();
- PrintVisitor::new("item").visit_impl_item(item);
- done();
++ check_item(cx, item.hir_id());
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
- if !has_attr(cx, item.hir_id()) {
- return;
- }
- prelude();
- PrintVisitor::new("item").visit_trait_item(item);
- done();
++ check_item(cx, item.hir_id());
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
- fn check_variant(&mut self, cx: &LateContext<'tcx>, var: &'tcx hir::Variant<'_>) {
- if !has_attr(cx, var.id) {
- return;
- }
- prelude();
- let parent_hir_id = cx.tcx.hir().get_parent_node(var.id);
- PrintVisitor::new("var").visit_variant(var, &hir::Generics::empty(), parent_hir_id);
- done();
- }
-
- fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'_>) {
- if !has_attr(cx, field.hir_id) {
- return;
- }
- prelude();
- PrintVisitor::new("field").visit_field_def(field);
- done();
++ check_item(cx, item.hir_id());
+ }
+
- if !has_attr(cx, expr.hir_id) {
- return;
- }
- prelude();
- PrintVisitor::new("expr").visit_expr(expr);
- done();
- }
-
- fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) {
- if !has_attr(cx, arm.hir_id) {
- return;
- }
- prelude();
- PrintVisitor::new("arm").visit_arm(arm);
- done();
++ fn check_arm(&mut self, cx: &LateContext<'tcx>, arm: &'tcx hir::Arm<'_>) {
++ check_node(cx, arm.hir_id, |v| {
++ v.arm(&v.bind("arm", arm));
++ });
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
- if !has_attr(cx, stmt.hir_id) {
- return;
- }
++ check_node(cx, expr.hir_id, |v| {
++ v.expr(&v.bind("expr", expr));
++ });
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'_>) {
- PrintVisitor::new("stmt").visit_stmt(stmt);
+ match stmt.kind {
+ StmtKind::Expr(e) | StmtKind::Semi(e) if has_attr(cx, e.hir_id) => return,
+ _ => {},
+ }
++ check_node(cx, stmt.hir_id, |v| {
++ v.stmt(&v.bind("stmt", stmt));
++ });
++ }
++}
++
++fn check_item(cx: &LateContext<'_>, hir_id: HirId) {
++ let hir = cx.tcx.hir();
++ if let Some(body_id) = hir.maybe_body_owned_by(hir_id) {
++ check_node(cx, hir_id, |v| {
++ v.expr(&v.bind("expr", &hir.body(body_id).value));
++ });
++ }
++}
++
++fn check_node(cx: &LateContext<'_>, hir_id: HirId, f: impl Fn(&PrintVisitor<'_, '_>)) {
++ if has_attr(cx, hir_id) {
+ prelude();
- fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ForeignItem<'_>) {
- if !has_attr(cx, item.hir_id()) {
- return;
++ f(&PrintVisitor::new(cx));
+ done();
+ }
++}
+
- prelude();
- PrintVisitor::new("item").visit_foreign_item(item);
- done();
++struct Binding<T> {
++ name: String,
++ value: T,
++}
++
++impl<T> Display for Binding<T> {
++ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
++ f.write_str(&self.name)
++ }
++}
++
++struct OptionPat<T> {
++ pub opt: Option<T>,
++}
++
++impl<T> OptionPat<T> {
++ fn new(opt: Option<T>) -> Self {
++ Self { opt }
++ }
++
++ fn if_some(&self, f: impl Fn(&T)) {
++ if let Some(t) = &self.opt {
++ f(t);
++ }
++ }
++}
++
++impl<T: Display> Display for OptionPat<T> {
++ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
++ match &self.opt {
++ None => f.write_str("None"),
++ Some(node) => write!(f, "Some({node})"),
+ }
- impl PrintVisitor {
- #[must_use]
- fn new(s: &'static str) -> Self {
+ }
+}
+
- ids: FxHashMap::default(),
- current: s.to_owned(),
++struct PrintVisitor<'a, 'tcx> {
++ cx: &'a LateContext<'tcx>,
++ /// Fields are the current index that needs to be appended to pattern
++ /// binding names
++ ids: std::cell::Cell<FxHashMap<&'static str, u32>>,
++}
++
++#[allow(clippy::unused_self)]
++impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
++ fn new(cx: &'a LateContext<'tcx>) -> Self {
+ Self {
- fn next(&mut self, s: &'static str) -> String {
- use std::collections::hash_map::Entry::{Occupied, Vacant};
- match self.ids.entry(s) {
- // already there: start numbering from `1`
- Occupied(mut occ) => {
- let val = occ.get_mut();
- *val += 1;
- format!("{}{}", s, *val)
- },
- // not there: insert and return name as given
- Vacant(vac) => {
- vac.insert(0);
- s.to_owned()
++ cx,
++ ids: std::cell::Cell::default(),
+ }
+ }
+
- fn print_qpath(&mut self, path: &QPath<'_>) {
- if let QPath::LangItem(lang_item, _) = *path {
- println!(
- " if matches!({}, QPath::LangItem(LangItem::{:?}, _));",
- self.current, lang_item,
- );
++ fn next(&self, s: &'static str) -> String {
++ let mut ids = self.ids.take();
++ let out = match *ids.entry(s).and_modify(|n| *n += 1).or_default() {
++ // first usage of the name, use it as is
++ 0 => s.to_string(),
++ // append a number starting with 1
++ n => format!("{s}{n}"),
++ };
++ self.ids.set(ids);
++ out
++ }
++
++ fn bind<T>(&self, name: &'static str, value: T) -> Binding<T> {
++ let name = self.next(name);
++ Binding { name, value }
++ }
++
++ fn option<T: Copy>(&self, option: &Binding<Option<T>>, name: &'static str, f: impl Fn(&Binding<T>)) {
++ match option.value {
++ None => out!("if {option}.is_none();"),
++ Some(value) => {
++ let value = &self.bind(name, value);
++ out!("if let Some({value}) = {option};");
++ f(value);
+ },
+ }
+ }
+
- print!(" if match_qpath({}, &[", self.current);
- print_path(path, &mut true);
- println!("]);");
++ fn slice<T>(&self, slice: &Binding<&[T]>, f: impl Fn(&Binding<&T>)) {
++ if slice.value.is_empty() {
++ out!("if {slice}.is_empty();");
+ } else {
- }
++ out!("if {slice}.len() == {};", slice.value.len());
++ for (i, value) in slice.value.iter().enumerate() {
++ let name = format!("{slice}[{i}]");
++ f(&Binding { name, value });
++ }
+ }
+ }
- struct PrintVisitor {
- /// Fields are the current index that needs to be appended to pattern
- /// binding names
- ids: FxHashMap<&'static str, usize>,
- /// the name that needs to be destructured
- current: String,
- }
+
- impl<'tcx> Visitor<'tcx> for PrintVisitor {
- type Map = Map<'tcx>;
++ fn destination(&self, destination: &Binding<hir::Destination>) {
++ self.option(field!(destination.label), "label", |label| {
++ self.ident(field!(label.ident));
++ });
++ }
++
++ fn ident(&self, ident: &Binding<Ident>) {
++ out!("if {ident}.as_str() == {:?};", ident.value.as_str());
++ }
++
++ fn symbol(&self, symbol: &Binding<Symbol>) {
++ out!("if {symbol}.as_str() == {:?};", symbol.value.as_str());
++ }
+
- fn visit_expr(&mut self, expr: &Expr<'_>) {
- print!(" if let ExprKind::");
- let current = format!("{}.kind", self.current);
- match expr.kind {
++ fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
++ if let QPath::LangItem(lang_item, _) = *qpath.value {
++ out!("if matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _));");
++ } else {
++ out!("if match_qpath({qpath}, &[{}]);", path_to_string(qpath.value));
++ }
++ }
++
++ fn lit(&self, lit: &Binding<&Lit>) {
++ let kind = |kind| out!("if let LitKind::{kind} = {lit}.node;");
++ macro_rules! kind {
++ ($($t:tt)*) => (kind(format_args!($($t)*)));
++ }
++
++ match lit.value.node {
++ LitKind::Bool(val) => kind!("Bool({val:?})"),
++ LitKind::Char(c) => kind!("Char({c:?})"),
++ LitKind::Err(val) => kind!("Err({val})"),
++ LitKind::Byte(b) => kind!("Byte({b})"),
++ LitKind::Int(i, suffix) => {
++ let int_ty = match suffix {
++ LitIntType::Signed(int_ty) => format!("LitIntType::Signed(IntTy::{int_ty:?})"),
++ LitIntType::Unsigned(uint_ty) => format!("LitIntType::Unsigned(UintTy::{uint_ty:?})"),
++ LitIntType::Unsuffixed => String::from("LitIntType::Unsuffixed"),
++ };
++ kind!("Int({i}, {int_ty})");
++ },
++ LitKind::Float(_, suffix) => {
++ let float_ty = match suffix {
++ LitFloatType::Suffixed(suffix_ty) => format!("LitFloatType::Suffixed(FloatTy::{suffix_ty:?})"),
++ LitFloatType::Unsuffixed => String::from("LitFloatType::Unsuffixed"),
++ };
++ kind!("Float(_, {float_ty})");
++ },
++ LitKind::ByteStr(ref vec) => {
++ bind!(self, vec);
++ kind!("ByteStr(ref {vec})");
++ out!("if let [{:?}] = **{vec};", vec.value);
++ },
++ LitKind::Str(s, _) => {
++ bind!(self, s);
++ kind!("Str({s}, _)");
++ self.symbol(s);
++ },
++ }
++ }
++
++ fn arm(&self, arm: &Binding<&hir::Arm<'_>>) {
++ self.pat(field!(arm.pat));
++ match arm.value.guard {
++ None => out!("if {arm}.guard.is_none();"),
++ Some(hir::Guard::If(expr)) => {
++ bind!(self, expr);
++ out!("if let Some(Guard::If({expr})) = {arm}.guard;");
++ self.expr(expr);
++ },
++ Some(hir::Guard::IfLet(pat, expr)) => {
++ bind!(self, pat, expr);
++ out!("if let Some(Guard::IfLet({pat}, {expr}) = {arm}.guard;");
++ self.pat(pat);
++ self.expr(expr);
++ },
++ }
++ self.expr(field!(arm.body));
++ }
+
+ #[allow(clippy::too_many_lines)]
- let let_pat = self.next("pat");
- let let_expr = self.next("expr");
- println!(" Let(ref {}, ref {}, _) = {};", let_pat, let_expr, current);
- self.current = let_expr;
- self.visit_expr(expr);
- self.current = let_pat;
- self.visit_pat(pat);
++ fn expr(&self, expr: &Binding<&hir::Expr<'_>>) {
++ if let Some(higher::While { condition, body }) = higher::While::hir(expr.value) {
++ bind!(self, condition, body);
++ out!(
++ "if let Some(higher::While {{ condition: {condition}, body: {body} }}) \
++ = higher::While::hir({expr});"
++ );
++ self.expr(condition);
++ self.expr(body);
++ return;
++ }
++
++ if let Some(higher::WhileLet {
++ let_pat,
++ let_expr,
++ if_then,
++ }) = higher::WhileLet::hir(expr.value)
++ {
++ bind!(self, let_pat, let_expr, if_then);
++ out!(
++ "if let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
++ = higher::WhileLet::hir({expr});"
++ );
++ self.pat(let_pat);
++ self.expr(let_expr);
++ self.expr(if_then);
++ return;
++ }
++
++ if let Some(higher::ForLoop { pat, arg, body, .. }) = higher::ForLoop::hir(expr.value) {
++ bind!(self, pat, arg, body);
++ out!(
++ "if let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
++ = higher::ForLoop::hir({expr});"
++ );
++ self.pat(pat);
++ self.expr(arg);
++ self.expr(body);
++ return;
++ }
++
++ let kind = |kind| out!("if let ExprKind::{kind} = {expr}.kind;");
++ macro_rules! kind {
++ ($($t:tt)*) => (kind(format_args!($($t)*)));
++ }
++
++ match expr.value.kind {
+ ExprKind::Let(pat, expr, _) => {
- let inner_pat = self.next("inner");
- println!("Box(ref {}) = {};", inner_pat, current);
- self.current = inner_pat;
- self.visit_expr(inner);
++ bind!(self, pat, expr);
++ kind!("Let({pat}, {expr}, _)");
++ self.pat(pat);
++ self.expr(expr);
+ },
+ ExprKind::Box(inner) => {
- let elements_pat = self.next("elements");
- println!("Array(ref {}) = {};", elements_pat, current);
- println!(" if {}.len() == {};", elements_pat, elements.len());
- for (i, element) in elements.iter().enumerate() {
- self.current = format!("{}[{}]", elements_pat, i);
- self.visit_expr(element);
- }
++ bind!(self, inner);
++ kind!("Box({inner})");
++ self.expr(inner);
+ },
+ ExprKind::Array(elements) => {
- let func_pat = self.next("func");
- let args_pat = self.next("args");
- println!("Call(ref {}, ref {}) = {};", func_pat, args_pat, current);
- self.current = func_pat;
- self.visit_expr(func);
- println!(" if {}.len() == {};", args_pat, args.len());
- for (i, arg) in args.iter().enumerate() {
- self.current = format!("{}[{}]", args_pat, i);
- self.visit_expr(arg);
- }
++ bind!(self, elements);
++ kind!("Array({elements})");
++ self.slice(elements, |e| self.expr(e));
+ },
+ ExprKind::Call(func, args) => {
- ExprKind::MethodCall(_method_name, ref _generics, _args, ref _fn_span) => {
- println!(
- "MethodCall(ref method_name, ref generics, ref args, ref fn_span) = {};",
- current
- );
- println!(" // unimplemented: `ExprKind::MethodCall` is not further destructured at the moment");
++ bind!(self, func, args);
++ kind!("Call({func}, {args})");
++ self.expr(func);
++ self.slice(args, |e| self.expr(e));
+ },
- let elements_pat = self.next("elements");
- println!("Tup(ref {}) = {};", elements_pat, current);
- println!(" if {}.len() == {};", elements_pat, elements.len());
- for (i, element) in elements.iter().enumerate() {
- self.current = format!("{}[{}]", elements_pat, i);
- self.visit_expr(element);
- }
- },
- ExprKind::Binary(ref op, left, right) => {
- let op_pat = self.next("op");
- let left_pat = self.next("left");
- let right_pat = self.next("right");
- println!(
- "Binary(ref {}, ref {}, ref {}) = {};",
- op_pat, left_pat, right_pat, current
- );
- println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat);
- self.current = left_pat;
- self.visit_expr(left);
- self.current = right_pat;
- self.visit_expr(right);
- },
- ExprKind::Unary(ref op, inner) => {
- let inner_pat = self.next("inner");
- println!("Unary(UnOp::{:?}, ref {}) = {};", op, inner_pat, current);
- self.current = inner_pat;
- self.visit_expr(inner);
++ ExprKind::MethodCall(method_name, _, args, _) => {
++ bind!(self, method_name, args);
++ kind!("MethodCall({method_name}, _, {args}, _)");
++ self.ident(field!(method_name.ident));
++ self.slice(args, |e| self.expr(e));
+ },
+ ExprKind::Tup(elements) => {
- let lit_pat = self.next("lit");
- println!("Lit(ref {}) = {};", lit_pat, current);
- match lit.node {
- LitKind::Bool(val) => println!(" if let LitKind::Bool({:?}) = {}.node;", val, lit_pat),
- LitKind::Char(c) => println!(" if let LitKind::Char({:?}) = {}.node;", c, lit_pat),
- LitKind::Err(val) => println!(" if let LitKind::Err({}) = {}.node;", val, lit_pat),
- LitKind::Byte(b) => println!(" if let LitKind::Byte({}) = {}.node;", b, lit_pat),
- // FIXME: also check int type
- LitKind::Int(i, _) => println!(" if let LitKind::Int({}, _) = {}.node;", i, lit_pat),
- LitKind::Float(_, LitFloatType::Suffixed(_)) => println!(
- " if let LitKind::Float(_, LitFloatType::Suffixed(_)) = {}.node;",
- lit_pat
- ),
- LitKind::Float(_, LitFloatType::Unsuffixed) => println!(
- " if let LitKind::Float(_, LitFloatType::Unsuffixed) = {}.node;",
- lit_pat
- ),
- LitKind::ByteStr(ref vec) => {
- let vec_pat = self.next("vec");
- println!(" if let LitKind::ByteStr(ref {}) = {}.node;", vec_pat, lit_pat);
- println!(" if let [{:?}] = **{};", vec, vec_pat);
- },
- LitKind::Str(ref text, _) => {
- let str_pat = self.next("s");
- println!(" if let LitKind::Str(ref {}, _) = {}.node;", str_pat, lit_pat);
- println!(" if {}.as_str() == {:?}", str_pat, &*text.as_str());
- },
++ bind!(self, elements);
++ kind!("Tup({elements})");
++ self.slice(elements, |e| self.expr(e));
++ },
++ ExprKind::Binary(op, left, right) => {
++ bind!(self, op, left, right);
++ kind!("Binary({op}, {left}, {right})");
++ out!("if BinOpKind::{:?} == {op}.node;", op.value.node);
++ self.expr(left);
++ self.expr(right);
++ },
++ ExprKind::Unary(op, inner) => {
++ bind!(self, inner);
++ kind!("Unary(UnOp::{op:?}, {inner})");
++ self.expr(inner);
+ },
+ ExprKind::Lit(ref lit) => {
- },
- ExprKind::Cast(expr, ty) => {
- let cast_pat = self.next("expr");
- let cast_ty = self.next("cast_ty");
- let qp_label = self.next("qp");
-
- println!("Cast(ref {}, ref {}) = {};", cast_pat, cast_ty, current);
- if let TyKind::Path(ref qp) = ty.kind {
- println!(" if let TyKind::Path(ref {}) = {}.kind;", qp_label, cast_ty);
- self.current = qp_label;
- self.print_qpath(qp);
- }
- self.current = cast_pat;
- self.visit_expr(expr);
++ bind!(self, lit);
++ kind!("Lit(ref {lit})");
++ self.lit(lit);
++ },
++ ExprKind::Cast(expr, cast_ty) => {
++ bind!(self, expr, cast_ty);
++ kind!("Cast({expr}, {cast_ty})");
++ if let TyKind::Path(ref qpath) = cast_ty.value.kind {
++ bind!(self, qpath);
++ out!("if let TyKind::Path(ref {qpath}) = {cast_ty}.kind;");
++ self.qpath(qpath);
+ }
- let cast_pat = self.next("expr");
- println!("Type(ref {}, _) = {};", cast_pat, current);
- self.current = cast_pat;
- self.visit_expr(expr);
- },
- ExprKind::Loop(body, _, des, _) => {
- let body_pat = self.next("body");
- let label_pat = self.next("label");
- println!(
- "Loop(ref {}, ref {}, LoopSource::{:?}) = {};",
- body_pat, label_pat, des, current
- );
- self.current = body_pat;
- self.visit_block(body);
- },
- ExprKind::If(cond, then, ref opt_else) => {
- let cond_pat = self.next("cond");
- let then_pat = self.next("then");
- if let Some(else_) = *opt_else {
- let else_pat = self.next("else_");
- println!(
- "If(ref {}, ref {}, Some(ref {})) = {};",
- cond_pat, then_pat, else_pat, current
- );
- self.current = else_pat;
- self.visit_expr(else_);
- } else {
- println!("If(ref {}, ref {}, None) = {};", cond_pat, then_pat, current);
- }
- self.current = cond_pat;
- self.visit_expr(cond);
- self.current = then_pat;
- self.visit_expr(then);
- },
- ExprKind::Match(expr, arms, des) => {
- let expr_pat = self.next("expr");
- let arms_pat = self.next("arms");
- println!(
- "Match(ref {}, ref {}, MatchSource::{:?}) = {};",
- expr_pat, arms_pat, des, current
- );
- self.current = expr_pat;
- self.visit_expr(expr);
- println!(" if {}.len() == {};", arms_pat, arms.len());
- for (i, arm) in arms.iter().enumerate() {
- self.current = format!("{}[{}].body", arms_pat, i);
- self.visit_expr(arm.body);
- if let Some(ref guard) = arm.guard {
- let guard_pat = self.next("guard");
- println!(" if let Some(ref {}) = {}[{}].guard;", guard_pat, arms_pat, i);
- match guard {
- hir::Guard::If(if_expr) => {
- let if_expr_pat = self.next("expr");
- println!(" if let Guard::If(ref {}) = {};", if_expr_pat, guard_pat);
- self.current = if_expr_pat;
- self.visit_expr(if_expr);
- },
- hir::Guard::IfLet(if_let_pat, if_let_expr) => {
- let if_let_pat_pat = self.next("pat");
- let if_let_expr_pat = self.next("expr");
- println!(
- " if let Guard::IfLet(ref {}, ref {}) = {};",
- if_let_pat_pat, if_let_expr_pat, guard_pat
- );
- self.current = if_let_expr_pat;
- self.visit_expr(if_let_expr);
- self.current = if_let_pat_pat;
- self.visit_pat(if_let_pat);
- },
- }
- }
- self.current = format!("{}[{}].pat", arms_pat, i);
- self.visit_pat(arm.pat);
- }
- },
- ExprKind::Closure(ref _capture_clause, _func, _, _, _) => {
- println!("Closure(ref capture_clause, ref func, _, _, _) = {};", current);
- println!(" // unimplemented: `ExprKind::Closure` is not further destructured at the moment");
- },
- ExprKind::Yield(sub, _) => {
- let sub_pat = self.next("sub");
- println!("Yield(ref sub) = {};", current);
- self.current = sub_pat;
- self.visit_expr(sub);
- },
- ExprKind::Block(block, _) => {
- let block_pat = self.next("block");
- println!("Block(ref {}) = {};", block_pat, current);
- self.current = block_pat;
- self.visit_block(block);
++ self.expr(expr);
+ },
+ ExprKind::Type(expr, _ty) => {
- let target_pat = self.next("target");
- let value_pat = self.next("value");
- println!(
- "Assign(ref {}, ref {}, ref _span) = {};",
- target_pat, value_pat, current
- );
- self.current = target_pat;
- self.visit_expr(target);
- self.current = value_pat;
- self.visit_expr(value);
- },
- ExprKind::AssignOp(ref op, target, value) => {
- let op_pat = self.next("op");
- let target_pat = self.next("target");
- let value_pat = self.next("value");
- println!(
- "AssignOp(ref {}, ref {}, ref {}) = {};",
- op_pat, target_pat, value_pat, current
- );
- println!(" if BinOpKind::{:?} == {}.node;", op.node, op_pat);
- self.current = target_pat;
- self.visit_expr(target);
- self.current = value_pat;
- self.visit_expr(value);
- },
- ExprKind::Field(object, ref field_ident) => {
- let obj_pat = self.next("object");
- let field_name_pat = self.next("field_name");
- println!("Field(ref {}, ref {}) = {};", obj_pat, field_name_pat, current);
- println!(" if {}.as_str() == {:?}", field_name_pat, field_ident.as_str());
- self.current = obj_pat;
- self.visit_expr(object);
++ bind!(self, expr);
++ kind!("Type({expr}, _)");
++ self.expr(expr);
++ },
++ ExprKind::Loop(body, label, des, _) => {
++ bind!(self, body);
++ opt_bind!(self, label);
++ kind!("Loop({body}, {label}, LoopSource::{des:?}, _)");
++ self.block(body);
++ label.if_some(|l| self.ident(field!(l.ident)));
++ },
++ ExprKind::If(cond, then, else_expr) => {
++ bind!(self, cond, then);
++ opt_bind!(self, else_expr);
++ kind!("If({cond}, {then}, {else_expr})");
++ self.expr(cond);
++ self.expr(then);
++ else_expr.if_some(|e| self.expr(e));
++ },
++ ExprKind::Match(scrutinee, arms, des) => {
++ bind!(self, scrutinee, arms);
++ kind!("Match({scrutinee}, {arms}, MatchSource::{des:?})");
++ self.expr(scrutinee);
++ self.slice(arms, |arm| self.arm(arm));
++ },
++ ExprKind::Closure(capture_by, fn_decl, body_id, _, movability) => {
++ let movability = OptionPat::new(movability.map(|m| format!("Movability::{m:?}")));
++
++ let ret_ty = match fn_decl.output {
++ FnRetTy::DefaultReturn(_) => "FnRetTy::DefaultReturn(_)",
++ FnRetTy::Return(_) => "FnRetTy::Return(_ty)",
++ };
++
++ bind!(self, fn_decl, body_id);
++ kind!("Closure(CaptureBy::{capture_by:?}, {fn_decl}, {body_id}, _, {movability})");
++ out!("if let {ret_ty} = {fn_decl}.output;");
++ self.body(body_id);
++ },
++ ExprKind::Yield(sub, source) => {
++ bind!(self, sub);
++ kind!("Yield(sub, YieldSource::{source:?})");
++ self.expr(sub);
++ },
++ ExprKind::Block(block, label) => {
++ bind!(self, block);
++ opt_bind!(self, label);
++ kind!("Block({block}, {label})");
++ self.block(block);
++ label.if_some(|l| self.ident(field!(l.ident)));
+ },
+ ExprKind::Assign(target, value, _) => {
- let object_pat = self.next("object");
- let index_pat = self.next("index");
- println!("Index(ref {}, ref {}) = {};", object_pat, index_pat, current);
- self.current = object_pat;
- self.visit_expr(object);
- self.current = index_pat;
- self.visit_expr(index);
- },
- ExprKind::Path(ref path) => {
- let path_pat = self.next("path");
- println!("Path(ref {}) = {};", path_pat, current);
- self.current = path_pat;
- self.print_qpath(path);
++ bind!(self, target, value);
++ kind!("Assign({target}, {value}, _span)");
++ self.expr(target);
++ self.expr(value);
++ },
++ ExprKind::AssignOp(op, target, value) => {
++ bind!(self, op, target, value);
++ kind!("AssignOp({op}, {target}, {value})");
++ out!("if BinOpKind::{:?} == {op}.node;", op.value.node);
++ self.expr(target);
++ self.expr(value);
++ },
++ ExprKind::Field(object, field_name) => {
++ bind!(self, object, field_name);
++ kind!("Field({object}, {field_name})");
++ self.ident(field_name);
++ self.expr(object);
+ },
+ ExprKind::Index(object, index) => {
- let inner_pat = self.next("inner");
- println!(
- "AddrOf(BorrowKind::{:?}, Mutability::{:?}, ref {}) = {};",
- kind, mutability, inner_pat, current
- );
- self.current = inner_pat;
- self.visit_expr(inner);
- },
- ExprKind::Break(ref _destination, ref opt_value) => {
- let destination_pat = self.next("destination");
- if let Some(value) = *opt_value {
- let value_pat = self.next("value");
- println!("Break(ref {}, Some(ref {})) = {};", destination_pat, value_pat, current);
- self.current = value_pat;
- self.visit_expr(value);
- } else {
- println!("Break(ref {}, None) = {};", destination_pat, current);
- }
- // FIXME: implement label printing
- },
- ExprKind::Continue(ref _destination) => {
- let destination_pat = self.next("destination");
- println!("Again(ref {}) = {};", destination_pat, current);
- // FIXME: implement label printing
- },
- ExprKind::Ret(ref opt_value) => {
- if let Some(value) = *opt_value {
- let value_pat = self.next("value");
- println!("Ret(Some(ref {})) = {};", value_pat, current);
- self.current = value_pat;
- self.visit_expr(value);
- } else {
- println!("Ret(None) = {};", current);
- }
++ bind!(self, object, index);
++ kind!("Index({object}, {index})");
++ self.expr(object);
++ self.expr(index);
++ },
++ ExprKind::Path(ref qpath) => {
++ bind!(self, qpath);
++ kind!("Path(ref {qpath})");
++ self.qpath(qpath);
+ },
+ ExprKind::AddrOf(kind, mutability, inner) => {
- println!("InlineAsm(_) = {};", current);
- println!(" // unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
++ bind!(self, inner);
++ kind!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})");
++ self.expr(inner);
++ },
++ ExprKind::Break(destination, value) => {
++ bind!(self, destination);
++ opt_bind!(self, value);
++ kind!("Break({destination}, {value})");
++ self.destination(destination);
++ value.if_some(|e| self.expr(e));
++ },
++ ExprKind::Continue(destination) => {
++ bind!(self, destination);
++ kind!("Continue({destination})");
++ self.destination(destination);
++ },
++ ExprKind::Ret(value) => {
++ opt_bind!(self, value);
++ kind!("Ret({value})");
++ value.if_some(|e| self.expr(e));
+ },
+ ExprKind::InlineAsm(_) => {
- println!("LlvmInlineAsm(_) = {};", current);
- println!(" // unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment");
- },
- ExprKind::Struct(path, fields, ref opt_base) => {
- let path_pat = self.next("path");
- let fields_pat = self.next("fields");
- if let Some(base) = *opt_base {
- let base_pat = self.next("base");
- println!(
- "Struct(ref {}, ref {}, Some(ref {})) = {};",
- path_pat, fields_pat, base_pat, current
- );
- self.current = base_pat;
- self.visit_expr(base);
- } else {
- println!("Struct(ref {}, ref {}, None) = {};", path_pat, fields_pat, current);
- }
- self.current = path_pat;
- self.print_qpath(path);
- println!(" if {}.len() == {};", fields_pat, fields.len());
- println!(" // unimplemented: field checks");
- },
- ExprKind::ConstBlock(_) => {
- let value_pat = self.next("value");
- println!("Const({})", value_pat);
- self.current = value_pat;
- },
- // FIXME: compute length (needs type info)
- ExprKind::Repeat(value, _) => {
- let value_pat = self.next("value");
- println!("Repeat(ref {}, _) = {};", value_pat, current);
- println!("// unimplemented: repeat count check");
- self.current = value_pat;
- self.visit_expr(value);
- },
- ExprKind::Err => {
- println!("Err = {}", current);
- },
++ kind!("InlineAsm(_)");
++ out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
+ },
+ ExprKind::LlvmInlineAsm(_) => {
- let expr_pat = self.next("expr");
- println!("DropTemps(ref {}) = {};", expr_pat, current);
- self.current = expr_pat;
- self.visit_expr(expr);
++ kind!("LlvmInlineAsm(_)");
++ out!("// unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment");
++ },
++ ExprKind::Struct(qpath, fields, base) => {
++ bind!(self, qpath, fields);
++ opt_bind!(self, base);
++ kind!("Struct({qpath}, {fields}, {base})");
++ self.qpath(qpath);
++ self.slice(fields, |field| {
++ self.ident(field!(field.ident));
++ self.expr(field!(field.expr));
++ });
++ base.if_some(|e| self.expr(e));
++ },
++ ExprKind::ConstBlock(_) => kind!("ConstBlock(_)"),
++ ExprKind::Repeat(value, length) => {
++ bind!(self, value, length);
++ kind!("Repeat({value}, {length})");
++ self.expr(value);
++ self.body(field!(length.body));
++ },
++ ExprKind::Err => kind!("Err"),
+ ExprKind::DropTemps(expr) => {
- fn visit_block(&mut self, block: &Block<'_>) {
- println!(" if {}.stmts.len() == {};", self.current, block.stmts.len());
- let block_name = self.current.clone();
- for (i, stmt) in block.stmts.iter().enumerate() {
- self.current = format!("{}.stmts[{}]", block_name, i);
- self.visit_stmt(stmt);
- }
- if let Some(expr) = block.expr {
- self.current = self.next("trailing_expr");
- println!(" if let Some({}) = &{}.expr;", self.current, block_name);
- self.visit_expr(expr);
- } else {
- println!(" if {}.expr.is_none();", block_name);
- }
++ bind!(self, expr);
++ kind!("DropTemps({expr})");
++ self.expr(expr);
+ },
+ }
+ }
+
- #[allow(clippy::too_many_lines)]
- fn visit_pat(&mut self, pat: &Pat<'_>) {
- print!(" if let PatKind::");
- let current = format!("{}.kind", self.current);
- match pat.kind {
- PatKind::Wild => println!("Wild = {};", current),
- PatKind::Binding(anno, .., ident, ref sub) => {
- let anno_pat = &format!("BindingAnnotation::{:?}", anno);
- let name_pat = self.next("name");
- if let Some(sub) = *sub {
- let sub_pat = self.next("sub");
- println!(
- "Binding({}, _, {}, Some(ref {})) = {};",
- anno_pat, name_pat, sub_pat, current
- );
- self.current = sub_pat;
- self.visit_pat(sub);
- } else {
- println!("Binding({}, _, {}, None) = {};", anno_pat, name_pat, current);
- }
- println!(" if {}.as_str() == \"{}\";", name_pat, ident.as_str());
- },
- PatKind::Struct(ref path, fields, ignore) => {
- let path_pat = self.next("path");
- let fields_pat = self.next("fields");
- println!(
- "Struct(ref {}, ref {}, {}) = {};",
- path_pat, fields_pat, ignore, current
- );
- self.current = path_pat;
- self.print_qpath(path);
- println!(" if {}.len() == {};", fields_pat, fields.len());
- println!(" // unimplemented: field checks");
++ fn block(&self, block: &Binding<&hir::Block<'_>>) {
++ self.slice(field!(block.stmts), |stmt| self.stmt(stmt));
++ self.option(field!(block.expr), "trailing_expr", |expr| {
++ self.expr(expr);
++ });
+ }
+
- let fields_pat = self.next("fields");
- println!("Or(ref {}) = {};", fields_pat, current);
- println!(" if {}.len() == {};", fields_pat, fields.len());
- println!(" // unimplemented: field checks");
- },
- PatKind::TupleStruct(ref path, fields, skip_pos) => {
- let path_pat = self.next("path");
- let fields_pat = self.next("fields");
- println!(
- "TupleStruct(ref {}, ref {}, {:?}) = {};",
- path_pat, fields_pat, skip_pos, current
- );
- self.current = path_pat;
- self.print_qpath(path);
- println!(" if {}.len() == {};", fields_pat, fields.len());
- println!(" // unimplemented: field checks");
- },
- PatKind::Path(ref path) => {
- let path_pat = self.next("path");
- println!("Path(ref {}) = {};", path_pat, current);
- self.current = path_pat;
- self.print_qpath(path);
++ fn body(&self, body_id: &Binding<hir::BodyId>) {
++ let expr = &self.cx.tcx.hir().body(body_id.value).value;
++ bind!(self, expr);
++ out!("let {expr} = &cx.tcx.hir().body({body_id}).value;");
++ self.expr(expr);
++ }
++
++ fn pat(&self, pat: &Binding<&hir::Pat<'_>>) {
++ let kind = |kind| out!("if let PatKind::{kind} = {pat}.kind;");
++ macro_rules! kind {
++ ($($t:tt)*) => (kind(format_args!($($t)*)));
++ }
++
++ match pat.value.kind {
++ PatKind::Wild => kind!("Wild"),
++ PatKind::Binding(anno, .., name, sub) => {
++ bind!(self, name);
++ opt_bind!(self, sub);
++ kind!("Binding(BindingAnnotation::{anno:?}, _, {name}, {sub})");
++ self.ident(name);
++ sub.if_some(|p| self.pat(p));
++ },
++ PatKind::Struct(ref qpath, fields, ignore) => {
++ bind!(self, qpath, fields);
++ kind!("Struct(ref {qpath}, {fields}, {ignore})");
++ self.qpath(qpath);
++ self.slice(fields, |field| {
++ self.ident(field!(field.ident));
++ self.pat(field!(field.pat));
++ });
+ },
+ PatKind::Or(fields) => {
- let fields_pat = self.next("fields");
- println!("Tuple(ref {}, {:?}) = {};", fields_pat, skip_pos, current);
- println!(" if {}.len() == {};", fields_pat, fields.len());
- println!(" // unimplemented: field checks");
++ bind!(self, fields);
++ kind!("Or({fields})");
++ self.slice(fields, |pat| self.pat(pat));
++ },
++ PatKind::TupleStruct(ref qpath, fields, skip_pos) => {
++ bind!(self, qpath, fields);
++ kind!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
++ self.qpath(qpath);
++ self.slice(fields, |pat| self.pat(pat));
++ },
++ PatKind::Path(ref qpath) => {
++ bind!(self, qpath);
++ kind!("Path(ref {qpath})");
++ self.qpath(qpath);
+ },
+ PatKind::Tuple(fields, skip_pos) => {
- let pat_pat = self.next("pat");
- println!("Box(ref {}) = {};", pat_pat, current);
- self.current = pat_pat;
- self.visit_pat(pat);
++ bind!(self, fields);
++ kind!("Tuple({fields}, {skip_pos:?})");
++ self.slice(fields, |field| self.pat(field));
+ },
+ PatKind::Box(pat) => {
- let pat_pat = self.next("pat");
- println!("Ref(ref {}, Mutability::{:?}) = {};", pat_pat, muta, current);
- self.current = pat_pat;
- self.visit_pat(pat);
++ bind!(self, pat);
++ kind!("Box({pat})");
++ self.pat(pat);
+ },
+ PatKind::Ref(pat, muta) => {
- let lit_expr_pat = self.next("lit_expr");
- println!("Lit(ref {}) = {}", lit_expr_pat, current);
- self.current = lit_expr_pat;
- self.visit_expr(lit_expr);
- },
- PatKind::Range(ref start, ref end, end_kind) => {
- let start_pat = self.next("start");
- let end_pat = self.next("end");
- println!(
- "Range(ref {}, ref {}, RangeEnd::{:?}) = {};",
- start_pat, end_pat, end_kind, current
- );
- self.current = start_pat;
- walk_list!(self, visit_expr, start);
- self.current = end_pat;
- walk_list!(self, visit_expr, end);
- },
- PatKind::Slice(start, ref middle, end) => {
- let start_pat = self.next("start");
- let end_pat = self.next("end");
- if let Some(middle) = middle {
- let middle_pat = self.next("middle");
- println!(
- "Slice(ref {}, Some(ref {}), ref {}) = {};",
- start_pat, middle_pat, end_pat, current
- );
- self.current = middle_pat;
- self.visit_pat(middle);
- } else {
- println!("Slice(ref {}, None, ref {}) = {};", start_pat, end_pat, current);
- }
- println!(" if {}.len() == {};", start_pat, start.len());
- for (i, pat) in start.iter().enumerate() {
- self.current = format!("{}[{}]", start_pat, i);
- self.visit_pat(pat);
- }
- println!(" if {}.len() == {};", end_pat, end.len());
- for (i, pat) in end.iter().enumerate() {
- self.current = format!("{}[{}]", end_pat, i);
- self.visit_pat(pat);
- }
++ bind!(self, pat);
++ kind!("Ref({pat}, Mutability::{muta:?})");
++ self.pat(pat);
+ },
+ PatKind::Lit(lit_expr) => {
- fn visit_stmt(&mut self, s: &Stmt<'_>) {
- print!(" if let StmtKind::");
- let current = format!("{}.kind", self.current);
- match s.kind {
- // A local (let) binding:
- StmtKind::Local(local) => {
- let local_pat = self.next("local");
- println!("Local(ref {}) = {};", local_pat, current);
- if let Some(init) = local.init {
- let init_pat = self.next("init");
- println!(" if let Some(ref {}) = {}.init;", init_pat, local_pat);
- self.current = init_pat;
- self.visit_expr(init);
- }
- self.current = format!("{}.pat", local_pat);
- self.visit_pat(local.pat);
- },
- // An item binding:
- StmtKind::Item(_) => {
- println!("Item(item_id) = {};", current);
- },
++ bind!(self, lit_expr);
++ kind!("Lit({lit_expr})");
++ self.expr(lit_expr);
++ },
++ PatKind::Range(start, end, end_kind) => {
++ opt_bind!(self, start, end);
++ kind!("Range({start}, {end}, RangeEnd::{end_kind:?})");
++ start.if_some(|e| self.expr(e));
++ end.if_some(|e| self.expr(e));
++ },
++ PatKind::Slice(start, middle, end) => {
++ bind!(self, start, end);
++ opt_bind!(self, middle);
++ kind!("Slice({start}, {middle}, {end})");
++ middle.if_some(|p| self.pat(p));
++ self.slice(start, |pat| self.pat(pat));
++ self.slice(end, |pat| self.pat(pat));
+ },
+ }
+ }
+
- // Expr without trailing semi-colon (must have unit type):
++ fn stmt(&self, stmt: &Binding<&hir::Stmt<'_>>) {
++ let kind = |kind| out!("if let StmtKind::{kind} = {stmt}.kind;");
++ macro_rules! kind {
++ ($($t:tt)*) => (kind(format_args!($($t)*)));
++ }
+
- let e_pat = self.next("e");
- println!("Expr(ref {}, _) = {}", e_pat, current);
- self.current = e_pat;
- self.visit_expr(e);
++ match stmt.value.kind {
++ StmtKind::Local(local) => {
++ bind!(self, local);
++ kind!("Local({local})");
++ self.option(field!(local.init), "init", |init| {
++ self.expr(init);
++ });
++ self.pat(field!(local.pat));
++ },
++ StmtKind::Item(_) => kind!("Item(item_id)"),
+ StmtKind::Expr(e) => {
-
- // Expr with trailing semi-colon (may have any type):
++ bind!(self, e);
++ kind!("Expr({e})");
++ self.expr(e);
+ },
- let e_pat = self.next("e");
- println!("Semi(ref {}, _) = {}", e_pat, current);
- self.current = e_pat;
- self.visit_expr(e);
+ StmtKind::Semi(e) => {
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
++ bind!(self, e);
++ kind!("Semi({e})");
++ self.expr(e);
+ },
+ }
+ }
- fn print_path(path: &QPath<'_>, first: &mut bool) {
- match *path {
- QPath::Resolved(_, path) => {
- for segment in path.segments {
- if *first {
- *first = false;
- } else {
- print!(", ");
- }
- print!("{:?}", segment.ident.as_str());
- }
- },
- QPath::TypeRelative(ty, segment) => match ty.kind {
- hir::TyKind::Path(ref inner_path) => {
- print_path(inner_path, first);
- if *first {
- *first = false;
- } else {
- print!(", ");
+}
+
+fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
+ let attrs = cx.tcx.hir().attrs(hir_id);
+ get_attr(cx.sess(), attrs, "author").count() > 0
+}
+
- print!("{:?}", segment.ident.as_str());
++fn path_to_string(path: &QPath<'_>) -> String {
++ fn inner(s: &mut String, path: &QPath<'_>) {
++ match *path {
++ QPath::Resolved(_, path) => {
++ for (i, segment) in path.segments.iter().enumerate() {
++ if i > 0 {
++ *s += ", ";
++ }
++ write!(s, "{:?}", segment.ident.as_str()).unwrap();
+ }
- ref other => print!("/* unimplemented: {:?}*/", other),
- },
- QPath::LangItem(..) => panic!("print_path: called for lang item qpath"),
+ },
++ QPath::TypeRelative(ty, segment) => match &ty.kind {
++ hir::TyKind::Path(inner_path) => {
++ inner(s, inner_path);
++ *s += ", ";
++ write!(s, "{:?}", segment.ident.as_str()).unwrap();
++ },
++ other => write!(s, "/* unimplemented: {:?}*/", other).unwrap(),
++ },
++ QPath::LangItem(..) => panic!("path_to_string: called for lang item qpath"),
++ }
+ }
++ let mut s = String::new();
++ inner(&mut s, path);
++ s
+}
--- /dev/null
- /// A single disallowed method, used by the `DISALLOWED_METHOD` lint.
+//! Read configurations files.
+
+#![allow(clippy::module_name_repetitions)]
+
+use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
+use serde::Deserialize;
+use std::error::Error;
+use std::path::{Path, PathBuf};
+use std::{env, fmt, fs, io};
+
+/// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
+#[derive(Clone, Debug, Deserialize)]
+pub struct Rename {
+ pub path: String,
+ pub rename: String,
+}
+
- /// A single disallowed type, used by the `DISALLOWED_TYPE` lint.
++/// A single disallowed method, used by the `DISALLOWED_METHODS` lint.
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
+pub enum DisallowedMethod {
+ Simple(String),
+ WithReason { path: String, reason: Option<String> },
+}
+
- /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT.
++/// A single disallowed type, used by the `DISALLOWED_TYPES` lint.
+#[derive(Clone, Debug, Deserialize)]
+#[serde(untagged)]
+pub enum DisallowedType {
+ Simple(String),
+ WithReason { path: String, reason: Option<String> },
+}
+
+/// Conf with parse errors
+#[derive(Default)]
+pub struct TryConf {
+ pub conf: Conf,
+ pub errors: Vec<String>,
+}
+
+impl TryConf {
+ fn from_error(error: impl Error) -> Self {
+ Self {
+ conf: Conf::default(),
+ errors: vec![error.to_string()],
+ }
+ }
+}
+
+macro_rules! define_Conf {
+ ($(
+ $(#[doc = $doc:literal])+
+ $(#[conf_deprecated($dep:literal)])?
+ ($name:ident: $ty:ty = $default:expr),
+ )*) => {
+ /// Clippy lint configuration
+ pub struct Conf {
+ $($(#[doc = $doc])+ pub $name: $ty,)*
+ }
+
+ mod defaults {
+ $(pub fn $name() -> $ty { $default })*
+ }
+
+ impl Default for Conf {
+ fn default() -> Self {
+ Self { $($name: defaults::$name(),)* }
+ }
+ }
+
+ impl<'de> Deserialize<'de> for TryConf {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
+ deserializer.deserialize_map(ConfVisitor)
+ }
+ }
+
+ #[derive(Deserialize)]
+ #[serde(field_identifier, rename_all = "kebab-case")]
+ #[allow(non_camel_case_types)]
+ enum Field { $($name,)* third_party, }
+
+ struct ConfVisitor;
+
+ impl<'de> Visitor<'de> for ConfVisitor {
+ type Value = TryConf;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("Conf")
+ }
+
+ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
+ let mut errors = Vec::new();
+ $(let mut $name = None;)*
+ // could get `Field` here directly, but get `str` first for diagnostics
+ while let Some(name) = map.next_key::<&str>()? {
+ match Field::deserialize(name.into_deserializer())? {
+ $(Field::$name => {
+ $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)?
+ match map.next_value() {
+ Err(e) => errors.push(e.to_string()),
+ Ok(value) => match $name {
+ Some(_) => errors.push(format!("duplicate field `{}`", name)),
+ None => $name = Some(value),
+ }
+ }
+ })*
+ // white-listed; ignore
+ Field::third_party => drop(map.next_value::<IgnoredAny>())
+ }
+ }
+ let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
+ Ok(TryConf { conf, errors })
+ }
+ }
+
+ #[cfg(feature = "metadata-collector-lint")]
+ pub mod metadata {
+ use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
+
+ macro_rules! wrap_option {
+ () => (None);
+ ($x:literal) => (Some($x));
+ }
+
+ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
+ vec![
+ $(
+ {
+ let deprecation_reason = wrap_option!($($dep)?);
+
+ ClippyConfiguration::new(
+ stringify!($name),
+ stringify!($ty),
+ format!("{:?}", super::defaults::$name()),
+ concat!($($doc, '\n',)*),
+ deprecation_reason,
+ )
+ },
+ )+
+ ]
+ }
+ }
+ };
+}
+
+define_Conf! {
+ /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
+ ///
+ /// Suppress lints whenever the suggested change would cause breakage for other crates.
+ (avoid_breaking_exported_api: bool = true),
- /// Lint: DISALLOWED_METHOD.
++ /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE.
+ ///
+ /// The minimum rust version that the project supports
+ (msrv: Option<String> = None),
+ /// Lint: BLACKLISTED_NAME.
+ ///
+ /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
+ (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: u64 = 25),
+ /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
+ ///
+ /// Use the Cognitive Complexity lint instead.
+ #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")]
+ (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: Vec<String> = [
+ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
+ "DirectX",
+ "ECMAScript",
+ "GPLv2", "GPLv3",
+ "GitHub", "GitLab",
+ "IPv4", "IPv6",
+ "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript",
+ "NaN", "NaNs",
+ "OAuth", "GraphQL",
+ "OCaml",
+ "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
+ "WebGL",
+ "TensorFlow",
+ "TrueType",
+ "iOS", "macOS", "FreeBSD",
+ "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: u64 = 7),
+ /// Lint: TYPE_COMPLEXITY.
+ ///
+ /// The maximum complexity a type can have
+ (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: u64 = 4),
+ /// Lint: BOXED_LOCAL, USELESS_VEC.
+ ///
+ /// The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
+ (too_large_for_stack: u64 = 200),
+ /// Lint: ENUM_VARIANT_NAMES.
+ ///
+ /// The minimum number of enum variants for the lints about variant names to trigger
+ (enum_variant_name_threshold: u64 = 3),
+ /// Lint: LARGE_ENUM_VARIANT.
+ ///
+ /// The maximum size of an enum's variant to avoid box suggestion
+ (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: u64 = 1),
+ /// Lint: DECIMAL_LITERAL_REPRESENTATION.
+ ///
+ /// The lower bound for linting decimal literals
+ (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: Option<u64> = None),
+ /// Lint: LARGE_TYPE_PASS_BY_MOVE.
+ ///
+ /// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
+ (pass_by_value_size_limit: u64 = 256),
+ /// Lint: TOO_MANY_LINES.
+ ///
+ /// The maximum number of lines a function or method can have
+ (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: 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: u64 = 4096),
+ /// Lint: TYPE_REPETITION_IN_BOUNDS.
+ ///
+ /// The maximum number of bounds a trait can have to be linted
+ (max_trait_bounds: u64 = 3),
+ /// Lint: STRUCT_EXCESSIVE_BOOLS.
+ ///
+ /// The maximum number of bool fields a struct can have
+ (max_struct_bools: u64 = 3),
+ /// Lint: FN_PARAMS_EXCESSIVE_BOOLS.
+ ///
+ /// The maximum number of bool parameters a function can have
+ (max_fn_params_bools: u64 = 3),
+ /// Lint: WILDCARD_IMPORTS.
+ ///
+ /// Whether to allow certain wildcard imports (prelude, super in tests).
+ (warn_on_all_wildcard_imports: bool = false),
- /// Lint: DISALLOWED_TYPE.
++ /// Lint: DISALLOWED_METHODS.
+ ///
+ /// The list of disallowed methods, written as fully qualified paths.
+ (disallowed_methods: Vec<crate::utils::conf::DisallowedMethod> = Vec::new()),
++ /// Lint: DISALLOWED_TYPES.
+ ///
+ /// The list of disallowed types, written as fully qualified paths.
+ (disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
+ /// Lint: UNREADABLE_LITERAL.
+ ///
+ /// Should the fraction of a decimal be linted to include separators.
+ (unreadable_literal_lint_fractions: bool = true),
+ /// Lint: UPPER_CASE_ACRONYMS.
+ ///
+ /// Enables verbose mode. Triggers if there is more than one uppercase char next to each other
+ (upper_case_acronyms_aggressive: bool = false),
+ /// Lint: _CARGO_COMMON_METADATA.
+ ///
+ /// For internal testing only, ignores the current `publish` settings in the Cargo manifest.
+ (cargo_ignore_publish: bool = false),
+ /// Lint: NONSTANDARD_MACRO_BRACES.
+ ///
+ /// Enforce the named macros always use the braces specified.
+ ///
+ /// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
+ /// is could be used with a full path two `MacroMatcher`s have to be added one with the full path
+ /// `crate_name::macro_name` and one with just the macro name.
+ (standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
+ /// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
+ ///
+ /// The list of imports to always rename, a fully qualified path followed by the rename.
+ (enforced_import_renames: Vec<crate::utils::conf::Rename> = Vec::new()),
+ /// Lint: DISALLOWED_SCRIPT_IDENTS.
+ ///
+ /// The list of unicode scripts allowed to be used in the scope.
+ (allowed_scripts: Vec<String> = ["Latin"].iter().map(ToString::to_string).collect()),
+ /// Lint: NON_SEND_FIELDS_IN_SEND_TY.
+ ///
+ /// Whether to apply the raw pointer heuristic to determine if a type is `Send`.
+ (enable_raw_pointer_heuristic_for_send: bool = true),
++ /// Lint: INDEX_REFUTABLE_SLICE.
++ ///
++ /// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
++ /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
++ /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
++ (max_suggested_slice_pattern_length: u64 = 3),
+}
+
+/// 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 {
+ if let Ok(config_file) = current.join(config_file_name).canonicalize() {
+ match fs::metadata(&config_file) {
+ Err(e) if e.kind() == io::ErrorKind::NotFound => {},
+ Err(e) => return Err(e),
+ Ok(md) if md.is_dir() => {},
+ Ok(_) => return Ok(Some(config_file)),
+ }
+ }
+ }
+
+ // If the current directory has no parent, we're done searching.
+ if !current.pop() {
+ return Ok(None);
+ }
+ }
+}
+
+/// Read the `toml` configuration file.
+///
+/// In case of error, the function tries to continue as much as possible.
+pub fn read(path: &Path) -> TryConf {
+ let content = match fs::read_to_string(path) {
+ Err(e) => return TryConf::from_error(e),
+ Ok(content) => content,
+ };
+ toml::from_str(&content).unwrap_or_else(TryConf::from_error)
+}
--- /dev/null
- use rustc_span::{BytePos, Span};
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::higher;
+use clippy_utils::source::snippet;
+use clippy_utils::ty::match_type;
+use clippy_utils::{
+ is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path, method_calls, path_to_res,
+ paths, SpanlessEq,
+};
+use if_chain::if_chain;
++use rustc_ast as ast;
+use rustc_ast::ast::{Crate, ItemKind, LitKind, ModKind, NodeId};
+use rustc_ast::visit::FnKind;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
+use rustc_hir::{
+ BinOpKind, Block, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty, TyKind,
+ UnOp,
+};
+use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_middle::hir::map::Map;
+use rustc_middle::mir::interpret::ConstValue;
+use rustc_middle::ty;
++use rustc_semver::RustcVersion;
+use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{Symbol, SymbolStr};
- impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]);
++use rustc_span::{sym, BytePos, Span};
+use rustc_typeck::hir_ty_to_ty;
+
+use std::borrow::{Borrow, Cow};
+
+#[cfg(feature = "metadata-collector-lint")]
+pub mod metadata_collector;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for various things we like to keep tidy in clippy.
+ ///
+ /// ### Why is this bad?
+ /// We like to pretend we're an example of tidy code.
+ ///
+ /// ### Example
+ /// Wrong ordering of the util::paths constants.
+ pub CLIPPY_LINTS_INTERNAL,
+ internal,
+ "various things that will negatively affect your clippy experience"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Ensures every lint is associated to a `LintPass`.
+ ///
+ /// ### Why is this bad?
+ /// The compiler only knows lints via a `LintPass`. Without
+ /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not
+ /// know the name of the lint.
+ ///
+ /// ### Known problems
+ /// Only checks for lints associated using the
+ /// `declare_lint_pass!`, `impl_lint_pass!`, and `lint_array!` macros.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// declare_lint! { pub LINT_1, ... }
+ /// declare_lint! { pub LINT_2, ... }
+ /// declare_lint! { pub FORGOTTEN_LINT, ... }
+ /// // ...
+ /// declare_lint_pass!(Pass => [LINT_1, LINT_2]);
+ /// // missing FORGOTTEN_LINT
+ /// ```
+ pub LINT_WITHOUT_LINT_PASS,
+ internal,
+ "declaring a lint without associating it in a LintPass"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `cx.span_lint*` and suggests to use the `utils::*`
+ /// variant of the function.
+ ///
+ /// ### Why is this bad?
+ /// The `utils::*` variants also add a link to the Clippy documentation to the
+ /// warning/error messages.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// cx.span_lint(LINT_NAME, "message");
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// utils::span_lint(cx, LINT_NAME, "message");
+ /// ```
+ pub COMPILER_LINT_FUNCTIONS,
+ internal,
+ "usage of the lint functions of the compiler instead of the utils::* variant"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `cx.outer().expn_data()` and suggests to use
+ /// the `cx.outer_expn_data()`
+ ///
+ /// ### Why is this bad?
+ /// `cx.outer_expn_data()` is faster and more concise.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// expr.span.ctxt().outer().expn_data()
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// expr.span.ctxt().outer_expn_data()
+ /// ```
+ pub OUTER_EXPN_EXPN_DATA,
+ internal,
+ "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Not an actual lint. This lint is only meant for testing our customized internal compiler
+ /// error message by calling `panic`.
+ ///
+ /// ### Why is this bad?
+ /// ICE in large quantities can damage your teeth
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// 🍦🍦🍦🍦🍦
+ /// ```
+ pub PRODUCE_ICE,
+ internal,
+ "this message should not appear anywhere as we ICE before and don't emit the lint"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for cases of an auto-generated lint without an updated description,
+ /// i.e. `default lint description`.
+ ///
+ /// ### Why is this bad?
+ /// Indicates that the lint is not finished.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// declare_lint! { pub COOL_LINT, nursery, "default lint description" }
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" }
+ /// ```
+ pub DEFAULT_LINT,
+ internal,
+ "found 'default lint description' in a lint declaration"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Lints `span_lint_and_then` function calls, where the
+ /// closure argument has only one statement and that statement is a method
+ /// call to `span_suggestion`, `span_help`, `span_note` (using the same
+ /// span), `help` or `note`.
+ ///
+ /// These usages of `span_lint_and_then` should be replaced with one of the
+ /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or
+ /// `span_lint_and_note`.
+ ///
+ /// ### Why is this bad?
+ /// Using the wrapper `span_lint_and_*` functions, is more
+ /// convenient, readable and less error prone.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_suggestion(
+ /// expr.span,
+ /// help_msg,
+ /// sugg.to_string(),
+ /// Applicability::MachineApplicable,
+ /// );
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_help(expr.span, help_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.help(help_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.span_note(expr.span, note_msg);
+ /// });
+ /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| {
+ /// diag.note(note_msg);
+ /// });
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// span_lint_and_sugg(
+ /// cx,
+ /// TEST_LINT,
+ /// expr.span,
+ /// lint_msg,
+ /// help_msg,
+ /// sugg.to_string(),
+ /// Applicability::MachineApplicable,
+ /// );
+ /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
+ /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
+ /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
+ /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
+ /// ```
+ pub COLLAPSIBLE_SPAN_LINT_CALLS,
+ internal,
+ "found collapsible `span_lint_and_then` calls"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `utils::match_type()` on a type diagnostic item
+ /// and suggests to use `utils::is_type_diagnostic_item()` instead.
+ ///
+ /// ### Why is this bad?
+ /// `utils::is_type_diagnostic_item()` does not require hardcoded paths.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// utils::match_type(cx, ty, &paths::VEC)
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// utils::is_type_diagnostic_item(cx, ty, sym::Vec)
+ /// ```
+ pub MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
+ internal,
+ "using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks the paths module for invalid paths.
+ ///
+ /// ### Why is this bad?
+ /// It indicates a bug in the code.
+ ///
+ /// ### Example
+ /// None.
+ pub INVALID_PATHS,
+ internal,
+ "invalid path"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for interning symbols that have already been pre-interned and defined as constants.
+ ///
+ /// ### Why is this bad?
+ /// It's faster and easier to use the symbol constant.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// let _ = sym!(f32);
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// let _ = sym::f32;
+ /// ```
+ pub INTERNING_DEFINED_SYMBOL,
+ internal,
+ "interning a symbol that is pre-interned and defined as a constant"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for unnecessary conversion from Symbol to a string.
+ ///
+ /// ### Why is this bad?
+ /// It's faster use symbols directly intead of strings.
+ ///
+ /// ### Example
+ /// Bad:
+ /// ```rust,ignore
+ /// symbol.as_str() == "clippy";
+ /// ```
+ ///
+ /// Good:
+ /// ```rust,ignore
+ /// symbol == sym::clippy;
+ /// ```
+ pub UNNECESSARY_SYMBOL_STR,
+ internal,
+ "unnecessary conversion between Symbol and string"
+}
+
+declare_clippy_lint! {
+ /// Finds unidiomatic usage of `if_chain!`
+ pub IF_CHAIN_STYLE,
+ internal,
+ "non-idiomatic `if_chain!` usage"
+}
+
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for invalid `clippy::version` attributes.
++ ///
++ /// Valid values are:
++ /// * "pre 1.29.0"
++ /// * any valid semantic version
++ pub INVALID_CLIPPY_VERSION_ATTRIBUTE,
++ internal,
++ "found an invalid `clippy::version` attribute"
++}
++
++declare_clippy_lint! {
++ /// ### What it does
++ /// Checks for declared clippy lints without the `clippy::version` attribute.
++ ///
++ pub MISSING_CLIPPY_VERSION_ATTRIBUTE,
++ internal,
++ "found clippy lint without `clippy::version` attribute"
++}
++
+declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
+
+impl EarlyLintPass for ClippyLintsInternal {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
+ if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") {
+ if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind {
+ if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") {
+ if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind {
+ let mut last_name: Option<SymbolStr> = None;
+ for item in items {
+ let name = item.ident.as_str();
+ if let Some(ref last_name) = last_name {
+ if **last_name > *name {
+ span_lint(
+ cx,
+ CLIPPY_LINTS_INTERNAL,
+ item.span,
+ "this constant should be before the previous constant due to lexical \
+ ordering",
+ );
+ }
+ }
+ last_name = Some(name);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct LintWithoutLintPass {
+ declared_lints: FxHashMap<Symbol, Span>,
+ registered_lints: FxHashSet<Symbol>,
+}
+
++impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS, INVALID_CLIPPY_VERSION_ATTRIBUTE, MISSING_CLIPPY_VERSION_ATTRIBUTE]);
+
+impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id()) {
+ return;
+ }
+
+ if let hir::ItemKind::Static(ty, Mutability::Not, body_id) = item.kind {
+ if is_lint_ref_type(cx, ty) {
++ check_invalid_clippy_version_attribute(cx, item);
++
+ let expr = &cx.tcx.hir().body(body_id).value;
+ if_chain! {
+ if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind;
+ if let ExprKind::Struct(_, fields, _) = inner_exp.kind;
+ let field = fields
+ .iter()
+ .find(|f| f.ident.as_str() == "desc")
+ .expect("lints must have a description field");
+ if let ExprKind::Lit(Spanned {
+ node: LitKind::Str(ref sym, _),
+ ..
+ }) = field.expr.kind;
+ if sym.as_str() == "default lint description";
+
+ then {
+ span_lint(
+ cx,
+ DEFAULT_LINT,
+ item.span,
+ &format!("the lint `{}` has the default lint description", item.ident.name),
+ );
+ }
+ }
+ self.declared_lints.insert(item.ident.name, item.span);
+ }
+ } else if is_expn_of(item.span, "impl_lint_pass").is_some()
+ || is_expn_of(item.span, "declare_lint_pass").is_some()
+ {
+ if let hir::ItemKind::Impl(hir::Impl {
+ of_trait: None,
+ items: impl_item_refs,
+ ..
+ }) = item.kind
+ {
+ let mut collector = LintCollector {
+ output: &mut self.registered_lints,
+ cx,
+ };
+ let body_id = cx.tcx.hir().body_owned_by(
+ impl_item_refs
+ .iter()
+ .find(|iiref| iiref.ident.as_str() == "get_lints")
+ .expect("LintPass needs to implement get_lints")
+ .id
+ .hir_id(),
+ );
+ collector.visit_expr(&cx.tcx.hir().body(body_id).value);
+ }
+ }
+ }
+
+ fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
+ if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) {
+ return;
+ }
+
+ for (lint_name, &lint_span) in &self.declared_lints {
+ // When using the `declare_tool_lint!` macro, the original `lint_span`'s
+ // file points to "<rustc macros>".
+ // `compiletest-rs` thinks that's an error in a different file and
+ // just ignores it. This causes the test in compile-fail/lint_pass
+ // not able to capture the error.
+ // Therefore, we need to climb the macro expansion tree and find the
+ // actual span that invoked `declare_tool_lint!`:
+ let lint_span = lint_span.ctxt().outer_expn_data().call_site;
+
+ if !self.registered_lints.contains(lint_name) {
+ span_lint(
+ cx,
+ LINT_WITHOUT_LINT_PASS,
+ lint_span,
+ &format!("the lint `{}` is not added to any `LintPass`", lint_name),
+ );
+ }
+ }
+ }
+}
+
+fn is_lint_ref_type<'tcx>(cx: &LateContext<'tcx>, ty: &Ty<'_>) -> bool {
+ if let TyKind::Rptr(
+ _,
+ MutTy {
+ ty: inner,
+ mutbl: Mutability::Not,
+ },
+ ) = ty.kind
+ {
+ if let TyKind::Path(ref path) = inner.kind {
+ if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) {
+ return match_def_path(cx, def_id, &paths::LINT);
+ }
+ }
+ }
+
+ false
+}
+
++fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) {
++ if let Some(value) = extract_clippy_version_value(cx, item) {
++ // The `sym!` macro doesn't work as it only expects a single token.
++ // It's better to keep it this way and have a direct `Symbol::intern` call here.
++ if value == Symbol::intern("pre 1.29.0") {
++ return;
++ }
++
++ if RustcVersion::parse(&*value.as_str()).is_err() {
++ span_lint_and_help(
++ cx,
++ INVALID_CLIPPY_VERSION_ATTRIBUTE,
++ item.span,
++ "this item has an invalid `clippy::version` attribute",
++ None,
++ "please use a valid sematic version, see `doc/adding_lints.md`",
++ );
++ }
++ } else {
++ span_lint_and_help(
++ cx,
++ MISSING_CLIPPY_VERSION_ATTRIBUTE,
++ item.span,
++ "this lint is missing the `clippy::version` attribute or version value",
++ None,
++ "please use a `clippy::version` attribute, see `doc/adding_lints.md`",
++ );
++ }
++}
++
++/// This function extracts the version value of a `clippy::version` attribute if the given value has
++/// one
++fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> {
++ let attrs = cx.tcx.hir().attrs(item.hir_id());
++ attrs.iter().find_map(|attr| {
++ if_chain! {
++ // Identify attribute
++ if let ast::AttrKind::Normal(ref attr_kind, _) = &attr.kind;
++ if let [tool_name, attr_name] = &attr_kind.path.segments[..];
++ if tool_name.ident.name == sym::clippy;
++ if attr_name.ident.name == sym::version;
++ if let Some(version) = attr.value_str();
++ then {
++ Some(version)
++ } else {
++ None
++ }
++ }
++ })
++}
++
+struct LintCollector<'a, 'tcx> {
+ output: &'a mut FxHashSet<Symbol>,
+ cx: &'a LateContext<'tcx>,
+}
+
+impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_path(&mut self, path: &'tcx Path<'_>, _: HirId) {
+ if path.segments.len() == 1 {
+ self.output.insert(path.segments[0].ident.name);
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct CompilerLintFunctions {
+ map: FxHashMap<&'static str, &'static str>,
+}
+
+impl CompilerLintFunctions {
+ #[must_use]
+ pub fn new() -> Self {
+ let mut map = FxHashMap::default();
+ map.insert("span_lint", "utils::span_lint");
+ map.insert("struct_span_lint", "utils::span_lint");
+ map.insert("lint", "utils::span_lint");
+ map.insert("span_lint_note", "utils::span_lint_and_note");
+ map.insert("span_lint_help", "utils::span_lint_and_help");
+ Self { map }
+ }
+}
+
+impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if is_lint_allowed(cx, COMPILER_LINT_FUNCTIONS, expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
+ let fn_name = path.ident;
+ if let Some(sugg) = self.map.get(&*fn_name.as_str());
+ let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
+ if match_type(cx, ty, &paths::EARLY_CONTEXT)
+ || match_type(cx, ty, &paths::LATE_CONTEXT);
+ then {
+ span_lint_and_help(
+ cx,
+ COMPILER_LINT_FUNCTIONS,
+ path.ident.span,
+ "usage of a compiler lint function",
+ None,
+ &format!("please use the Clippy variant of this function: `{}`", sugg),
+ );
+ }
+ }
+ }
+}
+
+declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]);
+
+impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_lint_allowed(cx, OUTER_EXPN_EXPN_DATA, expr.hir_id) {
+ return;
+ }
+
+ let (method_names, arg_lists, 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();
+ if_chain! {
+ if let ["expn_data", "outer_expn"] = method_names.as_slice();
+ let args = arg_lists[1];
+ if args.len() == 1;
+ let self_arg = &args[0];
+ let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
+ if match_type(cx, self_ty, &paths::SYNTAX_CONTEXT);
+ then {
+ span_lint_and_sugg(
+ cx,
+ OUTER_EXPN_EXPN_DATA,
+ spans[1].with_hi(expr.span.hi()),
+ "usage of `outer_expn().expn_data()`",
+ "try",
+ "outer_expn_data()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
+
+declare_lint_pass!(ProduceIce => [PRODUCE_ICE]);
+
+impl EarlyLintPass for ProduceIce {
+ fn check_fn(&mut self, _: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
+ assert!(!is_trigger_fn(fn_kind), "Would you like some help with that?");
+ }
+}
+
+fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool {
+ match fn_kind {
+ FnKind::Fn(_, ident, ..) => ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy",
+ FnKind::Closure(..) => false,
+ }
+}
+
+declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]);
+
+impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_lint_allowed(cx, COLLAPSIBLE_SPAN_LINT_CALLS, expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ if let ExprKind::Call(func, and_then_args) = expr.kind;
+ if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]);
+ if and_then_args.len() == 5;
+ if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind;
+ let body = cx.tcx.hir().body(*body_id);
+ if let ExprKind::Block(block, _) = &body.value.kind;
+ let stmts = &block.stmts;
+ if stmts.len() == 1 && block.expr.is_none();
+ if let StmtKind::Semi(only_expr) = &stmts[0].kind;
+ if let ExprKind::MethodCall(ps, _, span_call_args, _) = &only_expr.kind;
+ then {
+ let and_then_snippets = get_and_then_snippets(cx, and_then_args);
+ let mut sle = SpanlessEq::new(cx).deny_side_effects();
+ match &*ps.ident.as_str() {
+ "span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+ suggest_suggestion(cx, expr, &and_then_snippets, &span_suggestion_snippets(cx, span_call_args));
+ },
+ "span_help" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+ let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+ suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
+ },
+ "span_note" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
+ let note_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+ suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
+ },
+ "help" => {
+ let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+ suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
+ }
+ "note" => {
+ let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
+ suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+}
+
+struct AndThenSnippets<'a> {
+ cx: Cow<'a, str>,
+ lint: Cow<'a, str>,
+ span: Cow<'a, str>,
+ msg: Cow<'a, str>,
+}
+
+fn get_and_then_snippets<'a, 'hir>(cx: &LateContext<'_>, and_then_snippets: &'hir [Expr<'hir>]) -> AndThenSnippets<'a> {
+ let cx_snippet = snippet(cx, and_then_snippets[0].span, "cx");
+ let lint_snippet = snippet(cx, and_then_snippets[1].span, "..");
+ let span_snippet = snippet(cx, and_then_snippets[2].span, "span");
+ let msg_snippet = snippet(cx, and_then_snippets[3].span, r#""...""#);
+
+ AndThenSnippets {
+ cx: cx_snippet,
+ lint: lint_snippet,
+ span: span_snippet,
+ msg: msg_snippet,
+ }
+}
+
+struct SpanSuggestionSnippets<'a> {
+ help: Cow<'a, str>,
+ sugg: Cow<'a, str>,
+ applicability: Cow<'a, str>,
+}
+
+fn span_suggestion_snippets<'a, 'hir>(
+ cx: &LateContext<'_>,
+ span_call_args: &'hir [Expr<'hir>],
+) -> SpanSuggestionSnippets<'a> {
+ let help_snippet = snippet(cx, span_call_args[2].span, r#""...""#);
+ let sugg_snippet = snippet(cx, span_call_args[3].span, "..");
+ let applicability_snippet = snippet(cx, span_call_args[4].span, "Applicability::MachineApplicable");
+
+ SpanSuggestionSnippets {
+ help: help_snippet,
+ sugg: sugg_snippet,
+ applicability: applicability_snippet,
+ }
+}
+
+fn suggest_suggestion(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
+) {
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collapsible",
+ "collapse into",
+ format!(
+ "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})",
+ and_then_snippets.cx,
+ and_then_snippets.lint,
+ and_then_snippets.span,
+ and_then_snippets.msg,
+ span_suggestion_snippets.help,
+ span_suggestion_snippets.sugg,
+ span_suggestion_snippets.applicability
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn suggest_help(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ help: &str,
+ with_span: bool,
+) {
+ let option_span = if with_span {
+ format!("Some({})", and_then_snippets.span)
+ } else {
+ "None".to_string()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collapsible",
+ "collapse into",
+ format!(
+ "span_lint_and_help({}, {}, {}, {}, {}, {})",
+ and_then_snippets.cx,
+ and_then_snippets.lint,
+ and_then_snippets.span,
+ and_then_snippets.msg,
+ &option_span,
+ help
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+fn suggest_note(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ and_then_snippets: &AndThenSnippets<'_>,
+ note: &str,
+ with_span: bool,
+) {
+ let note_span = if with_span {
+ format!("Some({})", and_then_snippets.span)
+ } else {
+ "None".to_string()
+ };
+
+ span_lint_and_sugg(
+ cx,
+ COLLAPSIBLE_SPAN_LINT_CALLS,
+ expr.span,
+ "this call is collspible",
+ "collapse into",
+ format!(
+ "span_lint_and_note({}, {}, {}, {}, {}, {})",
+ and_then_snippets.cx,
+ and_then_snippets.lint,
+ and_then_snippets.span,
+ and_then_snippets.msg,
+ note_span,
+ note
+ ),
+ Applicability::MachineApplicable,
+ );
+}
+
+declare_lint_pass!(MatchTypeOnDiagItem => [MATCH_TYPE_ON_DIAGNOSTIC_ITEM]);
+
+impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if is_lint_allowed(cx, MATCH_TYPE_ON_DIAGNOSTIC_ITEM, expr.hir_id) {
+ return;
+ }
+
+ if_chain! {
+ // Check if this is a call to utils::match_type()
+ if let ExprKind::Call(fn_path, [context, ty, ty_path]) = expr.kind;
+ if is_expr_path_def_path(cx, fn_path, &["clippy_utils", "ty", "match_type"]);
+ // Extract the path to the matched type
+ if let Some(segments) = path_to_matched_type(cx, ty_path);
+ let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
+ if let Some(ty_did) = path_to_res(cx, &segments[..]).opt_def_id();
+ // Check if the matched type is a diagnostic item
+ if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did);
+ then {
+ // TODO: check paths constants from external crates.
+ let cx_snippet = snippet(cx, context.span, "_");
+ let ty_snippet = snippet(cx, ty.span, "_");
+
+ span_lint_and_sugg(
+ cx,
+ MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
+ expr.span,
+ "usage of `clippy_utils::ty::match_type()` on a type diagnostic item",
+ "try",
+ format!("clippy_utils::ty::is_type_diagnostic_item({}, {}, sym::{})", cx_snippet, ty_snippet, item_name),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+}
+
+fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Vec<SymbolStr>> {
+ use rustc_hir::ItemKind;
+
+ match &expr.kind {
+ ExprKind::AddrOf(.., expr) => return path_to_matched_type(cx, expr),
+ ExprKind::Path(qpath) => match cx.qpath_res(qpath, expr.hir_id) {
+ Res::Local(hir_id) => {
+ let parent_id = cx.tcx.hir().get_parent_node(hir_id);
+ if let Some(Node::Local(local)) = cx.tcx.hir().find(parent_id) {
+ if let Some(init) = local.init {
+ return path_to_matched_type(cx, init);
+ }
+ }
+ },
+ Res::Def(DefKind::Const | DefKind::Static, def_id) => {
+ if let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(def_id) {
+ if let ItemKind::Const(.., body_id) | ItemKind::Static(.., body_id) = item.kind {
+ let body = cx.tcx.hir().body(body_id);
+ return path_to_matched_type(cx, &body.value);
+ }
+ }
+ },
+ _ => {},
+ },
+ ExprKind::Array(exprs) => {
+ let segments: Vec<SymbolStr> = exprs
+ .iter()
+ .filter_map(|expr| {
+ if let ExprKind::Lit(lit) = &expr.kind {
+ if let LitKind::Str(sym, _) = lit.node {
+ return Some(sym.as_str());
+ }
+ }
+
+ None
+ })
+ .collect();
+
+ if segments.len() == exprs.len() {
+ return Some(segments);
+ }
+ },
+ _ => {},
+ }
+
+ None
+}
+
+// This is not a complete resolver for paths. It works on all the paths currently used in the paths
+// module. That's all it does and all it needs to do.
+pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
+ if path_to_res(cx, path) != Res::Err {
+ return true;
+ }
+
+ // Some implementations can't be found by `path_to_res`, particularly inherent
+ // implementations of native types. Check lang items.
+ let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
+ let lang_items = cx.tcx.lang_items();
+ for item_def_id in lang_items.items().iter().flatten() {
+ let lang_item_path = cx.get_def_path(*item_def_id);
+ if path_syms.starts_with(&lang_item_path) {
+ if let [item] = &path_syms[lang_item_path.len()..] {
+ for child in cx.tcx.item_children(*item_def_id) {
+ if child.ident.name == *item {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ false
+}
+
+declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
+ let local_def_id = &cx.tcx.parent_module(item.hir_id());
+ let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
+ if_chain! {
+ if mod_name.as_str() == "paths";
+ if let hir::ItemKind::Const(ty, body_id) = item.kind;
+ let ty = hir_ty_to_ty(cx.tcx, ty);
+ if let ty::Array(el_ty, _) = &ty.kind();
+ if let ty::Ref(_, el_ty, _) = &el_ty.kind();
+ if el_ty.is_str();
+ let body = cx.tcx.hir().body(body_id);
+ let typeck_results = cx.tcx.typeck_body(body_id);
+ if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, &body.value);
+ let path: Vec<&str> = path.iter().map(|x| {
+ if let Constant::Str(s) = x {
+ s.as_str()
+ } else {
+ // We checked the type of the constant above
+ unreachable!()
+ }
+ }).collect();
+ if !check_path(cx, &path[..]);
+ then {
+ span_lint(cx, INVALID_PATHS, item.span, "invalid path");
+ }
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct InterningDefinedSymbol {
+ // Maps the symbol value to the constant DefId.
+ symbol_map: FxHashMap<u32, DefId>,
+}
+
+impl_lint_pass!(InterningDefinedSymbol => [INTERNING_DEFINED_SYMBOL, UNNECESSARY_SYMBOL_STR]);
+
+impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ if !self.symbol_map.is_empty() {
+ return;
+ }
+
+ for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
+ if let Some(def_id) = path_to_res(cx, module).opt_def_id() {
+ for item in cx.tcx.item_children(def_id).iter() {
+ if_chain! {
+ if let Res::Def(DefKind::Const, item_def_id) = item.res;
+ let ty = cx.tcx.type_of(item_def_id);
+ if match_type(cx, ty, &paths::SYMBOL);
+ if let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id);
+ if let Ok(value) = value.to_u32();
+ then {
+ self.symbol_map.insert(value, item_def_id);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let ExprKind::Call(func, [arg]) = &expr.kind;
+ if let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind();
+ if match_def_path(cx, *def_id, &paths::SYMBOL_INTERN);
+ if let Some(Constant::Str(arg)) = constant_simple(cx, cx.typeck_results(), arg);
+ let value = Symbol::intern(&arg).as_u32();
+ if let Some(&def_id) = self.symbol_map.get(&value);
+ then {
+ span_lint_and_sugg(
+ cx,
+ INTERNING_DEFINED_SYMBOL,
+ is_expn_of(expr.span, "sym").unwrap_or(expr.span),
+ "interning a defined symbol",
+ "try",
+ cx.tcx.def_path_str(def_id),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ if let ExprKind::Binary(op, left, right) = expr.kind {
+ if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) {
+ let data = [
+ (left, self.symbol_str_expr(left, cx)),
+ (right, self.symbol_str_expr(right, cx)),
+ ];
+ match data {
+ // both operands are a symbol string
+ [(_, Some(left)), (_, Some(right))] => {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_SYMBOL_STR,
+ expr.span,
+ "unnecessary `Symbol` to string conversion",
+ "try",
+ format!(
+ "{} {} {}",
+ left.as_symbol_snippet(cx),
+ op.node.as_str(),
+ right.as_symbol_snippet(cx),
+ ),
+ Applicability::MachineApplicable,
+ );
+ },
+ // one of the operands is a symbol string
+ [(expr, Some(symbol)), _] | [_, (expr, Some(symbol))] => {
+ // creating an owned string for comparison
+ if matches!(symbol, SymbolStrExpr::Expr { is_to_owned: true, .. }) {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_SYMBOL_STR,
+ expr.span,
+ "unnecessary string allocation",
+ "try",
+ format!("{}.as_str()", symbol.as_symbol_snippet(cx)),
+ Applicability::MachineApplicable,
+ );
+ }
+ },
+ // nothing found
+ [(_, None), (_, None)] => {},
+ }
+ }
+ }
+ }
+}
+
+impl InterningDefinedSymbol {
+ fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> {
+ static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR, &paths::TO_STRING_METHOD];
+ static SYMBOL_STR_PATHS: &[&[&str]] = &[
+ &paths::SYMBOL_AS_STR,
+ &paths::SYMBOL_TO_IDENT_STRING,
+ &paths::TO_STRING_METHOD,
+ ];
+ // SymbolStr might be de-referenced: `&*symbol.as_str()`
+ let call = if_chain! {
+ if let ExprKind::AddrOf(_, _, e) = expr.kind;
+ if let ExprKind::Unary(UnOp::Deref, e) = e.kind;
+ then { e } else { expr }
+ };
+ if_chain! {
+ // is a method call
+ if let ExprKind::MethodCall(_, _, [item], _) = call.kind;
+ if let Some(did) = cx.typeck_results().type_dependent_def_id(call.hir_id);
+ let ty = cx.typeck_results().expr_ty(item);
+ // ...on either an Ident or a Symbol
+ if let Some(is_ident) = if match_type(cx, ty, &paths::SYMBOL) {
+ Some(false)
+ } else if match_type(cx, ty, &paths::IDENT) {
+ Some(true)
+ } else {
+ None
+ };
+ // ...which converts it to a string
+ let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS };
+ if let Some(path) = paths.iter().find(|path| match_def_path(cx, did, path));
+ then {
+ let is_to_owned = path.last().unwrap().ends_with("string");
+ return Some(SymbolStrExpr::Expr {
+ item,
+ is_ident,
+ is_to_owned,
+ });
+ }
+ }
+ // is a string constant
+ if let Some(Constant::Str(s)) = constant_simple(cx, cx.typeck_results(), expr) {
+ let value = Symbol::intern(&s).as_u32();
+ // ...which matches a symbol constant
+ if let Some(&def_id) = self.symbol_map.get(&value) {
+ return Some(SymbolStrExpr::Const(def_id));
+ }
+ }
+ None
+ }
+}
+
+enum SymbolStrExpr<'tcx> {
+ /// a string constant with a corresponding symbol constant
+ Const(DefId),
+ /// a "symbol to string" expression like `symbol.as_str()`
+ Expr {
+ /// part that evaluates to `Symbol` or `Ident`
+ item: &'tcx Expr<'tcx>,
+ is_ident: bool,
+ /// whether an owned `String` is created like `to_ident_string()`
+ is_to_owned: bool,
+ },
+}
+
+impl<'tcx> SymbolStrExpr<'tcx> {
+ /// Returns a snippet that evaluates to a `Symbol` and is const if possible
+ fn as_symbol_snippet(&self, cx: &LateContext<'_>) -> Cow<'tcx, str> {
+ match *self {
+ Self::Const(def_id) => cx.tcx.def_path_str(def_id).into(),
+ Self::Expr { item, is_ident, .. } => {
+ let mut snip = snippet(cx, item.span.source_callsite(), "..");
+ if is_ident {
+ // get `Ident.name`
+ snip.to_mut().push_str(".name");
+ }
+ snip
+ },
+ }
+ }
+}
+
+declare_lint_pass!(IfChainStyle => [IF_CHAIN_STYLE]);
+
+impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
+ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+ let (local, after, if_chain_span) = if_chain! {
+ if let [Stmt { kind: StmtKind::Local(local), .. }, after @ ..] = block.stmts;
+ if let Some(if_chain_span) = is_expn_of(block.span, "if_chain");
+ then { (local, after, if_chain_span) } else { return }
+ };
+ if is_first_if_chain_expr(cx, block.hir_id, if_chain_span) {
+ span_lint(
+ cx,
+ IF_CHAIN_STYLE,
+ if_chain_local_span(cx, local, if_chain_span),
+ "`let` expression should be above the `if_chain!`",
+ );
+ } else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) {
+ span_lint(
+ cx,
+ IF_CHAIN_STYLE,
+ if_chain_local_span(cx, local, if_chain_span),
+ "`let` expression should be inside `then { .. }`",
+ );
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ let (cond, then, els) = if let Some(higher::IfOrIfLet { cond, r#else, then }) = higher::IfOrIfLet::hir(expr) {
+ (cond, then, r#else.is_some())
+ } else {
+ return;
+ };
+ let then_block = match then.kind {
+ ExprKind::Block(block, _) => block,
+ _ => return,
+ };
+ let if_chain_span = is_expn_of(expr.span, "if_chain");
+ if !els {
+ check_nested_if_chains(cx, expr, then_block, if_chain_span);
+ }
+ let if_chain_span = match if_chain_span {
+ None => return,
+ Some(span) => span,
+ };
+ // check for `if a && b;`
+ if_chain! {
+ if let ExprKind::Binary(op, _, _) = cond.kind;
+ if op.node == BinOpKind::And;
+ if cx.sess().source_map().is_multiline(cond.span);
+ then {
+ span_lint(cx, IF_CHAIN_STYLE, cond.span, "`if a && b;` should be `if a; if b;`");
+ }
+ }
+ if is_first_if_chain_expr(cx, expr.hir_id, if_chain_span)
+ && is_if_chain_then(then_block.stmts, then_block.expr, if_chain_span)
+ {
+ span_lint(cx, IF_CHAIN_STYLE, expr.span, "`if_chain!` only has one `if`");
+ }
+ }
+}
+
+fn check_nested_if_chains(
+ cx: &LateContext<'_>,
+ if_expr: &Expr<'_>,
+ then_block: &Block<'_>,
+ if_chain_span: Option<Span>,
+) {
+ #[rustfmt::skip]
+ let (head, tail) = match *then_block {
+ Block { stmts, expr: Some(tail), .. } => (stmts, tail),
+ Block {
+ stmts: &[
+ ref head @ ..,
+ Stmt { kind: StmtKind::Expr(tail) | StmtKind::Semi(tail), .. }
+ ],
+ ..
+ } => (head, tail),
+ _ => return,
+ };
+ if_chain! {
+ if let Some(higher::IfOrIfLet { r#else: None, .. }) = higher::IfOrIfLet::hir(tail);
+ let sm = cx.sess().source_map();
+ if head
+ .iter()
+ .all(|stmt| matches!(stmt.kind, StmtKind::Local(..)) && !sm.is_multiline(stmt.span));
+ if if_chain_span.is_some() || !is_else_clause(cx.tcx, if_expr);
+ then {} else { return }
+ }
+ let (span, msg) = match (if_chain_span, is_expn_of(tail.span, "if_chain")) {
+ (None, Some(_)) => (if_expr.span, "this `if` can be part of the inner `if_chain!`"),
+ (Some(_), None) => (tail.span, "this `if` can be part of the outer `if_chain!`"),
+ (Some(a), Some(b)) if a != b => (b, "this `if_chain!` can be merged with the outer `if_chain!`"),
+ _ => return,
+ };
+ span_lint_and_then(cx, IF_CHAIN_STYLE, span, msg, |diag| {
+ let (span, msg) = match head {
+ [] => return,
+ [stmt] => (stmt.span, "this `let` statement can also be in the `if_chain!`"),
+ [a, .., b] => (
+ a.span.to(b.span),
+ "these `let` statements can also be in the `if_chain!`",
+ ),
+ };
+ diag.span_help(span, msg);
+ });
+}
+
+fn is_first_if_chain_expr(cx: &LateContext<'_>, hir_id: HirId, if_chain_span: Span) -> bool {
+ cx.tcx
+ .hir()
+ .parent_iter(hir_id)
+ .find(|(_, node)| {
+ #[rustfmt::skip]
+ !matches!(node, Node::Expr(Expr { kind: ExprKind::Block(..), .. }) | Node::Stmt(_))
+ })
+ .map_or(false, |(id, _)| {
+ is_expn_of(cx.tcx.hir().span(id), "if_chain") != Some(if_chain_span)
+ })
+}
+
+/// Checks a trailing slice of statements and expression of a `Block` to see if they are part
+/// of the `then {..}` portion of an `if_chain!`
+fn is_if_chain_then(stmts: &[Stmt<'_>], expr: Option<&Expr<'_>>, if_chain_span: Span) -> bool {
+ let span = if let [stmt, ..] = stmts {
+ stmt.span
+ } else if let Some(expr) = expr {
+ expr.span
+ } else {
+ // empty `then {}`
+ return true;
+ };
+ is_expn_of(span, "if_chain").map_or(true, |span| span != if_chain_span)
+}
+
+/// Creates a `Span` for `let x = ..;` in an `if_chain!` call.
+fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: Span) -> Span {
+ let mut span = local.pat.span;
+ if let Some(init) = local.init {
+ span = span.to(init.span);
+ }
+ span.adjust(if_chain_span.ctxt().outer_expn());
+ let sm = cx.sess().source_map();
+ let span = sm.span_extend_to_prev_str(span, "let", false);
+ let span = sm.span_extend_to_next_char(span, ';', false);
+ Span::new(
+ span.lo() - BytePos(3),
+ span.hi() + BytePos(1),
+ span.ctxt(),
+ span.parent(),
+ )
+}
--- /dev/null
- use crate::utils::internal_lints::is_lint_ref_type;
+//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json
+//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
+//!
+//! This module and therefor the entire lint is guarded by a feature flag called
+//! `metadata-collector-lint`
+//!
+//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
+//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
+//! a simple mistake)
+
+use if_chain::if_chain;
+use rustc_ast as ast;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir::{
+ self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath,
+};
+use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
+use rustc_middle::hir::map::Map;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::{sym, Loc, Span, Symbol};
+use serde::{ser::SerializeStruct, Serialize, Serializer};
+use std::collections::BinaryHeap;
+use std::fmt;
+use std::fs::{self, OpenOptions};
+use std::io::prelude::*;
+use std::path::Path;
+
- fn new(id: String, id_span: SerializableSpan, group: String, level: &'static str, docs: String) -> Self {
++use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type};
+use clippy_utils::{
+ diagnostics::span_lint, last_path_segment, match_def_path, match_function_call, match_path, paths, ty::match_type,
+ ty::walk_ptrs_ty_depth,
+};
+
+/// This is the output file of the lint collector.
+const OUTPUT_FILE: &str = "../util/gh-pages/lints.json";
+/// These lints are excluded from the export.
+const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "internal_metadata_collector"];
+/// These groups will be ignored by the lint group matcher. This is useful for collections like
+/// `clippy::all`
+const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"];
+/// Lints within this group will be excluded from the collection. These groups
+/// have to be defined without the `clippy::` prefix.
+const EXCLUDED_LINT_GROUPS: [&str; 1] = ["internal"];
+/// Collected deprecated lint will be assigned to this group in the JSON output
+const DEPRECATED_LINT_GROUP_STR: &str = "deprecated";
+/// This is the lint level for deprecated lints that will be displayed in the lint list
+const DEPRECATED_LINT_LEVEL: &str = "none";
+/// This array holds Clippy's lint groups with their corresponding default lint level. The
+/// lint level for deprecated lints is set in `DEPRECATED_LINT_LEVEL`.
+const DEFAULT_LINT_LEVELS: &[(&str, &str)] = &[
+ ("correctness", "deny"),
+ ("suspicious", "warn"),
+ ("restriction", "allow"),
+ ("style", "warn"),
+ ("pedantic", "allow"),
+ ("complexity", "warn"),
+ ("perf", "warn"),
+ ("cargo", "allow"),
+ ("nursery", "allow"),
+];
+/// This prefix is in front of the lint groups in the lint store. The prefix will be trimmed
+/// to only keep the actual lint group in the output.
+const CLIPPY_LINT_GROUP_PREFIX: &str = "clippy::";
+
+/// This template will be used to format the configuration section in the lint documentation.
+/// The `configurations` parameter will be replaced with one or multiple formatted
+/// `ClippyConfiguration` instances. See `CONFIGURATION_VALUE_TEMPLATE` for further customizations
+macro_rules! CONFIGURATION_SECTION_TEMPLATE {
+ () => {
+ r#"
+### Configuration
+This lint has the following configuration variables:
+
+{configurations}
+"#
+ };
+}
+/// This template will be used to format an individual `ClippyConfiguration` instance in the
+/// lint documentation.
+///
+/// The format function will provide strings for the following parameters: `name`, `ty`, `doc` and
+/// `default`
+macro_rules! CONFIGURATION_VALUE_TEMPLATE {
+ () => {
+ "* `{name}`: `{ty}`: {doc} (defaults to `{default}`)\n"
+ };
+}
+
+const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
+ &["clippy_utils", "diagnostics", "span_lint"],
+ &["clippy_utils", "diagnostics", "span_lint_and_help"],
+ &["clippy_utils", "diagnostics", "span_lint_and_note"],
+ &["clippy_utils", "diagnostics", "span_lint_hir"],
+ &["clippy_utils", "diagnostics", "span_lint_and_sugg"],
+ &["clippy_utils", "diagnostics", "span_lint_and_then"],
+ &["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
+];
+const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
+ ("span_suggestion", false),
+ ("span_suggestion_short", false),
+ ("span_suggestion_verbose", false),
+ ("span_suggestion_hidden", false),
+ ("tool_only_span_suggestion", false),
+ ("multipart_suggestion", true),
+ ("multipart_suggestions", true),
+ ("tool_only_multipart_suggestion", true),
+ ("span_suggestions", true),
+];
+const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [
+ &["clippy_utils", "diagnostics", "multispan_sugg"],
+ &["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"],
+];
+const DEPRECATED_LINT_TYPE: [&str; 3] = ["clippy_lints", "deprecated_lints", "ClippyDeprecatedLint"];
+
+/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
+const APPLICABILITY_NAME_INDEX: usize = 2;
+/// This applicability will be set for unresolved applicability values.
+const APPLICABILITY_UNRESOLVED_STR: &str = "Unresolved";
++/// The version that will be displayed if none has been defined
++const VERION_DEFAULT_STR: &str = "Unknown";
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Collects metadata about clippy lints for the website.
+ ///
+ /// This lint will be used to report problems of syntax parsing. You should hopefully never
+ /// see this but never say never I guess ^^
+ ///
+ /// ### Why is this bad?
+ /// This is not a bad thing but definitely a hacky way to do it. See
+ /// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion
+ /// about the implementation.
+ ///
+ /// ### Known problems
+ /// Hopefully none. It would be pretty uncool to have a problem here :)
+ ///
+ /// ### Example output
+ /// ```json,ignore
+ /// {
+ /// "id": "internal_metadata_collector",
+ /// "id_span": {
+ /// "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs",
+ /// "line": 1
+ /// },
+ /// "group": "clippy::internal",
+ /// "docs": " ### What it does\nCollects metadata about clippy lints for the website. [...] "
+ /// }
+ /// ```
++ #[clippy::version = "1.56.0"]
+ pub INTERNAL_METADATA_COLLECTOR,
+ internal_warn,
+ "A busy bee collection metadata about lints"
+}
+
+impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Clone)]
+pub struct MetadataCollector {
+ /// All collected lints
+ ///
+ /// We use a Heap here to have the lints added in alphabetic order in the export
+ lints: BinaryHeap<LintMetadata>,
+ applicability_info: FxHashMap<String, ApplicabilityInfo>,
+ config: Vec<ClippyConfiguration>,
+}
+
+impl MetadataCollector {
+ pub fn new() -> Self {
+ Self {
+ lints: BinaryHeap::<LintMetadata>::default(),
+ applicability_info: FxHashMap::<String, ApplicabilityInfo>::default(),
+ config: collect_configs(),
+ }
+ }
+
+ fn get_lint_configs(&self, lint_name: &str) -> Option<String> {
+ self.config
+ .iter()
+ .filter(|config| config.lints.iter().any(|lint| lint == lint_name))
+ .map(ToString::to_string)
+ .reduce(|acc, x| acc + &x)
+ .map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations))
+ }
+}
+
+impl Drop for MetadataCollector {
+ /// You might ask: How hacky is this?
+ /// My answer: YES
+ fn drop(&mut self) {
+ // The metadata collector gets dropped twice, this makes sure that we only write
+ // when the list is full
+ if self.lints.is_empty() {
+ return;
+ }
+
+ let mut applicability_info = std::mem::take(&mut self.applicability_info);
+
+ // Mapping the final data
+ let mut lints = std::mem::take(&mut self.lints).into_sorted_vec();
+ lints
+ .iter_mut()
+ .for_each(|x| x.applicability = Some(applicability_info.remove(&x.id).unwrap_or_default()));
+
+ // Outputting
+ if Path::new(OUTPUT_FILE).exists() {
+ fs::remove_file(OUTPUT_FILE).unwrap();
+ }
+ let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
+ writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
+ }
+}
+
+#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+struct LintMetadata {
+ id: String,
+ id_span: SerializableSpan,
+ group: String,
+ level: String,
+ docs: String,
++ version: String,
+ /// This field is only used in the output and will only be
+ /// mapped shortly before the actual output.
+ applicability: Option<ApplicabilityInfo>,
+}
+
+impl LintMetadata {
- // TODO xFrednet 2021-03-01: don't use or_else but rather a comparison
++ fn new(
++ id: String,
++ id_span: SerializableSpan,
++ group: String,
++ level: &'static str,
++ version: String,
++ docs: String,
++ ) -> Self {
+ Self {
+ id,
+ id_span,
+ group,
+ level: level.to_string(),
++ version,
+ docs,
+ applicability: None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+struct SerializableSpan {
+ path: String,
+ line: usize,
+}
+
+impl std::fmt::Display for SerializableSpan {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line)
+ }
+}
+
+impl SerializableSpan {
+ fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
+ Self::from_span(cx, item.ident.span)
+ }
+
+ fn from_span(cx: &LateContext<'_>, span: Span) -> Self {
+ let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo());
+
+ Self {
+ path: format!("{}", loc.file.name.prefer_remapped()),
+ line: loc.line,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
+struct ApplicabilityInfo {
+ /// Indicates if any of the lint emissions uses multiple spans. This is related to
+ /// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
+ /// currently not be applied automatically.
+ is_multi_part_suggestion: bool,
+ applicability: Option<usize>,
+}
+
+impl Serialize for ApplicabilityInfo {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut s = serializer.serialize_struct("ApplicabilityInfo", 2)?;
+ s.serialize_field("is_multi_part_suggestion", &self.is_multi_part_suggestion)?;
+ if let Some(index) = self.applicability {
+ s.serialize_field(
+ "applicability",
+ &paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX],
+ )?;
+ } else {
+ s.serialize_field("applicability", APPLICABILITY_UNRESOLVED_STR)?;
+ }
+ s.end()
+ }
+}
+
+// ==================================================================
+// Configuration
+// ==================================================================
+#[derive(Debug, Clone, Default)]
+pub struct ClippyConfiguration {
+ name: String,
+ config_type: &'static str,
+ default: String,
+ lints: Vec<String>,
+ doc: String,
+ #[allow(dead_code)]
+ deprecation_reason: Option<&'static str>,
+}
+
+impl ClippyConfiguration {
+ pub fn new(
+ name: &'static str,
+ config_type: &'static str,
+ default: String,
+ doc_comment: &'static str,
+ deprecation_reason: Option<&'static str>,
+ ) -> Self {
+ let (lints, doc) = parse_config_field_doc(doc_comment)
+ .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string()));
+
+ Self {
+ name: to_kebab(name),
+ lints,
+ doc,
+ config_type,
+ default,
+ deprecation_reason,
+ }
+ }
+}
+
+fn collect_configs() -> Vec<ClippyConfiguration> {
+ crate::utils::conf::metadata::get_configuration_metadata()
+}
+
+/// This parses the field documentation of the config struct.
+///
+/// ```rust, ignore
+/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin")
+/// ```
+///
+/// Would yield:
+/// ```rust, ignore
+/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin")
+/// ```
+fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
+ const DOC_START: &str = " Lint: ";
+ if_chain! {
+ if doc_comment.starts_with(DOC_START);
+ if let Some(split_pos) = doc_comment.find('.');
+ then {
+ let mut doc_comment = doc_comment.to_string();
+ let mut documentation = doc_comment.split_off(split_pos);
+
+ // Extract lints
+ doc_comment.make_ascii_lowercase();
+ let lints: Vec<String> = doc_comment.split_off(DOC_START.len()).split(", ").map(str::to_string).collect();
+
+ // Format documentation correctly
+ // split off leading `.` from lint name list and indent for correct formatting
+ documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n ");
+
+ Some((lints, documentation))
+ } else {
+ None
+ }
+ }
+}
+
+/// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
+fn to_kebab(config_name: &str) -> String {
+ config_name.replace('_', "-")
+}
+
+impl fmt::Display for ClippyConfiguration {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ CONFIGURATION_VALUE_TEMPLATE!(),
+ name = self.name,
+ ty = self.config_type,
+ doc = self.doc,
+ default = self.default
+ )
+ }
+}
+
+// ==================================================================
+// Lint pass
+// ==================================================================
+impl<'hir> LateLintPass<'hir> for MetadataCollector {
+ /// Collecting lint declarations like:
+ /// ```rust, ignore
+ /// declare_clippy_lint! {
+ /// /// ### What it does
+ /// /// Something IDK.
+ /// pub SOME_LINT,
+ /// internal,
+ /// "Who am I?"
+ /// }
+ /// ```
+ fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) {
+ if let ItemKind::Static(ty, Mutability::Not, _) = item.kind {
+ // Normal lint
+ if_chain! {
+ // item validation
+ if is_lint_ref_type(cx, ty);
+ // blacklist check
+ let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
+ if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
+ // metadata extraction
+ if let Some((group, level)) = get_lint_group_and_level_or_lint(cx, &lint_name, item);
+ if let Some(mut docs) = extract_attr_docs_or_lint(cx, item);
+ then {
+ if let Some(configuration_section) = self.get_lint_configs(&lint_name) {
+ docs.push_str(&configuration_section);
+ }
++ let version = get_lint_version(cx, item);
+
+ self.lints.push(LintMetadata::new(
+ lint_name,
+ SerializableSpan::from_item(cx, item),
+ group,
+ level,
++ version,
+ docs,
+ ));
+ }
+ }
+
+ if_chain! {
+ if is_deprecated_lint(cx, ty);
+ // blacklist check
+ let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
+ if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
+ // Metadata the little we can get from a deprecated lint
+ if let Some(docs) = extract_attr_docs_or_lint(cx, item);
+ then {
++ let version = get_lint_version(cx, item);
++
+ self.lints.push(LintMetadata::new(
+ lint_name,
+ SerializableSpan::from_item(cx, item),
+ DEPRECATED_LINT_GROUP_STR.to_string(),
+ DEPRECATED_LINT_LEVEL,
++ version,
+ docs,
+ ));
+ }
+ }
+ }
+ }
+
+ /// Collecting constant applicability from the actual lint emissions
+ ///
+ /// Example:
+ /// ```rust, ignore
+ /// span_lint_and_sugg(
+ /// cx,
+ /// SOME_LINT,
+ /// item.span,
+ /// "Le lint message",
+ /// "Here comes help:",
+ /// "#![allow(clippy::all)]",
+ /// Applicability::MachineApplicable, // <-- Extracts this constant value
+ /// );
+ /// ```
+ fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
+ if let Some(args) = match_lint_emission(cx, expr) {
+ let mut emission_info = extract_emission_info(cx, args);
+ if emission_info.is_empty() {
+ // See:
+ // - src/misc.rs:734:9
+ // - src/methods/mod.rs:3545:13
+ // - src/methods/mod.rs:3496:13
+ // We are basically unable to resolve the lint name itself.
+ return;
+ }
+
+ for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
+ let app_info = self.applicability_info.entry(lint_name).or_default();
+ app_info.applicability = applicability;
+ app_info.is_multi_part_suggestion = is_multi_part;
+ }
+ }
+ }
+}
+
+// ==================================================================
+// Lint definition extraction
+// ==================================================================
+fn sym_to_string(sym: Symbol) -> String {
+ sym.as_str().to_string()
+}
+
+fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
+ extract_attr_docs(cx, item).or_else(|| {
+ lint_collection_error_item(cx, item, "could not collect the lint documentation");
+ None
+ })
+}
+
+/// This function collects all documentation that has been added to an item using
+/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
+///
+/// ```ignore
+/// #[doc = r"Hello world!"]
+/// #[doc = r"=^.^="]
+/// struct SomeItem {}
+/// ```
+///
+/// Would result in `Hello world!\n=^.^=\n`
+///
+/// ---
+///
+/// This function may modify the doc comment to ensure that the string can be displayed using a
+/// markdown viewer in Clippy's lint list. The following modifications could be applied:
+/// * Removal of leading space after a new line. (Important to display tables)
+/// * Ensures that code blocks only contain language information
+fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
+ let attrs = cx.tcx.hir().attrs(item.hir_id());
+ let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str);
+ let mut docs = String::from(&*lines.next()?.as_str());
+ let mut in_code_block = false;
+ let mut is_code_block_rust = false;
+ for line in lines {
+ let line = line.as_str();
+ let line = &*line;
+
+ // Rustdoc hides code lines starting with `# ` and this removes them from Clippy's lint list :)
+ if is_code_block_rust && line.trim_start().starts_with("# ") {
+ continue;
+ }
+
+ // The line should be represented in the lint list, even if it's just an empty line
+ docs.push('\n');
+ if let Some(info) = line.trim_start().strip_prefix("```") {
+ in_code_block = !in_code_block;
+ is_code_block_rust = false;
+ if in_code_block {
+ let lang = info
+ .trim()
+ .split(',')
+ // remove rustdoc directives
+ .find(|&s| !matches!(s, "" | "ignore" | "no_run" | "should_panic"))
+ // if no language is present, fill in "rust"
+ .unwrap_or("rust");
+ docs.push_str("```");
+ docs.push_str(lang);
+
+ is_code_block_rust = lang == "rust";
+ continue;
+ }
+ }
+ // This removes the leading space that the macro translation introduces
+ if let Some(stripped_doc) = line.strip_prefix(' ') {
+ docs.push_str(stripped_doc);
+ } else if !line.is_empty() {
+ docs.push_str(line);
+ }
+ }
+ Some(docs)
+}
+
++fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String {
++ extract_clippy_version_value(cx, item).map_or_else(
++ || VERION_DEFAULT_STR.to_string(),
++ |version| version.as_str().to_string(),
++ )
++}
++
+fn get_lint_group_and_level_or_lint(
+ cx: &LateContext<'_>,
+ lint_name: &str,
+ item: &'hir Item<'_>,
+) -> Option<(String, &'static str)> {
+ let result = cx
+ .lint_store
+ .check_lint_name(cx.sess(), lint_name, Some(sym::clippy), &[]);
+ if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
+ if let Some(group) = get_lint_group(cx, lint_lst[0]) {
+ if EXCLUDED_LINT_GROUPS.contains(&group.as_str()) {
+ return None;
+ }
+
+ if let Some(level) = get_lint_level_from_group(&group) {
+ Some((group, level))
+ } else {
+ lint_collection_error_item(
+ cx,
+ item,
+ &format!("Unable to determine lint level for found group `{}`", group),
+ );
+ None
+ }
+ } else {
+ lint_collection_error_item(cx, item, "Unable to determine lint group");
+ None
+ }
+ } else {
+ lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
+ None
+ }
+}
+
+fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
+ for (group_name, lints, _) in &cx.lint_store.get_lint_groups() {
+ if IGNORED_LINT_GROUPS.contains(group_name) {
+ continue;
+ }
+
+ if lints.iter().any(|group_lint| *group_lint == lint_id) {
+ let group = group_name.strip_prefix(CLIPPY_LINT_GROUP_PREFIX).unwrap_or(group_name);
+ return Some((*group).to_string());
+ }
+ }
+
+ None
+}
+
+fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
+ DEFAULT_LINT_LEVELS
+ .iter()
+ .find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level))
+}
+
+fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if let hir::TyKind::Path(ref path) = ty.kind {
+ if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) {
+ return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE);
+ }
+ }
+
+ false
+}
+
+// ==================================================================
+// Lint emission
+// ==================================================================
+fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
+ span_lint(
+ cx,
+ INTERNAL_METADATA_COLLECTOR,
+ item.ident.span,
+ &format!("metadata collection error for `{}`: {}", item.ident.name, message),
+ );
+}
+
+// ==================================================================
+// Applicability
+// ==================================================================
+/// This function checks if a given expression is equal to a simple lint emission function call.
+/// It will return the function arguments if the emission matched any function.
+fn match_lint_emission<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) -> Option<&'hir [hir::Expr<'hir>]> {
+ LINT_EMISSION_FUNCTIONS
+ .iter()
+ .find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
+}
+
+fn take_higher_applicability(a: Option<usize>, b: Option<usize>) -> Option<usize> {
+ a.map_or(b, |a| a.max(b.unwrap_or_default()).into())
+}
+
+fn extract_emission_info<'hir>(
+ cx: &LateContext<'hir>,
+ args: &'hir [hir::Expr<'hir>],
+) -> Vec<(String, Option<usize>, bool)> {
+ let mut lints = Vec::new();
+ let mut applicability = None;
+ let mut multi_part = false;
+
+ for arg in args {
+ let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(arg));
+
+ if match_type(cx, arg_ty, &paths::LINT) {
+ // If we found the lint arg, extract the lint name
+ let mut resolved_lints = resolve_lints(cx, arg);
+ lints.append(&mut resolved_lints);
+ } else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
+ applicability = resolve_applicability(cx, arg);
+ } else if arg_ty.is_closure() {
+ multi_part |= check_is_multi_part(cx, arg);
+ applicability = applicability.or_else(|| resolve_applicability(cx, arg));
+ }
+ }
+
+ lints
+ .drain(..)
+ .map(|lint_name| (lint_name, applicability, multi_part))
+ .collect()
+}
+
+/// Resolves the possible lints that this expression could reference
+fn resolve_lints(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Vec<String> {
+ let mut resolver = LintResolver::new(cx);
+ resolver.visit_expr(expr);
+ resolver.lints
+}
+
+/// This function tries to resolve the linked applicability to the given expression.
+fn resolve_applicability(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<usize> {
+ let mut resolver = ApplicabilityResolver::new(cx);
+ resolver.visit_expr(expr);
+ resolver.complete()
+}
+
+fn check_is_multi_part(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
+ if let ExprKind::Closure(_, _, body_id, _, _) = closure_expr.kind {
+ let mut scanner = IsMultiSpanScanner::new(cx);
+ intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body_id));
+ return scanner.is_multi_part();
+ } else if let Some(local) = get_parent_local(cx, closure_expr) {
+ if let Some(local_init) = local.init {
+ return check_is_multi_part(cx, local_init);
+ }
+ }
+
+ false
+}
+
+struct LintResolver<'a, 'hir> {
+ cx: &'a LateContext<'hir>,
+ lints: Vec<String>,
+}
+
+impl<'a, 'hir> LintResolver<'a, 'hir> {
+ fn new(cx: &'a LateContext<'hir>) -> Self {
+ Self {
+ cx,
+ lints: Vec::<String>::default(),
+ }
+ }
+}
+
+impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ if_chain! {
+ if let ExprKind::Path(qpath) = &expr.kind;
+ if let QPath::Resolved(_, path) = qpath;
+
+ let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
+ if match_type(self.cx, expr_ty, &paths::LINT);
+ then {
+ if let hir::def::Res::Def(DefKind::Static, _) = path.res {
+ let lint_name = last_path_segment(qpath).ident.name;
+ self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
+ } else if let Some(local) = get_parent_local(self.cx, expr) {
+ if let Some(local_init) = local.init {
+ intravisit::walk_expr(self, local_init);
+ }
+ }
+ }
+ }
+
+ intravisit::walk_expr(self, expr);
+ }
+}
+
+/// This visitor finds the highest applicability value in the visited expressions
+struct ApplicabilityResolver<'a, 'hir> {
+ cx: &'a LateContext<'hir>,
+ /// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES`
+ applicability_index: Option<usize>,
+}
+
+impl<'a, 'hir> ApplicabilityResolver<'a, 'hir> {
+ fn new(cx: &'a LateContext<'hir>) -> Self {
+ Self {
+ cx,
+ applicability_index: None,
+ }
+ }
+
+ fn add_new_index(&mut self, new_index: usize) {
+ self.applicability_index = take_higher_applicability(self.applicability_index, Some(new_index));
+ }
+
+ fn complete(self) -> Option<usize> {
+ self.applicability_index
+ }
+}
+
+impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+
+ fn visit_path(&mut self, path: &'hir hir::Path<'hir>, _id: hir::HirId) {
+ for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
+ if match_path(path, enum_value) {
+ self.add_new_index(index);
+ return;
+ }
+ }
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
+
+ if_chain! {
+ if match_type(self.cx, expr_ty, &paths::APPLICABILITY);
+ if let Some(local) = get_parent_local(self.cx, expr);
+ if let Some(local_init) = local.init;
+ then {
+ intravisit::walk_expr(self, local_init);
+ }
+ };
+
+ intravisit::walk_expr(self, expr);
+ }
+}
+
+/// This returns the parent local node if the expression is a reference one
+fn get_parent_local(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Local<'hir>> {
+ if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind {
+ if let hir::def::Res::Local(local_hir) = path.res {
+ return get_parent_local_hir_id(cx, local_hir);
+ }
+ }
+
+ None
+}
+
+fn get_parent_local_hir_id(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
+ let map = cx.tcx.hir();
+
+ match map.find(map.get_parent_node(hir_id)) {
+ Some(hir::Node::Local(local)) => Some(local),
+ Some(hir::Node::Pat(pattern)) => get_parent_local_hir_id(cx, pattern.hir_id),
+ _ => None,
+ }
+}
+
+/// This visitor finds the highest applicability value in the visited expressions
+struct IsMultiSpanScanner<'a, 'hir> {
+ cx: &'a LateContext<'hir>,
+ suggestion_count: usize,
+}
+
+impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> {
+ fn new(cx: &'a LateContext<'hir>) -> Self {
+ Self {
+ cx,
+ suggestion_count: 0,
+ }
+ }
+
+ /// Add a new single expression suggestion to the counter
+ fn add_single_span_suggestion(&mut self) {
+ self.suggestion_count += 1;
+ }
+
+ /// Signals that a suggestion with possible multiple spans was found
+ fn add_multi_part_suggestion(&mut self) {
+ self.suggestion_count += 2;
+ }
+
+ /// Checks if the suggestions include multiple spanns
+ fn is_multi_part(&self) -> bool {
+ self.suggestion_count > 1
+ }
+}
+
+impl<'a, 'hir> intravisit::Visitor<'hir> for IsMultiSpanScanner<'a, 'hir> {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
+ // Early return if the lint is already multi span
+ if self.is_multi_part() {
+ return;
+ }
+
+ match &expr.kind {
+ ExprKind::Call(fn_expr, _args) => {
+ let found_function = SUGGESTION_FUNCTIONS
+ .iter()
+ .any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some());
+ if found_function {
+ // These functions are all multi part suggestions
+ self.add_single_span_suggestion();
+ }
+ },
+ ExprKind::MethodCall(path, _path_span, arg, _arg_span) => {
+ let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&arg[0]));
+ if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) {
+ let called_method = path.ident.name.as_str().to_string();
+ for (method_name, is_multi_part) in &SUGGESTION_DIAGNOSTIC_BUILDER_METHODS {
+ if *method_name == called_method {
+ if *is_multi_part {
+ self.add_multi_part_suggestion();
+ } else {
+ self.add_single_span_suggestion();
+ }
+ break;
+ }
+ }
+ }
+ },
+ _ => {},
+ }
+
+ intravisit::walk_expr(self, expr);
+ }
+}
--- /dev/null
+use clippy_utils::consts::{constant, Constant};
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher;
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_copy;
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::source_map::Span;
+
+#[allow(clippy::module_name_repetitions)]
+#[derive(Copy, Clone)]
+pub struct UselessVec {
+ pub too_large_for_stack: u64,
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `&vec![..]` when using `&[..]` would
+ /// be possible.
+ ///
+ /// ### Why is this bad?
+ /// This is less efficient.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # fn foo(my_vec: &[u8]) {}
+ ///
+ /// // Bad
+ /// foo(&vec![1, 2]);
+ ///
+ /// // Good
+ /// foo(&[1, 2]);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub USELESS_VEC,
+ perf,
+ "useless `vec!`"
+}
+
+impl_lint_pass!(UselessVec => [USELESS_VEC]);
+
+impl<'tcx> LateLintPass<'tcx> for UselessVec {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // search for `&vec![_]` expressions where the adjusted type is `&[_]`
+ if_chain! {
+ if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(expr).kind();
+ if let ty::Slice(..) = ty.kind();
+ if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind;
+ if let Some(vec_args) = higher::VecArgs::hir(cx, addressee);
+ then {
+ self.check_vec_macro(cx, &vec_args, mutability, expr.span);
+ }
+ }
+
+ // search for `for _ in vec![…]`
+ if_chain! {
+ if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr);
+ if let Some(vec_args) = higher::VecArgs::hir(cx, arg);
+ if is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(arg)));
+ then {
+ // report the error around the `vec!` not inside `<std macros>:`
+ let span = arg.span.ctxt().outer_expn_data().call_site;
+ self.check_vec_macro(cx, &vec_args, Mutability::Not, span);
+ }
+ }
+ }
+}
+
+impl UselessVec {
+ fn check_vec_macro<'tcx>(
+ self,
+ cx: &LateContext<'tcx>,
+ vec_args: &higher::VecArgs<'tcx>,
+ mutability: Mutability,
+ span: Span,
+ ) {
+ let mut applicability = Applicability::MachineApplicable;
+ let snippet = match *vec_args {
+ higher::VecArgs::Repeat(elem, len) => {
+ if let Some((Constant::Int(len_constant), _)) = constant(cx, cx.typeck_results(), len) {
+ #[allow(clippy::cast_possible_truncation)]
+ if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
+ return;
+ }
+
+ match mutability {
+ Mutability::Mut => {
+ format!(
+ "&mut [{}; {}]",
+ snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
+ snippet_with_applicability(cx, len.span, "len", &mut applicability)
+ )
+ },
+ Mutability::Not => {
+ format!(
+ "&[{}; {}]",
+ snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
+ snippet_with_applicability(cx, len.span, "len", &mut applicability)
+ )
+ },
+ }
+ } else {
+ return;
+ }
+ },
+ higher::VecArgs::Vec(args) => {
+ if let Some(last) = args.iter().last() {
+ #[allow(clippy::cast_possible_truncation)]
+ if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack {
+ return;
+ }
+ let span = args[0].span.to(last.span);
+
+ match mutability {
+ Mutability::Mut => {
+ format!(
+ "&mut [{}]",
+ snippet_with_applicability(cx, span, "..", &mut applicability)
+ )
+ },
+ Mutability::Not => {
+ format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability))
+ },
+ }
+ } else {
+ match mutability {
+ Mutability::Mut => "&mut []".into(),
+ Mutability::Not => "&[]".into(),
+ }
+ }
+ },
+ };
+
+ span_lint_and_sugg(
+ cx,
+ USELESS_VEC,
+ span,
+ "useless use of `vec!`",
+ "you can use a slice directly",
+ snippet,
+ applicability,
+ );
+ }
+}
+
+fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 {
+ let ty = cx.typeck_results().expr_ty_adjusted(expr);
+ cx.layout_of(ty).map_or(0, |l| l.size.bytes())
+}
+
+/// Returns the item type of the vector (i.e., the `T` in `Vec<T>`).
+fn vec_type(ty: Ty<'_>) -> Ty<'_> {
+ if let ty::Adt(_, substs) = ty.kind() {
+ substs.type_at(0)
+ } else {
+ panic!("The type of `vec!` is a not a struct?");
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
+use clippy_utils::source::snippet;
+use clippy_utils::{path_to_local, path_to_local_id};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, Stmt, StmtKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for calls to `push` immediately after creating a new `Vec`.
+ ///
+ /// ### Why is this bad?
+ /// The `vec![]` macro is both more performant and easier to read than
+ /// multiple `push` calls.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut v = Vec::new();
+ /// v.push(0);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let v = vec![0];
+ /// ```
++ #[clippy::version = "1.51.0"]
+ pub VEC_INIT_THEN_PUSH,
+ perf,
+ "`push` immediately after `Vec` creation"
+}
+
+impl_lint_pass!(VecInitThenPush => [VEC_INIT_THEN_PUSH]);
+
+#[derive(Default)]
+pub struct VecInitThenPush {
+ searcher: Option<VecPushSearcher>,
+}
+
+struct VecPushSearcher {
+ local_id: HirId,
+ init: VecInitKind,
+ lhs_is_local: bool,
+ lhs_span: Span,
+ err_span: Span,
+ found: u64,
+}
+impl VecPushSearcher {
+ fn display_err(&self, cx: &LateContext<'_>) {
+ match self.init {
+ _ if self.found == 0 => return,
+ VecInitKind::WithLiteralCapacity(x) if x > self.found => return,
+ VecInitKind::WithExprCapacity(_) => return,
+ _ => (),
+ };
+
+ let mut s = if self.lhs_is_local {
+ String::from("let ")
+ } else {
+ String::new()
+ };
+ s.push_str(&snippet(cx, self.lhs_span, ".."));
+ s.push_str(" = vec![..];");
+
+ span_lint_and_sugg(
+ cx,
+ VEC_INIT_THEN_PUSH,
+ self.err_span,
+ "calls to `push` immediately after creation",
+ "consider using the `vec![]` macro",
+ s,
+ Applicability::HasPlaceholders,
+ );
+ }
+}
+
+impl LateLintPass<'_> for VecInitThenPush {
+ fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
+ self.searcher = None;
+ }
+
+ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
+ if_chain! {
+ if !in_external_macro(cx.sess(), local.span);
+ if let Some(init) = local.init;
+ if let PatKind::Binding(BindingAnnotation::Mutable, id, _, None) = local.pat.kind;
+ if let Some(init_kind) = get_vec_init_kind(cx, init);
+ then {
+ self.searcher = Some(VecPushSearcher {
+ local_id: id,
+ init: init_kind,
+ lhs_is_local: true,
+ lhs_span: local.ty.map_or(local.pat.span, |t| local.pat.span.to(t.span)),
+ err_span: local.span,
+ found: 0,
+ });
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if self.searcher.is_none();
+ if !in_external_macro(cx.sess(), expr.span);
+ if let ExprKind::Assign(left, right, _) = expr.kind;
+ if let Some(id) = path_to_local(left);
+ if let Some(init_kind) = get_vec_init_kind(cx, right);
+ then {
+ self.searcher = Some(VecPushSearcher {
+ local_id: id,
+ init: init_kind,
+ lhs_is_local: false,
+ lhs_span: left.span,
+ err_span: expr.span,
+ found: 0,
+ });
+ }
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
+ if let Some(searcher) = self.searcher.take() {
+ if_chain! {
+ if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind;
+ if let ExprKind::MethodCall(path, _, [self_arg, _], _) = expr.kind;
+ if path_to_local_id(self_arg, searcher.local_id);
+ if path.ident.name.as_str() == "push";
+ then {
+ self.searcher = Some(VecPushSearcher {
+ found: searcher.found + 1,
+ err_span: searcher.err_span.to(stmt.span),
+ .. searcher
+ });
+ } else {
+ searcher.display_err(cx);
+ }
+ }
+ }
+ }
+
+ fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
+ if let Some(searcher) = self.searcher.take() {
+ searcher.display_err(cx);
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{match_def_path, paths};
+use if_chain::if_chain;
+use rustc_ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::Spanned;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Finds occurrences of `Vec::resize(0, an_int)`
+ ///
+ /// ### Why is this bad?
+ /// This is probably an argument inversion mistake.
+ ///
+ /// ### Example
+ /// ```rust
+ /// vec!(1, 2, 3, 4, 5).resize(0, 5)
+ /// ```
++ #[clippy::version = "1.46.0"]
+ pub VEC_RESIZE_TO_ZERO,
+ correctness,
+ "emptying a vector with `resize(0, an_int)` instead of `clear()` is probably an argument inversion mistake"
+}
+
+declare_lint_pass!(VecResizeToZero => [VEC_RESIZE_TO_ZERO]);
+
+impl<'tcx> LateLintPass<'tcx> for VecResizeToZero {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if_chain! {
+ if let hir::ExprKind::MethodCall(path_segment, _, args, _) = expr.kind;
+ if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
+ if match_def_path(cx, method_def_id, &paths::VEC_RESIZE) && args.len() == 3;
+ if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = args[1].kind;
+ if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = args[2].kind;
+ then {
+ let method_call_span = expr.span.with_lo(path_segment.ident.span.lo());
+ span_lint_and_then(
+ cx,
+ VEC_RESIZE_TO_ZERO,
+ expr.span,
+ "emptying a vector with `resize`",
+ |db| {
+ db.help("the arguments may be inverted...");
+ db.span_suggestion(
+ method_call_span,
+ "...or you can empty the vector with",
+ "clear()".to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ },
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::paths;
+use clippy_utils::ty::match_type;
+use if_chain::if_chain;
+use rustc_hir::{Expr, ExprKind, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of File::read_to_end and File::read_to_string.
+ ///
+ /// ### Why is this bad?
+ /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values.
+ /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html)
+ ///
+ /// ### Example
+ /// ```rust,no_run
+ /// # use std::io::Read;
+ /// # use std::fs::File;
+ /// let mut f = File::open("foo.txt").unwrap();
+ /// let mut bytes = Vec::new();
+ /// f.read_to_end(&mut bytes).unwrap();
+ /// ```
+ /// Can be written more concisely as
+ /// ```rust,no_run
+ /// # use std::fs;
+ /// let mut bytes = fs::read("foo.txt").unwrap();
+ /// ```
++ #[clippy::version = "1.44.0"]
+ pub VERBOSE_FILE_READS,
+ restriction,
+ "use of `File::read_to_end` or `File::read_to_string`"
+}
+
+declare_lint_pass!(VerboseFileReads => [VERBOSE_FILE_READS]);
+
+impl<'tcx> LateLintPass<'tcx> for VerboseFileReads {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if is_file_read_to_end(cx, expr) {
+ span_lint_and_help(
+ cx,
+ VERBOSE_FILE_READS,
+ expr.span,
+ "use of `File::read_to_end`",
+ None,
+ "consider using `fs::read` instead",
+ );
+ } else if is_file_read_to_string(cx, expr) {
+ span_lint_and_help(
+ cx,
+ VERBOSE_FILE_READS,
+ expr.span,
+ "use of `File::read_to_string`",
+ None,
+ "consider using `fs::read_to_string` instead",
+ );
+ }
+ }
+}
+
+fn is_file_read_to_end<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, _, exprs, _) = expr.kind;
+ if method_name.ident.as_str() == "read_to_end";
+ if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind;
+ let ty = cx.typeck_results().expr_ty(&exprs[0]);
+ if match_type(cx, ty, &paths::FILE);
+ then {
+ return true
+ }
+ }
+ false
+}
+
+fn is_file_read_to_string<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
+ if_chain! {
+ if let ExprKind::MethodCall(method_name, _, exprs, _) = expr.kind;
+ if method_name.ident.as_str() == "read_to_string";
+ if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind;
+ let ty = cx.typeck_results().expr_ty(&exprs[0]);
+ if match_type(cx, ty, &paths::FILE);
+ then {
+ return true
+ }
+ }
+ false
+}
--- /dev/null
+use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
+use rustc_hir::hir_id::CRATE_HIR_ID;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::source_map::DUMMY_SP;
+
+use if_chain::if_chain;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for wildcard dependencies in the `Cargo.toml`.
+ ///
+ /// ### Why is this bad?
+ /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html),
+ /// it is highly unlikely that you work with any possible version of your dependency,
+ /// and wildcard dependencies would cause unnecessary breakage in the ecosystem.
+ ///
+ /// ### Example
+ /// ```toml
+ /// [dependencies]
+ /// regex = "*"
+ /// ```
++ #[clippy::version = "1.32.0"]
+ pub WILDCARD_DEPENDENCIES,
+ cargo,
+ "wildcard dependencies being used"
+}
+
+declare_lint_pass!(WildcardDependencies => [WILDCARD_DEPENDENCIES]);
+
+impl LateLintPass<'_> for WildcardDependencies {
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ if is_lint_allowed(cx, WILDCARD_DEPENDENCIES, CRATE_HIR_ID) {
+ return;
+ }
+
+ let metadata = unwrap_cargo_metadata!(cx, WILDCARD_DEPENDENCIES, false);
+
+ for dep in &metadata.packages[0].dependencies {
+ // VersionReq::any() does not work
+ if_chain! {
+ if let Ok(wildcard_ver) = semver::VersionReq::parse("*");
+ if let Some(ref source) = dep.source;
+ if !source.starts_with("git");
+ if dep.req == wildcard_ver;
+ then {
+ span_lint(
+ cx,
+ WILDCARD_DEPENDENCIES,
+ DUMMY_SP,
+ &format!("wildcard dependency for `{}`", dep.name),
+ );
+ }
+ }
+ }
+ }
+}
--- /dev/null
- use clippy_utils::{in_macro, is_test_module_or_function};
+use clippy_utils::diagnostics::span_lint_and_sugg;
++use clippy_utils::is_test_module_or_function;
+use clippy_utils::source::{snippet, snippet_with_applicability};
- in_macro(item.span)
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::{
+ def::{DefKind, Res},
+ Item, ItemKind, PathSegment, UseKind,
+};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::kw;
+use rustc_span::{sym, BytePos};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `use Enum::*`.
+ ///
+ /// ### Why is this bad?
+ /// It is usually better style to use the prefixed name of
+ /// an enumeration variant, rather than importing variants.
+ ///
+ /// ### Known problems
+ /// Old-style enumerations that prefix the variants are
+ /// still around.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// use std::cmp::Ordering::*;
+ /// foo(Less);
+ ///
+ /// // Good
+ /// use std::cmp::Ordering;
+ /// foo(Ordering::Less)
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ENUM_GLOB_USE,
+ pedantic,
+ "use items that import all variants of an enum"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for wildcard imports `use _::*`.
+ ///
+ /// ### Why is this bad?
+ /// wildcard imports can pollute the namespace. This is especially bad if
+ /// you try to import something through a wildcard, that already has been imported by name from
+ /// a different source:
+ ///
+ /// ```rust,ignore
+ /// use crate1::foo; // Imports a function named foo
+ /// use crate2::*; // Has a function named foo
+ ///
+ /// foo(); // Calls crate1::foo
+ /// ```
+ ///
+ /// This can lead to confusing error messages at best and to unexpected behavior at worst.
+ ///
+ /// ### Exceptions
+ /// Wildcard imports are allowed from modules named `prelude`. Many crates (including the standard library)
+ /// provide modules named "prelude" specifically designed for wildcard import.
+ ///
+ /// `use super::*` is allowed in test modules. This is defined as any module with "test" in the name.
+ ///
+ /// These exceptions can be disabled using the `warn-on-all-wildcard-imports` configuration flag.
+ ///
+ /// ### Known problems
+ /// If macros are imported through the wildcard, this macro is not included
+ /// by the suggestion and has to be added by hand.
+ ///
+ /// Applying the suggestion when explicit imports of the things imported with a glob import
+ /// exist, may result in `unused_imports` warnings.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// // Bad
+ /// use crate1::*;
+ ///
+ /// foo();
+ /// ```
+ ///
+ /// ```rust,ignore
+ /// // Good
+ /// use crate1::foo;
+ ///
+ /// foo();
+ /// ```
++ #[clippy::version = "1.43.0"]
+ pub WILDCARD_IMPORTS,
+ pedantic,
+ "lint `use _::*` statements"
+}
+
+#[derive(Default)]
+pub struct WildcardImports {
+ warn_on_all: bool,
+ test_modules_deep: u32,
+}
+
+impl WildcardImports {
+ pub fn new(warn_on_all: bool) -> Self {
+ Self {
+ warn_on_all,
+ test_modules_deep: 0,
+ }
+ }
+}
+
+impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
+
+impl LateLintPass<'_> for WildcardImports {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_test_module_or_function(cx.tcx, item) {
+ self.test_modules_deep = self.test_modules_deep.saturating_add(1);
+ }
+ if item.vis.node.is_pub() || item.vis.node.is_pub_restricted() {
+ return;
+ }
+ if_chain! {
+ if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind;
+ if self.warn_on_all || !self.check_exceptions(item, use_path.segments);
+ let used_imports = cx.tcx.names_imported_by_glob_use(item.def_id);
+ if !used_imports.is_empty(); // Already handled by `unused_imports`
+ then {
+ let mut applicability = Applicability::MachineApplicable;
+ let import_source_snippet = snippet_with_applicability(cx, use_path.span, "..", &mut applicability);
+ let (span, braced_glob) = if import_source_snippet.is_empty() {
+ // This is a `_::{_, *}` import
+ // In this case `use_path.span` is empty and ends directly in front of the `*`,
+ // so we need to extend it by one byte.
+ (
+ use_path.span.with_hi(use_path.span.hi() + BytePos(1)),
+ true,
+ )
+ } else {
+ // In this case, the `use_path.span` ends right before the `::*`, so we need to
+ // extend it up to the `*`. Since it is hard to find the `*` in weird
+ // formattings like `use _ :: *;`, we extend it up to, but not including the
+ // `;`. In nested imports, like `use _::{inner::*, _}` there is no `;` and we
+ // can just use the end of the item span
+ let mut span = use_path.span.with_hi(item.span.hi());
+ if snippet(cx, span, "").ends_with(';') {
+ span = use_path.span.with_hi(item.span.hi() - BytePos(1));
+ }
+ (
+ span, false,
+ )
+ };
+
+ let imports_string = if used_imports.len() == 1 {
+ used_imports.iter().next().unwrap().to_string()
+ } else {
+ let mut imports = used_imports
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>();
+ imports.sort();
+ if braced_glob {
+ imports.join(", ")
+ } else {
+ format!("{{{}}}", imports.join(", "))
+ }
+ };
+
+ let sugg = if braced_glob {
+ imports_string
+ } else {
+ format!("{}::{}", import_source_snippet, imports_string)
+ };
+
+ let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res {
+ (ENUM_GLOB_USE, "usage of wildcard import for enum variants")
+ } else {
+ (WILDCARD_IMPORTS, "usage of wildcard import")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ lint,
+ span,
+ message,
+ "try",
+ sugg,
+ applicability,
+ );
+ }
+ }
+ }
+
+ fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ if is_test_module_or_function(cx.tcx, item) {
+ self.test_modules_deep = self.test_modules_deep.saturating_sub(1);
+ }
+ }
+}
+
+impl WildcardImports {
+ fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool {
++ item.span.from_expansion()
+ || is_prelude_import(segments)
+ || (is_super_only_import(segments) && self.test_modules_deep > 0)
+ }
+}
+
+// Allow "...prelude::..::*" imports.
+// Many crates have a prelude, and it is imported as a glob by design.
+fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
+ segments.iter().any(|ps| ps.ident.name == sym::prelude)
+}
+
+// Allow "super::*" imports in tests.
+fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
+ segments.len() == 1 && segments[0].ident.name == kw::Super
+}
--- /dev/null
- /// Only catches `print!` and `println!` calls.
+use std::borrow::Cow;
+use std::iter;
+use std::ops::{Deref, Range};
+
+use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
+use clippy_utils::source::{snippet_opt, snippet_with_applicability};
+use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
+use rustc_ast::token::{self, LitKind};
+use rustc_ast::tokenstream::TokenStream;
+use rustc_errors::Applicability;
+use rustc_lexer::unescape::{self, EscapeError};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_parse::parser;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{sym, BytePos, Span, DUMMY_SP};
+
+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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// println!("");
+ ///
+ /// // Good
+ /// println!();
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub PRINTLN_EMPTY_STRING,
+ style,
+ "using `println!(\"\")` with an empty string"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// This lint warns when you use `print!()` with a format
+ /// string that ends in a newline.
+ ///
+ /// ### Why is this bad?
+ /// You should use `println!()` instead, which appends the
+ /// newline.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let name = "World";
+ /// print!("Hello {}!\n", name);
+ /// ```
+ /// use println!() instead
+ /// ```rust
+ /// # let name = "World";
+ /// println!("Hello {}!", name);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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 `eprint!` and `eprintln!` calls.
++ /// * Only catches `print!` and `println!` calls.
++ /// * The lint level is unaffected by crate attributes. The level can still
++ /// be set for functions, modules and other items. To change the level for
++ /// the entire crate, please use command line flags. More information and a
++ /// configuration example can be found in [clippy#6610].
++ ///
++ /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558
+ ///
+ /// ### Example
+ /// ```rust
+ /// println!("Hello world!");
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub PRINT_STDOUT,
+ restriction,
+ "printing on stdout"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for printing on *stderr*. The purpose of this lint
+ /// is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// People often print on *stderr* while debugging an
+ /// application and might forget to remove those prints afterward.
+ ///
+ /// ### Known problems
- lit.token.symbol.as_str().replace("{", "{{").replace("}", "}}")
++ /// * Only catches `eprint!` and `eprintln!` calls.
++ /// * The lint level is unaffected by crate attributes. The level can still
++ /// be set for functions, modules and other items. To change the level for
++ /// the entire crate, please use command line flags. More information and a
++ /// configuration example can be found in [clippy#6610].
++ ///
++ /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558
+ ///
+ /// ### Example
+ /// ```rust
+ /// eprintln!("Hello world!");
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub PRINT_STDERR,
+ restriction,
+ "printing on stderr"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `Debug` formatting. The purpose of this
+ /// lint is to catch debugging remnants.
+ ///
+ /// ### Why is this bad?
+ /// The purpose of the `Debug` trait is to facilitate
+ /// debugging Rust code. It should not be used in user-facing output.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let foo = "bar";
+ /// println!("{:?}", foo);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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");
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::fmt::Write;
+ /// # let mut buf = String::new();
+ /// // Bad
+ /// writeln!(buf, "");
+ ///
+ /// // Good
+ /// writeln!(buf);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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.
+ ///
+ /// ### 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);
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ 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");
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub WRITE_LITERAL,
+ style,
+ "writing a literal with a format string"
+}
+
+#[derive(Default)]
+pub struct Write {
+ in_debug_impl: bool,
+}
+
+impl_lint_pass!(Write => [
+ PRINT_WITH_NEWLINE,
+ PRINTLN_EMPTY_STRING,
+ PRINT_STDOUT,
+ PRINT_STDERR,
+ USE_DEBUG,
+ PRINT_LITERAL,
+ WRITE_WITH_NEWLINE,
+ WRITELN_EMPTY_STRING,
+ WRITE_LITERAL
+]);
+
+impl EarlyLintPass for Write {
+ fn check_item(&mut self, _: &EarlyContext<'_>, item: &Item) {
+ if let ItemKind::Impl(box 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) {
+ fn is_build_script(cx: &EarlyContext<'_>) -> bool {
+ // Cargo sets the crate name for build scripts to `build_script_build`
+ cx.sess
+ .opts
+ .crate_name
+ .as_ref()
+ .map_or(false, |crate_name| crate_name == "build_script_build")
+ }
+
+ if mac.path == sym!(print) {
+ if !is_build_script(cx) {
+ span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
+ }
+ self.lint_print_with_newline(cx, mac);
+ } else if mac.path == sym!(println) {
+ if !is_build_script(cx) {
+ span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
+ }
+ self.lint_println_empty_string(cx, mac);
+ } else if mac.path == sym!(eprint) {
+ span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprint!`");
+ self.lint_print_with_newline(cx, mac);
+ } else if mac.path == sym!(eprintln) {
+ span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprintln!`");
+ self.lint_println_empty_string(cx, mac);
+ } else if mac.path == sym!(write) {
+ if let (Some(fmt_str), dest) = self.check_tts(cx, mac.args.inner_tokens(), true) {
+ if check_newlines(&fmt_str) {
+ let (nl_span, only_nl) = newline_span(&fmt_str);
+ let nl_span = match (dest, only_nl) {
+ // Special case of `write!(buf, "\n")`: Mark everything from the end of
+ // `buf` for removal so no trailing comma [`writeln!(buf, )`] remains.
+ (Some(dest_expr), true) => nl_span.with_lo(dest_expr.span.hi()),
+ _ => nl_span,
+ };
+ 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")), (nl_span, String::new())],
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ } else if mac.path == sym!(writeln) {
+ if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) {
+ if fmt_str.symbol == kw::Empty {
+ let mut applicability = Applicability::MachineApplicable;
+ // FIXME: remove this `#[allow(...)]` once the issue #5822 gets fixed
+ #[allow(clippy::option_if_let_else)]
+ let suggestion = if let Some(e) = expr {
+ snippet_with_applicability(cx, e.span, "v", &mut applicability)
+ } else {
+ applicability = Applicability::HasPlaceholders;
+ Cow::Borrowed("v")
+ };
+
+ span_lint_and_sugg(
+ cx,
+ WRITELN_EMPTY_STRING,
+ mac.span(),
+ format!("using `writeln!({}, \"\")`", suggestion).as_str(),
+ "replace it with",
+ format!("writeln!({})", suggestion),
+ applicability,
+ );
+ }
+ }
+ }
+ }
+}
+
+/// Given a format string that ends in a newline and its span, calculates the span of the
+/// newline, or the format string itself if the format string consists solely of a newline.
+/// Return this and a boolean indicating whether it only consisted of a newline.
+fn newline_span(fmtstr: &StrLit) -> (Span, bool) {
+ let sp = fmtstr.span;
+ let contents = &fmtstr.symbol.as_str();
+
+ if *contents == r"\n" {
+ return (sp, true);
+ }
+
+ 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), false)
+}
+
+/// Stores a list of replacement spans for each argument, but only if all the replacements used an
+/// empty format string.
+#[derive(Default)]
+struct SimpleFormatArgs {
+ unnamed: Vec<Vec<Span>>,
+ named: Vec<(Symbol, Vec<Span>)>,
+}
+impl SimpleFormatArgs {
+ fn get_unnamed(&self) -> impl Iterator<Item = &[Span]> {
+ self.unnamed.iter().map(|x| match x.as_slice() {
+ // Ignore the dummy span added from out of order format arguments.
+ [DUMMY_SP] => &[],
+ x => x,
+ })
+ }
+
+ fn get_named(&self, n: &Path) -> &[Span] {
+ self.named.iter().find(|x| *n == x.0).map_or(&[], |x| x.1.as_slice())
+ }
+
+ fn push(&mut self, arg: rustc_parse_format::Argument<'_>, span: Span) {
+ use rustc_parse_format::{
+ AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec,
+ };
+
+ const SIMPLE: FormatSpec<'_> = FormatSpec {
+ fill: None,
+ align: AlignUnknown,
+ flags: 0,
+ precision: CountImplied,
+ precision_span: None,
+ width: CountImplied,
+ width_span: None,
+ ty: "",
+ ty_span: None,
+ };
+
+ match arg.position {
+ ArgumentIs(n) | ArgumentImplicitlyIs(n) => {
+ if self.unnamed.len() <= n {
+ // Use a dummy span to mark all unseen arguments.
+ self.unnamed.resize_with(n, || vec![DUMMY_SP]);
+ if arg.format == SIMPLE {
+ self.unnamed.push(vec![span]);
+ } else {
+ self.unnamed.push(Vec::new());
+ }
+ } else {
+ let args = &mut self.unnamed[n];
+ match (args.as_mut_slice(), arg.format == SIMPLE) {
+ // A non-empty format string has been seen already.
+ ([], _) => (),
+ // Replace the dummy span, if it exists.
+ ([dummy @ DUMMY_SP], true) => *dummy = span,
+ ([_, ..], true) => args.push(span),
+ ([_, ..], false) => *args = Vec::new(),
+ }
+ }
+ },
+ ArgumentNamed(n) => {
+ if let Some(x) = self.named.iter_mut().find(|x| x.0 == n) {
+ match x.1.as_slice() {
+ // A non-empty format string has been seen already.
+ [] => (),
+ [_, ..] if arg.format == SIMPLE => x.1.push(span),
+ [_, ..] => x.1 = Vec::new(),
+ }
+ } else if arg.format == SIMPLE {
+ self.named.push((n, vec![span]));
+ } else {
+ self.named.push((n, Vec::new()));
+ }
+ },
+ };
+ }
+}
+
+impl Write {
+ /// Parses a format string into a collection of spans for each argument. This only keeps track
+ /// of empty format arguments. Will also lint usages of debug format strings outside of debug
+ /// impls.
+ fn parse_fmt_string(&self, cx: &EarlyContext<'_>, str_lit: &StrLit) -> Option<SimpleFormatArgs> {
+ use rustc_parse_format::{ParseMode, Parser, Piece};
+
+ let str_sym = str_lit.symbol_unescaped.as_str();
+ let style = match str_lit.style {
+ StrStyle::Cooked => None,
+ StrStyle::Raw(n) => Some(n as usize),
+ };
+
+ let mut parser = Parser::new(&str_sym, style, snippet_opt(cx, str_lit.span), false, ParseMode::Format);
+ let mut args = SimpleFormatArgs::default();
+
+ while let Some(arg) = parser.next() {
+ let arg = match arg {
+ Piece::String(_) => continue,
+ Piece::NextArgument(arg) => arg,
+ };
+ let span = parser
+ .arg_places
+ .last()
+ .map_or(DUMMY_SP, |&x| str_lit.span.from_inner(x));
+
+ 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, span, "use of `Debug`-based formatting");
+ }
+
+ args.push(arg, span);
+ }
+
+ parser.errors.is_empty().then(move || args)
+ }
+
+ /// 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>) {
+ let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
+ let expr = if is_write {
+ match parser
+ .parse_expr()
+ .map(rustc_ast::ptr::P::into_inner)
+ .map_err(|mut e| e.cancel())
+ {
+ // write!(e, ...)
+ Ok(p) if parser.eat(&token::Comma) => Some(p),
+ // write!(e) or error
+ e => return (None, e.ok()),
+ }
+ } else {
+ None
+ };
+
+ let fmtstr = match parser.parse_str_lit() {
+ Ok(fmtstr) => fmtstr,
+ Err(_) => return (None, expr),
+ };
+
+ let args = match self.parse_fmt_string(cx, &fmtstr) {
+ Some(args) => args,
+ None => return (Some(fmtstr), expr),
+ };
+
+ let lint = if is_write { WRITE_LITERAL } else { PRINT_LITERAL };
+ let mut unnamed_args = args.get_unnamed();
+ loop {
+ if !parser.eat(&token::Comma) {
+ return (Some(fmtstr), expr);
+ }
+
+ let comma_span = parser.prev_token.span;
+ let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
+ expr
+ } else {
+ return (Some(fmtstr), None);
+ };
+ let (fmt_spans, lit) = match &token_expr.kind {
+ ExprKind::Lit(lit) => (unnamed_args.next().unwrap_or(&[]), lit),
+ ExprKind::Assign(lhs, rhs, _) => match (&lhs.kind, &rhs.kind) {
+ (ExprKind::Path(_, p), ExprKind::Lit(lit)) => (args.get_named(p), lit),
+ _ => continue,
+ },
+ _ => {
+ unnamed_args.next();
+ continue;
+ },
+ };
+
+ let replacement: String = match lit.token.kind {
+ LitKind::Integer | LitKind::Float | LitKind::Err => continue,
+ LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => {
- lit.token.symbol.as_str().replace("{", "{{").replace("}", "}}")
++ lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
+ },
+ LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => {
++ lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
+ },
+ LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue,
+ LitKind::Byte | LitKind::Char => match &*lit.token.symbol.as_str() {
+ "\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"",
+ "\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue,
+ "\\\\" if matches!(fmtstr.style, StrStyle::Raw(_)) => "\\",
+ "\\'" => "'",
+ "{" => "{{",
+ "}" => "}}",
+ x if matches!(fmtstr.style, StrStyle::Raw(_)) && x.starts_with('\\') => continue,
+ x => x,
+ }
+ .into(),
+ LitKind::Bool => lit.token.symbol.as_str().deref().into(),
+ };
+
+ if !fmt_spans.is_empty() {
+ span_lint_and_then(
+ cx,
+ lint,
+ token_expr.span,
+ "literal with an empty format string",
+ |diag| {
+ diag.multipart_suggestion(
+ "try this",
+ iter::once((comma_span.to(token_expr.span), String::new()))
+ .chain(fmt_spans.iter().copied().zip(iter::repeat(replacement)))
+ .collect(),
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ }
+
+ fn lint_println_empty_string(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+ if fmt_str.symbol == kw::Empty {
+ let name = mac.path.segments[0].ident.name;
+ span_lint_and_sugg(
+ cx,
+ PRINTLN_EMPTY_STRING,
+ mac.span(),
+ &format!("using `{}!(\"\")`", name),
+ "replace it with",
+ format!("{}!()", name),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+
+ fn lint_print_with_newline(&self, cx: &EarlyContext<'_>, mac: &MacCall) {
+ if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
+ if check_newlines(&fmt_str) {
+ let name = mac.path.segments[0].ident.name;
+ let suggested = format!("{}ln", name);
+ span_lint_and_then(
+ cx,
+ PRINT_WITH_NEWLINE,
+ mac.span(),
+ &format!("using `{}!()` with a format string that ends in a single newline", name),
+ |err| {
+ err.multipart_suggestion(
+ &format!("use `{}!` instead", suggested),
+ vec![(mac.path.span, suggested), (newline_span(&fmt_str).0, String::new())],
+ Applicability::MachineApplicable,
+ );
+ },
+ );
+ }
+ }
+ }
+}
+
+/// Checks if the format string contains a single newline that terminates it.
+///
+/// Literal and escaped newlines are both checked (only literal for raw strings).
+fn check_newlines(fmtstr: &StrLit) -> bool {
+ let mut has_internal_newline = false;
+ let mut last_was_cr = false;
+ let mut should_lint = false;
+
+ let contents = &fmtstr.symbol.as_str();
+
+ let mut cb = |r: Range<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
+use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::diagnostics::span_lint_and_help;
+use if_chain::if_chain;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `0.0 / 0.0`.
+ ///
+ /// ### Why is this bad?
+ /// It's less readable than `f32::NAN` or `f64::NAN`.
+ ///
+ /// ### Example
+ /// ```rust
+ /// // Bad
+ /// let nan = 0.0f32 / 0.0;
+ ///
+ /// // Good
+ /// let nan = f32::NAN;
+ /// ```
++ #[clippy::version = "pre 1.29.0"]
+ pub ZERO_DIVIDED_BY_ZERO,
+ complexity,
+ "usage of `0.0 / 0.0` to obtain NaN instead of `f32::NAN` or `f64::NAN`"
+}
+
+declare_lint_pass!(ZeroDiv => [ZERO_DIVIDED_BY_ZERO]);
+
+impl<'tcx> LateLintPass<'tcx> for ZeroDiv {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // check for instances of 0.0/0.0
+ if_chain! {
+ if let ExprKind::Binary(ref op, left, right) = expr.kind;
+ if op.node == BinOpKind::Div;
+ // TODO - constant_simple does not fold many operations involving floats.
+ // That's probably fine for this lint - it's pretty unlikely that someone would
+ // do something like 0.0/(2.0 - 2.0), but it would be nice to warn on that case too.
+ if let Some(lhs_value) = constant_simple(cx, cx.typeck_results(), left);
+ if let Some(rhs_value) = constant_simple(cx, cx.typeck_results(), right);
+ if Constant::F32(0.0) == lhs_value || Constant::F64(0.0) == lhs_value;
+ if Constant::F32(0.0) == rhs_value || Constant::F64(0.0) == rhs_value;
+ then {
+ // since we're about to suggest a use of f32::NAN or f64::NAN,
+ // match the precision of the literals that are given.
+ let float_type = match (lhs_value, rhs_value) {
+ (Constant::F64(_), _)
+ | (_, Constant::F64(_)) => "f64",
+ _ => "f32"
+ };
+ span_lint_and_help(
+ cx,
+ ZERO_DIVIDED_BY_ZERO,
+ expr.span,
+ "constant division of `0.0` with `0.0` will always result in NaN",
+ None,
+ &format!(
+ "consider using `{}::NAN` if you would like a constant representing NaN",
+ float_type,
+ ),
+ );
+ }
+ }
+ }
+}
--- /dev/null
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::ty::{is_normalizable, is_type_diagnostic_item};
+use if_chain::if_chain;
+use rustc_hir::{self as hir, HirId, ItemKind, Node};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::layout::LayoutOf as _;
+use rustc_middle::ty::{Adt, Ty, TypeFoldable};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+use rustc_typeck::hir_ty_to_ty;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for maps with zero-sized value types anywhere in the code.
+ ///
+ /// ### Why is this bad?
+ /// Since there is only a single value for a zero-sized type, a map
+ /// containing zero sized values is effectively a set. Using a set in that case improves
+ /// readability and communicates intent more clearly.
+ ///
+ /// ### Known problems
+ /// * A zero-sized type cannot be recovered later if it contains private fields.
+ /// * This lints the signature of public items
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::collections::HashMap;
+ /// fn unique_words(text: &str) -> HashMap<&str, ()> {
+ /// todo!();
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::collections::HashSet;
+ /// fn unique_words(text: &str) -> HashSet<&str> {
+ /// todo!();
+ /// }
+ /// ```
++ #[clippy::version = "1.50.0"]
+ pub ZERO_SIZED_MAP_VALUES,
+ pedantic,
+ "usage of map with zero-sized value type"
+}
+
+declare_lint_pass!(ZeroSizedMapValues => [ZERO_SIZED_MAP_VALUES]);
+
+impl LateLintPass<'_> for ZeroSizedMapValues {
+ fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
+ if_chain! {
+ if !hir_ty.span.from_expansion();
+ if !in_trait_impl(cx, hir_ty.hir_id);
+ let ty = ty_from_hir_ty(cx, hir_ty);
+ if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap);
+ if let Adt(_, substs) = ty.kind();
+ let ty = substs.type_at(1);
+ // Fixes https://github.com/rust-lang/rust-clippy/issues/7447 because of
+ // https://github.com/rust-lang/rust/blob/master/compiler/rustc_middle/src/ty/sty.rs#L968
+ if !ty.has_escaping_bound_vars();
+ // Do this to prevent `layout_of` crashing, being unable to fully normalize `ty`.
+ if is_normalizable(cx, cx.param_env, ty);
+ if let Ok(layout) = cx.layout_of(ty);
+ if layout.is_zst();
+ then {
+ span_lint_and_help(cx, ZERO_SIZED_MAP_VALUES, hir_ty.span, "map with zero-sized value type", None, "consider using a set instead");
+ }
+ }
+ }
+}
+
+fn in_trait_impl(cx: &LateContext<'_>, hir_id: HirId) -> bool {
+ let parent_id = cx.tcx.hir().get_parent_item(hir_id);
+ if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_item(parent_id)) {
+ if let ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind {
+ return true;
+ }
+ }
+ false
+}
+
+fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Ty<'tcx> {
+ cx.maybe_typeck_results()
+ .and_then(|results| {
+ if results.hir_owner == hir_ty.hir_id.owner {
+ results.node_type_opt(hir_ty.hir_id)
+ } else {
+ None
+ }
+ })
+ .unwrap_or_else(|| hir_ty_to_ty(cx.tcx, hir_ty))
+}
--- /dev/null
- version = "0.1.58"
+[package]
+name = "clippy_utils"
++version = "0.1.59"
+edition = "2021"
+publish = false
+
+[dependencies]
+if_chain = "1.0"
+rustc-semver = "1.1"
+
+[features]
+deny-warnings = []
+internal-lints = []
+metadata-collector-lint = []
+
+[package.metadata.rust-analyzer]
+# This crate uses #[feature(rustc_private)]
+rustc_private = true
--- /dev/null
- (Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }),
- Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => {
+//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
+//!
+//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
+
+#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
+
+use crate::{both, over};
+use if_chain::if_chain;
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, *};
+use rustc_span::symbol::Ident;
+use std::mem;
+
+pub mod ident_iter;
+pub use ident_iter::IdentIter;
+
+pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
+ use BinOpKind::*;
+ matches!(
+ kind,
+ Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
+ )
+}
+
+/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
+pub fn unordered_over<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, eq_qself) && eq_path(lp, rp),
+ (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => {
+ eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r))
+ },
+ (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => {
+ lr == rr && eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat)
+ },
+ (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ _ => false,
+ }
+}
+
+pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
+ match (l, r) {
+ (RangeEnd::Excluded, RangeEnd::Excluded) => true,
+ (RangeEnd::Included(l), RangeEnd::Included(r)) => {
+ matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field_pat(l: &PatField, r: &PatField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_pat(&l.pat, &r.pat)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
+ l.position == r.position && eq_ty(&l.ty, &r.ty)
+}
+
+pub fn eq_maybe_qself(l: &Option<QSelf>, r: &Option<QSelf>) -> bool {
+ match (l, r) {
+ (Some(l), Some(r)) => eq_qself(l, r),
+ (None, None) => true,
+ _ => false,
+ }
+}
+
+pub fn eq_path(l: &Path, r: &Path) -> bool {
+ over(&l.segments, &r.segments, eq_path_seg)
+}
+
+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, eq_angle_arg),
+ (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_struct_rest(l: &StructRest, r: &StructRest) -> bool {
+ match (l, r) {
+ (StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb),
+ (StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true,
+ _ => false,
+ }
+}
+
+pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
+ use ExprKind::*;
+ if !over(&l.attrs, &r.attrs, eq_attr) {
+ 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, eq_arm),
+ (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, eq_qself) && eq_path(lp, rp),
+ (MacCall(l), MacCall(r)) => eq_mac_call(l, r),
+ (Struct(lse), Struct(rse)) => {
+ eq_maybe_qself(&lse.qself, &rse.qself)
+ && eq_path(&lse.path, &rse.path)
+ && eq_struct_rest(&lse.rest, &rse.rest)
+ && unordered_over(&lse.fields, &rse.fields, eq_field)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && eq_id(l.ident, r.ident)
+ && eq_expr(&l.expr, &r.expr)
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+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, eq_attr)
+}
+
+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, eq_stmt)
+}
+
+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_local_kind(&l.kind, &r.kind)
+ && over(&l.attrs, &r.attrs, eq_attr)
+ },
+ (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.style == r.style && eq_mac_call(&l.mac, &r.mac) && over(&l.attrs, &r.attrs, eq_attr)
+ },
+ _ => false,
+ }
+}
+
+pub fn eq_local_kind(l: &LocalKind, r: &LocalKind) -> bool {
+ use LocalKind::*;
+ match (l, r) {
+ (Decl, Decl) => true,
+ (Init(l), Init(r)) => eq_expr(l, r),
+ (InitElse(li, le), InitElse(ri, re)) => eq_expr(li, ri) && eq_block(le, re),
+ _ => 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, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
+}
+
++#[allow(clippy::too_many_lines)] // Just a big match statement
+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),
- (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }),
- TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => {
++ (
++ Fn(box ast::Fn {
++ defaultness: ld,
++ sig: lf,
++ generics: lg,
++ body: lb,
++ }),
++ Fn(box ast::Fn {
++ defaultness: rd,
++ sig: rf,
++ generics: rg,
++ body: rb,
++ }),
++ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
+ (Mod(lu, lmk), Mod(ru, rmk)) => {
+ lu == ru
+ && match (lmk, rmk) {
+ (ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) => {
+ linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind))
+ },
+ (ModKind::Unloaded, ModKind::Unloaded) => true,
+ _ => false,
+ }
+ },
+ (ForeignMod(l), ForeignMod(r)) => {
+ both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
+ },
- (Trait(box ast::Trait { is_auto: la, unsafety: lu, generics: lg, bounds: lb, items: li }),
- Trait(box ast::Trait { is_auto: ra, unsafety: ru, generics: rg, bounds: rb, items: ri })) => {
++ (
++ TyAlias(box ast::TyAlias {
++ defaultness: ld,
++ generics: lg,
++ bounds: lb,
++ ty: lt,
++ }),
++ TyAlias(box ast::TyAlias {
++ defaultness: rd,
++ generics: rg,
++ bounds: rb,
++ ty: rt,
++ }),
++ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && both(lt, rt, |l, r| eq_ty(l, r))
+ },
+ (Enum(le, lg), Enum(re, rg)) => over(&le.variants, &re.variants, eq_variant) && 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)
+ },
- (Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }),
- Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => {
++ (
++ Trait(box ast::Trait {
++ is_auto: la,
++ unsafety: lu,
++ generics: lg,
++ bounds: lb,
++ items: li,
++ }),
++ Trait(box ast::Trait {
++ is_auto: ra,
++ unsafety: ru,
++ generics: rg,
++ bounds: rb,
++ items: ri,
++ }),
++ ) => {
+ la == ra
+ && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && 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, eq_generic_bound),
+ (
+ Impl(box ast::Impl {
+ unsafety: lu,
+ polarity: lp,
+ defaultness: ld,
+ constness: lc,
+ generics: lg,
+ of_trait: lot,
+ self_ty: lst,
+ items: li,
+ }),
+ Impl(box ast::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),
- (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }),
- TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => {
++ (
++ Fn(box ast::Fn {
++ defaultness: ld,
++ sig: lf,
++ generics: lg,
++ body: lb,
++ }),
++ Fn(box ast::Fn {
++ defaultness: rd,
++ sig: rf,
++ generics: rg,
++ body: rb,
++ }),
++ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
- (Fn(box ast::Fn { defaultness: ld, sig: lf, generics: lg, body: lb }),
- Fn(box ast::Fn { defaultness: rd, sig: rf, generics: rg, body: rb })) => {
++ (
++ TyAlias(box ast::TyAlias {
++ defaultness: ld,
++ generics: lg,
++ bounds: lb,
++ ty: lt,
++ }),
++ TyAlias(box ast::TyAlias {
++ defaultness: rd,
++ generics: rg,
++ bounds: rb,
++ ty: rt,
++ }),
++ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && 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),
- (TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt }),
- TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt })) => {
++ (
++ Fn(box ast::Fn {
++ defaultness: ld,
++ sig: lf,
++ generics: lg,
++ body: lb,
++ }),
++ Fn(box ast::Fn {
++ defaultness: rd,
++ sig: rf,
++ generics: rg,
++ body: rb,
++ }),
++ ) => {
+ eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
+ },
++ (
++ TyAlias(box ast::TyAlias {
++ defaultness: ld,
++ generics: lg,
++ bounds: lb,
++ ty: lt,
++ }),
++ TyAlias(box ast::TyAlias {
++ defaultness: rd,
++ generics: rg,
++ bounds: rb,
++ ty: rt,
++ }),
++ ) => {
+ eq_defaultness(*ld, *rd)
+ && eq_generics(lg, rg)
+ && over(lb, rb, eq_generic_bound)
+ && 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, eq_attr)
+ && 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, eq_struct_field),
+ _ => false,
+ }
+}
+
+pub fn eq_struct_field(l: &FieldDef, r: &FieldDef) -> bool {
+ l.is_placeholder == r.is_placeholder
+ && over(&l.attrs, &r.attrs, eq_attr)
+ && 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, eq_generic_param)
+ && 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, eq_generic_bound)
+ },
+ (RegionPredicate(l), RegionPredicate(r)) => {
+ eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, eq_generic_bound)
+ },
+ (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_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
+ eq_expr(&l.value, &r.value)
+}
+
+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.kind, &r.kind) {
+ (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, eq_attr)
+ })
+}
+
+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, eq_generic_param)
+ && 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, eq_qself) && eq_path(lp, rp),
+ (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound),
+ (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound),
+ (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, eq_generic_bound)
+ && 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: lt,
+ kw_span: _,
+ default: ld,
+ },
+ Const {
+ ty: rt,
+ kw_span: _,
+ default: rd,
+ },
+ ) => eq_ty(lt, rt) && both(ld, rd, eq_anon_const),
+ _ => false,
+ }
+ && over(&l.attrs, &r.attrs, eq_attr)
+}
+
+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, eq_generic_bound),
+ _ => 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(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
+ (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(_, lt), Eq(_, rt)) => lt.kind == rt.kind,
+ _ => false,
+ }
+}
+
+/// Extract args from an assert-like macro.
+///
+/// Currently working with:
+/// - `assert_eq!` and `assert_ne!`
+/// - `debug_assert_eq!` and `debug_assert_ne!`
+///
+/// For example:
+///
+/// `debug_assert_eq!(a, b)` will return Some([a, b])
+pub fn extract_assert_macro_args(mut expr: &Expr) -> Option<[&Expr; 2]> {
+ if_chain! {
+ if let ExprKind::If(_, ref block, _) = expr.kind;
+ if let StmtKind::Semi(ref e) = block.stmts.get(0)?.kind;
+ then {
+ expr = e;
+ }
+ }
+ if_chain! {
+ if let ExprKind::Block(ref block, _) = expr.kind;
+ if let StmtKind::Expr(ref expr) = block.stmts.get(0)?.kind;
+ if let ExprKind::Match(ref match_expr, _) = expr.kind;
+ if let ExprKind::Tup(ref tup) = match_expr.kind;
+ if let [a, b, ..] = tup.as_slice();
+ if let (&ExprKind::AddrOf(_, _, ref a), &ExprKind::AddrOf(_, _, ref b)) = (&a.kind, &b.kind);
+ then {
+ return Some([&*a, &*b]);
+ }
+ }
+ None
+}
--- /dev/null
- ("author", DeprecationStatus::None),
- ("cognitive_complexity", DeprecationStatus::None),
- (
- "cyclomatic_complexity",
- DeprecationStatus::Replaced("cognitive_complexity"),
- ),
- ("dump", DeprecationStatus::None),
- ("msrv", DeprecationStatus::None),
+use rustc_ast::{ast, attr};
+use rustc_errors::Applicability;
+use rustc_session::Session;
+use rustc_span::sym;
+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,
+}
+
++#[rustfmt::skip]
+pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
++ ("author", DeprecationStatus::None),
++ ("version", DeprecationStatus::None),
++ ("cognitive_complexity", DeprecationStatus::None),
++ ("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")),
++ ("dump", DeprecationStatus::None),
++ ("msrv", 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.name == sym::clippy {
+ BUILTIN_ATTRIBUTES
+ .iter()
+ .find_map(|&(builtin_name, ref deprecation_status)| {
+ if attr_segments[1].ident.name.as_str() == builtin_name {
+ Some(deprecation_status)
+ } else {
+ None
+ }
+ })
+ .map_or_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.name.as_str() == 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");
+ }
+ }
+}
+
+pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
+ let mut unique_attr = None;
+ for attr in get_attr(sess, attrs, name) {
+ match attr.style {
+ ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
+ ast::AttrStyle::Inner => {
+ sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
+ .span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
+ .emit();
+ },
+ ast::AttrStyle::Outer => {
+ sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
+ },
+ }
+ }
+ unique_attr
+}
+
+/// Return true if the attributes contain any of `proc_macro`,
+/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
+pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
+}
+
+/// Return true if the attributes contain `#[doc(hidden)]`
+pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
+ attrs
+ .iter()
+ .filter(|attr| attr.has_name(sym::doc))
+ .filter_map(ast::Attribute::meta_item_list)
+ .any(|l| attr::list_contains_name(&l, sym::hidden))
+}
+
+/// Return true if the attributes contain `#[unstable]`
+pub fn is_unstable(attrs: &[ast::Attribute]) -> bool {
+ attrs.iter().any(|attr| attr.has_name(sym::unstable))
+}
--- /dev/null
- use crate::is_ctor_or_promotable_const_function;
- use crate::ty::is_type_diagnostic_item;
+//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
+//!
+//! Things to consider:
+//! - has the expression side-effects?
+//! - is the expression computationally expensive?
+//!
+//! See lints:
+//! - unnecessary-lazy-evaluations
+//! - or-fun-call
+//! - option-if-let-else
+
-
- use rustc_hir::intravisit;
- use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
-
- use rustc_hir::{Block, Expr, ExprKind, Path, QPath};
++use crate::ty::{all_predicates_of, is_copy};
++use crate::visitors::is_const_evaluatable;
+use rustc_hir::def::{DefKind, Res};
- use rustc_middle::hir::map::Map;
- use rustc_span::sym;
-
- /// Is the expr pure (is it free from side-effects)?
- /// This function is named so to stress that it isn't exhaustive and returns FNs.
- fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool {
- match expr.kind {
- ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
- ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr),
- ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(identify_some_pure_patterns),
- ExprKind::Struct(_, fields, expr) => {
- fields.iter().all(|f| identify_some_pure_patterns(f.expr)) && expr.map_or(true, identify_some_pure_patterns)
- },
- ExprKind::Call(
- &Expr {
- kind:
- ExprKind::Path(QPath::Resolved(
- _,
- Path {
- res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
- ..
- },
- )),
- ..
- },
- args,
- ) => args.iter().all(identify_some_pure_patterns),
- ExprKind::Block(
- &Block {
- stmts,
- expr: Some(expr),
- ..
- },
- _,
- ) => stmts.is_empty() && identify_some_pure_patterns(expr),
- ExprKind::Box(..)
- | ExprKind::Array(..)
- | ExprKind::Call(..)
- | ExprKind::MethodCall(..)
- | ExprKind::Binary(..)
- | ExprKind::Unary(..)
- | ExprKind::Let(..)
- | ExprKind::Cast(..)
- | ExprKind::Type(..)
- | ExprKind::DropTemps(..)
- | ExprKind::Loop(..)
- | ExprKind::If(..)
- | ExprKind::Match(..)
- | ExprKind::Closure(..)
- | ExprKind::Block(..)
- | ExprKind::Assign(..)
- | ExprKind::AssignOp(..)
- | ExprKind::Index(..)
- | ExprKind::Break(..)
- | ExprKind::Continue(..)
- | ExprKind::Ret(..)
- | ExprKind::InlineAsm(..)
- | ExprKind::LlvmInlineAsm(..)
- | ExprKind::Repeat(..)
- | ExprKind::Yield(..)
- | ExprKind::Err => false,
++use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor};
++use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp};
+use rustc_lint::LateContext;
-
- /// Identify some potentially computationally expensive patterns.
- /// This function is named so to stress that its implementation is non-exhaustive.
- /// It returns FNs and FPs.
- fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- // Searches an expression for method calls or function calls that aren't ctors
- struct FunCallFinder<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- found: bool,
++use rustc_middle::ty::{self, PredicateKind};
++use rustc_span::{sym, Symbol};
++use std::cmp;
++use std::ops;
++
++#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
++enum EagernessSuggestion {
++ // The expression is cheap and should be evaluated eagerly
++ Eager,
++ // The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to
++ // eager evaluation.
++ NoChange,
++ // The expression is likely expensive and should be evaluated lazily.
++ Lazy,
++ // The expression cannot be placed into a closure.
++ ForceNoChange,
++}
++impl ops::BitOr for EagernessSuggestion {
++ type Output = Self;
++ fn bitor(self, rhs: Self) -> Self {
++ cmp::max(self, rhs)
+ }
+}
- impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
- let call_found = match &expr.kind {
- // ignore enum and struct constructors
- ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
- ExprKind::Index(obj, _) => {
- let ty = self.cx.typeck_results().expr_ty(obj);
- is_type_diagnostic_item(self.cx, ty, sym::HashMap)
- || is_type_diagnostic_item(self.cx, ty, sym::BTreeMap)
- },
- ExprKind::MethodCall(..) => true,
- _ => false,
- };
++impl ops::BitOrAssign for EagernessSuggestion {
++ fn bitor_assign(&mut self, rhs: Self) {
++ *self = *self | rhs;
+ }
++}
+
- if call_found {
- self.found |= true;
- }
++/// Determine the eagerness of the given function call.
++fn fn_eagerness(cx: &LateContext<'tcx>, fn_id: DefId, name: Symbol, args: &'tcx [Expr<'_>]) -> EagernessSuggestion {
++ use EagernessSuggestion::{Eager, Lazy, NoChange};
++ let name = &*name.as_str();
+
- if !self.found {
- intravisit::walk_expr(self, expr);
++ let ty = match cx.tcx.impl_of_method(fn_id) {
++ Some(id) => cx.tcx.type_of(id),
++ None => return Lazy,
++ };
+
- let mut finder = FunCallFinder { cx, found: false };
- finder.visit_expr(expr);
- finder.found
++ if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 {
++ if matches!(
++ cx.tcx.crate_name(fn_id.krate),
++ sym::std | sym::core | sym::alloc | sym::proc_macro
++ ) {
++ Eager
++ } else {
++ NoChange
++ }
++ } else if let ty::Adt(def, subs) = ty.kind() {
++ // Types where the only fields are generic types (or references to) with no trait bounds other
++ // than marker traits.
++ // Due to the limited operations on these types functions should be fairly cheap.
++ if def
++ .variants
++ .iter()
++ .flat_map(|v| v.fields.iter())
++ .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_)))
++ && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
++ PredicateKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker,
++ _ => true,
++ })
++ && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
++ {
++ // Limit the function to either `(self) -> bool` or `(&self) -> bool`
++ match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output {
++ [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
++ _ => Lazy,
+ }
++ } else {
++ Lazy
+ }
++ } else {
++ Lazy
++ }
++}
+
++#[allow(clippy::too_many_lines)]
++fn expr_eagerness(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
++ struct V<'cx, 'tcx> {
++ cx: &'cx LateContext<'tcx>,
++ eagerness: EagernessSuggestion,
++ }
++
++ impl<'cx, 'tcx> Visitor<'tcx> for V<'cx, 'tcx> {
++ type Map = ErasedMap<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
++
++ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
++ use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
++ if self.eagerness == ForceNoChange {
++ return;
++ }
++ match e.kind {
++ ExprKind::Call(
++ &Expr {
++ kind: ExprKind::Path(ref path),
++ hir_id,
++ ..
++ },
++ args,
++ ) => match self.cx.qpath_res(path, hir_id) {
++ Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => (),
++ Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (),
++ // No need to walk the arguments here, `is_const_evaluatable` already did
++ Res::Def(..) if is_const_evaluatable(self.cx, e) => {
++ self.eagerness |= NoChange;
++ return;
++ },
++ Res::Def(_, id) => match path {
++ QPath::Resolved(_, p) => {
++ self.eagerness |= fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, args);
++ },
++ QPath::TypeRelative(_, name) => {
++ self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args);
++ },
++ QPath::LangItem(..) => self.eagerness = Lazy,
++ },
++ _ => self.eagerness = Lazy,
++ },
++ // No need to walk the arguments here, `is_const_evaluatable` already did
++ ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => {
++ self.eagerness |= NoChange;
++ return;
++ },
++ ExprKind::MethodCall(name, _, args, _) => {
++ self.eagerness |= self
++ .cx
++ .typeck_results()
++ .type_dependent_def_id(e.hir_id)
++ .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args));
++ },
++ ExprKind::Index(_, e) => {
++ let ty = self.cx.typeck_results().expr_ty_adjusted(e);
++ if is_copy(self.cx, ty) && !ty.is_ref() {
++ self.eagerness |= NoChange;
++ } else {
++ self.eagerness = Lazy;
++ }
++ },
++
++ // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe.
++ ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (),
++ ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange,
++
++ ExprKind::Unary(_, e)
++ if matches!(
++ self.cx.typeck_results().expr_ty(e).kind(),
++ ty::Bool | ty::Int(_) | ty::Uint(_),
++ ) => {},
++ ExprKind::Binary(_, lhs, rhs)
++ if self.cx.typeck_results().expr_ty(lhs).is_primitive()
++ && self.cx.typeck_results().expr_ty(rhs).is_primitive() => {},
++
++ // Can't be moved into a closure
++ ExprKind::Break(..)
++ | ExprKind::Continue(_)
++ | ExprKind::Ret(_)
++ | ExprKind::InlineAsm(_)
++ | ExprKind::LlvmInlineAsm(_)
++ | ExprKind::Yield(..)
++ | ExprKind::Err => {
++ self.eagerness = ForceNoChange;
++ return;
++ },
++
++ // Memory allocation, custom operator, loop, or call to an unknown function
++ ExprKind::Box(_)
++ | ExprKind::Unary(..)
++ | ExprKind::Binary(..)
++ | ExprKind::Loop(..)
++ | ExprKind::Call(..) => self.eagerness = Lazy,
++
++ ExprKind::ConstBlock(_)
++ | ExprKind::Array(_)
++ | ExprKind::Tup(_)
++ | ExprKind::Lit(_)
++ | ExprKind::Cast(..)
++ | ExprKind::Type(..)
++ | ExprKind::DropTemps(_)
++ | ExprKind::Let(..)
++ | ExprKind::If(..)
++ | ExprKind::Match(..)
++ | ExprKind::Closure(..)
++ | ExprKind::Field(..)
++ | ExprKind::Path(_)
++ | ExprKind::AddrOf(..)
++ | ExprKind::Struct(..)
++ | ExprKind::Repeat(..)
++ | ExprKind::Block(Block { stmts: [], .. }, _) => (),
++
++ // Assignment might be to a local defined earlier, so don't eagerly evaluate.
++ // Blocks with multiple statements might be expensive, so don't eagerly evaluate.
++ // TODO: Actually check if either of these are true here.
++ ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
++ }
++ walk_expr(self, e);
++ }
+ }
+
- pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- !identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr)
++ let mut v = V {
++ cx,
++ eagerness: EagernessSuggestion::Eager,
++ };
++ v.visit_expr(e);
++ v.eagerness
+}
+
- pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
- identify_some_potentially_expensive_patterns(cx, expr)
++/// Whether the given expression should be changed to evaluate eagerly
++pub fn switch_to_eager_eval(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
++ expr_eagerness(cx, expr) == EagernessSuggestion::Eager
+}
+
++/// Whether the given expression should be changed to evaluate lazily
++pub fn switch_to_lazy_eval(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
++ expr_eagerness(cx, expr) == EagernessSuggestion::Lazy
+}
--- /dev/null
- use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
+#![feature(box_patterns)]
+#![feature(in_band_lifetimes)]
+#![feature(iter_zip)]
++#![feature(let_else)]
+#![feature(rustc_private)]
+#![feature(control_flow_enum)]
+#![recursion_limit = "512"]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
+// warn on the same lints as `clippy_lints`
+#![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
+#![warn(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_infer;
+extern crate rustc_lexer;
+extern crate rustc_lint;
+extern crate rustc_middle;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_target;
+extern crate rustc_trait_selection;
+extern crate rustc_typeck;
+
+#[macro_use]
+pub mod sym_helper;
+
+#[allow(clippy::module_name_repetitions)]
+pub mod ast_utils;
+pub mod attrs;
+pub mod comparisons;
+pub mod consts;
+pub mod diagnostics;
+pub mod eager_or_lazy;
+pub mod higher;
+mod hir_utils;
+pub mod msrvs;
+pub mod numeric_literal;
+pub mod paths;
+pub mod ptr;
+pub mod qualify_min_const_fn;
+pub mod source;
+pub mod str_utils;
+pub mod sugg;
+pub mod ty;
+pub mod usage;
+pub mod visitors;
+
+pub use self::attrs::*;
+pub use self::hir_utils::{both, count_eq, eq_expr_value, over, SpanlessEq, SpanlessHash};
+
+use std::collections::hash_map::Entry;
+use std::hash::BuildHasherDefault;
+
+use if_chain::if_chain;
+use rustc_ast::ast::{self, Attribute, LitKind};
+use rustc_data_structures::unhash::UnhashMap;
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_hir::hir_id::{HirIdMap, HirIdSet};
- /// Returns `true` if this `span` was expanded by any macro.
- #[must_use]
- pub fn in_macro(span: Span) -> bool {
- span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
- }
-
++use rustc_hir::intravisit::{walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
+use rustc_hir::itemlikevisit::ItemLikeVisitor;
+use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
+use rustc_hir::{
+ def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, ForeignItem, GenericArgs,
+ HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node,
+ Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
+ UnOp,
+};
+use rustc_lint::{LateContext, Level, Lint, LintContext};
+use rustc_middle::hir::exports::Export;
+use rustc_middle::hir::map::Map;
+use rustc_middle::hir::place::PlaceBase;
+use rustc_middle::ty as rustc_ty;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
+use rustc_middle::ty::binding::BindingMode;
+use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture};
+use rustc_semver::RustcVersion;
+use rustc_session::Session;
+use rustc_span::hygiene::{ExpnKind, MacroKind};
+use rustc_span::source_map::original_sp;
+use rustc_span::sym;
+use rustc_span::symbol::{kw, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use rustc_target::abi::Integer;
+
+use crate::consts::{constant, Constant};
+use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
++use crate::visitors::expr_visitor_no_bodies;
+
+pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
+ if let Ok(version) = RustcVersion::parse(msrv) {
+ return Some(version);
+ } else if let Some(sess) = sess {
+ if let Some(span) = span {
+ sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
+ }
+ }
+ None
+}
+
+pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool {
+ msrv.map_or(true, |msrv| msrv.meets(*lint_msrv))
+}
+
+#[macro_export]
+macro_rules! extract_msrv_attr {
+ (LateContext) => {
+ extract_msrv_attr!(@LateContext, ());
+ };
+ (EarlyContext) => {
+ extract_msrv_attr!(@EarlyContext);
+ };
+ (@$context:ident$(, $call:tt)?) => {
+ fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) {
+ use $crate::get_unique_inner_attr;
+ match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") {
+ Some(msrv_attr) => {
+ if let Some(msrv) = msrv_attr.value_str() {
+ self.msrv = $crate::parse_msrv(
+ &msrv.to_string(),
+ Some(cx.sess$($call)?),
+ Some(msrv_attr.span),
+ );
+ } else {
+ cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute");
+ }
+ },
+ _ => (),
+ }
+ }
+ };
+}
+
+/// 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()
+}
+
+/// If the given expression is a local binding, find the initializer expression.
+/// If that initializer expression is another local binding, find its initializer again.
+/// This process repeats as long as possible (but usually no more than once). Initializer
+/// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`]
+/// instead.
+///
+/// Examples:
+/// ```ignore
+/// let abc = 1;
+/// // ^ output
+/// let def = abc;
+/// dbg!(def)
+/// // ^^^ input
+///
+/// // or...
+/// let abc = 1;
+/// let def = abc + 2;
+/// // ^^^^^^^ output
+/// dbg!(def)
+/// // ^^^ input
+/// ```
+pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
+ while let Some(init) = path_to_local(expr)
+ .and_then(|id| find_binding_init(cx, id))
+ .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
+ {
+ expr = init;
+ }
+ expr
+}
+
+/// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable.
+/// By only considering immutable bindings, we guarantee that the returned expression represents the
+/// value of the binding wherever it is referenced.
+///
+/// Example: For `let x = 1`, if the `HirId` of `x` is provided, the `Expr` `1` is returned.
+/// Note: If you have an expression that references a binding `x`, use `path_to_local` to get the
+/// canonical binding `HirId`.
+pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> {
+ let hir = cx.tcx.hir();
+ if_chain! {
+ if let Some(Node::Binding(pat)) = hir.find(hir_id);
+ if matches!(pat.kind, PatKind::Binding(BindingAnnotation::Unannotated, ..));
+ let parent = hir.get_parent_node(hir_id);
+ if let Some(Node::Local(local)) = hir.find(parent);
+ then {
+ return local.init;
+ }
+ }
+ None
+}
+
+/// 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,
+ }
+}
+
+/// Checks if a `QPath` resolves to a constructor of a `LangItem`.
+/// For example, use this to check whether a function call or a pattern is `Some(..)`.
+pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem) -> bool {
+ if let QPath::Resolved(_, path) = qpath {
+ if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res {
+ if let Ok(item_id) = cx.tcx.lang_items().require(lang_item) {
+ return cx.tcx.parent(ctor_id) == Some(item_id);
+ }
+ }
+ }
+ false
+}
+
- struct RetCallFinder {
- found: bool,
- }
-
- impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
- if self.found {
- return;
- }
+pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
+ matches!(
+ expr.kind,
+ ExprKind::Block(
+ Block {
+ stmts: [],
+ expr: None,
+ ..
+ },
+ _
+ ) | ExprKind::Tup([])
+ )
+}
+
+/// Checks if given pattern is a wildcard (`_`)
+pub fn is_wild(pat: &Pat<'_>) -> bool {
+ matches!(pat.kind, PatKind::Wild)
+}
+
+/// Checks if the first type parameter is a lang item.
+pub fn is_ty_param_lang_item(cx: &LateContext<'_>, qpath: &QPath<'tcx>, item: LangItem) -> Option<&'tcx hir::Ty<'tcx>> {
+ let ty = get_qpath_generic_tys(qpath).next()?;
+
+ if let TyKind::Path(qpath) = &ty.kind {
+ cx.qpath_res(qpath, ty.hir_id)
+ .opt_def_id()
+ .map_or(false, |id| {
+ cx.tcx.lang_items().require(item).map_or(false, |lang_id| id == lang_id)
+ })
+ .then(|| ty)
+ } else {
+ None
+ }
+}
+
+/// Checks if the first type parameter is a diagnostic item.
+pub fn is_ty_param_diagnostic_item(
+ cx: &LateContext<'_>,
+ qpath: &QPath<'tcx>,
+ item: Symbol,
+) -> Option<&'tcx hir::Ty<'tcx>> {
+ let ty = get_qpath_generic_tys(qpath).next()?;
+
+ if let TyKind::Path(qpath) = &ty.kind {
+ cx.qpath_res(qpath, ty.hir_id)
+ .opt_def_id()
+ .map_or(false, |id| cx.tcx.is_diagnostic_item(item, id))
+ .then(|| ty)
+ } else {
+ None
+ }
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+/// This is a deprecated function, consider using [`is_trait_method`].
+pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
+ let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+ let trt_id = cx.tcx.trait_of_item(def_id);
+ trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
+}
+
+/// Checks if a method is defined in an impl of a diagnostic item
+pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
+ if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
+ if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
+ return cx.tcx.is_diagnostic_item(diag_item, adt.did);
+ }
+ }
+ false
+}
+
+/// Checks if a method is in a diagnostic item trait
+pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
+ if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
+ return cx.tcx.is_diagnostic_item(diag_item, trait_did);
+ }
+ false
+}
+
+/// Checks if the method call given in `expr` belongs to the given trait.
+pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ cx.typeck_results()
+ .type_dependent_def_id(expr.hir_id)
+ .map_or(false, |did| is_diag_trait_item(cx, did, diag_item))
+}
+
+/// Checks if the given expression is a path referring an item on the trait
+/// that is marked with the given diagnostic item.
+///
+/// For checking method call expressions instead of path expressions, use
+/// [`is_trait_method`].
+///
+/// For example, this can be used to find if an expression like `u64::default`
+/// refers to an item of the trait `Default`, which is associated with the
+/// `diag_item` of `sym::Default`.
+pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ if let hir::ExprKind::Path(ref qpath) = expr.kind {
+ cx.qpath_res(qpath, expr.hir_id)
+ .opt_def_id()
+ .map_or(false, |def_id| is_diag_trait_item(cx, def_id, diag_item))
+ } else {
+ false
+ }
+}
+
+pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
+ match *path {
+ QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"),
+ QPath::TypeRelative(_, seg) => seg,
+ QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"),
+ }
+}
+
+pub fn get_qpath_generics(path: &QPath<'tcx>) -> Option<&'tcx GenericArgs<'tcx>> {
+ match path {
+ QPath::Resolved(_, p) => p.segments.last().and_then(|s| s.args),
+ QPath::TypeRelative(_, s) => s.args,
+ QPath::LangItem(..) => None,
+ }
+}
+
+pub fn get_qpath_generic_tys(path: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
+ get_qpath_generics(path)
+ .map_or([].as_ref(), |a| a.args)
+ .iter()
+ .filter_map(|a| {
+ if let hir::GenericArg::Type(ty) = a {
+ Some(ty)
+ } else {
+ None
+ }
+ })
+}
+
+pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
+ match *path {
+ QPath::Resolved(_, path) => path.segments.get(0),
+ QPath::TypeRelative(_, seg) => Some(seg),
+ QPath::LangItem(..) => None,
+ }
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// 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(_, path) => match_path(path, segments),
+ QPath::TypeRelative(ty, 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,
+ },
+ QPath::LangItem(..) => false,
+ }
+}
+
+/// If the expression is a path, resolve it. Otherwise, return `Res::Err`.
+pub fn expr_path_res(cx: &LateContext<'_>, expr: &Expr<'_>) -> Res {
+ if let ExprKind::Path(p) = &expr.kind {
+ cx.qpath_res(p, expr.hir_id)
+ } else {
+ Res::Err
+ }
+}
+
+/// Resolves the path to a `DefId` and checks if it matches the given path.
+pub fn is_qpath_def_path(cx: &LateContext<'_>, path: &QPath<'_>, hir_id: HirId, segments: &[&str]) -> bool {
+ cx.qpath_res(path, hir_id)
+ .opt_def_id()
+ .map_or(false, |id| match_def_path(cx, id, segments))
+}
+
+/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
+///
+/// Please use `is_expr_diagnostic_item` if the target is a diagnostic item.
+pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
+ expr_path_res(cx, expr)
+ .opt_def_id()
+ .map_or(false, |id| match_def_path(cx, id, segments))
+}
+
+/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given
+/// diagnostic item.
+pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
+ expr_path_res(cx, expr)
+ .opt_def_id()
+ .map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
+}
+
+/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
+/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
+/// `QPath::Resolved.1.res.opt_def_id()`.
+///
+/// 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)
+}
+
+/// If the expression is a path to a local, returns the canonical `HirId` of the local.
+pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
+ if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind {
+ if let Res::Local(id) = path.res {
+ return Some(id);
+ }
+ }
+ None
+}
+
+/// Returns true if the expression is a path to a local with the specified `HirId`.
+/// Use this function to see if an expression matches a function argument or a match binding.
+pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
+ path_to_local(expr) == Some(id)
+}
+
+/// Gets the definition associated to a path.
+pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
+ macro_rules! try_res {
+ ($e:expr) => {
+ match $e {
+ Some(e) => e,
+ None => return Res::Err,
+ }
+ };
+ }
+ fn item_child_by_name<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<&'tcx Export> {
+ tcx.item_children(def_id)
+ .iter()
+ .find(|item| item.ident.name.as_str() == name)
+ }
+
+ let (krate, first, path) = match *path {
+ [krate, first, ref path @ ..] => (krate, first, path),
+ [primitive] => {
+ return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy);
+ },
+ _ => return Res::Err,
+ };
+ let tcx = cx.tcx;
+ let crates = tcx.crates(());
+ let krate = try_res!(crates.iter().find(|&&num| tcx.crate_name(num).as_str() == krate));
+ let first = try_res!(item_child_by_name(tcx, krate.as_def_id(), first));
+ let last = path
+ .iter()
+ .copied()
+ // `get_def_path` seems to generate these empty segments for extern blocks.
+ // We can just ignore them.
+ .filter(|segment| !segment.is_empty())
+ // for each segment, find the child item
+ .try_fold(first, |item, segment| {
+ let def_id = item.res.def_id();
+ if let Some(item) = item_child_by_name(tcx, def_id, segment) {
+ Some(item)
+ } else if matches!(item.res, Res::Def(DefKind::Enum | DefKind::Struct, _)) {
+ // it is not a child item so check inherent impl items
+ tcx.inherent_impls(def_id)
+ .iter()
+ .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment))
+ } else {
+ None
+ }
+ });
+ try_res!(last).res.expect_non_local()
+}
+
+/// 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> {
+ match path_to_res(cx, path) {
+ Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
+ _ => None,
+ }
+}
+
+/// 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(impl_) = &item.kind;
+ then { return impl_.of_trait.as_ref(); }
+ }
+ None
+}
+
+/// This method will return tuple of projection stack and root of the expression,
+/// used in `can_mut_borrow_both`.
+///
+/// For example, if `e` represents the `v[0].a.b[x]`
+/// this method will return a tuple, composed of a `Vec`
+/// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]`
+/// and an `Expr` for root of them, `v`
+fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) {
+ let mut result = vec![];
+ let root = loop {
+ match e.kind {
+ ExprKind::Index(ep, _) | ExprKind::Field(ep, _) => {
+ result.push(e);
+ e = ep;
+ },
+ _ => break e,
+ };
+ };
+ result.reverse();
+ (result, root)
+}
+
+/// Checks if two expressions can be mutably borrowed simultaneously
+/// and they aren't dependent on borrowing same thing twice
+pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool {
+ let (s1, r1) = projection_stack(e1);
+ let (s2, r2) = projection_stack(e2);
+ if !eq_expr_value(cx, r1, r2) {
+ return true;
+ }
+ for (x1, x2) in s1.iter().zip(s2.iter()) {
+ match (&x1.kind, &x2.kind) {
+ (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => {
+ if i1 != i2 {
+ return true;
+ }
+ },
+ (ExprKind::Index(_, i1), ExprKind::Index(_, i2)) => {
+ if !eq_expr_value(cx, i1, i2) {
+ return false;
+ }
+ },
+ _ => return false,
+ }
+ }
+ false
+}
+
+/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent"
+/// constructor from the std library
+fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
+ let std_types_symbols = &[
+ sym::String,
+ sym::Vec,
+ sym::VecDeque,
+ sym::LinkedList,
+ sym::HashMap,
+ sym::BTreeMap,
+ sym::HashSet,
+ sym::BTreeSet,
+ sym::BinaryHeap,
+ ];
+
+ if let QPath::TypeRelative(_, method) = path {
+ if method.ident.name == sym::new {
+ if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
+ if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
+ return std_types_symbols
+ .iter()
+ .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
+ }
+ }
+ }
+ }
+ false
+}
+
+/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated.
+/// It doesn't cover all cases, for example indirect function calls (some of std
+/// functions are supported) but it is the best we have.
+pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Bool(false) | LitKind::Int(0, _) => true,
+ LitKind::Str(s, _) => s.is_empty(),
+ _ => false,
+ },
+ ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
+ ExprKind::Repeat(x, y) => if_chain! {
+ if let ExprKind::Lit(ref const_lit) = cx.tcx.hir().body(y.body).value.kind;
+ if let LitKind::Int(v, _) = const_lit.node;
+ if v <= 32 && is_default_equivalent(cx, x);
+ then {
+ true
+ }
+ else {
+ false
+ }
+ },
+ ExprKind::Call(repl_func, _) => if_chain! {
+ if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
+ if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
+ if is_diag_trait_item(cx, repl_def_id, sym::Default)
+ || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
+ then {
+ true
+ }
+ else {
+ false
+ }
+ },
+ ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone),
+ ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
+ _ => false,
+ }
+}
+
+/// Checks if the top level expression can be moved into a closure as is.
+/// Currently checks for:
+/// * Break/Continue outside the given loop HIR ids.
+/// * Yield/Return statements.
+/// * Inline assembly.
+/// * Usages of a field of a local where the type of the local can be partially moved.
+///
+/// For example, given the following function:
+///
+/// ```
+/// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) {
+/// for item in iter {
+/// let s = item.1;
+/// if item.0 > 10 {
+/// continue;
+/// } else {
+/// s.clear();
+/// }
+/// }
+/// }
+/// ```
+///
+/// When called on the expression `item.0` this will return false unless the local `item` is in the
+/// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it
+/// isn't always safe to move into a closure when only a single field is needed.
+///
+/// When called on the `continue` expression this will return false unless the outer loop expression
+/// is in the `loop_ids` set.
+///
+/// Note that this check is not recursive, so passing the `if` expression will always return true
+/// even though sub-expressions might return false.
+pub fn can_move_expr_to_closure_no_visit(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ loop_ids: &[HirId],
+ ignore_locals: &HirIdSet,
+) -> bool {
+ match expr.kind {
+ ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
+ | ExprKind::Continue(Destination { target_id: Ok(id), .. })
+ if loop_ids.contains(&id) =>
+ {
+ true
+ },
+ ExprKind::Break(..)
+ | ExprKind::Continue(_)
+ | ExprKind::Ret(_)
+ | ExprKind::Yield(..)
+ | ExprKind::InlineAsm(_)
+ | ExprKind::LlvmInlineAsm(_) => false,
+ // Accessing a field of a local value can only be done if the type isn't
+ // partially moved.
+ ExprKind::Field(
+ &Expr {
+ hir_id,
+ kind:
+ ExprKind::Path(QPath::Resolved(
+ _,
+ Path {
+ res: Res::Local(local_id),
+ ..
+ },
+ )),
+ ..
+ },
+ _,
+ ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
+ // TODO: check if the local has been partially moved. Assume it has for now.
+ false
+ },
+ _ => true,
+ }
+}
+
+/// How a local is captured by a closure
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum CaptureKind {
+ Value,
+ Ref(Mutability),
+}
+impl CaptureKind {
+ pub fn is_imm_ref(self) -> bool {
+ self == Self::Ref(Mutability::Not)
+ }
+}
+impl std::ops::BitOr for CaptureKind {
+ type Output = Self;
+ fn bitor(self, rhs: Self) -> Self::Output {
+ match (self, rhs) {
+ (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
+ (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
+ | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
+ (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
+ }
+ }
+}
+impl std::ops::BitOrAssign for CaptureKind {
+ fn bitor_assign(&mut self, rhs: Self) {
+ *self = *self | rhs;
+ }
+}
+
+/// Given an expression referencing a local, determines how it would be captured in a closure.
+/// Note as this will walk up to parent expressions until the capture can be determined it should
+/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
+/// function argument (other than a receiver).
+pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
+ fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
+ let mut capture = CaptureKind::Ref(Mutability::Not);
+ pat.each_binding_or_first(&mut |_, id, span, _| match cx
+ .typeck_results()
+ .extract_binding_mode(cx.sess(), id, span)
+ .unwrap()
+ {
+ BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => {
+ capture = CaptureKind::Value;
+ },
+ BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => {
+ capture = CaptureKind::Ref(Mutability::Mut);
+ },
+ _ => (),
+ });
+ capture
+ }
+
+ debug_assert!(matches!(
+ e.kind,
+ ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
+ ));
+
+ let mut child_id = e.hir_id;
+ let mut capture = CaptureKind::Value;
+ let mut capture_expr_ty = e;
+
+ for (parent_id, parent) in cx.tcx.hir().parent_iter(e.hir_id) {
+ if let [
+ Adjustment {
+ kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
+ target,
+ },
+ ref adjust @ ..,
+ ] = *cx
+ .typeck_results()
+ .adjustments()
+ .get(child_id)
+ .map_or(&[][..], |x| &**x)
+ {
+ if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) =
+ *adjust.last().map_or(target, |a| a.target).kind()
+ {
+ return CaptureKind::Ref(mutability);
+ }
+ }
+
+ match parent {
+ Node::Expr(e) => match e.kind {
+ ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
+ ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
+ ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
+ return CaptureKind::Ref(Mutability::Mut);
+ },
+ ExprKind::Field(..) => {
+ if capture == CaptureKind::Value {
+ capture_expr_ty = e;
+ }
+ },
+ ExprKind::Let(pat, ..) => {
+ let mutability = match pat_capture_kind(cx, pat) {
+ CaptureKind::Value => Mutability::Not,
+ CaptureKind::Ref(m) => m,
+ };
+ return CaptureKind::Ref(mutability);
+ },
+ ExprKind::Match(_, arms, _) => {
+ let mut mutability = Mutability::Not;
+ for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
+ match capture {
+ CaptureKind::Value => break,
+ CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
+ CaptureKind::Ref(Mutability::Not) => (),
+ }
+ }
+ return CaptureKind::Ref(mutability);
+ },
+ _ => break,
+ },
+ Node::Local(l) => match pat_capture_kind(cx, l.pat) {
+ CaptureKind::Value => break,
+ capture @ CaptureKind::Ref(_) => return capture,
+ },
+ _ => break,
+ }
+
+ child_id = parent_id;
+ }
+
+ if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
+ // Copy types are never automatically captured by value.
+ CaptureKind::Ref(Mutability::Not)
+ } else {
+ capture
+ }
+}
+
+/// Checks if the expression can be moved into a closure as is. This will return a list of captures
+/// if so, otherwise, `None`.
+pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
+ struct V<'cx, 'tcx> {
+ cx: &'cx LateContext<'tcx>,
+ // Stack of potential break targets contained in the expression.
+ loops: Vec<HirId>,
+ /// Local variables created in the expression. These don't need to be captured.
+ locals: HirIdSet,
+ /// Whether this expression can be turned into a closure.
+ allow_closure: bool,
+ /// Locals which need to be captured, and whether they need to be by value, reference, or
+ /// mutable reference.
+ captures: HirIdMap<CaptureKind>,
+ }
+ impl Visitor<'tcx> for V<'_, 'tcx> {
+ type Map = ErasedMap<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::None
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+ if !self.allow_closure {
+ return;
+ }
+
+ match e.kind {
+ ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
+ if !self.locals.contains(&l) {
+ let cap = capture_local_usage(self.cx, e);
+ self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
+ }
+ },
+ ExprKind::Closure(..) => {
+ let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id).to_def_id();
+ for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) {
+ let local_id = match capture.place.base {
+ PlaceBase::Local(id) => id,
+ PlaceBase::Upvar(var) => var.var_path.hir_id,
+ _ => continue,
+ };
+ if !self.locals.contains(&local_id) {
+ let capture = match capture.info.capture_kind {
+ UpvarCapture::ByValue(_) => CaptureKind::Value,
+ UpvarCapture::ByRef(borrow) => match borrow.kind {
+ BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not),
+ BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => {
+ CaptureKind::Ref(Mutability::Mut)
+ },
+ },
+ };
+ self.captures
+ .entry(local_id)
+ .and_modify(|e| *e |= capture)
+ .or_insert(capture);
+ }
+ }
+ },
+ ExprKind::Loop(b, ..) => {
+ self.loops.push(e.hir_id);
+ self.visit_block(b);
+ self.loops.pop();
+ },
+ _ => {
+ self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
+ walk_expr(self, e);
+ },
+ }
+ }
+
+ fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
+ p.each_binding_or_first(&mut |_, id, _, _| {
+ self.locals.insert(id);
+ });
+ }
+ }
+
+ let mut v = V {
+ cx,
+ allow_closure: true,
+ loops: Vec::new(),
+ locals: HirIdSet::default(),
+ captures: HirIdMap::default(),
+ };
+ v.visit_expr(expr);
+ v.allow_closure.then(|| v.captures)
+}
+
+/// 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(path, _, 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(())
+ .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id)
+}
+
+/// Returns `true` if the expression is in the program's `#[panic_handler]`.
+pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
+ let parent = cx.tcx.hir().get_parent_item(e.hir_id);
+ let def_id = cx.tcx.hir().local_def_id(parent).to_def_id();
+ Some(def_id) == cx.tcx.lang_items().panic_impl()
+}
+
+/// Gets the name of the item the expression is in, if available.
+pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
+ 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,
+ }
+}
+
+pub struct ContainsName {
+ pub name: Symbol,
+ pub result: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for ContainsName {
+ type Map = Map<'tcx>;
+
+ fn visit_name(&mut self, _: Span, name: Symbol) {
+ 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: Symbol, expr: &Expr<'_>) -> bool {
+ let mut cn = ContainsName { name, result: false };
+ cn.visit_expr(expr);
+ cn.result
+}
+
+/// Returns `true` if `expr` contains a return expression
+pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
- self.found = true;
- } else {
- hir::intravisit::walk_expr(self, expr);
++ let mut found = false;
++ expr_visitor_no_bodies(|expr| {
++ if !found {
+ if let hir::ExprKind::Ret(..) = &expr.kind {
-
- fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
- hir::intravisit::NestedVisitorMap::None
- }
- }
-
- let mut visitor = RetCallFinder { found: false };
- visitor.visit_expr(expr);
- visitor.found
- }
-
- struct FindMacroCalls<'a, 'b> {
- names: &'a [&'b str],
- result: Vec<Span>,
- }
-
- impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
- self.result.push(expr.span);
- }
- // and check sub-expressions
- intravisit::walk_expr(self, expr);
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
++ found = true;
+ }
+ }
- let mut fmc = FindMacroCalls {
- names,
- result: Vec::new(),
- };
- fmc.visit_expr(&body.value);
- fmc.result
++ !found
++ })
++ .visit_expr(expr);
++ found
+}
+
+/// Finds calls of the specified macros in a function body.
+pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
- attrs.iter().any(|attr| attr.has_name(sym::automatically_derived))
++ let mut result = Vec::new();
++ expr_visitor_no_bodies(|expr| {
++ if names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
++ result.push(expr.span);
++ }
++ true
++ })
++ .visit_expr(&body.value);
++ result
+}
+
+/// 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.with_lo(line_start)
+}
+
+/// Gets the parent node, if any.
+pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> {
+ tcx.hir().parent_iter(id).next().map(|(_, node)| node)
+}
+
+/// Gets the parent expression, if any –- this is useful to constrain a lint.
+pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ get_parent_expr_for_hir(cx, e.hir_id)
+}
+
+/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for
+/// constraint lints
+pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> {
+ match get_parent_node(cx.tcx, hir_id) {
+ Some(Node::Expr(parent)) => Some(parent),
+ _ => 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));
+ 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(block, _) => Some(block),
+ _ => None,
+ },
+ _ => None,
+ })
+}
+
+/// Gets the loop or closure enclosing the given expression, if any.
+pub fn get_enclosing_loop_or_closure(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ for (_, node) in tcx.hir().parent_iter(expr.hir_id) {
+ match node {
+ Node::Expr(
+ e @ Expr {
+ kind: ExprKind::Loop(..) | ExprKind::Closure(..),
+ ..
+ },
+ ) => return Some(e),
+ Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
+ _ => break,
+ }
+ }
+ None
+}
+
+/// Gets the parent node if it's an impl block.
+pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
+ match tcx.hir().parent_iter(id).next() {
+ Some((
+ _,
+ Node::Item(Item {
+ kind: ItemKind::Impl(imp),
+ ..
+ }),
+ )) => Some(imp),
+ _ => None,
+ }
+}
+
+/// Checks if the given expression is the else clause of either an `if` or `if let` expression.
+pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ let mut iter = tcx.hir().parent_iter(expr.hir_id);
+ match iter.next() {
+ Some((
+ _,
+ Node::Expr(Expr {
+ kind: ExprKind::If(_, _, Some(else_expr)),
+ ..
+ }),
+ )) => else_expr.hir_id == expr.hir_id,
+ _ => false,
+ }
+}
+
+/// 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 enclosing_body = cx.tcx.hir().local_def_id(cx.tcx.hir().enclosing_body_owner(e.hir_id));
+ if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
+ return value == v;
+ }
+ 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.typeck_results().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
+/// # macro_rules! foo { ($e:tt) => { $e } }; macro_rules! bar { ($e:expr) => { $e } }
+/// foo!(bar!(42));
+/// ```
+/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
+/// from `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)
+}
+
+/// 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(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/thir 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: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool {
+ i.into_iter().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(pat) | PatKind::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(pats) => {
+ // TODO: should be the honest check, that pats is exhaustive set
+ are_refutable(cx, pats)
+ },
+ PatKind::Tuple(pats, _) => are_refutable(cx, pats),
+ PatKind::Struct(ref qpath, fields, _) => {
+ is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat))
+ },
+ PatKind::TupleStruct(ref qpath, pats, _) => is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats),
+ PatKind::Slice(head, middle, tail) => {
+ match &cx.typeck_results().node_type(pat.hir_id).kind() {
+ rustc_ty::Slice(..) => {
+ // [..] is the only irrefutable slice pattern.
+ !head.is_empty() || middle.is_none() || !tail.is_empty()
+ },
+ rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())),
+ _ => {
+ // unreachable!()
+ true
+ },
+ }
+ },
+ }
+}
+
+/// If the pattern is an `or` pattern, call the function once for each sub pattern. Otherwise, call
+/// the function once on the given pattern.
+pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
+ if let PatKind::Or(pats) = pat.kind {
+ pats.iter().for_each(f);
+ } else {
+ f(pat);
+ }
+}
+
+/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d
+/// implementations have.
+pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool {
- pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
++ has_attr(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(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 let TyKind::Path(QPath::Resolved(None, path)) = slf.kind {
+ if let Res::SelfTy(..) = path.res {
+ 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>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
+ fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if_chain! {
+ if let PatKind::TupleStruct(ref path, pat, None) = arm.pat.kind;
+ if is_lang_ctor(cx, path, ResultOk);
+ if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
+ if path_to_local_id(arm.body, hir_id);
+ then {
+ return true;
+ }
+ }
+ false
+ }
+
+ fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
+ if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
+ is_lang_ctor(cx, path, ResultErr)
+ } else {
+ false
+ }
+ }
+
+ if let ExprKind::Match(_, arms, ref source) = expr.kind {
+ // desugared from a `?` operator
+ if *source == MatchSource::TryDesugar {
+ return Some(expr);
+ }
+
+ if_chain! {
+ if arms.len() == 2;
+ if arms[0].guard.is_none();
+ if arms[1].guard.is_none();
+ if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) ||
+ (is_ok(cx, &arms[1]) && is_err(cx, &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_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool {
+ cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow
+}
+
+pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> {
+ while let PatKind::Ref(subpat, _) = pat.kind {
+ pat = subpat;
+ }
+ pat
+}
+
+pub fn int_bits(tcx: TyCtxt<'_>, ity: rustc_ty::IntTy) -> u64 {
+ Integer::from_int_ty(&tcx, 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: rustc_ty::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: rustc_ty::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: rustc_ty::UintTy) -> u128 {
+ let bits = Integer::from_uint_ty(&tcx, ity).size().bits();
+ let amt = 128 - bits;
+ (u << amt) >> amt
+}
+
- if is_automatically_derived(map.attrs(enclosing_node)) {
++pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool {
++ attrs.iter().any(|attr| attr.has_name(symbol))
++}
++
++pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool {
+ let map = &tcx.hir();
+ let mut prev_enclosing_node = None;
+ let mut enclosing_node = node;
+ while Some(enclosing_node) != prev_enclosing_node {
++ if has_attr(map.attrs(enclosing_node), symbol) {
+ return true;
+ }
+ prev_enclosing_node = Some(enclosing_node);
+ enclosing_node = map.get_parent_item(enclosing_node);
+ }
++
+ false
+}
+
++pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool {
++ any_parent_has_attr(tcx, node, sym::automatically_derived)
++}
++
+/// Matches a function call with the given path and returns the arguments.
+///
+/// Usage:
+///
+/// ```rust,ignore
+/// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX);
+/// ```
+pub fn match_function_call<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'_>,
+ path: &[&str],
+) -> Option<&'tcx [Expr<'tcx>]> {
+ if_chain! {
+ if let ExprKind::Call(fun, 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 the given `DefId` matches any of the paths. Returns the index of matching path, if
+/// any.
+///
+/// Please use `match_any_diagnostic_items` if the targets are all diagnostic items.
+pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
+ let search_path = cx.get_def_path(did);
+ paths
+ .iter()
+ .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
+}
+
+/// Checks if the given `DefId` matches any of provided diagnostic items. Returns the index of
+/// matching path, if any.
+pub fn match_any_diagnostic_items(cx: &LateContext<'_>, def_id: DefId, diag_items: &[Symbol]) -> Option<usize> {
+ diag_items
+ .iter()
+ .position(|item| cx.tcx.is_diagnostic_item(*item, def_id))
+}
+
+/// Checks if the given `DefId` matches the path.
+pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
+ // We should probably move to Symbols in Clippy as well rather than interning every time.
+ let path = cx.get_def_path(did);
+ syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
+}
+
++/// Checks if the given `DefId` matches the `libc` item.
++pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool {
++ let path = cx.get_def_path(did);
++ // libc is meant to be used as a flat list of names, but they're all actually defined in different
++ // modules based on the target platform. Ignore everything but crate name and the item name.
++ path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name)
++}
++
+pub fn match_panic_call(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Call(func, [arg]) = expr.kind {
+ expr_path_res(cx, func)
+ .opt_def_id()
+ .map_or(false, |id| match_panic_def_id(cx, id))
+ .then(|| arg)
+ } else {
+ None
+ }
+}
+
+pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool {
+ match_any_def_paths(
+ cx,
+ did,
+ &[
+ &paths::BEGIN_PANIC,
+ &paths::PANIC_ANY,
+ &paths::PANICKING_PANIC,
+ &paths::PANICKING_PANIC_FMT,
+ &paths::PANICKING_PANIC_STR,
+ ],
+ )
+ .is_some()
+}
+
+/// 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>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) {
+ let mut conds = Vec::new();
+ let mut blocks: Vec<&Block<'_>> = Vec::new();
+
+ while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
+ conds.push(&*cond);
+ if let ExprKind::Block(block, _) = then.kind {
+ blocks.push(block);
+ } else {
+ panic!("ExprKind::If node is not an ExprKind::Block");
+ }
+
+ if let Some(else_expr) = r#else {
+ expr = else_expr;
+ } else {
+ break;
+ }
+ }
+
+ // final `else {..}`
+ if !blocks.is_empty() {
+ if let ExprKind::Block(block, _) = expr.kind {
+ blocks.push(block);
+ }
+ }
+
+ (conds, blocks)
+}
+
+/// Checks if the given function kind is an async function.
+pub fn is_async_fn(kind: FnKind<'_>) -> bool {
+ matches!(kind, FnKind::ItemFn(_, _, header, _) if header.asyncness == IsAsync::Async)
+}
+
+/// Peels away all the compiler generated code surrounding the body of an async function,
+pub fn get_async_fn_body(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Call(
+ _,
+ &[
+ Expr {
+ kind: ExprKind::Closure(_, _, body, _, _),
+ ..
+ },
+ ],
+ ) = body.value.kind
+ {
+ if let ExprKind::Block(
+ Block {
+ stmts: [],
+ expr:
+ Some(Expr {
+ kind: ExprKind::DropTemps(expr),
+ ..
+ }),
+ ..
+ },
+ _,
+ ) = tcx.hir().body(body).value.kind
+ {
+ return Some(expr);
+ }
+ };
+ None
+}
+
+// Finds the `#[must_use]` attribute, if any
+pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
+ attrs.iter().find(|a| a.has_name(sym::must_use))
+}
+
+// check if expr is calling method or function with #[must_use] attribute
+pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ let did = match expr.kind {
+ ExprKind::Call(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.typeck_results().type_dependent_def_id(expr.hir_id),
+ _ => None,
+ };
+
+ did.map_or(false, |did| must_use_attr(cx.tcx.get_attrs(did)).is_some())
+}
+
+/// Checks if an expression represents the identity function
+/// Only examines closures and `std::convert::identity`
+pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ /// Checks if a function's body represents the identity function. Looks for bodies of the form:
+ /// * `|x| x`
+ /// * `|x| return x`
+ /// * `|x| { return x }`
+ /// * `|x| { return x; }`
+ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
+ let id = if_chain! {
+ if let [param] = func.params;
+ if let PatKind::Binding(_, id, _, _) = param.pat.kind;
+ then {
+ id
+ } else {
+ return false;
+ }
+ };
+
+ let mut expr = &func.value;
+ loop {
+ match expr.kind {
+ #[rustfmt::skip]
+ ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, )
+ | ExprKind::Ret(Some(e)) => expr = e,
+ #[rustfmt::skip]
+ ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => {
+ if_chain! {
+ if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
+ if let ExprKind::Ret(Some(ret_val)) = e.kind;
+ then {
+ expr = ret_val;
+ } else {
+ return false;
+ }
+ }
+ },
+ _ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
+ }
+ }
+ }
+
+ match expr.kind {
+ ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)),
+ ExprKind::Path(ref path) => is_qpath_def_path(cx, path, expr.hir_id, &paths::CONVERT_IDENTITY),
+ _ => false,
+ }
+}
+
+/// Gets the node where an expression is either used, or it's type is unified with another branch.
+pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
+ let mut child_id = expr.hir_id;
+ let mut iter = tcx.hir().parent_iter(child_id);
+ loop {
+ match iter.next() {
+ None => break None,
+ Some((id, Node::Block(_))) => child_id = id,
+ Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
+ Some((_, Node::Expr(expr))) => match expr.kind {
+ ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
+ ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
+ ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
+ _ => break Some(Node::Expr(expr)),
+ },
+ Some((_, node)) => break Some(node),
+ }
+ }
+}
+
+/// Checks if the result of an expression is used, or it's type is unified with another branch.
+pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ !matches!(
+ get_expr_use_or_unification_node(tcx, expr),
+ None | Some(Node::Stmt(Stmt {
+ kind: StmtKind::Expr(_)
+ | StmtKind::Semi(_)
+ | StmtKind::Local(Local {
+ pat: Pat {
+ kind: PatKind::Wild,
+ ..
+ },
+ ..
+ }),
+ ..
+ }))
+ )
+}
+
+/// Checks if the expression is the final expression returned from a block.
+pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
+ matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
+}
+
++pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
++ if !is_no_std_crate(cx) {
++ Some("std")
++ } else if !is_no_core_crate(cx) {
++ Some("core")
++ } else {
++ None
++ }
++}
++
+pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
+ cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
+ if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
+ attr.path == sym::no_std
+ } else {
+ false
+ }
+ })
+}
+
++pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
++ cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
++ if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
++ attr.path == sym::no_core
++ } 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(hir::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(cx.tcx) { Some(*p) } else { None });
+ traits::impossible_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.typeck_results().type_dependent_def_id(expr.hir_id),
+ ExprKind::Call(
+ Expr {
+ kind: ExprKind::Path(qpath),
+ hir_id: path_hir_id,
+ ..
+ },
+ ..,
+ ) => cx.typeck_results().qpath_res(qpath, *path_hir_id).opt_def_id(),
+ _ => None,
+ }
+}
+
+/// Returns Option<String> where String is a textual representation of the type encapsulated in the
+/// slice iff the given expression is a slice of primitives (as defined in the
+/// `is_recursively_primitive_type` function) and None otherwise.
+pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
+ let expr_type = cx.typeck_results().expr_ty_adjusted(expr);
+ let expr_kind = expr_type.kind();
+ let is_primitive = match expr_kind {
+ rustc_ty::Slice(element_type) => is_recursively_primitive_type(element_type),
+ rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => {
+ if let rustc_ty::Slice(element_type) = inner_ty.kind() {
+ is_recursively_primitive_type(element_type)
+ } else {
+ unreachable!()
+ }
+ },
+ _ => false,
+ };
+
+ if is_primitive {
+ // if we have wrappers like Array, Slice or Tuple, print these
+ // and get the type enclosed in the slice ref
+ match expr_type.peel_refs().walk(cx.tcx).nth(1).unwrap().expect_ty().kind() {
+ rustc_ty::Slice(..) => return Some("slice".into()),
+ rustc_ty::Array(..) => return Some("array".into()),
+ rustc_ty::Tuple(..) => return Some("tuple".into()),
+ _ => {
+ // is_recursively_primitive_type() should have taken care
+ // of the rest and we can rely on the type that is found
+ let refs_peeled = expr_type.peel_refs();
+ return Some(refs_peeled.walk(cx.tcx).last().unwrap().to_string());
+ },
+ }
+ }
+ None
+}
+
+/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)`
+/// `hash` must be comformed with `eq`
+pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)>
+where
+ Hash: Fn(&T) -> u64,
+ Eq: Fn(&T, &T) -> bool,
+{
+ match exprs {
+ [a, b] if eq(a, b) => return vec![(a, b)],
+ _ if exprs.len() <= 2 => return vec![],
+ _ => {},
+ }
+
+ let mut match_expr_list: Vec<(&T, &T)> = Vec::new();
+
+ let mut map: UnhashMap<u64, Vec<&_>> =
+ UnhashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default());
+
+ for expr in exprs {
+ match map.entry(hash(expr)) {
+ Entry::Occupied(mut o) => {
+ for o in o.get() {
+ if eq(o, expr) {
+ match_expr_list.push((o, expr));
+ }
+ }
+ o.get_mut().push(expr);
+ },
+ Entry::Vacant(v) => {
+ v.insert(vec![expr]);
+ },
+ }
+ }
+
+ match_expr_list
+}
+
+/// Peels off all references on the pattern. Returns the underlying pattern and the number of
+/// references removed.
+pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
+ fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) {
+ if let PatKind::Ref(pat, _) = pat.kind {
+ peel(pat, count + 1)
+ } else {
+ (pat, count)
+ }
+ }
+ peel(pat, 0)
+}
+
+/// Peels of expressions while the given closure returns `Some`.
+pub fn peel_hir_expr_while<'tcx>(
+ mut expr: &'tcx Expr<'tcx>,
+ mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
+) -> &'tcx Expr<'tcx> {
+ while let Some(e) = f(expr) {
+ expr = e;
+ }
+ expr
+}
+
+/// Peels off up to the given number of references on the expression. Returns the underlying
+/// expression and the number of references removed.
+pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
+ let mut remaining = count;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
+ remaining -= 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count - remaining)
+}
+
+/// Peels off all references on the expression. Returns the underlying expression and the number of
+/// references removed.
+pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
+ let mut count = 0;
+ let e = peel_hir_expr_while(expr, |e| match e.kind {
+ ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
+ count += 1;
+ Some(e)
+ },
+ _ => None,
+ });
+ (e, count)
+}
+
+/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
+/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
+pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
+ loop {
+ match expr.kind {
+ ExprKind::AddrOf(_, _, e) => expr = e,
+ ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e,
+ _ => break,
+ }
+ }
+ expr
+}
+
+#[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;
+ },
+ }
+ }};
+}
+
+pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ if let Res::Def(_, def_id) = path.res {
+ return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr);
+ }
+ }
+ false
+}
+
+struct VisitConstTestStruct<'tcx> {
+ tcx: TyCtxt<'tcx>,
+ names: Vec<Symbol>,
+ found: bool,
+}
+impl<'hir> ItemLikeVisitor<'hir> for VisitConstTestStruct<'hir> {
+ fn visit_item(&mut self, item: &Item<'_>) {
+ if let ItemKind::Const(ty, _body) = item.kind {
+ if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
+ // We could also check for the type name `test::TestDescAndFn`
+ // and the `#[rustc_test_marker]` attribute?
+ if let Res::Def(DefKind::Struct, _) = path.res {
+ let has_test_marker = self
+ .tcx
+ .hir()
+ .attrs(item.hir_id())
+ .iter()
+ .any(|a| a.has_name(sym::rustc_test_marker));
+ if has_test_marker && self.names.contains(&item.ident.name) {
+ self.found = true;
+ }
+ }
+ }
+ }
+ }
+ fn visit_trait_item(&mut self, _: &TraitItem<'_>) {}
+ fn visit_impl_item(&mut self, _: &ImplItem<'_>) {}
+ fn visit_foreign_item(&mut self, _: &ForeignItem<'_>) {}
+}
+
+/// Checks if the function containing the given `HirId` is a `#[test]` function
+///
+/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
+pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
+ let names: Vec<_> = tcx
+ .hir()
+ .parent_iter(id)
+ // Since you can nest functions we need to collect all until we leave
+ // function scope
+ .filter_map(|(_id, node)| {
+ if let Node::Item(item) = node {
+ if let ItemKind::Fn(_, _, _) = item.kind {
+ return Some(item.ident.name);
+ }
+ }
+ None
+ })
+ .collect();
+ let parent_mod = tcx.parent_module(id);
+ let mut vis = VisitConstTestStruct {
+ tcx,
+ names,
+ found: false,
+ };
+ tcx.hir().visit_item_likes_in_module(parent_mod, &mut vis);
+ vis.found
+}
+
+/// Checks whether item either has `test` attribute applied, or
+/// is a module with `test` in its name.
+///
+/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
+pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
+ is_in_test_function(tcx, item.hir_id())
+ || matches!(item.kind, ItemKind::Mod(..))
+ && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
+}
+
+macro_rules! op_utils {
+ ($($name:ident $assign:ident)*) => {
+ /// Binary operation traits like `LangItem::Add`
+ pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*];
+
+ /// Operator-Assign traits like `LangItem::AddAssign`
+ pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*];
+
+ /// Converts `BinOpKind::Add` to `(LangItem::Add, LangItem::AddAssign)`, for example
+ pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> {
+ match kind {
+ $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)*
+ _ => None,
+ }
+ }
+ };
+}
+
+op_utils! {
+ Add AddAssign
+ Sub SubAssign
+ Mul MulAssign
+ Div DivAssign
+ Rem RemAssign
+ BitXor BitXorAssign
+ BitAnd BitAndAssign
+ BitOr BitOrAssign
+ Shl ShlAssign
+ Shr ShrAssign
+}
--- /dev/null
- 1,42,0 { MATCHES_MACRO }
+use rustc_semver::RustcVersion;
+
+macro_rules! msrv_aliases {
+ ($($major:literal,$minor:literal,$patch:literal {
+ $($name:ident),* $(,)?
+ })*) => {
+ $($(
+ pub const $name: RustcVersion = RustcVersion::new($major, $minor, $patch);
+ )*)*
+ };
+}
+
+// names may refer to stabilized feature flags or library items
+msrv_aliases! {
+ 1,53,0 { OR_PATTERNS }
+ 1,52,0 { STR_SPLIT_ONCE }
+ 1,50,0 { BOOL_THEN }
+ 1,47,0 { TAU }
+ 1,46,0 { CONST_IF_MATCH }
+ 1,45,0 { STR_STRIP_PREFIX }
+ 1,43,0 { LOG2_10, LOG10_2 }
- 1,30,0 { ITERATOR_FIND_MAP }
++ 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS }
+ 1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
+ 1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
+ 1,38,0 { POINTER_CAST }
+ 1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
+ 1,36,0 { ITERATOR_COPIED }
+ 1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
+ 1,34,0 { TRY_FROM }
++ 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
++ 1,28,0 { FROM_BOOL }
+ 1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST }
+ 1,16,0 { STR_REPEAT }
+}
--- /dev/null
- pub const LIBC_STRLEN: [&str; 2] = ["libc", "strlen"];
+//! 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] = ["core", "any", "Any"];
+#[cfg(feature = "metadata-collector-lint")]
+pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
+#[cfg(feature = "metadata-collector-lint")]
+pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
+ ["rustc_lint_defs", "Applicability", "Unspecified"],
+ ["rustc_lint_defs", "Applicability", "HasPlaceholders"],
+ ["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
+ ["rustc_lint_defs", "Applicability", "MachineApplicable"],
+];
+#[cfg(feature = "metadata-collector-lint")]
+pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
+pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const ASSERT_EQ_MACRO: [&str; 3] = ["core", "macros", "assert_eq"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const ASSERT_MACRO: [&str; 4] = ["core", "macros", "builtin", "assert"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const ASSERT_NE_MACRO: [&str; 3] = ["core", "macros", "assert_ne"];
+pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
+pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
+pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
+/// Preferably use the diagnostic item `sym::Borrow` where possible
+pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
++pub const BORROW_MUT_TRAIT: [&str; 3] = ["core", "borrow", "BorrowMut"];
+pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
+pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
+pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
+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_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
+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"];
+/// Preferably use the diagnostic item `sym::deref_method` where possible
+pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
+pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"];
+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 DURATION: [&str; 3] = ["core", "time", "Duration"];
+#[cfg(feature = "internal-lints")]
+pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const EPRINT_MACRO: [&str; 3] = ["std", "macros", "eprint"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const EPRINTLN_MACRO: [&str; 3] = ["std", "macros", "eprintln"];
+pub const EXIT: [&str; 3] = ["std", "process", "exit"];
+pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
+pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
+pub const FILE: [&str; 3] = ["std", "fs", "File"];
+pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const FORMAT_ARGS_MACRO: [&str; 4] = ["core", "macros", "builtin", "format_args"];
+pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
+pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
+pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
+pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"];
+pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
+pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
+pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
+pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
+pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
+#[cfg(feature = "internal-lints")]
+pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
+#[cfg(feature = "internal-lints")]
+pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
+pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
+pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
+pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
+pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
+pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
+pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
+pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
+pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
+#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
+#[cfg(feature = "internal-lints")]
+pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
+#[cfg(feature = "internal-lints")]
+pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
+#[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))]
+pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
+pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
+pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
+pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "ManuallyDrop"];
+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 MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
+pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
+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"];
+/// Preferably use the diagnostic item `sym::Option` where possible
+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_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(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"];
+pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"];
+pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"];
+pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
++pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"];
++pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"];
+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_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 PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
+pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"];
+pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
+pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
+pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const PRINT_MACRO: [&str; 3] = ["std", "macros", "print"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const PRINTLN_MACRO: [&str; 3] = ["std", "macros", "println"];
+pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
+pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
+pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
+pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
+pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
+pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
+pub const PTR_READ: [&str; 3] = ["core", "ptr", "read"];
+pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"];
+pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"];
+pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"];
+pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"];
+pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"];
+pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
+pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"];
+pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"];
+pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
+pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
+pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
+pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
+pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
+#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
+#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
+#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
+#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
+#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
+#[allow(clippy::invalid_paths)] // internal lints do not know about all external crates
+pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
+/// Preferably use the diagnostic item `sym::Result` where possible
+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; 3] = ["serde", "de", "Deserialize"];
+pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
+pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
+pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
+pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
+pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
+pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
+pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
+pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
+pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
+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 STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
+pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
+pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
+pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
+#[cfg(feature = "internal-lints")]
+pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
+#[cfg(feature = "internal-lints")]
+pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
+#[cfg(feature = "internal-lints")]
+pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
+pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
+pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
+pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
+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_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"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const WRITE_MACRO: [&str; 3] = ["core", "macros", "write"];
+#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
+pub const WRITELN_MACRO: [&str; 3] = ["core", "macros", "writeln"];
++pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"];
--- /dev/null
- use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
- use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, PatKind};
+use crate::source::snippet;
++use crate::visitors::expr_visitor_no_bodies;
+use crate::{path_to_local_id, strip_pat_refs};
- use rustc_middle::hir::map::Map;
++use rustc_hir::intravisit::Visitor;
++use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
+use rustc_lint::LateContext;
- let mut visitor = PtrCloneVisitor {
- cx,
- id,
- replace,
- spans: vec![],
- abort: false,
- };
- visitor.visit_body(body);
- if visitor.abort { None } else { Some(visitor.spans) }
- }
-
- struct PtrCloneVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- id: HirId,
- replace: &'a [(&'static str, &'static str)],
- spans: Vec<(Span, Cow<'static, str>)>,
- abort: bool,
- }
-
- impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
- type Map = Map<'tcx>;
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- if self.abort {
- return;
+use rustc_span::Span;
+use std::borrow::Cow;
+
+pub fn get_spans(
+ cx: &LateContext<'_>,
+ opt_body_id: Option<BodyId>,
+ idx: usize,
+ replacements: &[(&'static str, &'static str)],
+) -> Option<Vec<(Span, Cow<'static, str>)>> {
+ if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
+ if let PatKind::Binding(_, binding_id, _, _) = strip_pat_refs(body.params[idx].pat).kind {
+ extract_clone_suggestions(cx, binding_id, replacements, body)
+ } else {
+ Some(vec![])
+ }
+ } else {
+ Some(vec![])
+ }
+}
+
+fn extract_clone_suggestions<'tcx>(
+ cx: &LateContext<'tcx>,
+ id: HirId,
+ replace: &[(&'static str, &'static str)],
+ body: &'tcx Body<'_>,
+) -> Option<Vec<(Span, Cow<'static, str>)>> {
- if path_to_local_id(recv, self.id) {
++ let mut abort = false;
++ let mut spans = Vec::new();
++ expr_visitor_no_bodies(|expr| {
++ if abort {
++ return false;
+ }
+ if let ExprKind::MethodCall(seg, _, [recv], _) = expr.kind {
- self.abort = true;
- return;
++ if path_to_local_id(recv, id) {
+ if seg.ident.name.as_str() == "capacity" {
- for &(fn_name, suffix) in self.replace {
++ abort = true;
++ return false;
+ }
- self.spans.push((expr.span, snippet(self.cx, recv.span, "_") + suffix));
- return;
++ for &(fn_name, suffix) in replace {
+ if seg.ident.name.as_str() == fn_name {
- walk_expr(self, expr);
- }
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
- }
++ spans.push((expr.span, snippet(cx, recv.span, "_") + suffix));
++ return false;
+ }
+ }
+ }
+ }
++ !abort
++ })
++ .visit_body(body);
++ if abort { None } else { Some(spans) }
+}
--- /dev/null
- .skip(ignore_first as usize)
+//! Utils for extracting, inspecting or transforming source code
+
+#![allow(clippy::module_name_repetitions)]
+
+use crate::line_span;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::{LateContext, LintContext};
+use rustc_span::hygiene;
+use rustc_span::{BytePos, Pos, Span, SyntaxContext};
+use std::borrow::Cow;
+
+/// 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))
+ }
+}
+
+/// 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 {
+ 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);
+ snippet_opt(cx, line_span).and_then(|snip| {
+ snip.find(|c: char| !c.is_whitespace())
+ .map(|pos| line_span.lo() + BytePos::from_usize(pos))
+ })
+}
+
+/// 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> {
+ snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
+}
+
+/// Gets a snippet of the indentation of the line of a span
+pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ snippet_opt(cx, line_span(cx, span)).map(|mut s| {
+ let len = s.len() - s.trim_start().len();
+ s.truncate(len);
+ s
+ })
+}
+
+// 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
+}
+
+/// Returns the positon just before rarrow
+///
+/// ```rust,ignore
+/// fn into(self) -> () {}
+/// ^
+/// // in case of unformatted code
+/// fn into2(self)-> () {}
+/// ^
+/// fn into3(self) -> () {}
+/// ^
+/// ```
+pub fn position_before_rarrow(s: &str) -> Option<usize> {
+ s.rfind("->").map(|rpos| {
+ let mut rpos = rpos;
+ let chars: Vec<char> = s.chars().collect();
+ while rpos > 1 {
+ if let Some(c) = chars.get(rpos - 1) {
+ if c.is_whitespace() {
+ rpos -= 1;
+ continue;
+ }
+ }
+ break;
+ }
+ rpos
+ })
+}
+
+/// Reindent a multiline string with possibility of ignoring the first line.
+#[allow(clippy::needless_pass_by_value)]
+pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
+ let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
+ let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
+ reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
+}
+
+fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
+ let x = s
+ .lines()
- /// Converts a span to a code snippet if available, otherwise use default.
++ .skip(usize::from(ignore_first))
+ .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);
+ let indent = indent.unwrap_or(0);
+ s.lines()
+ .enumerate()
+ .map(|(i, l)| {
+ if (ignore_first && i == 0) || l.is_empty() {
+ l.to_owned()
+ } else if x > indent {
+ l.split_at(x - indent).1.to_owned()
+ } else {
+ " ".repeat(indent - x) + l
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n")
+}
+
- /// to convert a given `Span` to a `str`.
++/// Converts a span to a code snippet if available, otherwise returns the default.
+///
+/// This is useful if you want to provide suggestions for your lint or more generally, if you want
- /// snippet(cx, expr.span, "..")
++/// to convert a given `Span` to a `str`. To create suggestions consider using
++/// [`snippet_with_applicability`] to ensure that the applicability stays correct.
+///
+/// # Example
+/// ```rust,ignore
++/// // Given two spans one for `value` and one for the `init` expression.
++/// let value = Vec::new();
++/// // ^^^^^ ^^^^^^^^^^
++/// // span1 span2
++///
++/// // The snipped call would return the corresponding code snippet
++/// snippet(cx, span1, "..") // -> "value"
++/// snippet(cx, span2, "..") // -> "Vec::new()"
+/// ```
+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));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_block`, but adapts the applicability level by the rules of
+/// `snippet_with_applicability`.
+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));
+ reindent_multiline(snip, true, indent)
+}
+
+/// Same as `snippet_with_applicability`, but first walks the span up to the given context. This
+/// will result in the macro call, rather then the expansion, if the span is from a child context.
+/// If the span is not from a child context, it will be used directly instead.
+///
+/// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR node
+/// would result in `box []`. If given the context of the address of expression, this function will
+/// correctly get a snippet of `vec![]`.
+///
+/// This will also return whether or not the snippet is a macro call.
+pub fn snippet_with_context(
+ cx: &LateContext<'_>,
+ span: Span,
+ outer: SyntaxContext,
+ default: &'a str,
+ applicability: &mut Applicability,
+) -> (Cow<'a, str>, bool) {
+ let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
+ || {
+ // The span is from a macro argument, and the outer context is the macro using the argument
+ if *applicability != Applicability::Unspecified {
+ *applicability = Applicability::MaybeIncorrect;
+ }
+ // TODO: get the argument span.
+ (span, false)
+ },
+ |outer_span| (outer_span, span.ctxt() != outer),
+ );
+
+ (
+ snippet_with_applicability(cx, span, default, applicability),
+ is_macro_call,
+ )
+}
+
+/// Walks the span up to the target context, thereby returning the macro call site if the span is
+/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
+/// case of the span being in a macro expansion, but the target context is from expanding a macro
+/// argument.
+///
+/// Given the following
+///
+/// ```rust,ignore
+/// macro_rules! m { ($e:expr) => { f($e) }; }
+/// g(m!(0))
+/// ```
+///
+/// If called with a span of the call to `f` and a context of the call to `g` this will return a
+/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
+/// containing `0` as the context is the same as the outer context.
+///
+/// This will traverse through multiple macro calls. Given the following:
+///
+/// ```rust,ignore
+/// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
+/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
+/// g(m!(0))
+/// ```
+///
+/// If called with a span of the call to `f` and a context of the call to `g` this will return a
+/// span containing `m!(0)`.
+pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
+ let outer_span = hygiene::walk_chain(span, outer);
+ (outer_span.ctxt() == outer).then(|| outer_span)
+}
+
+/// 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
+}
+
+#[cfg(test)]
+mod test {
+ use super::{reindent_multiline, without_block_comments};
+
+ #[test]
+ fn test_reindent_multiline_single_line() {
+ assert_eq!("", reindent_multiline("".into(), false, None));
+ assert_eq!("...", reindent_multiline("...".into(), false, None));
+ assert_eq!("...", reindent_multiline(" ...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t...".into(), false, None));
+ assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_block() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+ } else {
+ z
+ }".into(), false, None));
+ assert_eq!("\
+ if x {
+ \ty
+ } else {
+ \tz
+ }", reindent_multiline(" if x {
+ \ty
+ } else {
+ \tz
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_empty_line() {
+ assert_eq!("\
+ if x {
+ y
+
+ } else {
+ z
+ }", reindent_multiline(" if x {
+ y
+
+ } else {
+ z
+ }".into(), false, None));
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn test_reindent_multiline_lines_deeper() {
+ assert_eq!("\
+ if x {
+ y
+ } else {
+ z
+ }", reindent_multiline("\
+ if x {
+ y
+ } else {
+ z
+ }".into(), true, Some(8)));
+ }
+
+ #[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
- use crate::higher;
- use crate::source::{snippet, snippet_opt, snippet_with_context, snippet_with_macro_callsite};
+//! Contains utility functions to generate suggestions.
+#![deny(clippy::missing_docs_in_private_items)]
+
- use rustc_span::source_map::{CharPos, Span};
- use rustc_span::{BytePos, Pos, SyntaxContext};
++use crate::source::{
++ snippet, snippet_opt, snippet_with_applicability, snippet_with_context, snippet_with_macro_callsite,
++};
++use crate::{get_parent_expr_for_hir, higher};
+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_hir::{ExprKind, HirId, MutTy, TyKind};
++use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::{EarlyContext, LateContext, LintContext};
++use rustc_middle::hir::place::ProjectionKind;
++use rustc_middle::mir::{FakeReadCause, Mutability};
++use rustc_middle::ty;
++use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext};
++use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+use std::borrow::Cow;
+use std::convert::TryInto;
+use std::fmt::Display;
++use std::iter;
+use std::ops::{Add, Neg, Not, Sub};
+
+/// A helper type to build suggestion correctly handling parentheses.
+#[derive(Clone, PartialEq)]
+pub enum Sugg<'a> {
+ /// An expression that never needs parentheses 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 `0`, for convenience.
+pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
+/// Literal constant `1`, for convenience.
+pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
+/// a constant represents an empty string, for convenience.
+pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
+
+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(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(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(expr, snippet)
+ }
+
+ /// Same as `hir`, but first walks the span up to the given context. This will result in the
+ /// macro call, rather then the expansion, if the span is from a child context. If the span is
+ /// not from a child context, it will be used directly instead.
+ ///
+ /// e.g. Given the expression `&vec![]`, getting a snippet from the span for `vec![]` as a HIR
+ /// node would result in `box []`. If given the context of the address of expression, this
+ /// function will correctly get a snippet of `vec![]`.
+ pub fn hir_with_context(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ ctxt: SyntaxContext,
+ default: &'a str,
+ applicability: &mut Applicability,
+ ) -> Self {
+ let (snippet, in_macro) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
+
+ if in_macro {
+ Sugg::NonParen(snippet)
+ } else {
+ Self::hir_from_snippet(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(expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
+ if let Some(range) = higher::Range::hir(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::If(..)
+ | hir::ExprKind::Let(..)
+ | 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::ConstBlock(..)
+ | 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(op.node.into()), 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 = if expr.span.from_expansion() {
+ snippet_with_macro_callsite(cx, expr.span, default)
+ } else {
+ 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::ConstBlock(..)
+ | ast::ExprKind::Lit(..)
+ | ast::ExprKind::Loop(..)
+ | ast::ExprKind::MacCall(..)
+ | ast::ExprKind::MethodCall(..)
+ | ast::ExprKind::Paren(..)
+ | ast::ExprKind::Underscore
+ | 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 parentheses 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 has_enclosing_paren(&sugg) {
+ Sugg::MaybeParen(sugg)
+ } else {
+ Sugg::NonParen(format!("({})", sugg).into())
+ }
+ },
+ Sugg::BinOp(_, sugg) => {
+ if has_enclosing_paren(&sugg) {
+ Sugg::NonParen(sugg)
+ } else {
+ Sugg::NonParen(format!("({})", sugg).into())
+ }
+ },
+ }
+ }
+}
+
+/// Return `true` if `sugg` is enclosed in parenthesis.
+fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
+ let mut chars = sugg.as_ref().chars();
+ if chars.next() == Some('(') {
+ let mut depth = 1;
+ for c in &mut chars {
+ if c == '(' {
+ depth += 1;
+ } else if c == ')' {
+ depth -= 1;
+ }
+ if depth == 0 {
+ break;
+ }
+ }
+ chars.next().is_none()
+ } else {
+ false
+ }
+}
+
+/// Copied from the rust standard library, and then edited
+macro_rules! forward_binop_impls_to_ref {
+ (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
+ impl $imp<$t> for &$t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(self, &other)
+ }
+ }
+
+ impl $imp<&$t> for $t {
+ type Output = $o;
+
+ fn $method(self, other: &$t) -> $o {
+ $imp::$method(&self, other)
+ }
+ }
+
+ impl $imp for $t {
+ type Output = $o;
+
+ fn $method(self, other: $t) -> $o {
+ $imp::$method(&self, &other)
+ }
+ }
+ };
+}
+
+impl Add for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Add, self, rhs)
+ }
+}
+
+impl Sub for &Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
+ make_binop(ast::BinOpKind::Sub, self, rhs)
+ }
+}
+
+forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
+forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
+
+impl Neg for Sugg<'_> {
+ type Output = Sugg<'static>;
+ fn neg(self) -> Sugg<'static> {
+ make_unop("-", self)
+ }
+}
+
+impl Not for Sugg<'_> {
+ 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_shift(op: AssocOp) -> bool {
+ matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
+ }
+
+ /// Returns `true` if the operator is an arithmetic operator
+ /// (i.e., `+`, `-`, `*`, `/`, `%`).
+ fn is_arith(op: AssocOp) -> bool {
+ matches!(
+ op,
+ AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
+ )
+ }
+
+ /// Returns `true` if the operator `op` needs parenthesis with the operator
+ /// `other` in the direction `dir`.
+ fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
+ other.precedence() < op.precedence()
+ || (other.precedence() == op.precedence()
+ && ((op != other && associativity(op) != dir)
+ || (op == other && associativity(op) != Associativity::Both)))
+ || is_shift(op) && is_arith(other)
+ || is_shift(other) && is_arith(op)
+ }
+
+ let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
+ needs_paren(op, lop, Associativity::Left)
+ } else {
+ false
+ };
+
+ 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]
+fn associativity(op: AssocOp) -> Associativity {
+ use rustc_ast::util::parser::AssocOp::{
+ Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
+ GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
+ };
+
+ match op {
+ Assign | AssignOp(_) => Associativity::Right,
+ Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
+ Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
+ | Subtract => Associativity::Left,
+ DotDot | DotDotEq => Associativity::None,
+ }
+}
+
+/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
+fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
+ use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
+
+ AssocOp::AssignOp(match op.node {
+ hir::BinOpKind::Add => Plus,
+ hir::BinOpKind::BitAnd => And,
+ hir::BinOpKind::BitOr => Or,
+ hir::BinOpKind::BitXor => Caret,
+ hir::BinOpKind::Div => Slash,
+ hir::BinOpKind::Mul => Star,
+ hir::BinOpKind::Rem => Percent,
+ hir::BinOpKind::Shl => Shl,
+ hir::BinOpKind::Shr => Shr,
+ hir::BinOpKind::Sub => Minus,
+
+ hir::BinOpKind::And
+ | hir::BinOpKind::Eq
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
+fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
+ use rustc_ast::ast::BinOpKind::{
+ Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
+ };
+ use rustc_ast::token::BinOpToken;
+
+ AssocOp::AssignOp(match op.node {
+ Add => BinOpToken::Plus,
+ BitAnd => BinOpToken::And,
+ BitOr => BinOpToken::Or,
+ BitXor => BinOpToken::Caret,
+ Div => BinOpToken::Slash,
+ Mul => BinOpToken::Star,
+ Rem => BinOpToken::Percent,
+ Shl => BinOpToken::Shl,
+ Shr => BinOpToken::Shr,
+ Sub => BinOpToken::Minus,
+ And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
+ })
+}
+
+/// Returns the indentation before `span` if there are nothing but `[ \t]`
+/// before it on its line.
+fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
+ let lo = cx.sess().source_map().lookup_char_pos(span.lo());
+ 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);
+ }
+}
+
++/// Suggestion results for handling closure
++/// args dereferencing and borrowing
++pub struct DerefClosure {
++ /// confidence on the built suggestion
++ pub applicability: Applicability,
++ /// gradually built suggestion
++ pub suggestion: String,
++}
++
++/// Build suggestion gradually by handling closure arg specific usages,
++/// such as explicit deref and borrowing cases.
++/// Returns `None` if no such use cases have been triggered in closure body
++///
++/// note: this only works on single line immutable closures with exactly one input parameter.
++pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<'_>) -> Option<DerefClosure> {
++ if let hir::ExprKind::Closure(_, fn_decl, body_id, ..) = closure.kind {
++ let closure_body = cx.tcx.hir().body(body_id);
++ // is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`)
++ // a type annotation is present if param `kind` is different from `TyKind::Infer`
++ let closure_arg_is_type_annotated_double_ref = if let TyKind::Rptr(_, MutTy { ty, .. }) = fn_decl.inputs[0].kind
++ {
++ matches!(ty.kind, TyKind::Rptr(_, MutTy { .. }))
++ } else {
++ false
++ };
++
++ let mut visitor = DerefDelegate {
++ cx,
++ closure_span: closure.span,
++ closure_arg_is_type_annotated_double_ref,
++ next_pos: closure.span.lo(),
++ suggestion_start: String::new(),
++ applicability: Applicability::MaybeIncorrect,
++ };
++
++ let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id);
++ cx.tcx.infer_ctxt().enter(|infcx| {
++ ExprUseVisitor::new(&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results())
++ .consume_body(closure_body);
++ });
++
++ if !visitor.suggestion_start.is_empty() {
++ return Some(DerefClosure {
++ applicability: visitor.applicability,
++ suggestion: visitor.finish(),
++ });
++ }
++ }
++ None
++}
++
++/// Visitor struct used for tracking down
++/// dereferencing and borrowing of closure's args
++struct DerefDelegate<'a, 'tcx> {
++ /// The late context of the lint
++ cx: &'a LateContext<'tcx>,
++ /// The span of the input closure to adapt
++ closure_span: Span,
++ /// Indicates if the arg of the closure is a type annotated double reference
++ closure_arg_is_type_annotated_double_ref: bool,
++ /// last position of the span to gradually build the suggestion
++ next_pos: BytePos,
++ /// starting part of the gradually built suggestion
++ suggestion_start: String,
++ /// confidence on the built suggestion
++ applicability: Applicability,
++}
++
++impl DerefDelegate<'_, 'tcx> {
++ /// build final suggestion:
++ /// - create the ending part of suggestion
++ /// - concatenate starting and ending parts
++ /// - potentially remove needless borrowing
++ pub fn finish(&mut self) -> String {
++ let end_span = Span::new(self.next_pos, self.closure_span.hi(), self.closure_span.ctxt(), None);
++ let end_snip = snippet_with_applicability(self.cx, end_span, "..", &mut self.applicability);
++ let sugg = format!("{}{}", self.suggestion_start, end_snip);
++ if self.closure_arg_is_type_annotated_double_ref {
++ sugg.replacen('&', "", 1)
++ } else {
++ sugg
++ }
++ }
++
++ /// indicates whether the function from `parent_expr` takes its args by double reference
++ fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool {
++ let (call_args, inputs) = match parent_expr.kind {
++ ExprKind::MethodCall(_, _, call_args, _) => {
++ if let Some(method_did) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) {
++ (call_args, self.cx.tcx.fn_sig(method_did).skip_binder().inputs())
++ } else {
++ return false;
++ }
++ },
++ ExprKind::Call(func, call_args) => {
++ let typ = self.cx.typeck_results().expr_ty(func);
++ (call_args, typ.fn_sig(self.cx.tcx).skip_binder().inputs())
++ },
++ _ => return false,
++ };
++
++ iter::zip(call_args, inputs)
++ .any(|(arg, ty)| arg.hir_id == cmt_hir_id && matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref()))
++ }
++}
++
++impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
++ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
++
++ #[allow(clippy::too_many_lines)]
++ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
++ if let PlaceBase::Local(id) = cmt.place.base {
++ let map = self.cx.tcx.hir();
++ let span = map.span(cmt.hir_id);
++ let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None);
++ let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
++
++ // identifier referring to the variable currently triggered (i.e.: `fp`)
++ let ident_str = map.name(id).to_string();
++ // full identifier that includes projection (i.e.: `fp.field`)
++ let ident_str_with_proj = snippet(self.cx, span, "..").to_string();
++
++ if cmt.place.projections.is_empty() {
++ // handle item without any projection, that needs an explicit borrowing
++ // i.e.: suggest `&x` instead of `x`
++ self.suggestion_start.push_str(&format!("{}&{}", start_snip, ident_str));
++ } else {
++ // cases where a parent `Call` or `MethodCall` is using the item
++ // i.e.: suggest `.contains(&x)` for `.find(|x| [1, 2, 3].contains(x)).is_none()`
++ //
++ // Note about method calls:
++ // - compiler automatically dereference references if the target type is a reference (works also for
++ // function call)
++ // - `self` arguments in the case of `x.is_something()` are also automatically (de)referenced, and
++ // no projection should be suggested
++ if let Some(parent_expr) = get_parent_expr_for_hir(self.cx, cmt.hir_id) {
++ match &parent_expr.kind {
++ // given expression is the self argument and will be handled completely by the compiler
++ // i.e.: `|x| x.is_something()`
++ ExprKind::MethodCall(_, _, [self_expr, ..], _) if self_expr.hir_id == cmt.hir_id => {
++ self.suggestion_start
++ .push_str(&format!("{}{}", start_snip, ident_str_with_proj));
++ self.next_pos = span.hi();
++ return;
++ },
++ // item is used in a call
++ // i.e.: `Call`: `|x| please(x)` or `MethodCall`: `|x| [1, 2, 3].contains(x)`
++ ExprKind::Call(_, [call_args @ ..]) | ExprKind::MethodCall(_, _, [_, call_args @ ..], _) => {
++ let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id);
++ let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind();
++
++ if matches!(arg_ty_kind, ty::Ref(_, _, Mutability::Not)) {
++ // suggest ampersand if call function is taking args by double reference
++ let takes_arg_by_double_ref =
++ self.func_takes_arg_by_double_ref(parent_expr, cmt.hir_id);
++
++ // compiler will automatically dereference field or index projection, so no need
++ // to suggest ampersand, but full identifier that includes projection is required
++ let has_field_or_index_projection =
++ cmt.place.projections.iter().any(|proj| {
++ matches!(proj.kind, ProjectionKind::Field(..) | ProjectionKind::Index)
++ });
++
++ // no need to bind again if the function doesn't take arg by double ref
++ // and if the item is already a double ref
++ let ident_sugg = if !call_args.is_empty()
++ && !takes_arg_by_double_ref
++ && (self.closure_arg_is_type_annotated_double_ref || has_field_or_index_projection)
++ {
++ let ident = if has_field_or_index_projection {
++ ident_str_with_proj
++ } else {
++ ident_str
++ };
++ format!("{}{}", start_snip, ident)
++ } else {
++ format!("{}&{}", start_snip, ident_str)
++ };
++ self.suggestion_start.push_str(&ident_sugg);
++ self.next_pos = span.hi();
++ return;
++ }
++
++ self.applicability = Applicability::Unspecified;
++ },
++ _ => (),
++ }
++ }
++
++ let mut replacement_str = ident_str;
++ let mut projections_handled = false;
++ cmt.place.projections.iter().enumerate().for_each(|(i, proj)| {
++ match proj.kind {
++ // Field projection like `|v| v.foo`
++ // no adjustment needed here, as field projections are handled by the compiler
++ ProjectionKind::Field(..) => match cmt.place.ty_before_projection(i).kind() {
++ ty::Adt(..) | ty::Tuple(_) => {
++ replacement_str = ident_str_with_proj.clone();
++ projections_handled = true;
++ },
++ _ => (),
++ },
++ // Index projection like `|x| foo[x]`
++ // the index is dropped so we can't get it to build the suggestion,
++ // so the span is set-up again to get more code, using `span.hi()` (i.e.: `foo[x]`)
++ // instead of `span.lo()` (i.e.: `foo`)
++ ProjectionKind::Index => {
++ let start_span = Span::new(self.next_pos, span.hi(), span.ctxt(), None);
++ start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
++ replacement_str.clear();
++ projections_handled = true;
++ },
++ // note: unable to trigger `Subslice` kind in tests
++ ProjectionKind::Subslice => (),
++ ProjectionKind::Deref => {
++ // Explicit derefs are typically handled later on, but
++ // some items do not need explicit deref, such as array accesses,
++ // so we mark them as already processed
++ // i.e.: don't suggest `*sub[1..4].len()` for `|sub| sub[1..4].len() == 3`
++ if let ty::Ref(_, inner, _) = cmt.place.ty_before_projection(i).kind() {
++ if matches!(inner.kind(), ty::Ref(_, innermost, _) if innermost.is_array()) {
++ projections_handled = true;
++ }
++ }
++ },
++ }
++ });
++
++ // handle `ProjectionKind::Deref` by removing one explicit deref
++ // if no special case was detected (i.e.: suggest `*x` instead of `**x`)
++ if !projections_handled {
++ let last_deref = cmt
++ .place
++ .projections
++ .iter()
++ .rposition(|proj| proj.kind == ProjectionKind::Deref);
++
++ if let Some(pos) = last_deref {
++ let mut projections = cmt.place.projections.clone();
++ projections.truncate(pos);
++
++ for item in projections {
++ if item.kind == ProjectionKind::Deref {
++ replacement_str = format!("*{}", replacement_str);
++ }
++ }
++ }
++ }
++
++ self.suggestion_start
++ .push_str(&format!("{}{}", start_snip, replacement_str));
++ }
++ self.next_pos = span.hi();
++ }
++ }
++
++ fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
++
++ fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
++}
++
+#[cfg(test)]
+mod test {
+ use super::Sugg;
+
+ use rustc_ast::util::parser::AssocOp;
+ 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());
+ }
+
+ #[test]
+ fn binop_maybe_par() {
+ let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into());
+ assert_eq!("(1 + 1)", sugg.maybe_par().to_string());
+
+ let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1) + (1 + 1)".into());
+ assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_par().to_string());
+ }
+}
--- /dev/null
- use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, TypeFoldable, UintTy};
- use rustc_span::sym;
- use rustc_span::symbol::{Ident, Symbol};
- use rustc_span::DUMMY_SP;
+//! Util methods for [`rustc_middle::ty`]
+
+#![allow(clippy::module_name_repetitions)]
+
+use rustc_ast::ast::Mutability;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_hir as hir;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{TyKind, Unsafety};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
- /// See also [`get_trait_def_id`](super::get_trait_def_id).
++use rustc_middle::ty::{self, AdtDef, IntTy, Predicate, Ty, TyCtxt, TypeFoldable, UintTy};
++use rustc_span::symbol::Ident;
++use rustc_span::{sym, Span, Symbol, DUMMY_SP};
+use rustc_trait_selection::infer::InferCtxtExt;
+use rustc_trait_selection::traits::query::normalize::AtExt;
++use std::iter;
+
+use crate::{match_def_path, must_use_attr};
+
++// Checks if the given type implements copy.
+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 whether a type can be partially moved.
+pub fn can_partially_move_ty(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if has_drop(cx, ty) || is_copy(cx, ty) {
+ return false;
+ }
+ match ty.kind() {
+ ty::Param(_) => false,
+ ty::Adt(def, subs) => def.all_fields().any(|f| !is_copy(cx, f.ty(cx.tcx, subs))),
+ _ => true,
+ }
+}
+
+/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty`
+pub fn contains_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, other_ty: Ty<'tcx>) -> bool {
+ ty.walk(tcx).any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty),
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+}
+
+/// Walks into `ty` and returns `true` if any inner type is an instance of the given adt
+/// constructor.
+pub fn contains_adt_constructor<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, adt: &'tcx AdtDef) -> bool {
+ ty.walk(tcx).any(|inner| match inner.unpack() {
+ GenericArgKind::Type(inner_ty) => inner_ty.ty_adt_def() == Some(adt),
+ GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
+ })
+}
+
+/// Resolves `<T as Iterator>::Item` for `T`
+/// Do not invoke without first verifying that the type implements `Iterator`
+pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ cx.tcx
+ .get_diagnostic_item(sym::Iterator)
+ .and_then(|iter_did| {
+ cx.tcx.associated_items(iter_did).find_by_name_and_kind(
+ cx.tcx,
+ Ident::from_str("Item"),
+ ty::AssocKind::Type,
+ iter_did,
+ )
+ })
+ .map(|assoc| {
+ let proj = cx.tcx.mk_projection(assoc.def_id, cx.tcx.mk_substs_trait(ty, &[]));
+ cx.tcx.normalize_erasing_regions(cx.param_env, proj)
+ })
+}
+
+/// Returns true if ty has `iter` or `iter_mut` methods
+pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<Symbol> {
+ // 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: &[Symbol] = &[
+ sym::Vec,
+ sym::Option,
+ sym::Result,
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::VecDeque,
+ sym::LinkedList,
+ sym::BinaryHeap,
+ sym::HashSet,
+ sym::HashMap,
+ sym::PathBuf,
+ sym::Path,
+ sym::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(sym::array),
+ ty::Slice(..) => return Some(sym::slice),
+ ty::Adt(adt, _) => adt.did,
+ _ => return None,
+ };
+
+ for &name in into_iter_collections {
+ if cx.tcx.is_diagnostic_item(name, def_id) {
+ return Some(cx.tcx.item_name(def_id));
+ }
+ }
+ None
+}
+
+/// Checks whether a type implements a trait.
+/// The function returns false in case the type contains an inference variable.
- /// Checks if the type is equal to a diagnostic item
++///
++/// See:
++/// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`].
++/// * [Common tools for writing lints] for an example how to use this function and other options.
++///
++/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait
+pub fn implements_trait<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ trait_id: DefId,
+ ty_params: &[GenericArg<'tcx>],
+) -> bool {
+ // Clippy shouldn't have infer types
+ assert!(!ty.needs_infer());
+
+ let ty = cx.tcx.erase_regions(ty);
+ if ty.has_escaping_bound_vars() {
+ return false;
+ }
+ let ty_params = cx.tcx.mk_substs(ty_params.iter());
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ infcx
+ .type_implements_trait(trait_id, ty, ty_params, cx.param_env)
+ .must_apply_modulo_regions()
+ })
+}
+
+/// 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 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(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(ty) | ty::Array(ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::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(substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)),
+ ty::Opaque(ref def_id, _) => {
+ for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) {
+ if let ty::PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() {
+ if must_use_attr(cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ ty::Dynamic(binder, _) => {
+ for predicate in binder.iter() {
+ if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
+ if must_use_attr(cx.tcx.get_attrs(trait_ref.def_id)).is_some() {
+ return true;
+ }
+ }
+ }
+ false
+ },
+ _ => false,
+ }
+}
+
+// FIXME: Per https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.normalize
+// this function can be removed once the `normalize` method does not panic when normalization does
+// not succeed
+/// 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 {
+ is_normalizable_helper(cx, param_env, ty, &mut FxHashMap::default())
+}
+
+fn is_normalizable_helper<'tcx>(
+ cx: &LateContext<'tcx>,
+ param_env: ty::ParamEnv<'tcx>,
+ ty: Ty<'tcx>,
+ cache: &mut FxHashMap<Ty<'tcx>, bool>,
+) -> bool {
+ if let Some(&cached_result) = cache.get(ty) {
+ return cached_result;
+ }
+ // prevent recursive loops, false-negative is better than endless loop leading to stack overflow
+ cache.insert(ty, false);
+ let result = cx.tcx.infer_ctxt().enter(|infcx| {
+ let cause = rustc_middle::traits::ObligationCause::dummy();
+ if infcx.at(&cause, param_env).normalize(ty).is_ok() {
+ match ty.kind() {
+ ty::Adt(def, substs) => def.variants.iter().all(|variant| {
+ variant
+ .fields
+ .iter()
+ .all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, substs), cache))
+ }),
+ _ => ty.walk(cx.tcx).all(|generic_arg| match generic_arg.unpack() {
+ GenericArgKind::Type(inner_ty) if inner_ty != ty => {
+ is_normalizable_helper(cx, param_env, inner_ty, cache)
+ },
+ _ => true, // if inner_ty == ty, we've already checked it
+ }),
+ }
+ } else {
+ false
+ }
+ });
+ cache.insert(ty, result);
+ result
+}
+
+/// Returns `true` if the given type is a non aggregate primitive (a `bool` or `char`, any
+/// integer or floating-point number type). For checking aggregation of primitive types (e.g.
+/// tuples and slices of primitive type) see `is_recursively_primitive_type`
+pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool {
+ matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_))
+}
+
+/// Returns `true` if the given type is a primitive (a `bool` or `char`, any integer or
+/// floating-point number type, a `str`, or an array, slice, or tuple of those types).
+pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {
+ match ty.kind() {
+ ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
+ ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true,
+ ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type),
+ ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type),
+ _ => false,
+ }
+}
+
+/// Checks if the type is a reference equals to a diagnostic item
+pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
+ match ty.kind() {
+ ty::Ref(_, ref_ty, _) => match ref_ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+ _ => false,
+ },
+ _ => false,
+ }
+}
+
++/// Checks if the type is equal to a diagnostic item. To check if a type implements a
++/// trait marked with a diagnostic item use [`implements_trait`].
++///
++/// For a further exploitation what diagnostic items are see [diagnostic items] in
++/// rustc-dev-guide.
++///
++/// ---
+///
+/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
++///
++/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
+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 type is equal to a lang item.
+///
+/// Returns `false` if the `LangItem` is not defined.
+pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool {
+ match ty.kind() {
+ ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).map_or(false, |li| li == adt.did),
+ _ => false,
+ }
+}
+
+/// Return `true` if the passed `typ` is `isize` or `usize`.
+pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
+ matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+}
+
+/// Checks if type is struct, enum or union type with the given def path.
+///
+/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead.
+/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
+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,
+ }
+}
+
+/// Peels off all references on the type. Returns the underlying type and the number of references
+/// removed.
+pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) {
+ fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) {
+ if let ty::Ref(_, ty, _) = ty.kind() {
+ peel(ty, count + 1)
+ } else {
+ (ty, count)
+ }
+ }
+ peel(ty, 0)
+}
+
+/// Peels off all references on the type.Returns the underlying type, the number of references
+/// removed, and whether the pointer is ultimately mutable or not.
+pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) {
+ fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) {
+ match ty.kind() {
+ ty::Ref(_, ty, Mutability::Mut) => f(ty, count + 1, mutability),
+ ty::Ref(_, ty, Mutability::Not) => f(ty, count + 1, Mutability::Not),
+ _ => (ty, count, mutability),
+ }
+ }
+ f(ty, 0, Mutability::Mut)
+}
+
+/// 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,
+ }
+}
+
+/// 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, 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)
+}
+
+/// Returns `true` if types `a` and `b` are same types having same `Const` generic args,
+/// otherwise returns `false`
+pub fn same_type_and_consts(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
+ match (&a.kind(), &b.kind()) {
+ (&ty::Adt(did_a, substs_a), &ty::Adt(did_b, substs_b)) => {
+ if did_a != did_b {
+ return false;
+ }
+
+ substs_a
+ .iter()
+ .zip(substs_b.iter())
+ .all(|(arg_a, arg_b)| match (arg_a.unpack(), arg_b.unpack()) {
+ (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b,
+ (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => {
+ same_type_and_consts(type_a, type_b)
+ },
+ _ => true,
+ })
+ },
+ _ => a == b,
+ }
+}
+
+/// Checks if a given type looks safe to be uninitialized.
+pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
+ match ty.kind() {
+ ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
+ ty::Tuple(types) => types.types().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
+ ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did),
+ _ => false,
+ }
+}
++
++/// Gets an iterator over all predicates which apply to the given item.
++pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(Predicate<'_>, Span)> {
++ let mut next_id = Some(id);
++ iter::from_fn(move || {
++ next_id.take().map(|id| {
++ let preds = tcx.predicates_of(id);
++ next_id = preds.parent;
++ preds.predicates.iter()
++ })
++ })
++ .flatten()
++}
--- /dev/null
- use rustc_hir::intravisit;
- use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
+use crate as utils;
++use crate::visitors::{expr_visitor, expr_visitor_no_bodies};
+use rustc_hir as hir;
- 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;
++use rustc_hir::intravisit::{self, Visitor};
+use rustc_hir::HirIdSet;
+use rustc_hir::{Expr, ExprKind, HirId};
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+use rustc_middle::mir::FakeReadCause;
+use rustc_middle::ty;
+use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
+
+/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
+pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {
+ let mut delegate = MutVarsDelegate {
+ used_mutably: HirIdSet::default(),
+ skip: false,
+ };
+ cx.tcx.infer_ctxt().enter(|infcx| {
+ ExprUseVisitor::new(
+ &mut delegate,
+ &infcx,
+ expr.hir_id.owner,
+ cx.param_env,
+ cx.typeck_results(),
+ )
+ .walk_expr(expr);
+ });
+
+ if delegate.skip {
+ return None;
+ }
+ Some(delegate.used_mutably)
+}
+
+pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
+ mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable))
+}
+
+struct MutVarsDelegate {
+ used_mutably: HirIdSet,
+ skip: bool,
+}
+
+impl<'tcx> MutVarsDelegate {
+ #[allow(clippy::similar_names)]
+ fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
+ match cat.place.base {
+ PlaceBase::Local(id) => {
+ self.used_mutably.insert(id);
+ },
+ PlaceBase::Upvar(_) => {
+ //FIXME: This causes false negatives. We can't get the `NodeId` from
+ //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
+ //`while`-body, not just the ones in the condition.
+ self.skip = true;
+ },
+ _ => {},
+ }
+ }
+}
+
+impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
+ fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
+
+ fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
+ if bk == ty::BorrowKind::MutBorrow {
+ self.update(cmt);
+ }
+ }
+
+ fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
+ self.update(cmt);
+ }
+
+ fn fake_read(&mut self, _: rustc_typeck::expr_use_visitor::Place<'tcx>, _: FakeReadCause, _: HirId) {}
+}
+
+pub struct ParamBindingIdCollector {
+ binding_hir_ids: Vec<hir::HirId>,
+}
+impl<'tcx> ParamBindingIdCollector {
+ fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
+ let mut hir_ids: Vec<hir::HirId> = Vec::new();
+ for param in body.params.iter() {
+ let mut finder = ParamBindingIdCollector {
+ binding_hir_ids: Vec::new(),
+ };
+ finder.visit_param(param);
+ for hir_id in &finder.binding_hir_ids {
+ hir_ids.push(*hir_id);
+ }
+ }
+ hir_ids
+ }
+}
+impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
+ type Map = Map<'tcx>;
+
+ fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
+ if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
+ self.binding_hir_ids.push(hir_id);
+ }
+ intravisit::walk_pat(self, pat);
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+}
+
+pub struct BindingUsageFinder<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ binding_ids: Vec<hir::HirId>,
+ usage_found: bool,
+}
+impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
+ pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
+ let mut finder = BindingUsageFinder {
+ cx,
+ binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
+ usage_found: false,
+ };
+ finder.visit_body(body);
+ finder.usage_found
+ }
+}
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
+ type Map = Map<'tcx>;
+
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+ if !self.usage_found {
+ intravisit::walk_expr(self, expr);
+ }
+ }
+
+ fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
+ if let hir::def::Res::Local(id) = path.res {
+ if self.binding_ids.contains(&id) {
+ self.usage_found = true;
+ }
+ }
+ }
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+}
+
- self.seen_return_break_continue = true;
++pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
++ let mut seen_return_break_continue = false;
++ expr_visitor_no_bodies(|ex| {
++ if seen_return_break_continue {
++ return false;
+ }
+ match &ex.kind {
+ ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
- if utils::in_macro(ex.span) {
- self.seen_return_break_continue = true;
- } else {
- rustc_hir::intravisit::walk_expr(self, ex);
++ 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.
+ _ => {
- }
- }
-
- pub 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
- }
-
- pub struct UsedAfterExprVisitor<'a, 'tcx> {
- cx: &'a LateContext<'tcx>,
- expr: &'tcx Expr<'tcx>,
- definition: HirId,
- past_expr: bool,
- used_after_expr: bool,
- }
- impl<'a, 'tcx> UsedAfterExprVisitor<'a, 'tcx> {
- pub fn is_found(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
- utils::path_to_local(expr).map_or(false, |definition| {
- let mut visitor = UsedAfterExprVisitor {
- cx,
- expr,
- definition,
- past_expr: false,
- used_after_expr: false,
- };
- utils::get_enclosing_block(cx, definition).map_or(false, |block| {
- visitor.visit_block(block);
- visitor.used_after_expr
- })
- })
- }
- }
-
- impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedAfterExprVisitor<'a, 'tcx> {
- type Map = Map<'tcx>;
-
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
- }
-
- fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
- if self.used_after_expr {
- return;
++ if ex.span.from_expansion() {
++ seen_return_break_continue = true;
+ }
+ },
+ }
- if expr.hir_id == self.expr.hir_id {
- self.past_expr = true;
- } else if self.past_expr && utils::path_to_local_id(expr, self.definition) {
- self.used_after_expr = true;
- } else {
- intravisit::walk_expr(self, expr);
++ !seen_return_break_continue
++ })
++ .visit_expr(expression);
++ seen_return_break_continue
++}
++
++pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
++ let Some(block) = utils::get_enclosing_block(cx, local_id) else { return false };
++ let mut used_after_expr = false;
++ let mut past_expr = false;
++ expr_visitor(cx, |expr| {
++ if used_after_expr {
++ return false;
+ }
+
- }
++ if expr.hir_id == after.hir_id {
++ past_expr = true;
++ } else if past_expr && utils::path_to_local_id(expr, local_id) {
++ used_after_expr = true;
+ }
++ !used_after_expr
++ })
++ .visit_block(block);
++ used_after_expr
+}
--- /dev/null
- use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
- use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
+use crate::path_to_local_id;
+use rustc_hir as hir;
- use std::ops::ControlFlow;
++use rustc_hir::def::{DefKind, Res};
++use rustc_hir::intravisit::{self, walk_block, walk_expr, NestedVisitorMap, Visitor};
++use rustc_hir::{
++ Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, Unsafety,
++};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
- /// returns `true` if expr contains match expr desugared from try
- fn contains_try(expr: &hir::Expr<'_>) -> bool {
- struct TryFinder {
- found: bool,
++use rustc_middle::ty;
+
- impl<'hir> intravisit::Visitor<'hir> for TryFinder {
- type Map = Map<'hir>;
++/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
++/// bodies (i.e. closures) are visited.
++/// If the callback returns `true`, the expr just provided to the callback is walked.
++#[must_use]
++pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
++ struct V<'tcx, F> {
++ hir: Map<'tcx>,
++ f: F,
+ }
++ impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
++ type Map = Map<'tcx>;
++ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
++ NestedVisitorMap::OnlyBodies(self.hir)
++ }
+
- fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
- intravisit::NestedVisitorMap::None
++ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
++ if (self.f)(expr) {
++ walk_expr(self, expr);
++ }
++ }
++ }
++ V { hir: cx.tcx.hir(), f }
++}
+
- fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
- if self.found {
- return;
- }
- match expr.kind {
- hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
- _ => intravisit::walk_expr(self, expr),
++/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
++/// bodies (i.e. closures) are not visited.
++/// If the callback returns `true`, the expr just provided to the callback is walked.
++#[must_use]
++pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
++ struct V<F>(F);
++ impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
++ type Map = intravisit::ErasedMap<'tcx>;
++ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
++ NestedVisitorMap::None
+ }
+
- let mut visitor = TryFinder { found: false };
- visitor.visit_expr(expr);
- visitor.found
++ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
++ if (self.0)(e) {
++ walk_expr(self, e);
+ }
+ }
+ }
++ V(f)
++}
+
- /// Calls the given function for each break expression.
- pub fn visit_break_exprs<'tcx>(
- node: impl Visitable<'tcx>,
- f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>),
- ) {
- struct V<F>(F);
- impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V<F> {
- type Map = ErasedMap<'tcx>;
- fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
- NestedVisitorMap::None
++/// returns `true` if expr contains match expr desugared from try
++fn contains_try(expr: &hir::Expr<'_>) -> bool {
++ let mut found = false;
++ expr_visitor_no_bodies(|e| {
++ if !found {
++ found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
++ }
++ !found
++ })
++ .visit_expr(expr);
++ found
+}
+
+pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
+where
+ F: FnMut(&'hir hir::Expr<'hir>) -> bool,
+{
+ struct RetFinder<F> {
+ in_stmt: bool,
+ failed: bool,
+ cb: F,
+ }
+
+ struct WithStmtGuarg<'a, F> {
+ val: &'a mut RetFinder<F>,
+ prev_in_stmt: bool,
+ }
+
+ impl<F> RetFinder<F> {
+ fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
+ let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
+ WithStmtGuarg {
+ val: self,
+ prev_in_stmt,
+ }
+ }
+ }
+
+ impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
+ type Target = RetFinder<F>;
+
+ fn deref(&self) -> &Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.val
+ }
+ }
+
+ impl<F> Drop for WithStmtGuarg<'_, F> {
+ fn drop(&mut self) {
+ self.val.in_stmt = self.prev_in_stmt;
+ }
+ }
+
+ impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
+ type Map = Map<'hir>;
+
+ fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
+ intravisit::NestedVisitorMap::None
+ }
+
+ fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
+ intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt);
+ }
+
+ fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
+ if self.failed {
+ return;
+ }
+ if self.in_stmt {
+ match expr.kind {
+ hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
+ _ => intravisit::walk_expr(self, expr),
+ }
+ } else {
+ match expr.kind {
+ hir::ExprKind::If(cond, then, else_opt) => {
+ self.inside_stmt(true).visit_expr(cond);
+ self.visit_expr(then);
+ if let Some(el) = else_opt {
+ self.visit_expr(el);
+ }
+ },
+ hir::ExprKind::Match(cond, arms, _) => {
+ self.inside_stmt(true).visit_expr(cond);
+ for arm in arms {
+ self.visit_expr(arm.body);
+ }
+ },
+ hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
+ hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
+ _ => self.failed |= !(self.cb)(expr),
+ }
+ }
+ }
+ }
+
+ !contains_try(expr) && {
+ let mut ret_finder = RetFinder {
+ in_stmt: false,
+ failed: false,
+ cb: callback,
+ };
+ ret_finder.visit_expr(expr);
+ !ret_finder.failed
+ }
+}
+
+/// A type which can be visited.
+pub trait Visitable<'tcx> {
+ /// Calls the corresponding `visit_*` function on the visitor.
+ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
+}
+macro_rules! visitable_ref {
+ ($t:ident, $f:ident) => {
+ impl Visitable<'tcx> for &'tcx $t<'tcx> {
+ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+ visitor.$f(self);
+ }
+ }
+ };
+}
+visitable_ref!(Arm, visit_arm);
+visitable_ref!(Block, visit_block);
+visitable_ref!(Body, visit_body);
+visitable_ref!(Expr, visit_expr);
+visitable_ref!(Stmt, visit_stmt);
+
+// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
+// where
+// I::Item: Visitable<'tcx>,
+// {
+// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+// for x in self {
+// x.visit(visitor);
+// }
+// }
+// }
+
- fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
- if let ExprKind::Break(dest, sub_expr) = e.kind {
- self.0(e, dest, sub_expr);
++/// Checks if the given resolved path is used in the given body.
++pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
++ let mut found = false;
++ expr_visitor(cx, |e| {
++ if found {
++ return false;
+ }
+
- walk_expr(self, e);
++ if let ExprKind::Path(p) = &e.kind {
++ if cx.qpath_res(p, e.hir_id) == res {
++ found = true;
+ }
- }
+ }
- node.visit(&mut V(f));
++ !found
++ })
++ .visit_expr(&cx.tcx.hir().body(body).value);
++ found
++}
+
- /// Checks if the given resolved path is used in the given body.
- pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
++/// Checks if the given local is used.
++pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
++ let mut is_used = false;
++ let mut visitor = expr_visitor(cx, |expr| {
++ if !is_used {
++ is_used = path_to_local_id(expr, id);
++ }
++ !is_used
++ });
++ visitable.visit(&mut visitor);
++ drop(visitor);
++ is_used
+}
+
- res: Res,
- found: bool,
++/// Checks if the given expression is a constant.
++pub fn is_const_evaluatable(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
+ struct V<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
- impl Visitor<'tcx> for V<'_, 'tcx> {
++ is_const: bool,
+ }
- if self.found {
++ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type Map = Map<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
+
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
- if let ExprKind::Path(p) = &e.kind {
- if self.cx.qpath_res(p, e.hir_id) == self.res {
- self.found = true;
- }
- } else {
- walk_expr(self, e);
++ if !self.is_const {
+ return;
+ }
++ match e.kind {
++ ExprKind::ConstBlock(_) => return,
++ ExprKind::Call(
++ &Expr {
++ kind: ExprKind::Path(ref p),
++ hir_id,
++ ..
++ },
++ _,
++ ) if self
++ .cx
++ .qpath_res(p, hir_id)
++ .opt_def_id()
++ .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
++ ExprKind::MethodCall(..)
++ if self
++ .cx
++ .typeck_results()
++ .type_dependent_def_id(e.hir_id)
++ .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
++ ExprKind::Binary(_, lhs, rhs)
++ if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
++ && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
++ ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
++ ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
++ ExprKind::Index(base, _)
++ if matches!(
++ self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
++ ty::Slice(_) | ty::Array(..)
++ ) => {},
++ ExprKind::Path(ref p)
++ if matches!(
++ self.cx.qpath_res(p, e.hir_id),
++ Res::Def(
++ DefKind::Const
++ | DefKind::AssocConst
++ | DefKind::AnonConst
++ | DefKind::ConstParam
++ | DefKind::Ctor(..)
++ | DefKind::Fn
++ | DefKind::AssocFn,
++ _
++ ) | Res::SelfCtor(_)
++ ) => {},
+
- let mut v = V { cx, res, found: false };
- v.visit_expr(&cx.tcx.hir().body(body).value);
- v.found
++ ExprKind::AddrOf(..)
++ | ExprKind::Array(_)
++ | ExprKind::Block(..)
++ | ExprKind::Cast(..)
++ | ExprKind::DropTemps(_)
++ | ExprKind::Field(..)
++ | ExprKind::If(..)
++ | ExprKind::Let(..)
++ | ExprKind::Lit(_)
++ | ExprKind::Match(..)
++ | ExprKind::Repeat(..)
++ | ExprKind::Struct(..)
++ | ExprKind::Tup(_)
++ | ExprKind::Type(..) => (),
++
++ _ => {
++ self.is_const = false;
++ return;
++ },
+ }
++ walk_expr(self, e);
+ }
+ }
+
- /// Calls the given function for each usage of the given local.
- pub fn for_each_local_usage<'tcx, B>(
- cx: &LateContext<'tcx>,
- visitable: impl Visitable<'tcx>,
- id: HirId,
- f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
- ) -> ControlFlow<B> {
- struct V<'tcx, B, F> {
- map: Map<'tcx>,
- id: HirId,
- f: F,
- res: ControlFlow<B>,
++ let mut v = V { cx, is_const: true };
++ v.visit_expr(e);
++ v.is_const
+}
+
- impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
++/// Checks if the given expression performs an unsafe operation outside of an unsafe block.
++pub fn is_expr_unsafe(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
++ struct V<'a, 'tcx> {
++ cx: &'a LateContext<'tcx>,
++ is_unsafe: bool,
+ }
- NestedVisitorMap::OnlyBodies(self.map)
++ impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+ type Map = Map<'tcx>;
+ fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
-
++ NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
+ }
- if self.res.is_continue() {
- if path_to_local_id(e, self.id) {
- self.res = (self.f)(e);
- } else {
- walk_expr(self, e);
- }
+ fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
-
- let mut v = V {
- map: cx.tcx.hir(),
- id,
- f,
- res: ControlFlow::CONTINUE,
- };
- visitable.visit(&mut v);
- v.res
- }
-
- /// Checks if the given local is used.
- pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
- for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break()
++ if self.is_unsafe {
++ return;
++ }
++ match e.kind {
++ ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => {
++ self.is_unsafe = true;
++ },
++ ExprKind::MethodCall(..)
++ if self
++ .cx
++ .typeck_results()
++ .type_dependent_def_id(e.hir_id)
++ .map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) =>
++ {
++ self.is_unsafe = true;
++ },
++ ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() {
++ ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
++ ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
++ _ => walk_expr(self, e),
++ },
++ ExprKind::Path(ref p)
++ if self
++ .cx
++ .qpath_res(p, e.hir_id)
++ .opt_def_id()
++ .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) =>
++ {
++ self.is_unsafe = true;
++ },
++ _ => walk_expr(self, e),
++ }
++ }
++ fn visit_block(&mut self, b: &'tcx Block<'_>) {
++ if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) {
++ walk_block(self, b);
++ }
++ }
++ fn visit_nested_item(&mut self, id: ItemId) {
++ if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind {
++ self.is_unsafe = i.unsafety == Unsafety::Unsafe;
+ }
+ }
+ }
++ let mut v = V { cx, is_unsafe: false };
++ v.visit_expr(e);
++ v.is_unsafe
+}
--- /dev/null
- env __CLIPPY_INTERNAL_TESTS=true cargo run --bin clippy-driver -- -L ./target/debug input.rs
+# Adding a new lint
+
+You are probably here because you want to add a new lint to Clippy. If this is
+the first time you're contributing to Clippy, this document guides you through
+creating an example lint from scratch.
+
+To get started, we will create a lint that detects functions called `foo`,
+because that's clearly a non-descriptive name.
+
+- [Adding a new lint](#adding-a-new-lint)
+ - [Setup](#setup)
+ - [Getting Started](#getting-started)
+ - [Testing](#testing)
+ - [Cargo lints](#cargo-lints)
+ - [Rustfix tests](#rustfix-tests)
+ - [Edition 2018 tests](#edition-2018-tests)
+ - [Testing manually](#testing-manually)
+ - [Lint declaration](#lint-declaration)
+ - [Lint registration](#lint-registration)
+ - [Lint passes](#lint-passes)
+ - [Emitting a lint](#emitting-a-lint)
+ - [Adding the lint logic](#adding-the-lint-logic)
+ - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv)
+ - [Author lint](#author-lint)
+ - [Documentation](#documentation)
+ - [Running rustfmt](#running-rustfmt)
+ - [Debugging](#debugging)
+ - [PR Checklist](#pr-checklist)
+ - [Adding configuration to a lint](#adding-configuration-to-a-lint)
+ - [Cheatsheet](#cheatsheet)
+
+## Setup
+
+See the [Basics](basics.md#get-the-code) documentation.
+
+## Getting Started
+
+There is a bit of boilerplate code that needs to be set up when creating a new
+lint. Fortunately, you can use the clippy dev tools to handle this for you. We
+are naming our new lint `foo_functions` (lints are generally written in snake
+case), and we don't need type information so it will have an early pass type
+(more on this later on). If you're not sure if the name you chose fits the lint,
+take a look at our [lint naming guidelines][lint_naming]. To get started on this
+lint you can run `cargo dev new_lint --name=foo_functions --pass=early
+--category=pedantic` (category will default to nursery if not provided). This
+command will create two files: `tests/ui/foo_functions.rs` and
+`clippy_lints/src/foo_functions.rs`, as well as
+[registering the lint](#lint-registration). For cargo lints, two project
+hierarchies (fail/pass) will be created by default under `tests/ui-cargo`.
+
+Next, we'll open up these files and add our lint!
+
+## Testing
+
+Let's write some tests first that we can execute while we iterate on our lint.
+
+Clippy uses UI tests for testing. UI tests check that the output of Clippy is
+exactly as expected. Each test is just a plain Rust file that contains the code
+we want to check. The output of Clippy is compared against a `.stderr` file.
+Note that you don't have to create this file yourself, we'll get to
+generating the `.stderr` files further down.
+
+We start by opening the test file created at `tests/ui/foo_functions.rs`.
+
+Update the file with some examples to get started:
+
+```rust
+#![warn(clippy::foo_functions)]
+
+// Impl methods
+struct A;
+impl A {
+ pub fn fo(&self) {}
+ pub fn foo(&self) {}
+ pub fn food(&self) {}
+}
+
+// Default trait methods
+trait B {
+ fn fo(&self) {}
+ fn foo(&self) {}
+ fn food(&self) {}
+}
+
+// Plain functions
+fn fo() {}
+fn foo() {}
+fn food() {}
+
+fn main() {
+ // We also don't want to lint method calls
+ foo();
+ let a = A;
+ a.foo();
+}
+```
+
+Now we can run the test with `TESTNAME=foo_functions cargo uitest`,
+currently this test is meaningless though.
+
+While we are working on implementing our lint, we can keep running the UI
+test. That allows us to check if the output is turning into what we want.
+
+Once we are satisfied with the output, we need to run
+`cargo dev bless` to update the `.stderr` file for our lint.
+Please note that, we should run `TESTNAME=foo_functions cargo uitest`
+every time before running `cargo dev bless`.
+Running `TESTNAME=foo_functions cargo uitest` should pass then. When we commit
+our lint, we need to commit the generated `.stderr` files, too. In general, you
+should only commit files changed by `cargo dev bless` for the
+specific lint you are creating/editing. Note that if the generated files are
+empty, they should be removed.
+
+Note that you can run multiple test files by specifying a comma separated list:
+`TESTNAME=foo_functions,test2,test3`.
+
+### Cargo lints
+
+For cargo lints, the process of testing differs in that we are interested in
+the `Cargo.toml` manifest file. We also need a minimal crate associated
+with that manifest.
+
+If our new lint is named e.g. `foo_categories`, after running `cargo dev new_lint`
+we will find by default two new crates, each with its manifest file:
+
+* `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the new lint to raise an error.
+* `tests/ui-cargo/foo_categories/pass/Cargo.toml`: this file should not trigger the lint.
+
+If you need more cases, you can copy one of those crates (under `foo_categories`) and rename it.
+
+The process of generating the `.stderr` file is the same, and prepending the `TESTNAME`
+variable to `cargo uitest` works too.
+
+## Rustfix tests
+
+If the lint you are working on is making use of structured suggestions, the
+test file should include a `// run-rustfix` comment at the top. This will
+additionally run [rustfix] for that test. Rustfix will apply the suggestions
+from the lint to the code of the test file and compare that to the contents of
+a `.fixed` file.
+
+Use `cargo dev bless` to automatically generate the
+`.fixed` file after running the tests.
+
+[rustfix]: https://github.com/rust-lang/rustfix
+
+## Edition 2018 tests
+
+Some features require the 2018 edition to work (e.g. `async_await`), but
+compile-test tests run on the 2015 edition by default. To change this behavior
+add `// edition:2018` at the top of the test file (note that it's space-sensitive).
+
+## Testing manually
+
+Manually testing against an example file can be useful if you have added some
+`println!`s and the test suite output becomes unreadable. To try Clippy with
+your local modifications, run
+
+```
- is already in here (`implements_trait`, `match_def_path`, `snippet`, etc)
++cargo dev lint input.rs
+```
+
+from the working copy root. With tests in place, let's have a look at
+implementing our lint now.
+
+## Lint declaration
+
+Let's start by opening the new file created in the `clippy_lints` crate
+at `clippy_lints/src/foo_functions.rs`. That's the crate where all the
+lint code is. This file has already imported some initial things we will need:
+
+```rust
+use rustc_lint::{EarlyLintPass, EarlyContext};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_ast::ast::*;
+```
+
+The next step is to update the lint declaration. Lints are declared using the
+[`declare_clippy_lint!`][declare_clippy_lint] macro, and we just need to update
+the auto-generated lint declaration to have a real description, something like this:
+
+```rust
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// ### Why is this bad?
+ ///
+ /// ### Example
+ /// ```rust
+ /// // example code
+ /// ```
++ #[clippy::version = "1.29.0"]
+ pub FOO_FUNCTIONS,
+ pedantic,
+ "function named `foo`, which is not a descriptive name"
+}
+```
+
+* The section of lines prefixed with `///` constitutes the lint documentation
+ section. This is the default documentation style and will be displayed
+ [like this][example_lint_page]. To render and open this documentation locally
+ in a browser, run `cargo dev serve`.
++* The `#[clippy::version]` attribute will be rendered as part of the lint documentation.
++ The value should be set to the current Rust version that the lint is developed in,
++ it can be retrieved by running `rustc -vV` in the rust-clippy directory. The version
++ is listed under *release*. (Use the version without the `-nightly`) suffix.
+* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the
+ [lint naming guidelines][lint_naming] here when naming your lint.
+ In short, the name should state the thing that is being checked for and
+ read well when used with `allow`/`warn`/`deny`.
+* `pedantic` sets the lint level to `Allow`.
+ The exact mapping can be found [here][category_level_mapping]
+* The last part should be a text that explains what exactly is wrong with the
+ code
+
+The rest of this file contains an empty implementation for our lint pass,
+which in this case is `EarlyLintPass` and should look like this:
+
+```rust
+// clippy_lints/src/foo_functions.rs
+
+// .. imports and lint declaration ..
+
+declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]);
+
+impl EarlyLintPass for FooFunctions {}
+```
+
+[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60
+[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
+[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
+[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110
+
+## Lint registration
+
+When using `cargo dev new_lint`, the lint is automatically registered and
+nothing more has to be done.
+
+When declaring a new lint by hand and `cargo dev update_lints` is used, the lint
+pass may have to be registered manually in the `register_plugins` function in
+`clippy_lints/src/lib.rs`:
+
+```rust
+store.register_early_pass(|| Box::new(foo_functions::FooFunctions));
+```
+
+As one may expect, there is a corresponding `register_late_pass` method
+available as well. Without a call to one of `register_early_pass` or
+`register_late_pass`, the lint pass in question will not be run.
+
+One reason that `cargo dev update_lints` does not automate this step is that
+multiple lints can use the same lint pass, so registering the lint pass may
+already be done when adding a new lint. Another reason that this step is not
+automated is that the order that the passes are registered determines the order
+the passes actually run, which in turn affects the order that any emitted lints
+are output in.
+
+## Lint passes
+
+Writing a lint that only checks for the name of a function means that we only
+have to deal with the AST and don't have to deal with the type system at all.
+This is good, because it makes writing this particular lint less complicated.
+
+We have to make this decision with every new Clippy lint. It boils down to using
+either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass].
+
+In short, the `LateLintPass` has access to type information while the
+`EarlyLintPass` doesn't. If you don't need access to type information, use the
+`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed
+hasn't really been a concern with Clippy so far.
+
+Since we don't need type information for checking the function name, we used
+`--pass=early` when running the new lint automation and all the imports were
+added accordingly.
+
+[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html
+[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html
+
+## Emitting a lint
+
+With UI tests and the lint declaration in place, we can start working on the
+implementation of the lint logic.
+
+Let's start by implementing the `EarlyLintPass` for our `FooFunctions`:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ // TODO: Emit lint here
+ }
+}
+```
+
+We implement the [`check_fn`][check_fn] method from the
+[`EarlyLintPass`][early_lint_pass] trait. This gives us access to various
+information about the function that is currently being checked. More on that in
+the next section. Let's worry about the details later and emit our lint for
+*every* function definition first.
+
+Depending on how complex we want our lint message to be, we can choose from a
+variety of lint emission functions. They can all be found in
+[`clippy_utils/src/diagnostics.rs`][diagnostics].
+
+`span_lint_and_help` seems most appropriate in this case. It allows us to
+provide an extra help message and we can't really suggest a better name
+automatically. This is how it looks:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ span_lint_and_help(
+ cx,
+ FOO_FUNCTIONS,
+ span,
+ "function named `foo`",
+ None,
+ "consider using a more meaningful name"
+ );
+ }
+}
+```
+
+Running our UI test should now produce output that contains the lint message.
+
+According to [the rustc-dev-guide], the text should be matter of fact and avoid
+capitalization and periods, unless multiple sentences are needed.
+When code or an identifier must appear in a message or label, it should be
+surrounded with single grave accents \`.
+
+[check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn
+[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/diagnostics.rs
+[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html
+
+## Adding the lint logic
+
+Writing the logic for your lint will most likely be different from our example,
+so this section is kept rather short.
+
+Using the [`check_fn`][check_fn] method gives us access to [`FnKind`][fn_kind]
+that has the [`FnKind::Fn`] variant. It provides access to the name of the
+function/method via an [`Ident`][ident].
+
+With that we can expand our `check_fn` method to:
+
+```rust
+impl EarlyLintPass for FooFunctions {
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) {
+ if is_foo_fn(fn_kind) {
+ span_lint_and_help(
+ cx,
+ FOO_FUNCTIONS,
+ span,
+ "function named `foo`",
+ None,
+ "consider using a more meaningful name"
+ );
+ }
+ }
+}
+```
+
+We separate the lint conditional from the lint emissions because it makes the
+code a bit easier to read. In some cases this separation would also allow to
+write some unit tests (as opposed to only UI tests) for the separate function.
+
+In our example, `is_foo_fn` looks like:
+
+```rust
+// use statements, impl EarlyLintPass, check_fn, ..
+
+fn is_foo_fn(fn_kind: FnKind<'_>) -> bool {
+ match fn_kind {
+ FnKind::Fn(_, ident, ..) => {
+ // check if `fn` name is `foo`
+ ident.name.as_str() == "foo"
+ }
+ // ignore closures
+ FnKind::Closure(..) => false
+ }
+}
+```
+
+Now we should also run the full test suite with `cargo test`. At this point
+running `cargo test` should produce the expected output. Remember to run
+`cargo dev bless` to update the `.stderr` file.
+
+`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint
+implementation is not violating any Clippy lints itself.
+
+That should be it for the lint implementation. Running `cargo test` should now
+pass.
+
+[fn_kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html
+[`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn
+[ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html
+
+## Specifying the lint's minimum supported Rust version (MSRV)
+
+Sometimes a lint makes suggestions that require a certain version of Rust. For example, the `manual_strip` lint suggests
+using `str::strip_prefix` and `str::strip_suffix` which is only available after Rust 1.45. In such cases, you need to
+ensure that the MSRV configured for the project is >= the MSRV of the required Rust feature. If multiple features are
+required, just use the one with a lower MSRV.
+
+First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`](/clippy_utils/src/msrvs.rs). This can be
+accessed later as `msrvs::STR_STRIP_PREFIX`, for example.
+
+```rust
+msrv_aliases! {
+ ..
+ 1,45,0 { STR_STRIP_PREFIX }
+}
+```
+
+In order to access the project-configured MSRV, you need to have an `msrv` field in the LintPass struct, and a
+constructor to initialize the field. The `msrv` value is passed to the constructor in `clippy_lints/lib.rs`.
+
+```rust
+pub struct ManualStrip {
+ msrv: Option<RustcVersion>,
+}
+
+impl ManualStrip {
+ #[must_use]
+ pub fn new(msrv: Option<RustcVersion>) -> Self {
+ Self { msrv }
+ }
+}
+```
+
+The project's MSRV can then be matched against the feature MSRV in the LintPass
+using the `meets_msrv` utility function.
+
+``` rust
+if !meets_msrv(self.msrv.as_ref(), &msrvs::STR_STRIP_PREFIX) {
+ return;
+}
+```
+
+The project's MSRV can also be specified as an inner attribute, which overrides
+the value from `clippy.toml`. This can be accounted for using the
+`extract_msrv_attr!(LintContext)` macro and passing
+`LateContext`/`EarlyContext`.
+
+```rust
+impl<'tcx> LateLintPass<'tcx> for ManualStrip {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ ...
+ }
+ extract_msrv_attr!(LateContext);
+}
+```
+
+Once the `msrv` is added to the lint, a relevant test case should be added to
+`tests/ui/min_rust_version_attr.rs` which verifies that the lint isn't emitted
+if the project's MSRV is lower.
+
+As a last step, the lint should be added to the lint documentation. This is done
+in `clippy_lints/src/utils/conf.rs`:
+
+```rust
+define_Conf! {
+ /// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports
+ (msrv: Option<String> = None),
+ ...
+}
+```
+
+## Author lint
+
+If you have trouble implementing your lint, there is also the internal `author`
+lint to generate Clippy code that detects the offending pattern. It does not
+work for all of the Rust syntax, but can give a good starting point.
+
+The quickest way to use it, is the
+[Rust playground: play.rust-lang.org][author_example].
+Put the code you want to lint into the editor and add the `#[clippy::author]`
+attribute above the item. Then run Clippy via `Tools -> Clippy` and you should
+see the generated code in the output below.
+
+[Here][author_example] is an example on the playground.
+
+If the command was executed successfully, you can copy the code over to where
+you are implementing your lint.
+
+[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55
+
+## Documentation
+
+The final thing before submitting our PR is to add some documentation to our
+lint declaration.
+
+Please document your lint with a doc comment akin to the following:
+
+```rust
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for ... (describe what the lint matches).
+ ///
+ /// ### Why is this bad?
+ /// Supply the reason for linting the code.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,ignore
+ /// // Bad
+ /// Insert a short example of code that triggers the lint
+ ///
+ /// // Good
+ /// Insert a short example of improved code that doesn't trigger the lint
+ /// ```
++ #[clippy::version = "1.29.0"]
+ pub FOO_FUNCTIONS,
+ pedantic,
+ "function named `foo`, which is not a descriptive name"
+}
+```
+
+Once your lint is merged, this documentation will show up in the [lint
+list][lint_list].
+
+[lint_list]: https://rust-lang.github.io/rust-clippy/master/index.html
+
+## Running rustfmt
+
+[Rustfmt] is a tool for formatting Rust code according to style guidelines.
+Your code has to be formatted by `rustfmt` before a PR can be merged.
+Clippy uses nightly `rustfmt` in the CI.
+
+It can be installed via `rustup`:
+
+```bash
+rustup component add rustfmt --toolchain=nightly
+```
+
+Use `cargo dev fmt` to format the whole codebase. Make sure that `rustfmt` is
+installed for the nightly toolchain.
+
+[Rustfmt]: https://github.com/rust-lang/rustfmt
+
+## Debugging
+
+If you want to debug parts of your lint implementation, you can use the [`dbg!`]
+macro anywhere in your code. Running the tests should then include the debug
+output in the `stdout` part.
+
+[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html
+
+## PR Checklist
+
+Before submitting your PR make sure you followed all of the basic requirements:
+
+<!-- Sync this with `.github/PULL_REQUEST_TEMPLATE` -->
+
+- \[ ] Followed [lint naming conventions][lint_naming]
+- \[ ] Added passing UI tests (including committed `.stderr` file)
+- \[ ] `cargo test` passes locally
+- \[ ] Executed `cargo dev update_lints`
+- \[ ] Added lint documentation
+- \[ ] Run `cargo dev fmt`
+
+## Adding configuration to a lint
+
+Clippy supports the configuration of lints values using a `clippy.toml` file in the workspace
+directory. Adding a configuration to a lint can be useful for thresholds or to constrain some
+behavior that can be seen as a false positive for some users. Adding a configuration is done
+in the following steps:
+
+1. Adding a new configuration entry to [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs)
+ like this:
+ ```rust
+ /// Lint: LINT_NAME.
+ ///
+ /// <The configuration field doc comment>
+ (configuration_ident: Type = DefaultValue),
+ ```
+ The doc comment is automatically added to the documentation of the listed lints. The default
+ value will be formatted using the `Debug` implementation of the type.
+2. Adding the configuration value to the lint impl struct:
+ 1. This first requires the definition of a lint impl struct. Lint impl structs are usually
+ generated with the `declare_lint_pass!` macro. This struct needs to be defined manually
+ to add some kind of metadata to it:
+ ```rust
+ // Generated struct definition
+ declare_lint_pass!(StructName => [
+ LINT_NAME
+ ]);
+
+ // New manual definition struct
+ #[derive(Copy, Clone)]
+ pub struct StructName {}
+
+ impl_lint_pass!(StructName => [
+ LINT_NAME
+ ]);
+ ```
+
+ 2. Next add the configuration value and a corresponding creation method like this:
+ ```rust
+ #[derive(Copy, Clone)]
+ pub struct StructName {
+ configuration_ident: Type,
+ }
+
+ // ...
+
+ impl StructName {
+ pub fn new(configuration_ident: Type) -> Self {
+ Self {
+ configuration_ident,
+ }
+ }
+ }
+ ```
+3. Passing the configuration value to the lint impl struct:
+
+ First find the struct construction in the [clippy_lints lib file](/clippy_lints/src/lib.rs).
+ The configuration value is now cloned or copied into a local value that is then passed to the
+ impl struct like this:
+ ```rust
+ // Default generated registration:
+ store.register_*_pass(|| box module::StructName);
+
+ // New registration with configuration value
+ let configuration_ident = conf.configuration_ident.clone();
+ store.register_*_pass(move || box module::StructName::new(configuration_ident));
+ ```
+
+ Congratulations the work is almost done. The configuration value can now be accessed
+ in the linting code via `self.configuration_ident`.
+
+4. Adding tests:
+ 1. The default configured value can be tested like any normal lint in [`tests/ui`](/tests/ui).
+ 2. The configuration itself will be tested separately in [`tests/ui-toml`](/tests/ui-toml).
+ Simply add a new subfolder with a fitting name. This folder contains a `clippy.toml` file
+ with the configuration value and a rust file that should be linted by Clippy. The test can
+ otherwise be written as usual.
+
+## Cheatsheet
+
+Here are some pointers to things you are likely going to need for every lint:
+
+* [Clippy utils][utils] - Various helper functions. Maybe the function you need
- [utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs
++ is already in here ([`is_type_diagnostic_item`], [`implements_trait`], [`snippet`], etc)
+* [Clippy diagnostics][diagnostics]
+* [The `if_chain` macro][if_chain]
+* [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro]
+* [`Span`][span]
+* [`Applicability`][applicability]
+* [Common tools for writing lints](common_tools_writing_lints.md) helps with common operations
+* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler concepts
+* [The nightly rustc docs][nightly_docs] which has been linked to throughout
+ this guide
+
+For `EarlyLintPass` lints:
+
+* [`EarlyLintPass`][early_lint_pass]
+* [`rustc_ast::ast`][ast]
+
+For `LateLintPass` lints:
+
+* [`LateLintPass`][late_lint_pass]
+* [`Ty::TyKind`][ty]
+
+While most of Clippy's lint utils are documented, most of rustc's internals lack
+documentation currently. This is unfortunate, but in most cases you can probably
+get away with copying things from existing similar lints. If you are stuck,
+don't hesitate to ask on [Zulip] or in the issue/PR.
+
++[utils]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/index.html
++[`is_type_diagnostic_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.is_type_diagnostic_item.html
++[`implements_trait`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.implements_trait.html
++[`snippet`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/fn.snippet.html
+[if_chain]: https://docs.rs/if_chain/*/if_chain/
+[from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
+[in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html
+[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html
+[applicability]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html
+[rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/
+[nightly_docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/
+[ast]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/index.html
+[ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/index.html
+[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy
--- /dev/null
- Usually you want to wirte the changelog of the **upcoming stable release**. Make
+# Changelog Update
+
+If you want to help with updating the [changelog][changelog], you're in the right place.
+
+## When to update
+
+Typos and other small fixes/additions are _always_ welcome.
+
+Special care needs to be taken when it comes to updating the changelog for a new
+Rust release. For that purpose, the changelog is ideally updated during the week
+before an upcoming stable release. You can find the release dates on the [Rust
+Forge][forge].
+
+Most of the time we only need to update the changelog for minor Rust releases. It's
+been very rare that Clippy changes were included in a patch release.
+
+## Changelog update walkthrough
+
+### 1. Finding the relevant Clippy commits
+
+Each Rust release ships with its own version of Clippy. The Clippy subtree can
+be found in the `tools` directory of the Rust repository.
+
+Depending on the current time and what exactly you want to update, the following
+bullet points might be helpful:
+
+* When writing the release notes for the **upcoming stable release** you need to check
+ out the Clippy commit of the current Rust `beta` branch. [Link][rust_beta_tools]
+* When writing the release notes for the **upcoming beta release**, you need to check
+ out the Clippy commit of the current Rust `master`. [Link][rust_master_tools]
+* When writing the (forgotten) release notes for a **past stable release**, you
+ need to check out the Rust release tag of the stable release.
+ [Link][rust_stable_tools]
+
++Usually you want to write the changelog of the **upcoming stable release**. Make
+sure though, that `beta` was already branched in the Rust repository.
+
+To find the commit hash, issue the following command when in a `rust-lang/rust` checkout:
+```
+git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g"
+```
+
+### 2. Fetching the PRs between those commits
+
+Once you've got the correct commit range, run
+
+ util/fetch_prs_between.sh commit1 commit2 > changes.txt
+
+and open that file in your editor of choice.
+
+When updating the changelog it's also a good idea to make sure that `commit1` is
+already correct in the current changelog.
+
+### 3. Authoring the final changelog
+
+The above script should have dumped all the relevant PRs to the file you
+specified. It should have filtered out most of the irrelevant PRs
+already, but it's a good idea to do a manual cleanup pass where you look for
+more irrelevant PRs. If you're not sure about some PRs, just leave them in for
+the review and ask for feedback.
+
+With the PRs filtered, you can start to take each PR and move the
+`changelog: ` content to `CHANGELOG.md`. Adapt the wording as you see fit but
+try to keep it somewhat coherent.
+
+The order should roughly be:
+
+1. New lints
+2. Moves or deprecations of lints
+3. Changes that expand what code existing lints cover
+4. False positive fixes
+5. Suggestion fixes/improvements
+6. ICE fixes
+7. Documentation improvements
+8. Others
+
+As section headers, we use:
+
+```
+### New Lints
+### Moves and Deprecations
+### Enhancements
+### False Positive Fixes
+### Suggestion Fixes/Improvements
+### ICE Fixes
+### Documentation Improvements
+### Others
+```
+
+Please also be sure to update the Beta/Unreleased sections at the top with the
+relevant commit ranges.
+
+[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md
+[forge]: https://forge.rust-lang.org/
+[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy
+[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy
+[rust_stable_tools]: https://github.com/rust-lang/rust/releases
--- /dev/null
- - [Checking if an expression is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
+# Common tools for writing lints
+
+You may need following tooltips to catch up with common operations.
+
+- [Common tools for writing lints](#common-tools-for-writing-lints)
+ - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression)
- - [Dealing with macros](#dealing-with-macros)
++ - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method)
++ - [Checking for a specific type](#checking-for-a-specific-type)
+ - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait)
+ - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method)
- # Retrieving the type of an expression
++ - [Dealing with macros](#dealing-with-macros-and-expansions)
+
+Useful Rustc dev guide links:
+- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
+- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html)
+- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
+- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
+
- # Checking if an expr is calling a specific method
++## Retrieving the type of an expression
+
+Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions:
+
+- which type does this expression correspond to (using its [`TyKind`][TyKind])?
+- is it a sized type?
+- is it a primitive type?
+- does it implement a trait?
+
+This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct,
+that gives you access to the underlying structure [`TyS`][TyS].
+
+Example of use:
+```rust
+impl LateLintPass<'_> for MyStructLint {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // Get type of `expr`
+ let ty = cx.typeck_results().expr_ty(expr);
+ // Match its kind to enter its type
+ match ty.kind {
+ ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"),
+ _ => ()
+ }
+ }
+}
+```
+
+Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method
+to retrieve a type from a pattern.
+
+Two noticeable items here:
+- `cx` is the lint context [`LateContext`][LateContext]. The two most useful
+ data structures in this context are `tcx` and the `TypeckResults` returned by
+ `LateContext::typeck_results`, allowing us to jump to type definitions and
+ other compilation stages such as HIR.
+- `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is
+ created by type checking step, it includes useful information such as types
+ of expressions, ways to resolve methods and so on.
+
- if let hir::ExprKind::MethodCall(path, _, _args, _) = &expr.kind;
++## Checking if an expr is calling a specific method
+
+Starting with an `expr`, you can check whether it is calling a specific method `some_method`:
+
+```rust
+impl LateLintPass<'_> for MyStructLint {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
+ if_chain! {
+ // Check our expr is calling a method
- # Checking if a type implements a specific trait
++ if let hir::ExprKind::MethodCall(path, _, [_self_arg, ..], _) = &expr.kind;
+ // Check the name of this method is `some_method`
+ if path.ident.name == sym!(some_method);
++ // Optionally, check the type of the self argument.
++ // - See "Checking for a specific type"
+ then {
+ // ...
+ }
+ }
+ }
+}
+```
+
- # Checking if a type defines a specific method
++## Checking for a specific type
++
++There are three ways to check if an expression type is a specific type we want to check for.
++All of these methods only check for the base type, generic arguments have to be checked separately.
++
++```rust
++use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
++use clippy_utils::{paths, match_def_path};
++use rustc_span::symbol::sym;
++use rustc_hir::LangItem;
++
++impl LateLintPass<'_> for MyStructLint {
++ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
++ // Getting the expression type
++ let ty = cx.typeck_results().expr_ty(expr);
++
++ // 1. Using diagnostic items
++ // The last argument is the diagnostic item to check for
++ if is_type_diagnostic_item(cx, ty, sym::Option) {
++ // The type is an `Option`
++ }
++
++ // 2. Using lang items
++ if is_type_lang_item(cx, ty, LangItem::RangeFull) {
++ // The type is a full range like `.drain(..)`
++ }
++
++ // 3. Using the type path
++ // This method should be avoided if possible
++ if match_def_path(cx, def_id, &paths::RESULT) {
++ // The type is a `core::result::Result`
++ }
++ }
++}
++```
++
++Prefer using diagnostic items and lang items where possible.
++
++## Checking if a type implements a specific trait
+
+There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
+
+```rust
+use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
+use rustc_span::symbol::sym;
+
+impl LateLintPass<'_> for MyStructLint {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ // 1. Using diagnostic items with the expression
+ // we use `is_trait_method` function from Clippy's utils
+ if is_trait_method(cx, expr, sym::Iterator) {
+ // method call in `expr` belongs to `Iterator` trait
+ }
+
+ // 2. Using lang items with the expression type
+ let ty = cx.typeck_results().expr_ty(expr);
+ if cx.tcx.lang_items()
+ // we are looking for the `DefId` of `Drop` trait in lang items
+ .drop_trait()
+ // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils
+ .map_or(false, |id| implements_trait(cx, ty, id, &[])) {
+ // `expr` implements `Drop` trait
+ }
+
+ // 3. Using the type path with the expression
+ // we use `match_trait_method` function from Clippy's utils
++ // (This method should be avoided if possible)
+ if match_trait_method(cx, expr, &paths::INTO) {
+ // `expr` implements `Into` trait
+ }
+ }
+}
+```
+
+> Prefer using diagnostic and lang items, if the target trait has one.
+
+We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
+A list of defined paths for Clippy can be found in [paths.rs][paths]
+
- # Dealing with macros
++## Checking if a type defines a specific method
+
+To check if our type defines a method called `some_method`:
+
+```rust
+use clippy_utils::{is_type_diagnostic_item, return_ty};
+
+impl<'tcx> LateLintPass<'tcx> for MyTypeImpl {
+ fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) {
+ if_chain! {
+ // Check if item is a method/function
+ if let ImplItemKind::Fn(ref signature, _) = impl_item.kind;
+ // Check the method is named `some_method`
+ if impl_item.ident.name == sym!(some_method);
+ // We can also check it has a parameter `self`
+ if signature.decl.implicit_self.has_implicit_self();
+ // We can go further and even check if its return type is `String`
+ if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type));
+ then {
+ // ...
+ }
+ }
+ }
+}
+```
+
- There are several helpers in [`clippy_utils`][utils] to deal with macros:
++## Dealing with macros and expansions
+
- - `in_macro()`: detect if the given span is expanded by a macro
++Keep in mind that macros are already expanded and desugaring is already applied
++to the code representation that you are working with in Clippy. This unfortunately causes a lot of
++false positives because macro expansions are "invisible" unless you actively check for them.
++Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be
++dynamic in ways that are difficult or impossible to see.
++Use the following functions to deal with macros:
+
- You may want to use this for example to not start linting in any macro.
++- `span.from_expansion()`: detects if a span is from macro expansion or desugaring.
++ Checking this is a common first step in a lint.
+
- ```rust
- macro_rules! foo {
- ($param:expr) => {
- match $param {
- "bar" => println!("whatever"),
- _ => ()
- }
- };
- }
++ ```rust
++ if expr.span.from_expansion() {
++ // just forget it
++ return;
++ }
++ ```
+
- foo!("bar");
++- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it.
++ It is sometimes useful to check if the context of two spans are equal.
+
- // if we lint the `match` of `foo` call and test its span
- assert_eq!(in_macro(match_span), true);
- ```
++ ```rust
++ // expands to `1 + 0`, but don't lint
++ 1 + mac!()
++ ```
++ ```rust
++ if left.span.ctxt() != right.span.ctxt() {
++ // the coder most likely cannot modify this expression
++ return;
++ }
++ ```
++ Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can
++ be assumed to have the same context. And so just using `span.from_expansion()` is often good enough.
+
- - `in_external_macro()`: detect if the given span is from an external macro, defined in a foreign crate
+
- You may want to use it for example to not start linting in macros from other crates
++- `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate.
++ If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros
++ not defined in the current crate. It doesn't make sense to lint code that the coder can't change.
+
- ```rust
- #[macro_use]
- extern crate a_crate_with_macros;
++ You may want to use it for example to not start linting in macros from other crates
+
- // `foo` is defined in `a_crate_with_macros`
- foo!("bar");
++ ```rust
++ #[macro_use]
++ extern crate a_crate_with_macros;
+
- // if we lint the `match` of `foo` call and test its span
- assert_eq!(in_external_macro(cx.sess(), match_span), true);
- ```
++ // `foo` is defined in `a_crate_with_macros`
++ foo!("bar");
+
- ```rust
- macro_rules! m {
- ($a:expr, $b:expr) => {
- if $a.is_some() {
- $b;
- }
- }
- }
++ // if we lint the `match` of `foo` call and test its span
++ assert_eq!(in_external_macro(cx.sess(), match_span), true);
++ ```
+
+- `differing_macro_contexts()`: returns true if the two given spans are not from the same context
+
- let x: Option<u32> = Some(42);
- m!(x, x.unwrap());
++ ```rust
++ macro_rules! m {
++ ($a:expr, $b:expr) => {
++ if $a.is_some() {
++ $b;
++ }
++ }
++ }
+
- // These spans are not from the same context
- // x.is_some() is from inside the macro
- // x.unwrap() is from outside the macro
- assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
- ```
++ let x: Option<u32> = Some(42);
++ m!(x, x.unwrap());
+
- [utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs
++ // These spans are not from the same context
++ // x.is_some() is from inside the macro
++ // x.unwrap() is from outside the macro
++ assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
++ ```
+
+[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html
+[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html
+[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html
+[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty
+[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html
+[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html
+[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty
+[paths]: ../clippy_utils/src/paths.rs
--- /dev/null
- /// Generate a short list of occuring lints-types and their count
+// Run clippy on a fixed set of crates and collect the warnings.
+// This helps observing the impact clippy changes have on a set of real-world code (and not just our
+// testsuite).
+//
+// When a new lint is introduced, we can search the results for new warnings and check for false
+// positives.
+
+#![allow(clippy::collapsible_else_if)]
+
+use std::ffi::OsStr;
+use std::process::Command;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::{collections::HashMap, io::ErrorKind};
+use std::{
+ env, fmt,
+ fs::write,
+ path::{Path, PathBuf},
+ thread,
+ time::Duration,
+};
+
+use clap::{App, Arg, ArgMatches};
+use rayon::prelude::*;
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use walkdir::{DirEntry, WalkDir};
+
+#[cfg(not(windows))]
+const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver";
+#[cfg(not(windows))]
+const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy";
+
+#[cfg(windows)]
+const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver.exe";
+#[cfg(windows)]
+const CARGO_CLIPPY_PATH: &str = "target/debug/cargo-clippy.exe";
+
+const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads";
+const LINTCHECK_SOURCES: &str = "target/lintcheck/sources";
+
+/// List of sources to check, loaded from a .toml file
+#[derive(Debug, Serialize, Deserialize)]
+struct SourceList {
+ crates: HashMap<String, TomlCrate>,
+}
+
+/// A crate source stored inside the .toml
+/// will be translated into on one of the `CrateSource` variants
+#[derive(Debug, Serialize, Deserialize)]
+struct TomlCrate {
+ name: String,
+ versions: Option<Vec<String>>,
+ git_url: Option<String>,
+ git_hash: Option<String>,
+ path: Option<String>,
+ options: Option<Vec<String>>,
+}
+
+/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder
+/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate`
+#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)]
+enum CrateSource {
+ CratesIo {
+ name: String,
+ version: String,
+ options: Option<Vec<String>>,
+ },
+ Git {
+ name: String,
+ url: String,
+ commit: String,
+ options: Option<Vec<String>>,
+ },
+ Path {
+ name: String,
+ path: PathBuf,
+ options: Option<Vec<String>>,
+ },
+}
+
+/// Represents the actual source code of a crate that we ran "cargo clippy" on
+#[derive(Debug)]
+struct Crate {
+ version: String,
+ name: String,
+ // path to the extracted sources that clippy can check
+ path: PathBuf,
+ options: Option<Vec<String>>,
+}
+
+/// A single warning that clippy issued while checking a `Crate`
+#[derive(Debug)]
+struct ClippyWarning {
+ crate_name: String,
+ crate_version: String,
+ file: String,
+ line: String,
+ column: String,
+ linttype: String,
+ message: String,
+ is_ice: bool,
+}
+
+impl std::fmt::Display for ClippyWarning {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ writeln!(
+ f,
+ r#"target/lintcheck/sources/{}-{}/{}:{}:{} {} "{}""#,
+ &self.crate_name, &self.crate_version, &self.file, &self.line, &self.column, &self.linttype, &self.message
+ )
+ }
+}
+
+fn get(path: &str) -> Result<ureq::Response, ureq::Error> {
+ const MAX_RETRIES: u8 = 4;
+ let mut retries = 0;
+ loop {
+ match ureq::get(path).call() {
+ Ok(res) => return Ok(res),
+ Err(e) if retries >= MAX_RETRIES => return Err(e),
+ Err(ureq::Error::Transport(e)) => eprintln!("Error: {}", e),
+ Err(e) => return Err(e),
+ }
+ eprintln!("retrying in {} seconds...", retries);
+ thread::sleep(Duration::from_secs(retries as u64));
+ retries += 1;
+ }
+}
+
+impl CrateSource {
+ /// Makes the sources available on the disk for clippy to check.
+ /// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or
+ /// copies a local folder
+ fn download_and_extract(&self) -> Crate {
+ match self {
+ CrateSource::CratesIo { name, version, options } => {
+ let extract_dir = PathBuf::from(LINTCHECK_SOURCES);
+ let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS);
+
+ // url to download the crate from crates.io
+ let url = format!("https://crates.io/api/v1/crates/{}/{}/download", name, version);
+ println!("Downloading and extracting {} {} from {}", name, version, url);
+ create_dirs(&krate_download_dir, &extract_dir);
+
+ let krate_file_path = krate_download_dir.join(format!("{}-{}.crate.tar.gz", name, version));
+ // don't download/extract if we already have done so
+ if !krate_file_path.is_file() {
+ // create a file path to download and write the crate data into
+ let mut krate_dest = std::fs::File::create(&krate_file_path).unwrap();
+ let mut krate_req = get(&url).unwrap().into_reader();
+ // copy the crate into the file
+ std::io::copy(&mut krate_req, &mut krate_dest).unwrap();
+
+ // unzip the tarball
+ let ungz_tar = flate2::read::GzDecoder::new(std::fs::File::open(&krate_file_path).unwrap());
+ // extract the tar archive
+ let mut archive = tar::Archive::new(ungz_tar);
+ archive.unpack(&extract_dir).expect("Failed to extract!");
+ }
+ // crate is extracted, return a new Krate object which contains the path to the extracted
+ // sources that clippy can check
+ Crate {
+ version: version.clone(),
+ name: name.clone(),
+ path: extract_dir.join(format!("{}-{}/", name, version)),
+ options: options.clone(),
+ }
+ },
+ CrateSource::Git {
+ name,
+ url,
+ commit,
+ options,
+ } => {
+ let repo_path = {
+ let mut repo_path = PathBuf::from(LINTCHECK_SOURCES);
+ // add a -git suffix in case we have the same crate from crates.io and a git repo
+ repo_path.push(format!("{}-git", name));
+ repo_path
+ };
+ // clone the repo if we have not done so
+ if !repo_path.is_dir() {
+ println!("Cloning {} and checking out {}", url, commit);
+ if !Command::new("git")
+ .arg("clone")
+ .arg(url)
+ .arg(&repo_path)
+ .status()
+ .expect("Failed to clone git repo!")
+ .success()
+ {
+ eprintln!("Failed to clone {} into {}", url, repo_path.display())
+ }
+ }
+ // check out the commit/branch/whatever
+ if !Command::new("git")
+ .arg("checkout")
+ .arg(commit)
+ .current_dir(&repo_path)
+ .status()
+ .expect("Failed to check out commit")
+ .success()
+ {
+ eprintln!("Failed to checkout {} of repo at {}", commit, repo_path.display())
+ }
+
+ Crate {
+ version: commit.clone(),
+ name: name.clone(),
+ path: repo_path,
+ options: options.clone(),
+ }
+ },
+ CrateSource::Path { name, path, options } => {
+ // copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
+ // The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory
+ // as a result of this filter.
+ let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name);
+ if dest_crate_root.exists() {
+ println!("Deleting existing directory at {:?}", dest_crate_root);
+ std::fs::remove_dir_all(&dest_crate_root).unwrap();
+ }
+
+ println!("Copying {:?} to {:?}", path, dest_crate_root);
+
+ fn is_cache_dir(entry: &DirEntry) -> bool {
+ std::fs::read(entry.path().join("CACHEDIR.TAG"))
+ .map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
+ .unwrap_or(false)
+ }
+
+ for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) {
+ let entry = entry.unwrap();
+ let entry_path = entry.path();
+ let relative_entry_path = entry_path.strip_prefix(path).unwrap();
+ let dest_path = dest_crate_root.join(relative_entry_path);
+ let metadata = entry_path.symlink_metadata().unwrap();
+
+ if metadata.is_dir() {
+ std::fs::create_dir(dest_path).unwrap();
+ } else if metadata.is_file() {
+ std::fs::copy(entry_path, dest_path).unwrap();
+ }
+ }
+
+ Crate {
+ version: String::from("local"),
+ name: name.clone(),
+ path: dest_crate_root,
+ options: options.clone(),
+ }
+ },
+ }
+ }
+}
+
+impl Crate {
+ /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy
+ /// issued
+ fn run_clippy_lints(
+ &self,
+ cargo_clippy_path: &Path,
+ target_dir_index: &AtomicUsize,
+ thread_limit: usize,
+ total_crates_to_lint: usize,
+ fix: bool,
+ ) -> Vec<ClippyWarning> {
+ // advance the atomic index by one
+ let index = target_dir_index.fetch_add(1, Ordering::SeqCst);
+ // "loop" the index within 0..thread_limit
+ let thread_index = index % thread_limit;
+ let perc = (index * 100) / total_crates_to_lint;
+
+ if thread_limit == 1 {
+ println!(
+ "{}/{} {}% Linting {} {}",
+ index, total_crates_to_lint, perc, &self.name, &self.version
+ );
+ } else {
+ println!(
+ "{}/{} {}% Linting {} {} in target dir {:?}",
+ index, total_crates_to_lint, perc, &self.name, &self.version, thread_index
+ );
+ }
+
+ let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap();
+
+ let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir");
+
+ let mut args = if fix {
+ vec!["--fix", "--allow-no-vcs", "--", "--cap-lints=warn"]
+ } else {
+ vec!["--", "--message-format=json", "--", "--cap-lints=warn"]
+ };
+
+ if let Some(options) = &self.options {
+ for opt in options {
+ args.push(opt);
+ }
+ } else {
+ args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"])
+ }
+
+ let all_output = std::process::Command::new(&cargo_clippy_path)
+ // use the looping index to create individual target dirs
+ .env(
+ "CARGO_TARGET_DIR",
+ shared_target_dir.join(format!("_{:?}", thread_index)),
+ )
+ // lint warnings will look like this:
+ // src/cargo/ops/cargo_compile.rs:127:35: warning: usage of `FromIterator::from_iter`
+ .args(&args)
+ .current_dir(&self.path)
+ .output()
+ .unwrap_or_else(|error| {
+ panic!(
+ "Encountered error:\n{:?}\ncargo_clippy_path: {}\ncrate path:{}\n",
+ error,
+ &cargo_clippy_path.display(),
+ &self.path.display()
+ );
+ });
+ let stdout = String::from_utf8_lossy(&all_output.stdout);
+ let stderr = String::from_utf8_lossy(&all_output.stderr);
+ let status = &all_output.status;
+
+ if !status.success() {
+ eprintln!(
+ "\nWARNING: bad exit status after checking {} {} \n",
+ self.name, self.version
+ );
+ }
+
+ if fix {
+ if let Some(stderr) = stderr
+ .lines()
+ .find(|line| line.contains("failed to automatically apply fixes suggested by rustc to crate"))
+ {
+ let subcrate = &stderr[63..];
+ println!(
+ "ERROR: failed to apply some suggetion to {} / to (sub)crate {}",
+ self.name, subcrate
+ );
+ }
+ // fast path, we don't need the warnings anyway
+ return Vec::new();
+ }
+
+ let output_lines = stdout.lines();
+ let warnings: Vec<ClippyWarning> = output_lines
+ .into_iter()
+ // get all clippy warnings and ICEs
+ .filter(|line| filter_clippy_warnings(&line))
+ .map(|json_msg| parse_json_message(json_msg, &self))
+ .collect();
+
+ warnings
+ }
+}
+
+#[derive(Debug)]
+struct LintcheckConfig {
+ // max number of jobs to spawn (default 1)
+ max_jobs: usize,
+ // we read the sources to check from here
+ sources_toml_path: PathBuf,
+ // we save the clippy lint results here
+ lintcheck_results_path: PathBuf,
+ // whether to just run --fix and not collect all the warnings
+ fix: bool,
+}
+
+impl LintcheckConfig {
+ fn from_clap(clap_config: &ArgMatches) -> Self {
+ // first, check if we got anything passed via the LINTCHECK_TOML env var,
+ // if not, ask clap if we got any value for --crates-toml <foo>
+ // if not, use the default "lintcheck/lintcheck_crates.toml"
+ let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| {
+ clap_config
+ .value_of("crates-toml")
+ .clone()
+ .unwrap_or("lintcheck/lintcheck_crates.toml")
+ .to_string()
+ });
+
+ let sources_toml_path = PathBuf::from(sources_toml);
+
+ // for the path where we save the lint results, get the filename without extension (so for
+ // wasd.toml, use "wasd"...)
+ let filename: PathBuf = sources_toml_path.file_stem().unwrap().into();
+ let lintcheck_results_path = PathBuf::from(format!("lintcheck-logs/{}_logs.txt", filename.display()));
+
+ // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and
+ // use half of that for the physical core count
+ // by default use a single thread
+ let max_jobs = match clap_config.value_of("threads") {
+ Some(threads) => {
+ let threads: usize = threads
+ .parse()
+ .unwrap_or_else(|_| panic!("Failed to parse '{}' to a digit", threads));
+ if threads == 0 {
+ // automatic choice
+ // Rayon seems to return thread count so half that for core count
+ (rayon::current_num_threads() / 2) as usize
+ } else {
+ threads
+ }
+ },
+ // no -j passed, use a single thread
+ None => 1,
+ };
+ let fix: bool = clap_config.is_present("fix");
+
+ LintcheckConfig {
+ max_jobs,
+ sources_toml_path,
+ lintcheck_results_path,
+ fix,
+ }
+ }
+}
+
+/// takes a single json-formatted clippy warnings and returns true (we are interested in that line)
+/// or false (we aren't)
+fn filter_clippy_warnings(line: &str) -> bool {
+ // we want to collect ICEs because clippy might have crashed.
+ // these are summarized later
+ if line.contains("internal compiler error: ") {
+ return true;
+ }
+ // in general, we want all clippy warnings
+ // however due to some kind of bug, sometimes there are absolute paths
+ // to libcore files inside the message
+ // or we end up with cargo-metadata output (https://github.com/rust-lang/rust-clippy/issues/6508)
+
+ // filter out these message to avoid unnecessary noise in the logs
+ if line.contains("clippy::")
+ && !(line.contains("could not read cargo metadata")
+ || (line.contains(".rustup") && line.contains("toolchains")))
+ {
+ return true;
+ }
+ false
+}
+
+/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
+fn build_clippy() {
+ let status = Command::new("cargo")
+ .arg("build")
+ .status()
+ .expect("Failed to build clippy!");
+ if !status.success() {
+ eprintln!("Error: Failed to compile Clippy!");
+ std::process::exit(1);
+ }
+}
+
+/// Read a `toml` file and return a list of `CrateSources` that we want to check with clippy
+fn read_crates(toml_path: &Path) -> Vec<CrateSource> {
+ let toml_content: String =
+ std::fs::read_to_string(&toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
+ let crate_list: SourceList =
+ toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{}", toml_path.display(), e));
+ // parse the hashmap of the toml file into a list of crates
+ let tomlcrates: Vec<TomlCrate> = crate_list
+ .crates
+ .into_iter()
+ .map(|(_cratename, tomlcrate)| tomlcrate)
+ .collect();
+
+ // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
+ // multiple Cratesources)
+ let mut crate_sources = Vec::new();
+ tomlcrates.into_iter().for_each(|tk| {
+ if let Some(ref path) = tk.path {
+ crate_sources.push(CrateSource::Path {
+ name: tk.name.clone(),
+ path: PathBuf::from(path),
+ options: tk.options.clone(),
+ });
+ }
+
+ // if we have multiple versions, save each one
+ if let Some(ref versions) = tk.versions {
+ versions.iter().for_each(|ver| {
+ crate_sources.push(CrateSource::CratesIo {
+ name: tk.name.clone(),
+ version: ver.to_string(),
+ options: tk.options.clone(),
+ });
+ })
+ }
+ // otherwise, we should have a git source
+ if tk.git_url.is_some() && tk.git_hash.is_some() {
+ crate_sources.push(CrateSource::Git {
+ name: tk.name.clone(),
+ url: tk.git_url.clone().unwrap(),
+ commit: tk.git_hash.clone().unwrap(),
+ options: tk.options.clone(),
+ });
+ }
+ // if we have a version as well as a git data OR only one git data, something is funky
+ if tk.versions.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some())
+ || tk.git_hash.is_some() != tk.git_url.is_some()
+ {
+ eprintln!("tomlkrate: {:?}", tk);
+ if tk.git_hash.is_some() != tk.git_url.is_some() {
+ panic!("Error: Encountered TomlCrate with only one of git_hash and git_url!");
+ }
+ if tk.path.is_some() && (tk.git_hash.is_some() || tk.versions.is_some()) {
+ panic!("Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields");
+ }
+ unreachable!("Failed to translate TomlCrate into CrateSource!");
+ }
+ });
+ // sort the crates
+ crate_sources.sort();
+
+ crate_sources
+}
+
+/// Parse the json output of clippy and return a `ClippyWarning`
+fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning {
+ let jmsg: Value = serde_json::from_str(&json_message).unwrap_or_else(|e| panic!("Failed to parse json:\n{:?}", e));
+
+ let file: String = jmsg["message"]["spans"][0]["file_name"]
+ .to_string()
+ .trim_matches('"')
+ .into();
+
+ let file = if file.contains(".cargo") {
+ // if we deal with macros, a filename may show the origin of a macro which can be inside a dep from
+ // the registry.
+ // don't show the full path in that case.
+
+ // /home/matthias/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.63/src/custom_keyword.rs
+ let path = PathBuf::from(file);
+ let mut piter = path.iter();
+ // consume all elements until we find ".cargo", so that "/home/matthias" is skipped
+ let _: Option<&OsStr> = piter.find(|x| x == &std::ffi::OsString::from(".cargo"));
+ // collect the remaining segments
+ let file = piter.collect::<PathBuf>();
+ format!("{}", file.display())
+ } else {
+ file
+ };
+
+ ClippyWarning {
+ crate_name: krate.name.to_string(),
+ crate_version: krate.version.to_string(),
+ file,
+ line: jmsg["message"]["spans"][0]["line_start"]
+ .to_string()
+ .trim_matches('"')
+ .into(),
+ column: jmsg["message"]["spans"][0]["text"][0]["highlight_start"]
+ .to_string()
+ .trim_matches('"')
+ .into(),
+ linttype: jmsg["message"]["code"]["code"].to_string().trim_matches('"').into(),
+ message: jmsg["message"]["message"].to_string().trim_matches('"').into(),
+ is_ice: json_message.contains("internal compiler error: "),
+ }
+}
+
- // order to achive some kind of parallelism
++/// Generate a short list of occurring lints-types and their count
+fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) {
+ // count lint type occurrences
+ let mut counter: HashMap<&String, usize> = HashMap::new();
+ clippy_warnings
+ .iter()
+ .for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1);
+
+ // collect into a tupled list for sorting
+ let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect();
+ // sort by "000{count} {clippy::lintname}"
+ // to not have a lint with 200 and 2 warnings take the same spot
+ stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint));
+
+ let stats_string = stats
+ .iter()
+ .map(|(lint, count)| format!("{} {}\n", lint, count))
+ .collect::<String>();
+
+ (stats_string, counter)
+}
+
+/// check if the latest modification of the logfile is older than the modification date of the
+/// clippy binary, if this is true, we should clean the lintchec shared target directory and recheck
+fn lintcheck_needs_rerun(lintcheck_logs_path: &Path) -> bool {
+ if !lintcheck_logs_path.exists() {
+ return true;
+ }
+
+ let clippy_modified: std::time::SystemTime = {
+ let mut times = [CLIPPY_DRIVER_PATH, CARGO_CLIPPY_PATH].iter().map(|p| {
+ std::fs::metadata(p)
+ .expect("failed to get metadata of file")
+ .modified()
+ .expect("failed to get modification date")
+ });
+ // the oldest modification of either of the binaries
+ std::cmp::max(times.next().unwrap(), times.next().unwrap())
+ };
+
+ let logs_modified: std::time::SystemTime = std::fs::metadata(lintcheck_logs_path)
+ .expect("failed to get metadata of file")
+ .modified()
+ .expect("failed to get modification date");
+
+ // time is represented in seconds since X
+ // logs_modified 2 and clippy_modified 5 means clippy binary is older and we need to recheck
+ logs_modified < clippy_modified
+}
+
+fn is_in_clippy_root() -> bool {
+ if let Ok(pb) = std::env::current_dir() {
+ if let Some(file) = pb.file_name() {
+ return file == PathBuf::from("rust-clippy");
+ }
+ }
+
+ false
+}
+
+/// lintchecks `main()` function
+///
+/// # Panics
+///
+/// This function panics if the clippy binaries don't exist
+/// or if lintcheck is executed from the wrong directory (aka none-repo-root)
+pub fn main() {
+ // assert that we launch lintcheck from the repo root (via cargo lintcheck)
+ if !is_in_clippy_root() {
+ eprintln!("lintcheck needs to be run from clippys repo root!\nUse `cargo lintcheck` alternatively.");
+ std::process::exit(3);
+ }
+
+ let clap_config = &get_clap_config();
+
+ let config = LintcheckConfig::from_clap(clap_config);
+
+ println!("Compiling clippy...");
+ build_clippy();
+ println!("Done compiling");
+
+ // if the clippy bin is newer than our logs, throw away target dirs to force clippy to
+ // refresh the logs
+ if lintcheck_needs_rerun(&config.lintcheck_results_path) {
+ let shared_target_dir = "target/lintcheck/shared_target_dir";
+ // if we get an Err here, the shared target dir probably does simply not exist
+ if let Ok(metadata) = std::fs::metadata(&shared_target_dir) {
+ if metadata.is_dir() {
+ println!("Clippy is newer than lint check logs, clearing lintcheck shared target dir...");
+ std::fs::remove_dir_all(&shared_target_dir)
+ .expect("failed to remove target/lintcheck/shared_target_dir");
+ }
+ }
+ }
+
+ let cargo_clippy_path: PathBuf = PathBuf::from(CARGO_CLIPPY_PATH)
+ .canonicalize()
+ .expect("failed to canonicalize path to clippy binary");
+
+ // assert that clippy is found
+ assert!(
+ cargo_clippy_path.is_file(),
+ "target/debug/cargo-clippy binary not found! {}",
+ cargo_clippy_path.display()
+ );
+
+ let clippy_ver = std::process::Command::new(CARGO_CLIPPY_PATH)
+ .arg("--version")
+ .output()
+ .map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
+ .expect("could not get clippy version!");
+
+ // download and extract the crates, then run clippy on them and collect clippys warnings
+ // flatten into one big list of warnings
+
+ let crates = read_crates(&config.sources_toml_path);
+ let old_stats = read_stats_from_file(&config.lintcheck_results_path);
+
+ let counter = AtomicUsize::new(1);
+
+ let clippy_warnings: Vec<ClippyWarning> = if let Some(only_one_crate) = clap_config.value_of("only") {
+ // if we don't have the specified crate in the .toml, throw an error
+ if !crates.iter().any(|krate| {
+ let name = match krate {
+ CrateSource::CratesIo { name, .. } | CrateSource::Git { name, .. } | CrateSource::Path { name, .. } => {
+ name
+ },
+ };
+ name == only_one_crate
+ }) {
+ eprintln!(
+ "ERROR: could not find crate '{}' in lintcheck/lintcheck_crates.toml",
+ only_one_crate
+ );
+ std::process::exit(1);
+ }
+
+ // only check a single crate that was passed via cmdline
+ crates
+ .into_iter()
+ .map(|krate| krate.download_and_extract())
+ .filter(|krate| krate.name == only_one_crate)
+ .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &AtomicUsize::new(0), 1, 1, config.fix))
+ .collect()
+ } else {
+ if config.max_jobs > 1 {
+ // run parallel with rayon
+
+ // Ask rayon for thread count. Assume that half of that is the number of physical cores
+ // Use one target dir for each core so that we can run N clippys in parallel.
+ // We need to use different target dirs because cargo would lock them for a single build otherwise,
+ // killing the parallelism. However this also means that deps will only be reused half/a
+ // quarter of the time which might result in a longer wall clock runtime
+
+ // This helps when we check many small crates with dep-trees that don't have a lot of branches in
++ // order to achieve some kind of parallelism
+
+ // by default, use a single thread
+ let num_cpus = config.max_jobs;
+ let num_crates = crates.len();
+
+ // check all crates (default)
+ crates
+ .into_par_iter()
+ .map(|krate| krate.download_and_extract())
+ .flat_map(|krate| {
+ krate.run_clippy_lints(&cargo_clippy_path, &counter, num_cpus, num_crates, config.fix)
+ })
+ .collect()
+ } else {
+ // run sequential
+ let num_crates = crates.len();
+ crates
+ .into_iter()
+ .map(|krate| krate.download_and_extract())
+ .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, 1, num_crates, config.fix))
+ .collect()
+ }
+ };
+
+ // if we are in --fix mode, don't change the log files, terminate here
+ if config.fix {
+ return;
+ }
+
+ // generate some stats
+ let (stats_formatted, new_stats) = gather_stats(&clippy_warnings);
+
+ // grab crashes/ICEs, save the crate name and the ice message
+ let ices: Vec<(&String, &String)> = clippy_warnings
+ .iter()
+ .filter(|warning| warning.is_ice)
+ .map(|w| (&w.crate_name, &w.message))
+ .collect();
+
+ let mut all_msgs: Vec<String> = clippy_warnings.iter().map(ToString::to_string).collect();
+ all_msgs.sort();
+ all_msgs.push("\n\n\n\nStats:\n".into());
+ all_msgs.push(stats_formatted);
+
+ // save the text into lintcheck-logs/logs.txt
+ let mut text = clippy_ver; // clippy version number on top
+ text.push_str(&format!("\n{}", all_msgs.join("")));
+ text.push_str("ICEs:\n");
+ ices.iter()
+ .for_each(|(cratename, msg)| text.push_str(&format!("{}: '{}'", cratename, msg)));
+
+ println!("Writing logs to {}", config.lintcheck_results_path.display());
+ write(&config.lintcheck_results_path, text).unwrap();
+
+ print_stats(old_stats, new_stats);
+}
+
+/// read the previous stats from the lintcheck-log file
+fn read_stats_from_file(file_path: &Path) -> HashMap<String, usize> {
+ let file_content: String = match std::fs::read_to_string(file_path).ok() {
+ Some(content) => content,
+ None => {
+ return HashMap::new();
+ },
+ };
+
+ let lines: Vec<String> = file_content.lines().map(ToString::to_string).collect();
+
+ // search for the beginning "Stats:" and the end "ICEs:" of the section we want
+ let start = lines.iter().position(|line| line == "Stats:").unwrap();
+ let end = lines.iter().position(|line| line == "ICEs:").unwrap();
+
+ let stats_lines = &lines[start + 1..end];
+
+ stats_lines
+ .iter()
+ .map(|line| {
+ let mut spl = line.split(' ');
+ (
+ spl.next().unwrap().to_string(),
+ spl.next().unwrap().parse::<usize>().unwrap(),
+ )
+ })
+ .collect::<HashMap<String, usize>>()
+}
+
+/// print how lint counts changed between runs
+fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>) {
+ let same_in_both_hashmaps = old_stats
+ .iter()
+ .filter(|(old_key, old_val)| new_stats.get::<&String>(&old_key) == Some(old_val))
+ .map(|(k, v)| (k.to_string(), *v))
+ .collect::<Vec<(String, usize)>>();
+
+ let mut old_stats_deduped = old_stats;
+ let mut new_stats_deduped = new_stats;
+
+ // remove duplicates from both hashmaps
+ same_in_both_hashmaps.iter().for_each(|(k, v)| {
+ assert!(old_stats_deduped.remove(k) == Some(*v));
+ assert!(new_stats_deduped.remove(k) == Some(*v));
+ });
+
+ println!("\nStats:");
+
+ // list all new counts (key is in new stats but not in old stats)
+ new_stats_deduped
+ .iter()
+ .filter(|(new_key, _)| old_stats_deduped.get::<str>(&new_key).is_none())
+ .for_each(|(new_key, new_value)| {
+ println!("{} 0 => {}", new_key, new_value);
+ });
+
+ // list all changed counts (key is in both maps but value differs)
+ new_stats_deduped
+ .iter()
+ .filter(|(new_key, _new_val)| old_stats_deduped.get::<str>(&new_key).is_some())
+ .for_each(|(new_key, new_val)| {
+ let old_val = old_stats_deduped.get::<str>(&new_key).unwrap();
+ println!("{} {} => {}", new_key, old_val, new_val);
+ });
+
+ // list all gone counts (key is in old status but not in new stats)
+ old_stats_deduped
+ .iter()
+ .filter(|(old_key, _)| new_stats_deduped.get::<&String>(&old_key).is_none())
+ .for_each(|(old_key, old_value)| {
+ println!("{} {} => 0", old_key, old_value);
+ });
+}
+
+/// Create necessary directories to run the lintcheck tool.
+///
+/// # Panics
+///
+/// This function panics if creating one of the dirs fails.
+fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) {
+ std::fs::create_dir("target/lintcheck/").unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create lintcheck target dir");
+ }
+ });
+ std::fs::create_dir(&krate_download_dir).unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create crate download dir");
+ }
+ });
+ std::fs::create_dir(&extract_dir).unwrap_or_else(|err| {
+ if err.kind() != ErrorKind::AlreadyExists {
+ panic!("cannot create crate extraction dir");
+ }
+ });
+}
+
+fn get_clap_config<'a>() -> ArgMatches<'a> {
+ App::new("lintcheck")
+ .about("run clippy on a set of crates and check output")
+ .arg(
+ Arg::with_name("only")
+ .takes_value(true)
+ .value_name("CRATE")
+ .long("only")
+ .help("only process a single crate of the list"),
+ )
+ .arg(
+ Arg::with_name("crates-toml")
+ .takes_value(true)
+ .value_name("CRATES-SOURCES-TOML-PATH")
+ .long("crates-toml")
+ .help("set the path for a crates.toml where lintcheck should read the sources from"),
+ )
+ .arg(
+ Arg::with_name("threads")
+ .takes_value(true)
+ .value_name("N")
+ .short("j")
+ .long("jobs")
+ .help("number of threads to use, 0 automatic choice"),
+ )
+ .arg(
+ Arg::with_name("fix")
+ .long("--fix")
+ .help("runs cargo clippy --fix and checks if all suggestions apply"),
+ )
+ .get_matches()
+}
+
+/// Returns the path to the Clippy project directory
+///
+/// # Panics
+///
+/// Panics if the current directory could not be retrieved, there was an error reading any of the
+/// Cargo.toml files or ancestor directory is the clippy root directory
+#[must_use]
+pub fn clippy_project_root() -> PathBuf {
+ let current_dir = std::env::current_dir().unwrap();
+ for path in current_dir.ancestors() {
+ let result = std::fs::read_to_string(path.join("Cargo.toml"));
+ if let Err(err) = &result {
+ if err.kind() == std::io::ErrorKind::NotFound {
+ continue;
+ }
+ }
+
+ let content = result.unwrap();
+ if content.contains("[package]\nname = \"clippy\"") {
+ return path.to_path_buf();
+ }
+ }
+ panic!("error: Can't determine root of project. Please run inside a Clippy working dir.");
+}
+
+#[test]
+fn lintcheck_test() {
+ let args = [
+ "run",
+ "--target-dir",
+ "lintcheck/target",
+ "--manifest-path",
+ "./lintcheck/Cargo.toml",
+ "--",
+ "--crates-toml",
+ "lintcheck/test_sources.toml",
+ ];
+ let status = std::process::Command::new("cargo")
+ .args(&args)
+ .current_dir("..") // repo root
+ .status();
+ //.output();
+
+ assert!(status.unwrap().success());
+}
--- /dev/null
- channel = "nightly-2021-11-04"
- components = ["llvm-tools-preview", "rustc-dev", "rust-src"]
+[toolchain]
++channel = "nightly-2021-12-02"
++components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
--- /dev/null
- clippy_lints::register_pre_expansion_lints(lint_store);
+#![feature(rustc_private)]
+#![feature(once_cell)]
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+// warn on lints, that are included in `rust-lang/rust`s bootstrap
+#![warn(rust_2018_idioms, unused_lifetimes)]
+// warn on rustc internal lints
+#![warn(rustc::internal)]
+
+// FIXME: switch to something more ergonomic here, once available.
+// (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate rustc_driver;
+extern crate rustc_errors;
+extern crate rustc_interface;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use rustc_interface::interface;
+use rustc_session::parse::ParseSess;
+use rustc_span::symbol::Symbol;
+use rustc_tools_util::VersionInfo;
+
+use std::borrow::Cow;
+use std::env;
+use std::lazy::SyncLazy;
+use std::ops::Deref;
+use std::panic;
+use std::path::{Path, PathBuf};
+use std::process::{exit, Command};
+
+/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
+/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
+fn arg_value<'a, T: Deref<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);
+}
+
+fn track_clippy_args(parse_sess: &mut ParseSess, args_env_var: &Option<String>) {
+ parse_sess.env_depinfo.get_mut().insert((
+ Symbol::intern("CLIPPY_ARGS"),
+ args_env_var.as_deref().map(Symbol::intern),
+ ));
+}
+
+struct DefaultCallbacks;
+impl rustc_driver::Callbacks for DefaultCallbacks {}
+
+/// This is different from `DefaultCallbacks` that it will inform Cargo to track the value of
+/// `CLIPPY_ARGS` environment variable.
+struct RustcCallbacks {
+ clippy_args_var: Option<String>,
+}
+
+impl rustc_driver::Callbacks for RustcCallbacks {
+ fn config(&mut self, config: &mut interface::Config) {
+ let clippy_args_var = self.clippy_args_var.take();
+ config.parse_sess_created = Some(Box::new(move |parse_sess| {
+ track_clippy_args(parse_sess, &clippy_args_var);
+ }));
+ }
+}
+
+struct ClippyCallbacks {
+ clippy_args_var: Option<String>,
+}
+
+impl rustc_driver::Callbacks for ClippyCallbacks {
+ fn config(&mut self, config: &mut interface::Config) {
+ let previous = config.register_lints.take();
+ let clippy_args_var = self.clippy_args_var.take();
+ config.parse_sess_created = Some(Box::new(move |parse_sess| {
+ track_clippy_args(parse_sess, &clippy_args_var);
+ }));
+ config.register_lints = Some(Box::new(move |sess, 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(lint_store, sess, &conf);
++ clippy_lints::register_pre_expansion_lints(lint_store, sess, &conf);
+ clippy_lints::register_renamed(lint_store);
+ }));
+
+ // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be
+ // run on the unoptimized MIR. On the other hand this results in some false negatives. If
+ // MIR passes can be enabled / disabled separately, we should figure out, what passes to
+ // use for Clippy.
+ config.opts.debugging_opts.mir_opt_level = Some(0);
+ }
+}
+
+fn display_help() {
+ println!(
+ "\
+Checks a package to catch common mistakes and improve your Rust code.
+
+Usage:
+ cargo clippy [options] [--] [<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";
+
+static ICE_HOOK: SyncLazy<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> = SyncLazy::new(|| {
+ let hook = panic::take_hook();
+ panic::set_hook(Box::new(|info| report_clippy_ice(info, BUG_REPORT_URL)));
+ hook
+});
+
+fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
+ // Invoke our ICE handler, which prints the actual panic message and optionally a backtrace
+ (*ICE_HOOK)(info);
+
+ // Separate the output with an empty line
+ eprintln!();
+
+ let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr(
+ rustc_errors::ColorConfig::Auto,
+ None,
+ false,
+ false,
+ None,
+ false,
+ ));
+ let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
+
+ // a .span_bug or .bug call has already printed what
+ // it wants to print.
+ if !info.payload().is::<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(note);
+ }
+
+ // If backtraces are enabled, also print the query stack
+ let backtrace = env::var_os("RUST_BACKTRACE").map_or(false, |x| &x != "0");
+
+ let num_frames = if backtrace { None } else { Some(2) };
+
+ interface::try_print_query_stack(&handler, num_frames);
+}
+
+fn toolchain_path(home: Option<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
+ })
+ })
+}
+
+#[allow(clippy::too_many_lines)]
+pub fn main() {
+ rustc_driver::init_rustc_env_logger();
+ SyncLazy::force(&ICE_HOOK);
+ exit(rustc_driver::catch_with_exit_code(move || {
+ let mut orig_args: Vec<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::RunCompiler::new(&args, &mut DefaultCallbacks).run();
+ }
+
+ if orig_args.iter().any(|a| a == "--version" || a == "-V") {
+ let version_info = rustc_tools_util::get_version_info!();
+ println!("{}", version_info);
+ exit(0);
+ }
+
+ // Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.
+ // We're invoking the compiler programmatically, so we ignore this/
+ let wrapper_mode = orig_args.get(1).map(Path::new).and_then(Path::file_stem) == Some("rustc".as_ref());
+
+ if wrapper_mode {
+ // we still want to be able to invoke it normally though
+ orig_args.remove(1);
+ }
+
+ if !wrapper_mode && (orig_args.iter().any(|a| a == "--help" || a == "-h") || orig_args.len() == 1) {
+ display_help();
+ exit(0);
+ }
+
+ // this conditional check for the --sysroot flag is there so users can call
+ // `clippy_driver` directly
+ // without having to pass --sysroot or anything
+ let mut args: Vec<String> = orig_args.clone();
+ if !have_sys_root_arg {
+ args.extend(vec!["--sysroot".into(), sys_root]);
+ };
+
+ let mut no_deps = false;
+ let clippy_args_var = env::var("CLIPPY_ARGS").ok();
+ let clippy_args = clippy_args_var
+ .as_deref()
+ .unwrap_or_default()
+ .split("__CLIPPY_HACKERY__")
+ .filter_map(|s| match s {
+ "" => None,
+ "--no-deps" => {
+ no_deps = true;
+ None
+ },
+ _ => Some(s.to_string()),
+ })
+ .chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()])
+ .collect::<Vec<String>>();
+
+ // We enable Clippy if one of the following conditions is met
+ // - IF Clippy is run on its test suite OR
+ // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN
+ // - IF `--no-deps` is not set (`!no_deps`) OR
+ // - IF `--no-deps` is set and Clippy is run on the specified primary package
+ let clippy_tests_set = env::var("__CLIPPY_INTERNAL_TESTS").map_or(false, |val| val == "true");
+ let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some();
+ let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok();
+
+ let clippy_enabled = clippy_tests_set || (!cap_lints_allow && (!no_deps || in_primary_package));
+ if clippy_enabled {
+ args.extend(clippy_args);
+ }
+
+ if clippy_enabled {
+ rustc_driver::RunCompiler::new(&args, &mut ClippyCallbacks { clippy_args_var }).run()
+ } else {
+ rustc_driver::RunCompiler::new(&args, &mut RustcCallbacks { clippy_args_var }).run()
+ }
+ }))
+}
--- /dev/null
+#![feature(test)] // compiletest_rs requires this attribute
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use compiletest_rs as compiletest;
+use compiletest_rs::common::Mode as TestMode;
+
+use std::collections::HashMap;
+use std::env::{self, remove_var, set_var, var_os};
+use std::ffi::{OsStr, OsString};
+use std::fs;
+use std::io;
+use std::path::{Path, PathBuf};
+
+mod cargo;
+
+// whether to run internal tests or not
+const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
+
+/// All crates used in UI tests are listed here
+static TEST_DEPENDENCIES: &[&str] = &[
+ "clippy_utils",
+ "derive_new",
+ "if_chain",
+ "itertools",
+ "quote",
+ "regex",
+ "serde",
+ "serde_derive",
+ "syn",
++ "parking_lot",
+];
+
+// Test dependencies may need an `extern crate` here to ensure that they show up
+// in the depinfo file (otherwise cargo thinks they are unused)
+#[allow(unused_extern_crates)]
+extern crate clippy_utils;
+#[allow(unused_extern_crates)]
+extern crate derive_new;
+#[allow(unused_extern_crates)]
+extern crate if_chain;
+#[allow(unused_extern_crates)]
+extern crate itertools;
+#[allow(unused_extern_crates)]
++extern crate parking_lot;
++#[allow(unused_extern_crates)]
+extern crate quote;
+#[allow(unused_extern_crates)]
+extern crate syn;
+
+/// Produces a string with an `--extern` flag for all UI test crate
+/// dependencies.
+///
+/// The dependency files are located by parsing the depinfo file for this test
+/// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
+/// dependencies must be added to Cargo.toml at the project root. Test
+/// dependencies that are not *directly* used by this test module require an
+/// `extern crate` declaration.
+fn extern_flags() -> String {
+ let current_exe_depinfo = {
+ let mut path = env::current_exe().unwrap();
+ path.set_extension("d");
+ std::fs::read_to_string(path).unwrap()
+ };
+ let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
+ for line in current_exe_depinfo.lines() {
+ // each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
+ let parse_name_path = || {
+ if line.starts_with(char::is_whitespace) {
+ return None;
+ }
+ let path_str = line.strip_suffix(':')?;
+ let path = Path::new(path_str);
+ if !matches!(path.extension()?.to_str()?, "rlib" | "so" | "dylib" | "dll") {
+ return None;
+ }
+ let (name, _hash) = path.file_stem()?.to_str()?.rsplit_once('-')?;
+ // the "lib" prefix is not present for dll files
+ let name = name.strip_prefix("lib").unwrap_or(name);
+ Some((name, path_str))
+ };
+ if let Some((name, path)) = parse_name_path() {
+ if TEST_DEPENDENCIES.contains(&name) {
+ // A dependency may be listed twice if it is available in sysroot,
+ // and the sysroot dependencies are listed first. As of the writing,
+ // this only seems to apply to if_chain.
+ crates.insert(name, path);
+ }
+ }
+ }
+ let not_found: Vec<&str> = TEST_DEPENDENCIES
+ .iter()
+ .copied()
+ .filter(|n| !crates.contains_key(n))
+ .collect();
+ assert!(
+ not_found.is_empty(),
+ "dependencies not found in depinfo: {:?}\n\
+ help: Make sure the `-Z binary-dep-depinfo` rust flag is enabled\n\
+ help: Try adding to dev-dependencies in Cargo.toml",
+ not_found
+ );
+ crates
+ .into_iter()
+ .map(|(name, path)| format!(" --extern {}={}", name, path))
+ .collect()
+}
+
+fn default_config() -> compiletest::Config {
+ let mut config = compiletest::Config {
+ edition: Some("2021".into()),
+ ..compiletest::Config::default()
+ };
+
+ if let Ok(filters) = env::var("TESTNAME") {
+ config.filters = filters.split(',').map(std::string::ToString::to_string).collect();
+ }
+
+ if let Some(path) = option_env!("RUSTC_LIB_PATH") {
+ let path = PathBuf::from(path);
+ config.run_lib_path = path.clone();
+ config.compile_lib_path = path;
+ }
+ let current_exe_path = std::env::current_exe().unwrap();
+ let deps_path = current_exe_path.parent().unwrap();
+ let profile_path = deps_path.parent().unwrap();
+
+ // Using `-L dependency={}` enforces that external dependencies are added with `--extern`.
+ // This is valuable because a) it allows us to monitor what external dependencies are used
+ // and b) it ensures that conflicting rlibs are resolved properly.
+ let host_libs = option_env!("HOST_LIBS")
+ .map(|p| format!(" -L dependency={}", Path::new(p).join("deps").display()))
+ .unwrap_or_default();
+ config.target_rustcflags = Some(format!(
+ "--emit=metadata -Dwarnings -Zui-testing -L dependency={}{}{}",
+ deps_path.display(),
+ host_libs,
+ extern_flags(),
+ ));
+
+ config.build_base = profile_path.join("test");
+ config.rustc_path = profile_path.join(if cfg!(windows) {
+ "clippy-driver.exe"
+ } else {
+ "clippy-driver"
+ });
+ config
+}
+
+fn run_ui(cfg: &mut compiletest::Config) {
+ cfg.mode = TestMode::Ui;
+ cfg.src_base = Path::new("tests").join("ui");
+ // use tests/clippy.toml
+ let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
+ compiletest::run_tests(cfg);
+}
+
+fn run_ui_test(cfg: &mut compiletest::Config) {
+ cfg.mode = TestMode::Ui;
+ cfg.src_base = Path::new("tests").join("ui_test");
+ let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
+ let rustcflags = cfg.target_rustcflags.get_or_insert_with(Default::default);
+ let len = rustcflags.len();
+ rustcflags.push_str(" --test");
+ compiletest::run_tests(cfg);
+ if let Some(ref mut flags) = &mut cfg.target_rustcflags {
+ flags.truncate(len);
+ }
+}
+
+fn run_internal_tests(cfg: &mut compiletest::Config) {
+ // only run internal tests with the internal-tests feature
+ if !RUN_INTERNAL_TESTS {
+ return;
+ }
+ cfg.mode = TestMode::Ui;
+ cfg.src_base = Path::new("tests").join("ui-internal");
+ compiletest::run_tests(cfg);
+}
+
+fn run_ui_toml(config: &mut compiletest::Config) {
+ fn run_tests(config: &compiletest::Config, mut tests: Vec<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();
+ let _g = VarGuard::set("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) {
+ fn run_tests(
+ config: &compiletest::Config,
+ filters: &[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();
+ for filter in filters {
+ if !dir_path.ends_with(filter) {
+ continue;
+ }
+ }
+
+ for case in fs::read_dir(&dir_path)? {
+ let case = case?;
+ if !case.file_type()?.is_dir() {
+ continue;
+ }
+
+ let src_path = case.path().join("src");
+
+ // When switching between branches, if the previous branch had a test
+ // that the current branch does not have, the directory is not removed
+ // because an ignored Cargo.lock file exists.
+ if !src_path.exists() {
+ continue;
+ }
+
+ env::set_current_dir(&src_path)?;
+ for file in fs::read_dir(&src_path)? {
+ let file = file?;
+ if file.file_type()?.is_dir() {
+ continue;
+ }
+
+ // Search for the main file to avoid running a test for each file in the project
+ let file_path = file.path();
+ match file_path.file_name().and_then(OsStr::to_str) {
+ Some("main.rs") => {},
+ _ => continue,
+ }
+ let _g = VarGuard::set("CLIPPY_CONF_DIR", case.path());
+ let paths = compiletest::common::TestPaths {
+ file: file_path,
+ base: config.src_base.clone(),
+ relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(),
+ };
+ let test_name = compiletest::make_test_name(config, &paths);
+ let index = tests
+ .iter()
+ .position(|test| test.desc.name == test_name)
+ .expect("The test should be in there");
+ result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
+ }
+ }
+ }
+ Ok(result)
+ }
+
+ if cargo::is_rustc_test_suite() {
+ return;
+ }
+
+ config.mode = TestMode::Ui;
+ config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap();
+
+ let tests = compiletest::make_tests(config);
+
+ let current_dir = env::current_dir().unwrap();
+ let res = run_tests(config, &config.filters, 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_INTERNAL_TESTS", "true");
+ //set_var("RUST_BACKTRACE", "0");
+}
+
+#[test]
+fn compile_test() {
+ prepare_env();
+ let mut config = default_config();
+ run_ui(&mut config);
+ run_ui_test(&mut config);
+ run_ui_toml(&mut config);
+ run_ui_cargo(&mut config);
+ run_internal_tests(&mut config);
+}
+
+/// Restores an env var on drop
+#[must_use]
+struct VarGuard {
+ key: &'static str,
+ value: Option<OsString>,
+}
+
+impl VarGuard {
+ fn set(key: &'static str, val: impl AsRef<OsStr>) -> Self {
+ let value = var_os(key);
+ set_var(key, val);
+ Self { key, value }
+ }
+}
+
+impl Drop for VarGuard {
+ fn drop(&mut self) {
+ match self.value.as_deref() {
+ None => remove_var(self.key),
+ Some(value) => set_var(self.key, value),
+ }
+ }
+}
--- /dev/null
- let rustup_output = Command::new("rustup")
- .args(&["component", "list", "--toolchain", "nightly"])
- .output()
- .unwrap();
+#![cfg_attr(feature = "deny-warnings", deny(warnings))]
+#![warn(rust_2018_idioms, unused_lifetimes)]
+
+use std::path::PathBuf;
+use std::process::Command;
+
+#[test]
+fn fmt() {
+ if option_env!("RUSTC_TEST_SUITE").is_some() || option_env!("NO_FMT_TEST").is_some() {
+ return;
+ }
+
+ // Skip this test if nightly rustfmt is unavailable
++ let rustup_output = Command::new("rustup").args(&["component", "list"]).output().unwrap();
+ assert!(rustup_output.status.success());
+ let component_output = String::from_utf8_lossy(&rustup_output.stdout);
+ if !component_output.contains("rustfmt") {
+ return;
+ }
+
+ let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ let output = Command::new("cargo")
+ .current_dir(root_dir)
+ .args(&["dev", "fmt", "--check"])
+ .output()
+ .unwrap();
+
+ println!("status: {}", output.status);
+ println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
+ println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
+
+ assert!(
+ output.status.success(),
+ "Formatting check failed. Run `cargo dev fmt` to update formatting."
+ );
+}
--- /dev/null
--- /dev/null
++#![deny(clippy::internal)]
++#![feature(rustc_private)]
++
++#[macro_use]
++extern crate rustc_middle;
++#[macro_use]
++extern crate rustc_session;
++extern crate rustc_lint;
++
++///////////////////////
++// Valid descriptions
++///////////////////////
++declare_tool_lint! {
++ #[clippy::version = "pre 1.29.0"]
++ pub clippy::VALID_ONE,
++ Warn,
++ "One",
++ report_in_external_macro: true
++}
++
++declare_tool_lint! {
++ #[clippy::version = "1.29.0"]
++ pub clippy::VALID_TWO,
++ Warn,
++ "Two",
++ report_in_external_macro: true
++}
++
++declare_tool_lint! {
++ #[clippy::version = "1.59.0"]
++ pub clippy::VALID_THREE,
++ Warn,
++ "Three",
++ report_in_external_macro: true
++}
++
++///////////////////////
++// Invalid attributes
++///////////////////////
++declare_tool_lint! {
++ #[clippy::version = "1.2.3.4.5.6"]
++ pub clippy::INVALID_ONE,
++ Warn,
++ "One",
++ report_in_external_macro: true
++}
++
++declare_tool_lint! {
++ #[clippy::version = "I'm a string"]
++ pub clippy::INVALID_TWO,
++ Warn,
++ "Two",
++ report_in_external_macro: true
++}
++
++///////////////////////
++// Missing attribute test
++///////////////////////
++declare_tool_lint! {
++ #[clippy::version]
++ pub clippy::MISSING_ATTRIBUTE_ONE,
++ Warn,
++ "Two",
++ report_in_external_macro: true
++}
++
++declare_tool_lint! {
++ pub clippy::MISSING_ATTRIBUTE_TWO,
++ Warn,
++ "Two",
++ report_in_external_macro: true
++}
++
++#[allow(clippy::missing_clippy_version_attribute)]
++mod internal_clippy_lints {
++ declare_tool_lint! {
++ pub clippy::ALLOW_MISSING_ATTRIBUTE_ONE,
++ Warn,
++ "Two",
++ report_in_external_macro: true
++ }
++}
++
++use crate::internal_clippy_lints::ALLOW_MISSING_ATTRIBUTE_ONE;
++declare_lint_pass!(Pass2 => [VALID_ONE, VALID_TWO, VALID_THREE, INVALID_ONE, INVALID_TWO, MISSING_ATTRIBUTE_ONE, MISSING_ATTRIBUTE_TWO, ALLOW_MISSING_ATTRIBUTE_ONE]);
++
++fn main() {}
--- /dev/null
--- /dev/null
++error: this item has an invalid `clippy::version` attribute
++ --> $DIR/check_clippy_version_attribute.rs:40:1
++ |
++LL | / declare_tool_lint! {
++LL | | #[clippy::version = "1.2.3.4.5.6"]
++LL | | pub clippy::INVALID_ONE,
++LL | | Warn,
++LL | | "One",
++LL | | report_in_external_macro: true
++LL | | }
++ | |_^
++ |
++note: the lint level is defined here
++ --> $DIR/check_clippy_version_attribute.rs:1:9
++ |
++LL | #![deny(clippy::internal)]
++ | ^^^^^^^^^^^^^^^^
++ = note: `#[deny(clippy::invalid_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
++ = help: please use a valid sematic version, see `doc/adding_lints.md`
++ = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: this item has an invalid `clippy::version` attribute
++ --> $DIR/check_clippy_version_attribute.rs:48:1
++ |
++LL | / declare_tool_lint! {
++LL | | #[clippy::version = "I'm a string"]
++LL | | pub clippy::INVALID_TWO,
++LL | | Warn,
++LL | | "Two",
++LL | | report_in_external_macro: true
++LL | | }
++ | |_^
++ |
++ = help: please use a valid sematic version, see `doc/adding_lints.md`
++ = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: this lint is missing the `clippy::version` attribute or version value
++ --> $DIR/check_clippy_version_attribute.rs:59:1
++ |
++LL | / declare_tool_lint! {
++LL | | #[clippy::version]
++LL | | pub clippy::MISSING_ATTRIBUTE_ONE,
++LL | | Warn,
++LL | | "Two",
++LL | | report_in_external_macro: true
++LL | | }
++ | |_^
++ |
++note: the lint level is defined here
++ --> $DIR/check_clippy_version_attribute.rs:1:9
++ |
++LL | #![deny(clippy::internal)]
++ | ^^^^^^^^^^^^^^^^
++ = note: `#[deny(clippy::missing_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
++ = help: please use a `clippy::version` attribute, see `doc/adding_lints.md`
++ = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: this lint is missing the `clippy::version` attribute or version value
++ --> $DIR/check_clippy_version_attribute.rs:67:1
++ |
++LL | / declare_tool_lint! {
++LL | | pub clippy::MISSING_ATTRIBUTE_TWO,
++LL | | Warn,
++LL | | "Two",
++LL | | report_in_external_macro: true
++LL | | }
++ | |_^
++ |
++ = help: please use a `clippy::version` attribute, see `doc/adding_lints.md`
++ = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
++
++error: aborting due to 4 previous errors
++
--- /dev/null
+// run-rustfix
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate clippy_utils;
+extern crate rustc_ast;
+extern crate rustc_errors;
+extern crate rustc_lint;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
+use rustc_ast::ast::Expr;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_tool_lint! {
+ pub clippy::TEST_LINT,
+ Warn,
+ "",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+impl EarlyLintPass for Pass {
+ fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
+ let lint_msg = "lint message";
+ let help_msg = "help message";
+ let note_msg = "note message";
+ let sugg = "new_call()";
+ let predicate = true;
+
+ span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable);
+ span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg);
+ span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg);
+ span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg);
+ span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg);
+
+ // This expr shouldn't trigger this lint.
+ span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+ db.note(note_msg);
+ if predicate {
+ db.note(note_msg);
+ }
+ })
+ }
+}
+
+fn main() {}
--- /dev/null
+// run-rustfix
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate clippy_utils;
+extern crate rustc_ast;
+extern crate rustc_errors;
+extern crate rustc_lint;
+extern crate rustc_session;
+extern crate rustc_span;
+
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
+use rustc_ast::ast::Expr;
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_tool_lint! {
+ pub clippy::TEST_LINT,
+ Warn,
+ "",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+impl EarlyLintPass for Pass {
+ fn check_expr(&mut self, cx: &EarlyContext, expr: &Expr) {
+ let lint_msg = "lint message";
+ let help_msg = "help message";
+ let note_msg = "note message";
+ let sugg = "new_call()";
+ let predicate = true;
+
+ span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+ db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
+ });
+ span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+ db.span_help(expr.span, help_msg);
+ });
+ span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+ db.help(help_msg);
+ });
+ span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+ db.span_note(expr.span, note_msg);
+ });
+ span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+ db.note(note_msg);
+ });
+
+ // This expr shouldn't trigger this lint.
+ span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+ db.note(note_msg);
+ if predicate {
+ db.note(note_msg);
+ }
+ })
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/collapsible_span_lint_calls.rs:35:9
+error: this call is collapsible
- --> $DIR/collapsible_span_lint_calls.rs:38:9
++ --> $DIR/collapsible_span_lint_calls.rs:36:9
+ |
+LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
+LL | | });
+ | |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, help_msg, sugg.to_string(), Applicability::MachineApplicable)`
+ |
+note: the lint level is defined here
+ --> $DIR/collapsible_span_lint_calls.rs:2:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]`
+
+error: this call is collapsible
- --> $DIR/collapsible_span_lint_calls.rs:41:9
++ --> $DIR/collapsible_span_lint_calls.rs:39:9
+ |
+LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | | db.span_help(expr.span, help_msg);
+LL | | });
+ | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)`
+
+error: this call is collapsible
- --> $DIR/collapsible_span_lint_calls.rs:44:9
++ --> $DIR/collapsible_span_lint_calls.rs:42:9
+ |
+LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | | db.help(help_msg);
+LL | | });
+ | |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)`
+
+error: this call is collspible
- --> $DIR/collapsible_span_lint_calls.rs:47:9
++ --> $DIR/collapsible_span_lint_calls.rs:45:9
+ |
+LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | | db.span_note(expr.span, note_msg);
+LL | | });
+ | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)`
+
+error: this call is collspible
++ --> $DIR/collapsible_span_lint_calls.rs:48:9
+ |
+LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
+LL | | db.note(note_msg);
+LL | | });
+ | |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)`
+
+error: aborting due to 5 previous errors
+
--- /dev/null
+// rustc-env:RUST_BACKTRACE=0
+// normalize-stderr-test: "Clippy version: .*" -> "Clippy version: foo"
+// normalize-stderr-test: "internal_lints.rs:\d*:\d*" -> "internal_lints.rs"
+// normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints"
+
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+
+fn it_looks_like_you_are_trying_to_kill_clippy() {}
+
+fn main() {}
--- /dev/null
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+#[macro_use]
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+extern crate rustc_lint;
+
+declare_tool_lint! {
+ pub clippy::TEST_LINT,
+ Warn,
+ "",
+ report_in_external_macro: true
+}
+
+declare_tool_lint! {
+ pub clippy::TEST_LINT_DEFAULT,
+ Warn,
+ "default lint description",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+declare_lint_pass!(Pass2 => [TEST_LINT_DEFAULT]);
+
+fn main() {}
--- /dev/null
- --> $DIR/default_lint.rs:17:1
+error: the lint `TEST_LINT_DEFAULT` has the default lint description
++ --> $DIR/default_lint.rs:18:1
+ |
+LL | / declare_tool_lint! {
+LL | | pub clippy::TEST_LINT_DEFAULT,
+LL | | Warn,
+LL | | "default lint description",
+LL | | report_in_external_macro: true
+LL | | }
+ | |_^
+ |
+note: the lint level is defined here
+ --> $DIR/default_lint.rs:1:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::default_lint)]` implied by `#[deny(clippy::internal)]`
+ = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to previous error
+
--- /dev/null
- #![allow(clippy::no_effect)]
+#![warn(clippy::if_chain_style)]
++#![allow(clippy::no_effect, clippy::nonminimal_bool, clippy::missing_clippy_version_attribute)]
+
+extern crate if_chain;
+
+use if_chain::if_chain;
+
+fn main() {
+ if true {
+ let x = "";
+ // `if_chain!` inside `if`
+ if_chain! {
+ if true;
+ if true;
+ then {}
+ }
+ }
+ if_chain! {
+ if true
+ // multi-line AND'ed conditions
+ && false;
+ if let Some(1) = Some(1);
+ // `let` before `then`
+ let x = "";
+ then {
+ ();
+ }
+ }
+ if_chain! {
+ // single `if` condition
+ if true;
+ then {
+ let x = "";
+ // nested if
+ if true {}
+ }
+ }
+ if_chain! {
+ // starts with `let ..`
+ let x = "";
+ if let Some(1) = Some(1);
+ then {
+ let x = "";
+ let x = "";
+ // nested if_chain!
+ if_chain! {
+ if true;
+ if true;
+ then {}
+ }
+ }
+ }
+}
+
+fn negative() {
+ if true {
+ ();
+ if_chain! {
+ if true;
+ if true;
+ then { (); }
+ }
+ }
+ if_chain! {
+ if true;
+ let x = "";
+ if true;
+ then { (); }
+ }
+ if_chain! {
+ if true;
+ if true;
+ then {
+ if true { 1 } else { 2 }
+ } else {
+ 3
+ }
+ };
+ if true {
+ if_chain! {
+ if true;
+ if true;
+ then {}
+ }
+ } else if false {
+ if_chain! {
+ if true;
+ if false;
+ then {}
+ }
+ }
+}
--- /dev/null
+// run-rustfix
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_span;
+
+use rustc_span::symbol::Symbol;
+
+macro_rules! sym {
+ ($tt:tt) => {
+ rustc_span::symbol::Symbol::intern(stringify!($tt))
+ };
+}
+
+fn main() {
+ // Direct use of Symbol::intern
+ let _ = rustc_span::sym::f32;
+
+ // Using a sym macro
+ let _ = rustc_span::sym::f32;
+
+ // Correct suggestion when symbol isn't stringified constant name
+ let _ = rustc_span::sym::proc_dash_macro;
+
+ // interning a keyword
+ let _ = rustc_span::symbol::kw::SelfLower;
+
+ // Interning a symbol that is not defined
+ let _ = Symbol::intern("xyz123");
+ let _ = sym!(xyz123);
+
+ // Using a different `intern` function
+ let _ = intern("f32");
+}
+
+fn intern(_: &str) {}
--- /dev/null
+// run-rustfix
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_span;
+
+use rustc_span::symbol::Symbol;
+
+macro_rules! sym {
+ ($tt:tt) => {
+ rustc_span::symbol::Symbol::intern(stringify!($tt))
+ };
+}
+
+fn main() {
+ // Direct use of Symbol::intern
+ let _ = Symbol::intern("f32");
+
+ // Using a sym macro
+ let _ = sym!(f32);
+
+ // Correct suggestion when symbol isn't stringified constant name
+ let _ = Symbol::intern("proc-macro");
+
+ // interning a keyword
+ let _ = Symbol::intern("self");
+
+ // Interning a symbol that is not defined
+ let _ = Symbol::intern("xyz123");
+ let _ = sym!(xyz123);
+
+ // Using a different `intern` function
+ let _ = intern("f32");
+}
+
+fn intern(_: &str) {}
--- /dev/null
- --> $DIR/interning_defined_symbol.rs:17:13
+error: interning a defined symbol
- --> $DIR/interning_defined_symbol.rs:20:13
++ --> $DIR/interning_defined_symbol.rs:18:13
+ |
+LL | let _ = Symbol::intern("f32");
+ | ^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::f32`
+ |
+note: the lint level is defined here
+ --> $DIR/interning_defined_symbol.rs:2:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::interning_defined_symbol)]` implied by `#[deny(clippy::internal)]`
+
+error: interning a defined symbol
- --> $DIR/interning_defined_symbol.rs:23:13
++ --> $DIR/interning_defined_symbol.rs:21:13
+ |
+LL | let _ = sym!(f32);
+ | ^^^^^^^^^ help: try: `rustc_span::sym::f32`
+
+error: interning a defined symbol
- --> $DIR/interning_defined_symbol.rs:26:13
++ --> $DIR/interning_defined_symbol.rs:24:13
+ |
+LL | let _ = Symbol::intern("proc-macro");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::proc_dash_macro`
+
+error: interning a defined symbol
++ --> $DIR/interning_defined_symbol.rs:27:13
+ |
+LL | let _ = Symbol::intern("self");
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::symbol::kw::SelfLower`
+
+error: aborting due to 4 previous errors
+
--- /dev/null
+#![warn(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+
+mod paths {
+ // Good path
+ pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
+
+ // Path to method on inherent impl of a primitive type
+ pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
+
+ // Path to method on inherent impl
+ pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
+
+ // Path with empty segment
+ pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
+
+ // Path with bad crate
+ pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
+
+ // Path with bad module
+ pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
+
+ // Path to method on an enum inherent impl
+ pub const OPTION_IS_SOME: [&str; 4] = ["core", "option", "Option", "is_some"];
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/invalid_paths.rs:17:5
+error: invalid path
- --> $DIR/invalid_paths.rs:20:5
++ --> $DIR/invalid_paths.rs:18:5
+ |
+LL | pub const BAD_CRATE_PATH: [&str; 2] = ["bad", "path"];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::invalid-paths` implied by `-D warnings`
+
+error: invalid path
++ --> $DIR/invalid_paths.rs:21:5
+ |
+LL | pub const BAD_MOD_PATH: [&str; 2] = ["std", "xxx"];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
--- /dev/null
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+#[macro_use]
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+extern crate rustc_lint;
+use rustc_lint::LintPass;
+
+declare_tool_lint! {
+ pub clippy::TEST_LINT,
+ Warn,
+ "",
+ report_in_external_macro: true
+}
+
+declare_tool_lint! {
+ pub clippy::TEST_LINT_REGISTERED,
+ Warn,
+ "",
+ report_in_external_macro: true
+}
+
+declare_tool_lint! {
+ pub clippy::TEST_LINT_REGISTERED_ONLY_IMPL,
+ Warn,
+ "",
+ report_in_external_macro: true
+}
+
+pub struct Pass;
+impl LintPass for Pass {
+ fn name(&self) -> &'static str {
+ "TEST_LINT"
+ }
+}
+
+declare_lint_pass!(Pass2 => [TEST_LINT_REGISTERED]);
+
+pub struct Pass3;
+impl_lint_pass!(Pass3 => [TEST_LINT_REGISTERED_ONLY_IMPL]);
+
+fn main() {}
--- /dev/null
- --> $DIR/lint_without_lint_pass.rs:11:1
+error: the lint `TEST_LINT` is not added to any `LintPass`
++ --> $DIR/lint_without_lint_pass.rs:12:1
+ |
+LL | / declare_tool_lint! {
+LL | | pub clippy::TEST_LINT,
+LL | | Warn,
+LL | | "",
+LL | | report_in_external_macro: true
+LL | | }
+ | |_^
+ |
+note: the lint level is defined here
+ --> $DIR/lint_without_lint_pass.rs:1:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::lint_without_lint_pass)]` implied by `#[deny(clippy::internal)]`
+ = note: this error originates in the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to previous error
+
--- /dev/null
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate clippy_utils;
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_middle;
+
+#[macro_use]
+extern crate rustc_session;
+use clippy_utils::{paths, ty::match_type};
+use rustc_hir::Expr;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Ty;
+
+declare_lint! {
+ pub TEST_LINT,
+ Warn,
+ ""
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+static OPTION: [&str; 3] = ["core", "option", "Option"];
+
+impl<'tcx> LateLintPass<'tcx> for Pass {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr) {
+ let ty = cx.typeck_results().expr_ty(expr);
+
+ let _ = match_type(cx, ty, &OPTION);
+ let _ = match_type(cx, ty, &["core", "result", "Result"]);
+
+ let rc_path = &["alloc", "rc", "Rc"];
+ let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/match_type_on_diag_item.rs:30:17
+error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item
- --> $DIR/match_type_on_diag_item.rs:31:17
++ --> $DIR/match_type_on_diag_item.rs:31:17
+ |
+LL | let _ = match_type(cx, ty, &OPTION);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::Option)`
+ |
+note: the lint level is defined here
+ --> $DIR/match_type_on_diag_item.rs:1:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::match_type_on_diagnostic_item)]` implied by `#[deny(clippy::internal)]`
+
+error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item
- --> $DIR/match_type_on_diag_item.rs:34:17
++ --> $DIR/match_type_on_diag_item.rs:32:17
+ |
+LL | let _ = match_type(cx, ty, &["core", "result", "Result"]);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::Result)`
+
+error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item
++ --> $DIR/match_type_on_diag_item.rs:35:17
+ |
+LL | let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::Rc)`
+
+error: aborting due to 3 previous errors
+
--- /dev/null
+// run-rustfix
+
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+use rustc_hir::Expr;
+use rustc_lint::{LateContext, LateLintPass};
+
+declare_lint! {
+ pub TEST_LINT,
+ Warn,
+ ""
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+impl<'tcx> LateLintPass<'tcx> for Pass {
+ fn check_expr(&mut self, _cx: &LateContext<'tcx>, expr: &'tcx Expr) {
+ let _ = expr.span.ctxt().outer_expn_data();
+ }
+}
+
+fn main() {}
--- /dev/null
+// run-rustfix
+
+#![deny(clippy::internal)]
++#![allow(clippy::missing_clippy_version_attribute)]
+#![feature(rustc_private)]
+
+extern crate rustc_hir;
+extern crate rustc_lint;
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+use rustc_hir::Expr;
+use rustc_lint::{LateContext, LateLintPass};
+
+declare_lint! {
+ pub TEST_LINT,
+ Warn,
+ ""
+}
+
+declare_lint_pass!(Pass => [TEST_LINT]);
+
+impl<'tcx> LateLintPass<'tcx> for Pass {
+ fn check_expr(&mut self, _cx: &LateContext<'tcx>, expr: &'tcx Expr) {
+ let _ = expr.span.ctxt().outer_expn().expn_data();
+ }
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/outer_expn_data.rs:24:34
+error: usage of `outer_expn().expn_data()`
++ --> $DIR/outer_expn_data.rs:25:34
+ |
+LL | let _ = expr.span.ctxt().outer_expn().expn_data();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `outer_expn_data()`
+ |
+note: the lint level is defined here
+ --> $DIR/outer_expn_data.rs:3:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::outer_expn_expn_data)]` implied by `#[deny(clippy::internal)]`
+
+error: aborting due to previous error
+
--- /dev/null
- #![allow(clippy::unnecessary_operation, unused_must_use)]
+// run-rustfix
+#![feature(rustc_private)]
+#![deny(clippy::internal)]
++#![allow(
++ clippy::unnecessary_operation,
++ unused_must_use,
++ clippy::missing_clippy_version_attribute
++)]
+
+extern crate rustc_span;
+
+use rustc_span::symbol::{Ident, Symbol};
+
+fn main() {
+ Symbol::intern("foo") == rustc_span::sym::clippy;
+ Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower;
+ Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper;
+ Ident::empty().name == rustc_span::sym::clippy;
+ rustc_span::sym::clippy == Ident::empty().name;
+}
--- /dev/null
- #![allow(clippy::unnecessary_operation, unused_must_use)]
+// run-rustfix
+#![feature(rustc_private)]
+#![deny(clippy::internal)]
++#![allow(
++ clippy::unnecessary_operation,
++ unused_must_use,
++ clippy::missing_clippy_version_attribute
++)]
+
+extern crate rustc_span;
+
+use rustc_span::symbol::{Ident, Symbol};
+
+fn main() {
+ Symbol::intern("foo").as_str() == "clippy";
+ Symbol::intern("foo").to_string() == "self";
+ Symbol::intern("foo").to_ident_string() != "Self";
+ &*Ident::empty().as_str() == "clippy";
+ "clippy" == Ident::empty().to_string();
+}
--- /dev/null
- --> $DIR/unnecessary_symbol_str.rs:11:5
+error: unnecessary `Symbol` to string conversion
- --> $DIR/unnecessary_symbol_str.rs:12:5
++ --> $DIR/unnecessary_symbol_str.rs:15:5
+ |
+LL | Symbol::intern("foo").as_str() == "clippy";
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::sym::clippy`
+ |
+note: the lint level is defined here
+ --> $DIR/unnecessary_symbol_str.rs:3:9
+ |
+LL | #![deny(clippy::internal)]
+ | ^^^^^^^^^^^^^^^^
+ = note: `#[deny(clippy::unnecessary_symbol_str)]` implied by `#[deny(clippy::internal)]`
+
+error: unnecessary `Symbol` to string conversion
- --> $DIR/unnecessary_symbol_str.rs:13:5
++ --> $DIR/unnecessary_symbol_str.rs:16:5
+ |
+LL | Symbol::intern("foo").to_string() == "self";
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower`
+
+error: unnecessary `Symbol` to string conversion
- --> $DIR/unnecessary_symbol_str.rs:14:5
++ --> $DIR/unnecessary_symbol_str.rs:17:5
+ |
+LL | Symbol::intern("foo").to_ident_string() != "Self";
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper`
+
+error: unnecessary `Symbol` to string conversion
- --> $DIR/unnecessary_symbol_str.rs:15:5
++ --> $DIR/unnecessary_symbol_str.rs:18:5
+ |
+LL | &*Ident::empty().as_str() == "clippy";
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ident::empty().name == rustc_span::sym::clippy`
+
+error: unnecessary `Symbol` to string conversion
++ --> $DIR/unnecessary_symbol_str.rs:19:5
+ |
+LL | "clippy" == Ident::empty().to_string();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::clippy == Ident::empty().name`
+
+error: aborting due to 5 previous errors
+
--- /dev/null
--- /dev/null
++max-suggested-slice-pattern-length = 8
--- /dev/null
--- /dev/null
++#![deny(clippy::index_refutable_slice)]
++
++fn below_limit() {
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice) = slice {
++ // This would usually not be linted but is included now due to the
++ // index limit in the config file
++ println!("{}", slice[7]);
++ }
++}
++
++fn above_limit() {
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice) = slice {
++ // This will not be linted as 8 is above the limit
++ println!("{}", slice[8]);
++ }
++}
++
++fn main() {
++ below_limit();
++ above_limit();
++}
--- /dev/null
--- /dev/null
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/index_refutable_slice.rs:5:17
++ |
++LL | if let Some(slice) = slice {
++ | ^^^^^
++ |
++note: the lint level is defined here
++ --> $DIR/index_refutable_slice.rs:1:9
++ |
++LL | #![deny(clippy::index_refutable_slice)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++help: try using a slice pattern here
++ |
++LL | if let Some([_, _, _, _, _, _, _, slice_7, ..]) = slice {
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{}", slice_7);
++ | ~~~~~~~
++
++error: aborting due to previous error
++
--- /dev/null
+#![allow(clippy::redundant_clone)]
+#![warn(clippy::manual_non_exhaustive)]
+
+use std::ops::Deref;
+
+mod enums {
+ enum E {
+ A,
+ B,
+ #[doc(hidden)]
+ _C,
+ }
+
+ // user forgot to remove the marker
+ #[non_exhaustive]
+ enum Ep {
+ A,
+ B,
+ #[doc(hidden)]
+ _C,
+ }
+}
+
+fn option_as_ref_deref() {
+ let mut opt = Some(String::from("123"));
+
+ let _ = opt.as_ref().map(String::as_str);
+ let _ = opt.as_ref().map(|x| x.as_str());
+ let _ = opt.as_mut().map(String::as_mut_str);
+ let _ = opt.as_mut().map(|x| x.as_mut_str());
+}
+
+fn match_like_matches() {
+ let _y = match Some(5) {
+ Some(0) => true,
+ _ => false,
+ };
+}
+
+fn match_same_arms() {
+ match (1, 2, 3) {
+ (1, .., 3) => 42,
+ (.., 3) => 42, //~ ERROR match arms have same body
+ _ => 0,
+ };
+}
+
+fn match_same_arms2() {
+ let _ = match Some(42) {
+ Some(_) => 24,
+ None => 24, //~ ERROR match arms have same body
+ };
+}
+
+fn manual_strip_msrv() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+}
+
++fn check_index_refutable_slice() {
++ // This shouldn't trigger `clippy::index_refutable_slice` as the suggestion
++ // would only be valid from 1.42.0 onward
++ let slice: Option<&[u32]> = Some(&[1]);
++ if let Some(slice) = slice {
++ println!("{}", slice[0]);
++ }
++}
++
+fn main() {
+ option_as_ref_deref();
+ match_like_matches();
+ match_same_arms();
+ match_same_arms2();
+ manual_strip_msrv();
++ check_index_refutable_slice();
+}
--- /dev/null
--- /dev/null
++disallowed-methods = [
++ # just a string is shorthand for path only
++ "std::iter::Iterator::sum",
++ # can give path and reason with an inline table
++ { path = "regex::Regex::is_match", reason = "no matching allowed" },
++ # can use an inline table but omit reason
++ { path = "regex::Regex::new" },
++]
--- /dev/null
--- /dev/null
++#![warn(clippy::disallowed_methods)]
++
++extern crate regex;
++use regex::Regex;
++
++fn main() {
++ let re = Regex::new(r"ab.*c").unwrap();
++ re.is_match("abc");
++
++ let a = vec![1, 2, 3, 4];
++ a.iter().sum::<i32>();
++}
--- /dev/null
--- /dev/null
++error: use of a disallowed method `regex::Regex::new`
++ --> $DIR/conf_disallowed_methods.rs:7:14
++ |
++LL | let re = Regex::new(r"ab.*c").unwrap();
++ | ^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::disallowed-methods` implied by `-D warnings`
++
++error: use of a disallowed method `regex::Regex::is_match`
++ --> $DIR/conf_disallowed_methods.rs:8:5
++ |
++LL | re.is_match("abc");
++ | ^^^^^^^^^^^^^^^^^^
++ |
++ = note: no matching allowed (from clippy.toml)
++
++error: use of a disallowed method `std::iter::Iterator::sum`
++ --> $DIR/conf_disallowed_methods.rs:11:5
++ |
++LL | a.iter().sum::<i32>();
++ | ^^^^^^^^^^^^^^^^^^^^^
++
++error: aborting due to 3 previous errors
++
--- /dev/null
--- /dev/null
++disallowed-types = [
++ "std::collections::HashMap",
++ "std::sync::atomic::AtomicU32",
++ "syn::TypePath",
++ "proc_macro2::Ident",
++ "std::thread::Thread",
++ "std::time::Instant",
++ "std::io::Read",
++ "std::primitive::usize",
++ "bool",
++ # can give path and reason with an inline table
++ { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
++ # can use an inline table but omit reason
++ { path = "std::net::TcpListener" },
++]
--- /dev/null
--- /dev/null
++#![warn(clippy::disallowed_types)]
++
++extern crate quote;
++extern crate syn;
++
++use std::sync as foo;
++use std::sync::atomic::AtomicU32;
++use std::time::Instant as Sneaky;
++
++struct HashMap;
++
++fn bad_return_type() -> fn() -> Sneaky {
++ todo!()
++}
++
++fn bad_arg_type(_: impl Fn(Sneaky) -> foo::atomic::AtomicU32) {}
++
++fn trait_obj(_: &dyn std::io::Read) {}
++
++fn full_and_single_path_prim(_: usize, _: bool) {}
++
++fn const_generics<const C: usize>() {}
++
++struct GenArg<const U: usize>([u8; U]);
++
++static BAD: foo::atomic::AtomicPtr<()> = foo::atomic::AtomicPtr::new(std::ptr::null_mut());
++
++fn ip(_: std::net::Ipv4Addr) {}
++
++fn listener(_: std::net::TcpListener) {}
++
++#[allow(clippy::diverging_sub_expression)]
++fn main() {
++ let _: std::collections::HashMap<(), ()> = std::collections::HashMap::new();
++ let _ = Sneaky::now();
++ let _ = foo::atomic::AtomicU32::new(0);
++ static FOO: std::sync::atomic::AtomicU32 = foo::atomic::AtomicU32::new(1);
++ let _: std::collections::BTreeMap<(), syn::TypePath> = Default::default();
++ let _ = syn::Ident::new("", todo!());
++ let _ = HashMap;
++ let _: usize = 64_usize;
++}
--- /dev/null
--- /dev/null
++error: `std::sync::atomic::AtomicU32` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:7:1
++ |
++LL | use std::sync::atomic::AtomicU32;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = note: `-D clippy::disallowed-types` implied by `-D warnings`
++
++error: `std::time::Instant` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:8:1
++ |
++LL | use std::time::Instant as Sneaky;
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: `std::time::Instant` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:12:33
++ |
++LL | fn bad_return_type() -> fn() -> Sneaky {
++ | ^^^^^^
++
++error: `std::time::Instant` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:16:28
++ |
++LL | fn bad_arg_type(_: impl Fn(Sneaky) -> foo::atomic::AtomicU32) {}
++ | ^^^^^^
++
++error: `std::sync::atomic::AtomicU32` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:16:39
++ |
++LL | fn bad_arg_type(_: impl Fn(Sneaky) -> foo::atomic::AtomicU32) {}
++ | ^^^^^^^^^^^^^^^^^^^^^^
++
++error: `std::io::Read` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:18:22
++ |
++LL | fn trait_obj(_: &dyn std::io::Read) {}
++ | ^^^^^^^^^^^^^
++
++error: `usize` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:20:33
++ |
++LL | fn full_and_single_path_prim(_: usize, _: bool) {}
++ | ^^^^^
++
++error: `bool` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:20:43
++ |
++LL | fn full_and_single_path_prim(_: usize, _: bool) {}
++ | ^^^^
++
++error: `usize` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:22:28
++ |
++LL | fn const_generics<const C: usize>() {}
++ | ^^^^^
++
++error: `usize` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:24:24
++ |
++LL | struct GenArg<const U: usize>([u8; U]);
++ | ^^^^^
++
++error: `std::net::Ipv4Addr` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:28:10
++ |
++LL | fn ip(_: std::net::Ipv4Addr) {}
++ | ^^^^^^^^^^^^^^^^^^
++ |
++ = note: no IPv4 allowed (from clippy.toml)
++
++error: `std::net::TcpListener` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:30:16
++ |
++LL | fn listener(_: std::net::TcpListener) {}
++ | ^^^^^^^^^^^^^^^^^^^^^
++
++error: `std::collections::HashMap` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:34:48
++ |
++LL | let _: std::collections::HashMap<(), ()> = std::collections::HashMap::new();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: `std::collections::HashMap` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:34:12
++ |
++LL | let _: std::collections::HashMap<(), ()> = std::collections::HashMap::new();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: `std::time::Instant` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:35:13
++ |
++LL | let _ = Sneaky::now();
++ | ^^^^^^
++
++error: `std::sync::atomic::AtomicU32` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:36:13
++ |
++LL | let _ = foo::atomic::AtomicU32::new(0);
++ | ^^^^^^^^^^^^^^^^^^^^^^
++
++error: `std::sync::atomic::AtomicU32` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:37:17
++ |
++LL | static FOO: std::sync::atomic::AtomicU32 = foo::atomic::AtomicU32::new(1);
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: `std::sync::atomic::AtomicU32` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:37:48
++ |
++LL | static FOO: std::sync::atomic::AtomicU32 = foo::atomic::AtomicU32::new(1);
++ | ^^^^^^^^^^^^^^^^^^^^^^
++
++error: `syn::TypePath` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:38:43
++ |
++LL | let _: std::collections::BTreeMap<(), syn::TypePath> = Default::default();
++ | ^^^^^^^^^^^^^
++
++error: `syn::Ident` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:39:13
++ |
++LL | let _ = syn::Ident::new("", todo!());
++ | ^^^^^^^^^^
++
++error: `usize` is not allowed according to config
++ --> $DIR/conf_disallowed_types.rs:41:12
++ |
++LL | let _: usize = 64_usize;
++ | ^^^^^
++
++error: aborting due to 21 previous errors
++
--- /dev/null
- error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `avoid-breaking-exported-api`, `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `disallowed-types`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `standard-macro-braces`, `enforced-import-renames`, `allowed-scripts`, `enable-raw-pointer-heuristic-for-send`, `third-party` at line 5 column 1
++error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `avoid-breaking-exported-api`, `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `disallowed-types`, `unreadable-literal-lint-fractions`, `upper-case-acronyms-aggressive`, `cargo-ignore-publish`, `standard-macro-braces`, `enforced-import-renames`, `allowed-scripts`, `enable-raw-pointer-heuristic-for-send`, `max-suggested-slice-pattern-length`, `third-party` at line 5 column 1
+
+error: aborting due to previous error
+
--- /dev/null
- if let StmtKind::Local(ref local) = stmt.kind;
- if let Some(ref init) = local.init;
- if let ExprKind::Cast(ref expr, ref cast_ty) = init.kind;
- if let TyKind::Path(ref qp) = cast_ty.kind;
- if match_qpath(qp, &["char"]);
+if_chain! {
- if let LitKind::Int(69, _) = lit.node;
++ if let StmtKind::Local(local) = stmt.kind;
++ if let Some(init) = local.init;
++ if let ExprKind::Cast(expr, cast_ty) = init.kind;
++ if let TyKind::Path(ref qpath) = cast_ty.kind;
++ if match_qpath(qpath, &["char"]);
+ if let ExprKind::Lit(ref lit) = expr.kind;
++ if let LitKind::Int(69, LitIntType::Unsuffixed) = lit.node;
+ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind;
+ if name.as_str() == "x";
+ then {
+ // report your lint here
+ }
+}
--- /dev/null
++// edition:2018
++
+#![allow(redundant_semicolons, clippy::no_effect)]
++#![feature(stmt_expr_attributes)]
++#![feature(async_closure)]
+
+#[rustfmt::skip]
+fn main() {
+ #[clippy::author]
+ {
+ let x = 42i32;
++ let _t = 1f32;
++
+ -x;
+ };
+ #[clippy::author]
+ {
+ let expr = String::new();
+ drop(expr)
+ };
++
++ #[clippy::author]
++ async move || {};
+}
--- /dev/null
- if let ExprKind::Block(ref block) = expr.kind;
- if block.stmts.len() == 2;
- if let StmtKind::Local(ref local) = block.stmts[0].kind;
- if let Some(ref init) = local.init;
+if_chain! {
- if let LitKind::Int(42, _) = lit.node;
++ if let ExprKind::Block(block, None) = expr.kind;
++ if block.stmts.len() == 3;
++ if let StmtKind::Local(local) = block.stmts[0].kind;
++ if let Some(init) = local.init;
+ if let ExprKind::Lit(ref lit) = init.kind;
- if let StmtKind::Semi(ref e, _) = block.stmts[1].kind
- if let ExprKind::Unary(UnOp::Neg, ref inner) = e.kind;
- if let ExprKind::Path(ref path) = inner.kind;
- if match_qpath(path, &["x"]);
++ if let LitKind::Int(42, LitIntType::Signed(IntTy::I32)) = lit.node;
+ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind;
+ if name.as_str() == "x";
- if let ExprKind::Block(ref block) = expr.kind;
++ if let StmtKind::Local(local1) = block.stmts[1].kind;
++ if let Some(init1) = local1.init;
++ if let ExprKind::Lit(ref lit1) = init1.kind;
++ if let LitKind::Float(_, LitFloatType::Suffixed(FloatTy::F32)) = lit1.node;
++ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local1.pat.kind;
++ if name1.as_str() == "_t";
++ if let StmtKind::Semi(e) = block.stmts[2].kind;
++ if let ExprKind::Unary(UnOp::Neg, inner) = e.kind;
++ if let ExprKind::Path(ref qpath) = inner.kind;
++ if match_qpath(qpath, &["x"]);
+ if block.expr.is_none();
+ then {
+ // report your lint here
+ }
+}
+if_chain! {
- if let StmtKind::Local(ref local) = block.stmts[0].kind;
- if let Some(ref init) = local.init;
- if let ExprKind::Call(ref func, ref args) = init.kind;
- if let ExprKind::Path(ref path) = func.kind;
- if match_qpath(path, &["String", "new"]);
- if args.len() == 0;
++ if let ExprKind::Block(block, None) = expr.kind;
+ if block.stmts.len() == 1;
- if let Some(trailing_expr) = &block.expr;
- if let ExprKind::Call(ref func1, ref args1) = trailing_expr.kind;
- if let ExprKind::Path(ref path1) = func1.kind;
- if match_qpath(path1, &["drop"]);
++ if let StmtKind::Local(local) = block.stmts[0].kind;
++ if let Some(init) = local.init;
++ if let ExprKind::Call(func, args) = init.kind;
++ if let ExprKind::Path(ref qpath) = func.kind;
++ if match_qpath(qpath, &["String", "new"]);
++ if args.is_empty();
+ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind;
+ if name.as_str() == "expr";
- if let ExprKind::Path(ref path2) = args1[0].kind;
- if match_qpath(path2, &["expr"]);
++ if let Some(trailing_expr) = block.expr;
++ if let ExprKind::Call(func1, args1) = trailing_expr.kind;
++ if let ExprKind::Path(ref qpath1) = func1.kind;
++ if match_qpath(qpath1, &["drop"]);
+ if args1.len() == 1;
++ if let ExprKind::Path(ref qpath2) = args1[0].kind;
++ if match_qpath(qpath2, &["expr"]);
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let ExprKind::Closure(CaptureBy::Value, fn_decl, body_id, _, None) = expr.kind;
++ if let FnRetTy::DefaultReturn(_) = fn_decl.output;
++ let expr1 = &cx.tcx.hir().body(body_id).value;
++ if let ExprKind::Call(func, args) = expr1.kind;
++ if let ExprKind::Path(ref qpath) = func.kind;
++ if matches!(qpath, QPath::LangItem(LangItem::FromGenerator, _));
++ if args.len() == 1;
++ if let ExprKind::Closure(CaptureBy::Value, fn_decl1, body_id1, _, Some(Movability::Static)) = args[0].kind;
++ if let FnRetTy::DefaultReturn(_) = fn_decl1.output;
++ let expr2 = &cx.tcx.hir().body(body_id1).value;
++ if let ExprKind::Block(block, None) = expr2.kind;
++ if block.stmts.is_empty();
++ if block.expr.is_none();
+ then {
+ // report your lint here
+ }
+}
--- /dev/null
- if let StmtKind::Local(ref local) = stmt.kind;
- if let Some(ref init) = local.init;
- if let ExprKind::Call(ref func, ref args) = init.kind;
- if let ExprKind::Path(ref path) = func.kind;
- if match_qpath(path, &["{{root}}", "std", "cmp", "min"]);
+if_chain! {
- if let LitKind::Int(3, _) = lit.node;
++ if let StmtKind::Local(local) = stmt.kind;
++ if let Some(init) = local.init;
++ if let ExprKind::Call(func, args) = init.kind;
++ if let ExprKind::Path(ref qpath) = func.kind;
++ if match_qpath(qpath, &["{{root}}", "std", "cmp", "min"]);
+ if args.len() == 2;
+ if let ExprKind::Lit(ref lit) = args[0].kind;
- if let LitKind::Int(4, _) = lit1.node;
++ if let LitKind::Int(3, LitIntType::Unsuffixed) = lit.node;
+ if let ExprKind::Lit(ref lit1) = args[1].kind;
++ if let LitKind::Int(4, LitIntType::Unsuffixed) = lit1.node;
+ if let PatKind::Wild = local.pat.kind;
+ then {
+ // report your lint here
+ }
+}
--- /dev/null
+#[allow(clippy::all)]
+
+fn main() {
+ #[clippy::author]
+ let _ = if true {
+ 1 == 1;
+ } else {
+ 2 == 2;
+ };
++
++ let a = true;
++
++ #[clippy::author]
++ if let true = a {
++ } else {
++ };
+}
--- /dev/null
- if let StmtKind::Local(ref local) = stmt.kind;
- if let Some(ref init) = local.init;
- if let ExprKind::If(ref cond, ref then, Some(ref else_)) = init.kind;
- if let ExprKind::Block(ref block) = else_.kind;
+if_chain! {
- if let StmtKind::Semi(ref e, _) = block.stmts[0].kind
- if let ExprKind::Binary(ref op, ref left, ref right) = e.kind;
++ if let StmtKind::Local(local) = stmt.kind;
++ if let Some(init) = local.init;
++ if let ExprKind::If(cond, then, Some(else_expr)) = init.kind;
++ if let ExprKind::DropTemps(expr) = cond.kind;
++ if let ExprKind::Lit(ref lit) = expr.kind;
++ if let LitKind::Bool(true) = lit.node;
++ if let ExprKind::Block(block, None) = then.kind;
+ if block.stmts.len() == 1;
- if let ExprKind::Lit(ref lit) = left.kind;
- if let LitKind::Int(2, _) = lit.node;
- if let ExprKind::Lit(ref lit1) = right.kind;
- if let LitKind::Int(2, _) = lit1.node;
++ if let StmtKind::Semi(e) = block.stmts[0].kind;
++ if let ExprKind::Binary(op, left, right) = e.kind;
+ if BinOpKind::Eq == op.node;
- if let ExprKind::DropTemps(ref expr) = cond.kind;
- if let ExprKind::Lit(ref lit2) = expr.kind;
- if let LitKind::Bool(true) = lit2.node;
- if let ExprKind::Block(ref block1) = then.kind;
++ if let ExprKind::Lit(ref lit1) = left.kind;
++ if let LitKind::Int(1, LitIntType::Unsuffixed) = lit1.node;
++ if let ExprKind::Lit(ref lit2) = right.kind;
++ if let LitKind::Int(1, LitIntType::Unsuffixed) = lit2.node;
+ if block.expr.is_none();
- if let StmtKind::Semi(ref e1, _) = block1.stmts[0].kind
- if let ExprKind::Binary(ref op1, ref left1, ref right1) = e1.kind;
++ if let ExprKind::Block(block1, None) = else_expr.kind;
+ if block1.stmts.len() == 1;
- if let LitKind::Int(1, _) = lit3.node;
++ if let StmtKind::Semi(e1) = block1.stmts[0].kind;
++ if let ExprKind::Binary(op1, left1, right1) = e1.kind;
+ if BinOpKind::Eq == op1.node;
+ if let ExprKind::Lit(ref lit3) = left1.kind;
- if let LitKind::Int(1, _) = lit4.node;
++ if let LitKind::Int(2, LitIntType::Unsuffixed) = lit3.node;
+ if let ExprKind::Lit(ref lit4) = right1.kind;
++ if let LitKind::Int(2, LitIntType::Unsuffixed) = lit4.node;
+ if block1.expr.is_none();
+ if let PatKind::Wild = local.pat.kind;
+ then {
+ // report your lint here
+ }
+}
++if_chain! {
++ if let ExprKind::If(cond, then, Some(else_expr)) = expr.kind;
++ if let ExprKind::Let(pat, expr1, _) = cond.kind;
++ if let PatKind::Lit(lit_expr) = pat.kind;
++ if let ExprKind::Lit(ref lit) = lit_expr.kind;
++ if let LitKind::Bool(true) = lit.node;
++ if let ExprKind::Path(ref qpath) = expr1.kind;
++ if match_qpath(qpath, &["a"]);
++ if let ExprKind::Block(block, None) = then.kind;
++ if block.stmts.is_empty();
++ if block.expr.is_none();
++ if let ExprKind::Block(block1, None) = else_expr.kind;
++ if block1.stmts.is_empty();
++ if block1.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
--- /dev/null
- if let StmtKind::Local(ref local) = stmt.kind;
- if let Some(ref init) = local.init;
- if let ExprKind::Call(ref func, ref args) = init.kind;
- if let ExprKind::Path(ref path) = func.kind;
- if match_qpath(path, &["std", "mem", "transmute"]);
+if_chain! {
- if let ExprKind::Path(ref path1) = args[0].kind;
- if match_qpath(path1, &["ZPTR"]);
++ if let StmtKind::Local(local) = stmt.kind;
++ if let Some(init) = local.init;
++ if let ExprKind::Call(func, args) = init.kind;
++ if let ExprKind::Path(ref qpath) = func.kind;
++ if match_qpath(qpath, &["std", "mem", "transmute"]);
+ if args.len() == 1;
++ if let ExprKind::Path(ref qpath1) = args[0].kind;
++ if match_qpath(qpath1, &["ZPTR"]);
+ if let PatKind::Wild = local.pat.kind;
+ then {
+ // report your lint here
+ }
+}
--- /dev/null
--- /dev/null
++#![feature(stmt_expr_attributes)]
++#![allow(clippy::never_loop, clippy::while_immutable_condition)]
++
++fn main() {
++ #[clippy::author]
++ for y in 0..10 {
++ let z = y;
++ }
++
++ #[clippy::author]
++ for _ in 0..10 {
++ break;
++ }
++
++ #[clippy::author]
++ 'label: for _ in 0..10 {
++ break 'label;
++ }
++
++ let a = true;
++
++ #[clippy::author]
++ while a {
++ break;
++ }
++
++ #[clippy::author]
++ while let true = a {
++ break;
++ }
++
++ #[clippy::author]
++ loop {
++ break;
++ }
++}
--- /dev/null
--- /dev/null
++if_chain! {
++ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr);
++ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = pat.kind;
++ if name.as_str() == "y";
++ if let ExprKind::Struct(qpath, fields, None) = arg.kind;
++ if matches!(qpath, QPath::LangItem(LangItem::Range, _));
++ if fields.len() == 2;
++ if fields[0].ident.as_str() == "start";
++ if let ExprKind::Lit(ref lit) = fields[0].expr.kind;
++ if let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node;
++ if fields[1].ident.as_str() == "end";
++ if let ExprKind::Lit(ref lit1) = fields[1].expr.kind;
++ if let LitKind::Int(10, LitIntType::Unsuffixed) = lit1.node;
++ if let ExprKind::Block(block, None) = body.kind;
++ if block.stmts.len() == 1;
++ if let StmtKind::Local(local) = block.stmts[0].kind;
++ if let Some(init) = local.init;
++ if let ExprKind::Path(ref qpath1) = init.kind;
++ if match_qpath(qpath1, &["y"]);
++ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local.pat.kind;
++ if name1.as_str() == "z";
++ if block.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr);
++ if let PatKind::Wild = pat.kind;
++ if let ExprKind::Struct(qpath, fields, None) = arg.kind;
++ if matches!(qpath, QPath::LangItem(LangItem::Range, _));
++ if fields.len() == 2;
++ if fields[0].ident.as_str() == "start";
++ if let ExprKind::Lit(ref lit) = fields[0].expr.kind;
++ if let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node;
++ if fields[1].ident.as_str() == "end";
++ if let ExprKind::Lit(ref lit1) = fields[1].expr.kind;
++ if let LitKind::Int(10, LitIntType::Unsuffixed) = lit1.node;
++ if let ExprKind::Block(block, None) = body.kind;
++ if block.stmts.len() == 1;
++ if let StmtKind::Semi(e) = block.stmts[0].kind;
++ if let ExprKind::Break(destination, None) = e.kind;
++ if destination.label.is_none();
++ if block.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr);
++ if let PatKind::Wild = pat.kind;
++ if let ExprKind::Struct(qpath, fields, None) = arg.kind;
++ if matches!(qpath, QPath::LangItem(LangItem::Range, _));
++ if fields.len() == 2;
++ if fields[0].ident.as_str() == "start";
++ if let ExprKind::Lit(ref lit) = fields[0].expr.kind;
++ if let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node;
++ if fields[1].ident.as_str() == "end";
++ if let ExprKind::Lit(ref lit1) = fields[1].expr.kind;
++ if let LitKind::Int(10, LitIntType::Unsuffixed) = lit1.node;
++ if let ExprKind::Block(block, None) = body.kind;
++ if block.stmts.len() == 1;
++ if let StmtKind::Semi(e) = block.stmts[0].kind;
++ if let ExprKind::Break(destination, None) = e.kind;
++ if let Some(label) = destination.label;
++ if label.ident.as_str() == "'label";
++ if block.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let Some(higher::While { condition: condition, body: body }) = higher::While::hir(expr);
++ if let ExprKind::Path(ref qpath) = condition.kind;
++ if match_qpath(qpath, &["a"]);
++ if let ExprKind::Block(block, None) = body.kind;
++ if block.stmts.len() == 1;
++ if let StmtKind::Semi(e) = block.stmts[0].kind;
++ if let ExprKind::Break(destination, None) = e.kind;
++ if destination.label.is_none();
++ if block.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let Some(higher::WhileLet { let_pat: let_pat, let_expr: let_expr, if_then: if_then }) = higher::WhileLet::hir(expr);
++ if let PatKind::Lit(lit_expr) = let_pat.kind;
++ if let ExprKind::Lit(ref lit) = lit_expr.kind;
++ if let LitKind::Bool(true) = lit.node;
++ if let ExprKind::Path(ref qpath) = let_expr.kind;
++ if match_qpath(qpath, &["a"]);
++ if let ExprKind::Block(block, None) = if_then.kind;
++ if block.stmts.len() == 1;
++ if let StmtKind::Semi(e) = block.stmts[0].kind;
++ if let ExprKind::Break(destination, None) = e.kind;
++ if destination.label.is_none();
++ if block.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let ExprKind::Loop(body, None, LoopSource::Loop, _) = expr.kind;
++ if body.stmts.len() == 1;
++ if let StmtKind::Semi(e) = body.stmts[0].kind;
++ if let ExprKind::Break(destination, None) = e.kind;
++ if destination.label.is_none();
++ if body.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
--- /dev/null
- if let StmtKind::Local(ref local) = stmt.kind;
- if let Some(ref init) = local.init;
- if let ExprKind::Match(ref expr, ref arms, MatchSource::Normal) = init.kind;
- if let ExprKind::Lit(ref lit) = expr.kind;
- if let LitKind::Int(42, _) = lit.node;
+if_chain! {
- if let ExprKind::Lit(ref lit1) = arms[0].body.kind;
- if let LitKind::Int(5, _) = lit1.node;
- if let PatKind::Lit(ref lit_expr) = arms[0].pat.kind
- if let ExprKind::Lit(ref lit2) = lit_expr.kind;
- if let LitKind::Int(16, _) = lit2.node;
- if let ExprKind::Block(ref block) = arms[1].body.kind;
++ if let StmtKind::Local(local) = stmt.kind;
++ if let Some(init) = local.init;
++ if let ExprKind::Match(scrutinee, arms, MatchSource::Normal) = init.kind;
++ if let ExprKind::Lit(ref lit) = scrutinee.kind;
++ if let LitKind::Int(42, LitIntType::Unsuffixed) = lit.node;
+ if arms.len() == 3;
- if let StmtKind::Local(ref local1) = block.stmts[0].kind;
- if let Some(ref init1) = local1.init;
- if let ExprKind::Lit(ref lit3) = init1.kind;
- if let LitKind::Int(3, _) = lit3.node;
++ if let PatKind::Lit(lit_expr) = arms[0].pat.kind;
++ if let ExprKind::Lit(ref lit1) = lit_expr.kind;
++ if let LitKind::Int(16, LitIntType::Unsuffixed) = lit1.node;
++ if arms[0].guard.is_none();
++ if let ExprKind::Lit(ref lit2) = arms[0].body.kind;
++ if let LitKind::Int(5, LitIntType::Unsuffixed) = lit2.node;
++ if let PatKind::Lit(lit_expr1) = arms[1].pat.kind;
++ if let ExprKind::Lit(ref lit3) = lit_expr1.kind;
++ if let LitKind::Int(17, LitIntType::Unsuffixed) = lit3.node;
++ if arms[1].guard.is_none();
++ if let ExprKind::Block(block, None) = arms[1].body.kind;
+ if block.stmts.len() == 1;
- if let Some(trailing_expr) = &block.expr;
- if let ExprKind::Path(ref path) = trailing_expr.kind;
- if match_qpath(path, &["x"]);
- if let PatKind::Lit(ref lit_expr1) = arms[1].pat.kind
- if let ExprKind::Lit(ref lit4) = lit_expr1.kind;
- if let LitKind::Int(17, _) = lit4.node;
- if let ExprKind::Lit(ref lit5) = arms[2].body.kind;
- if let LitKind::Int(1, _) = lit5.node;
++ if let StmtKind::Local(local1) = block.stmts[0].kind;
++ if let Some(init1) = local1.init;
++ if let ExprKind::Lit(ref lit4) = init1.kind;
++ if let LitKind::Int(3, LitIntType::Unsuffixed) = lit4.node;
+ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local1.pat.kind;
+ if name.as_str() == "x";
++ if let Some(trailing_expr) = block.expr;
++ if let ExprKind::Path(ref qpath) = trailing_expr.kind;
++ if match_qpath(qpath, &["x"]);
+ if let PatKind::Wild = arms[2].pat.kind;
++ if arms[2].guard.is_none();
++ if let ExprKind::Lit(ref lit5) = arms[2].body.kind;
++ if let LitKind::Int(1, LitIntType::Unsuffixed) = lit5.node;
+ if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local.pat.kind;
+ if name1.as_str() == "a";
+ then {
+ // report your lint here
+ }
+}
--- /dev/null
--- /dev/null
++#[allow(clippy::no_effect)]
++fn main() {
++ #[clippy::author]
++ [1_u8; 5];
++}
--- /dev/null
--- /dev/null
++if_chain! {
++ if let ExprKind::Repeat(value, length) = expr.kind;
++ if let ExprKind::Lit(ref lit) = value.kind;
++ if let LitKind::Int(1, LitIntType::Unsigned(UintTy::U8)) = lit.node;
++ let expr1 = &cx.tcx.hir().body(length.body).value;
++ if let ExprKind::Lit(ref lit1) = expr1.kind;
++ if let LitKind::Int(5, LitIntType::Unsuffixed) = lit1.node;
++ then {
++ // report your lint here
++ }
++}
--- /dev/null
--- /dev/null
++#[allow(clippy::unnecessary_operation, clippy::single_match)]
++fn main() {
++ struct Test {
++ field: u32,
++ }
++
++ #[clippy::author]
++ Test {
++ field: if true { 1 } else { 0 },
++ };
++
++ let test = Test { field: 1 };
++
++ match test {
++ #[clippy::author]
++ Test { field: 1 } => {},
++ _ => {},
++ }
++
++ struct TestTuple(u32);
++
++ let test_tuple = TestTuple(1);
++
++ match test_tuple {
++ #[clippy::author]
++ TestTuple(1) => {},
++ _ => {},
++ }
++
++ struct TestMethodCall(u32);
++
++ impl TestMethodCall {
++ fn test(&self) {}
++ }
++
++ let test_method_call = TestMethodCall(1);
++
++ #[clippy::author]
++ test_method_call.test();
++}
--- /dev/null
--- /dev/null
++if_chain! {
++ if let ExprKind::Struct(qpath, fields, None) = expr.kind;
++ if match_qpath(qpath, &["Test"]);
++ if fields.len() == 1;
++ if fields[0].ident.as_str() == "field";
++ if let ExprKind::If(cond, then, Some(else_expr)) = fields[0].expr.kind;
++ if let ExprKind::DropTemps(expr1) = cond.kind;
++ if let ExprKind::Lit(ref lit) = expr1.kind;
++ if let LitKind::Bool(true) = lit.node;
++ if let ExprKind::Block(block, None) = then.kind;
++ if block.stmts.is_empty();
++ if let Some(trailing_expr) = block.expr;
++ if let ExprKind::Lit(ref lit1) = trailing_expr.kind;
++ if let LitKind::Int(1, LitIntType::Unsuffixed) = lit1.node;
++ if let ExprKind::Block(block1, None) = else_expr.kind;
++ if block1.stmts.is_empty();
++ if let Some(trailing_expr1) = block1.expr;
++ if let ExprKind::Lit(ref lit2) = trailing_expr1.kind;
++ if let LitKind::Int(0, LitIntType::Unsuffixed) = lit2.node;
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let PatKind::Struct(ref qpath, fields, false) = arm.pat.kind;
++ if match_qpath(qpath, &["Test"]);
++ if fields.len() == 1;
++ if fields[0].ident.as_str() == "field";
++ if let PatKind::Lit(lit_expr) = fields[0].pat.kind;
++ if let ExprKind::Lit(ref lit) = lit_expr.kind;
++ if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node;
++ if arm.guard.is_none();
++ if let ExprKind::Block(block, None) = arm.body.kind;
++ if block.stmts.is_empty();
++ if block.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let PatKind::TupleStruct(ref qpath, fields, None) = arm.pat.kind;
++ if match_qpath(qpath, &["TestTuple"]);
++ if fields.len() == 1;
++ if let PatKind::Lit(lit_expr) = fields[0].kind;
++ if let ExprKind::Lit(ref lit) = lit_expr.kind;
++ if let LitKind::Int(1, LitIntType::Unsuffixed) = lit.node;
++ if arm.guard.is_none();
++ if let ExprKind::Block(block, None) = arm.body.kind;
++ if block.stmts.is_empty();
++ if block.expr.is_none();
++ then {
++ // report your lint here
++ }
++}
++if_chain! {
++ if let ExprKind::MethodCall(method_name, _, args, _) = expr.kind;
++ if method_name.ident.as_str() == "test";
++ if args.len() == 1;
++ if let ExprKind::Path(ref qpath) = args[0].kind;
++ if match_qpath(qpath, &["test_method_call"]);
++ then {
++ // report your lint here
++ }
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(dead_code)]
++#![warn(clippy::cast_lossless)]
++
++fn main() {
++ // Test clippy::cast_lossless with casts to integer types
++ let _ = u8::from(true);
++ let _ = u16::from(true);
++ let _ = u32::from(true);
++ let _ = u64::from(true);
++ let _ = u128::from(true);
++ let _ = usize::from(true);
++
++ let _ = i8::from(true);
++ let _ = i16::from(true);
++ let _ = i32::from(true);
++ let _ = i64::from(true);
++ let _ = i128::from(true);
++ let _ = isize::from(true);
++
++ // Test with an expression wrapped in parens
++ let _ = u16::from(true | false);
++}
++
++// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const,
++// so we skip the lint if the expression is in a const fn.
++// See #3656
++const fn abc(input: bool) -> u32 {
++ input as u32
++}
++
++// Same as the above issue. We can't suggest `::from` in const fns in impls
++mod cast_lossless_in_impl {
++ struct A;
++
++ impl A {
++ pub const fn convert(x: bool) -> u64 {
++ x as u64
++ }
++ }
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(dead_code)]
++#![warn(clippy::cast_lossless)]
++
++fn main() {
++ // Test clippy::cast_lossless with casts to integer types
++ let _ = true as u8;
++ let _ = true as u16;
++ let _ = true as u32;
++ let _ = true as u64;
++ let _ = true as u128;
++ let _ = true as usize;
++
++ let _ = true as i8;
++ let _ = true as i16;
++ let _ = true as i32;
++ let _ = true as i64;
++ let _ = true as i128;
++ let _ = true as isize;
++
++ // Test with an expression wrapped in parens
++ let _ = (true | false) as u16;
++}
++
++// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const,
++// so we skip the lint if the expression is in a const fn.
++// See #3656
++const fn abc(input: bool) -> u32 {
++ input as u32
++}
++
++// Same as the above issue. We can't suggest `::from` in const fns in impls
++mod cast_lossless_in_impl {
++ struct A;
++
++ impl A {
++ pub const fn convert(x: bool) -> u64 {
++ x as u64
++ }
++ }
++}
--- /dev/null
--- /dev/null
++error: casting `bool` to `u8` is more cleanly stated with `u8::from(_)`
++ --> $DIR/cast_lossless_bool.rs:8:13
++ |
++LL | let _ = true as u8;
++ | ^^^^^^^^^^ help: try: `u8::from(true)`
++ |
++ = note: `-D clippy::cast-lossless` implied by `-D warnings`
++
++error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)`
++ --> $DIR/cast_lossless_bool.rs:9:13
++ |
++LL | let _ = true as u16;
++ | ^^^^^^^^^^^ help: try: `u16::from(true)`
++
++error: casting `bool` to `u32` is more cleanly stated with `u32::from(_)`
++ --> $DIR/cast_lossless_bool.rs:10:13
++ |
++LL | let _ = true as u32;
++ | ^^^^^^^^^^^ help: try: `u32::from(true)`
++
++error: casting `bool` to `u64` is more cleanly stated with `u64::from(_)`
++ --> $DIR/cast_lossless_bool.rs:11:13
++ |
++LL | let _ = true as u64;
++ | ^^^^^^^^^^^ help: try: `u64::from(true)`
++
++error: casting `bool` to `u128` is more cleanly stated with `u128::from(_)`
++ --> $DIR/cast_lossless_bool.rs:12:13
++ |
++LL | let _ = true as u128;
++ | ^^^^^^^^^^^^ help: try: `u128::from(true)`
++
++error: casting `bool` to `usize` is more cleanly stated with `usize::from(_)`
++ --> $DIR/cast_lossless_bool.rs:13:13
++ |
++LL | let _ = true as usize;
++ | ^^^^^^^^^^^^^ help: try: `usize::from(true)`
++
++error: casting `bool` to `i8` is more cleanly stated with `i8::from(_)`
++ --> $DIR/cast_lossless_bool.rs:15:13
++ |
++LL | let _ = true as i8;
++ | ^^^^^^^^^^ help: try: `i8::from(true)`
++
++error: casting `bool` to `i16` is more cleanly stated with `i16::from(_)`
++ --> $DIR/cast_lossless_bool.rs:16:13
++ |
++LL | let _ = true as i16;
++ | ^^^^^^^^^^^ help: try: `i16::from(true)`
++
++error: casting `bool` to `i32` is more cleanly stated with `i32::from(_)`
++ --> $DIR/cast_lossless_bool.rs:17:13
++ |
++LL | let _ = true as i32;
++ | ^^^^^^^^^^^ help: try: `i32::from(true)`
++
++error: casting `bool` to `i64` is more cleanly stated with `i64::from(_)`
++ --> $DIR/cast_lossless_bool.rs:18:13
++ |
++LL | let _ = true as i64;
++ | ^^^^^^^^^^^ help: try: `i64::from(true)`
++
++error: casting `bool` to `i128` is more cleanly stated with `i128::from(_)`
++ --> $DIR/cast_lossless_bool.rs:19:13
++ |
++LL | let _ = true as i128;
++ | ^^^^^^^^^^^^ help: try: `i128::from(true)`
++
++error: casting `bool` to `isize` is more cleanly stated with `isize::from(_)`
++ --> $DIR/cast_lossless_bool.rs:20:13
++ |
++LL | let _ = true as isize;
++ | ^^^^^^^^^^^^^ help: try: `isize::from(true)`
++
++error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)`
++ --> $DIR/cast_lossless_bool.rs:23:13
++ |
++LL | let _ = (true | false) as u16;
++ | ^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::from(true | false)`
++
++error: aborting due to 13 previous errors
++
--- /dev/null
--- /dev/null
++fn zero() {
++ // SAFETY:
++ unsafe { 0 };
++}
--- /dev/null
--- /dev/null
++#![warn(clippy::undocumented_unsafe_blocks)]
++#![allow(clippy::no_effect)]
++
++#[path = "auxiliary/ice-7934-aux.rs"]
++mod zero;
++
++fn main() {}
--- /dev/null
--- /dev/null
++#![no_std]
++#![feature(lang_items, start, libc)]
++#![crate_type = "lib"]
++
++use core::panic::PanicInfo;
++
++#[warn(clippy::all)]
++fn main() {
++ let mut a = 42;
++ let mut b = 1337;
++
++ a = b;
++ b = a;
++}
--- /dev/null
--- /dev/null
++error: this looks like you are trying to swap `a` and `b`
++ --> $DIR/no_std_swap.rs:12:5
++ |
++LL | / a = b;
++LL | | b = a;
++ | |_________^ help: try: `core::mem::swap(&mut a, &mut b)`
++ |
++ = note: `-D clippy::almost-swapped` implied by `-D warnings`
++ = note: or maybe you should use `core::mem::replace`?
++
++error: aborting due to previous error
++
--- /dev/null
- | ^^^^^^^ help: try: ``foo_bar``
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:9:9
+ |
+LL | /// The foo_bar function does _nothing_. See also foo::bar. (note the dot there)
- | ^^^^^^^^ help: try: ``foo::bar``
++ | ^^^^^^^
+ |
+ = note: `-D clippy::doc-markdown` implied by `-D warnings`
++help: try
++ |
++LL | /// The `foo_bar` function does _nothing_. See also foo::bar. (note the dot there)
++ | ~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:9:51
+ |
+LL | /// The foo_bar function does _nothing_. See also foo::bar. (note the dot there)
- | ^^^^^^^^^^^^^ help: try: ``Foo::some_fun``
++ | ^^^^^^^^
++ |
++help: try
++ |
++LL | /// The foo_bar function does _nothing_. See also `foo::bar`. (note the dot there)
++ | ~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:10:83
+ |
+LL | /// Markdown is _weird_. I mean _really weird_. This /_ is ok. So is `_`. But not Foo::some_fun
- | ^^^^^^^^^^^^^^^^ help: try: ``::a::global:path``
++ | ^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// Markdown is _weird_. I mean _really weird_. This /_ is ok. So is `_`. But not `Foo::some_fun`
++ | ~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:12:13
+ |
+LL | /// Here be ::a::global:path, and _::another::global::path_. :: is not a path though.
- | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``::another::global::path``
++ | ^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// Here be `::a::global:path`, and _::another::global::path_. :: is not a path though.
++ | ~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:12:36
+ |
+LL | /// Here be ::a::global:path, and _::another::global::path_. :: is not a path though.
- | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``::awesome::global::blob::``
++ | ^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// Here be ::a::global:path, and _`::another::global::path`_. :: is not a path though.
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:13:25
+ |
+LL | /// Import an item from ::awesome::global::blob:: (Intended postfix)
- | ^^^^^ help: try: ``::Cat``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// Import an item from `::awesome::global::blob::` (Intended postfix)
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:14:31
+ |
+LL | /// These are the options for ::Cat: (Intended trailing single colon, shouldn't be linted)
- | ^^^^^^^^^^^^^^ help: try: ``NotInCodeBlock``
++ | ^^^^^
++ |
++help: try
++ |
++LL | /// These are the options for `::Cat`: (Intended trailing single colon, shouldn't be linted)
++ | ~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:15:22
+ |
+LL | /// That's not code ~NotInCodeBlock~.
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// That's not code ~`NotInCodeBlock`~.
++ | ~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:16:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:30:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:37:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:51:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:74:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^ help: try: ``link_with_underscores``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:78:22
+ |
+LL | /// This test has [a link_with_underscores][chunked-example] inside it. See #823.
- | ^^^^^^^^^^^^ help: try: ``inline_link2``
++ | ^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// This test has [a `link_with_underscores`][chunked-example] inside it. See #823.
++ | ~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:81:21
+ |
+LL | /// It can also be [inline_link2].
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// It can also be [`inline_link2`].
++ | ~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:91:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^ help: try: ``CamelCaseThing``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:99:8
+ |
+LL | /// ## CamelCaseThing
- | ^^^^^^^^^^^^^^ help: try: ``CamelCaseThing``
++ | ^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// ## `CamelCaseThing`
++ | ~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:102:7
+ |
+LL | /// # CamelCaseThing
- | ^^^^^^^^^^^^^^ help: try: ``CamelCaseThing``
++ | ^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// # `CamelCaseThing`
++ | ~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:104:22
+ |
+LL | /// Not a title #897 CamelCaseThing
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// Not a title #897 `CamelCaseThing`
++ | ~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:105:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:112:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:125:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^ help: try: ``FooBar``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:136:43
+ |
+LL | /** E.g., serialization of an empty list: FooBar
- | ^^^^^^ help: try: ``BarQuz``
++ | ^^^^^^
++ |
++help: try
++ |
++LL | /** E.g., serialization of an empty list: `FooBar`
++ | ~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:141:5
+ |
+LL | And BarQuz too.
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^
++ |
++help: try
++ |
++LL | And `BarQuz` too.
++ | ~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:142:1
+ |
+LL | be_sure_we_got_to_the_end_of_it
- | ^^^^^^ help: try: ``FooBar``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | `be_sure_we_got_to_the_end_of_it`
++ |
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:147:43
+ |
+LL | /** E.g., serialization of an empty list: FooBar
- | ^^^^^^ help: try: ``BarQuz``
++ | ^^^^^^
++ |
++help: try
++ |
++LL | /** E.g., serialization of an empty list: `FooBar`
++ | ~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:152:5
+ |
+LL | And BarQuz too.
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^
++ |
++help: try
++ |
++LL | And `BarQuz` too.
++ | ~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:153:1
+ |
+LL | be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: ``be_sure_we_got_to_the_end_of_it``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | `be_sure_we_got_to_the_end_of_it`
++ |
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:164:5
+ |
+LL | /// be_sure_we_got_to_the_end_of_it
- | ^^^^^^^^^^^^^^^^^^^ help: try: ``mycrate::Collection``
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// `be_sure_we_got_to_the_end_of_it`
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: item in documentation is missing backticks
+ --> $DIR/doc-fixable.rs:183:22
+ |
+LL | /// An iterator over mycrate::Collection's values.
++ | ^^^^^^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// An iterator over `mycrate::Collection`'s values.
++ | ~~~~~~~~~~~~~~~~~~~~~
+
+error: aborting due to 30 previous errors
+
--- /dev/null
- | ^^^^^^^^^ help: try: ``should_be``
+error: backticks are unbalanced
+ --> $DIR/unbalanced_ticks.rs:7:1
+ |
+LL | / /// This is a doc comment with `unbalanced_tick marks and several words that
+LL | | /// should be `encompassed_by` tick marks because they `contain_underscores`.
+LL | | /// Because of the initial `unbalanced_tick` pair, the error message is
+LL | | /// very `confusing_and_misleading`.
+ | |____________________________________^
+ |
+ = note: `-D clippy::doc-markdown` implied by `-D warnings`
+ = help: a backtick may be missing a pair
+
+error: backticks are unbalanced
+ --> $DIR/unbalanced_ticks.rs:13:1
+ |
+LL | /// This paragraph has `unbalanced_tick marks and should stop_linting.
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: a backtick may be missing a pair
+
+error: item in documentation is missing backticks
+ --> $DIR/unbalanced_ticks.rs:15:32
+ |
+LL | /// This paragraph is fine and should_be linted normally.
- | ^^^^^^^^ help: try: ``not_fine``
++ | ^^^^^^^^^
++ |
++help: try
++ |
++LL | /// This paragraph is fine and `should_be` linted normally.
++ | ~~~~~~~~~~~
+
+error: backticks are unbalanced
+ --> $DIR/unbalanced_ticks.rs:17:1
+ |
+LL | /// Double unbalanced backtick from ``here to here` should lint.
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: a backtick may be missing a pair
+
+error: item in documentation is missing backticks
+ --> $DIR/unbalanced_ticks.rs:30:8
+ |
+LL | /// ## not_fine
- | ^^^^^^^^^^^^^^ help: try: ``backticks_here``
++ | ^^^^^^^^
++ |
++help: try
++ |
++LL | /// ## `not_fine`
++ | ~~~~~~~~~~
+
+error: backticks are unbalanced
+ --> $DIR/unbalanced_ticks.rs:32:1
+ |
+LL | /// ### `unbalanced
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+ = help: a backtick may be missing a pair
+
+error: backticks are unbalanced
+ --> $DIR/unbalanced_ticks.rs:34:1
+ |
+LL | /// - This `item has unbalanced tick marks
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: a backtick may be missing a pair
+
+error: item in documentation is missing backticks
+ --> $DIR/unbalanced_ticks.rs:35:23
+ |
+LL | /// - This item needs backticks_here
++ | ^^^^^^^^^^^^^^
++ |
++help: try
++ |
++LL | /// - This item needs `backticks_here`
++ | ~~~~~~~~~~~~~~~~
+
+error: aborting due to 8 previous errors
+
--- /dev/null
+#![warn(clippy::explicit_counter_loop)]
+
+fn main() {
+ let mut vec = vec![1, 2, 3, 4];
+ let mut _index = 0;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 1;
+ _index = 0;
+ for _v in &vec {
+ _index += 1
+ }
+
+ let mut _index = 0;
+ for _v in &mut vec {
+ _index += 1;
+ }
+
+ let mut _index = 0;
+ for _v in vec {
+ _index += 1;
+ }
+}
+
+mod issue_1219 {
+ pub fn test() {
+ // should not trigger the lint because variable is used after the loop #473
+ let vec = vec![1, 2, 3];
+ let mut index = 0;
+ for _v in &vec {
+ index += 1
+ }
+ println!("index: {}", index);
+
+ // should not trigger the lint because the count is conditional #1219
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ if ch == 'a' {
+ continue;
+ }
+ count += 1;
+ }
+
+ // should not trigger the lint because the count is conditional
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ if ch == 'a' {
+ count += 1;
+ }
+ }
+
+ // should trigger the lint because the count is not conditional
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ count += 1;
+ if ch == 'a' {
+ continue;
+ }
+ }
+
+ // should trigger the lint because the count is not conditional
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ count += 1;
+ for i in 0..2 {
+ let _ = 123;
+ }
+ }
+
+ // should not trigger the lint because the count is incremented multiple times
+ let text = "banana";
+ let mut count = 0;
+ for ch in text.chars() {
+ println!("{}", count);
+ count += 1;
+ for i in 0..2 {
+ count += 1;
+ }
+ }
+ }
+}
+
+mod issue_3308 {
+ pub fn test() {
+ // should not trigger the lint because the count is incremented multiple times
+ let mut skips = 0;
+ let erasures = vec![];
+ for i in 0..10 {
+ println!("{}", skips);
+ while erasures.contains(&(i + skips)) {
+ skips += 1;
+ }
+ }
+
+ // should not trigger the lint because the count is incremented multiple times
+ let mut skips = 0;
+ for i in 0..10 {
+ println!("{}", skips);
+ let mut j = 0;
+ while j < 5 {
+ skips += 1;
+ j += 1;
+ }
+ }
+
+ // should not trigger the lint because the count is incremented multiple times
+ let mut skips = 0;
+ for i in 0..10 {
+ println!("{}", skips);
+ for j in 0..5 {
+ skips += 1;
+ }
+ }
+ }
+}
+
+mod issue_1670 {
+ pub fn test() {
+ let mut count = 0;
+ for _i in 3..10 {
+ count += 1;
+ }
+ }
+}
+
+mod issue_4732 {
+ pub fn test() {
+ let slice = &[1, 2, 3];
+ let mut index = 0;
+
+ // should not trigger the lint because the count is used after the loop
+ for _v in slice {
+ index += 1
+ }
+ let _closure = || println!("index: {}", index);
+ }
+}
+
+mod issue_4677 {
+ pub fn test() {
+ let slice = &[1, 2, 3];
+
+ // should not trigger the lint because the count is used after incremented
+ let mut count = 0;
+ for _i in slice {
+ count += 1;
+ println!("{}", count);
+ }
+ }
+}
++
++mod issue_7920 {
++ pub fn test() {
++ let slice = &[1, 2, 3];
++
++ let index_usize: usize = 0;
++ let mut idx_usize: usize = 0;
++
++ // should suggest `enumerate`
++ for _item in slice {
++ if idx_usize == index_usize {
++ break;
++ }
++
++ idx_usize += 1;
++ }
++
++ let index_u32: u32 = 0;
++ let mut idx_u32: u32 = 0;
++
++ // should suggest `zip`
++ for _item in slice {
++ if idx_u32 == index_u32 {
++ break;
++ }
++
++ idx_u32 += 1;
++ }
++ }
++}
--- /dev/null
- error: aborting due to 7 previous errors
+error: the variable `_index` is used as a loop counter
+ --> $DIR/explicit_counter_loop.rs:6:5
+ |
+LL | for _v in &vec {
+ | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
+ |
+ = note: `-D clippy::explicit-counter-loop` implied by `-D warnings`
+
+error: the variable `_index` is used as a loop counter
+ --> $DIR/explicit_counter_loop.rs:12:5
+ |
+LL | for _v in &vec {
+ | ^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter().enumerate()`
+
+error: the variable `_index` is used as a loop counter
+ --> $DIR/explicit_counter_loop.rs:17:5
+ |
+LL | for _v in &mut vec {
+ | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.iter_mut().enumerate()`
+
+error: the variable `_index` is used as a loop counter
+ --> $DIR/explicit_counter_loop.rs:22:5
+ |
+LL | for _v in vec {
+ | ^^^^^^^^^^^^^ help: consider using: `for (_index, _v) in vec.into_iter().enumerate()`
+
+error: the variable `count` is used as a loop counter
+ --> $DIR/explicit_counter_loop.rs:61:9
+ |
+LL | for ch in text.chars() {
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
+
+error: the variable `count` is used as a loop counter
+ --> $DIR/explicit_counter_loop.rs:72:9
+ |
+LL | for ch in text.chars() {
+ | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `for (count, ch) in text.chars().enumerate()`
+
+error: the variable `count` is used as a loop counter
+ --> $DIR/explicit_counter_loop.rs:130:9
+ |
+LL | for _i in 3..10 {
+ | ^^^^^^^^^^^^^^^ help: consider using: `for (count, _i) in (3..10).enumerate()`
+
++error: the variable `idx_usize` is used as a loop counter
++ --> $DIR/explicit_counter_loop.rs:170:9
++ |
++LL | for _item in slice {
++ | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.into_iter().enumerate()`
++
++error: the variable `idx_u32` is used as a loop counter
++ --> $DIR/explicit_counter_loop.rs:182:9
++ |
++LL | for _item in slice {
++ | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.into_iter())`
++ |
++ = note: `idx_u32` is of type `u32`, making it ineligible for `Iterator::enumerate`
++
++error: aborting due to 9 previous errors
+
--- /dev/null
+// run-rustfix
++#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
++/// Allow suboptimal ops in constant context
++pub const fn in_const_context(num: f64) -> f64 {
++ if num >= 0.0 { num } else { -num }
++}
++
+struct A {
+ a: f64,
+ b: f64,
+}
+
+fn fake_abs1(num: f64) -> f64 {
+ num.abs()
+}
+
+fn fake_abs2(num: f64) -> f64 {
+ num.abs()
+}
+
+fn fake_abs3(a: A) -> f64 {
+ a.a.abs()
+}
+
+fn fake_abs4(num: f64) -> f64 {
+ num.abs()
+}
+
+fn fake_abs5(a: A) -> f64 {
+ a.a.abs()
+}
+
+fn fake_nabs1(num: f64) -> f64 {
+ -num.abs()
+}
+
+fn fake_nabs2(num: f64) -> f64 {
+ -num.abs()
+}
+
+fn fake_nabs3(a: A) -> A {
+ A {
+ a: -a.a.abs(),
+ b: a.b,
+ }
+}
+
+fn not_fake_abs1(num: f64) -> f64 {
+ if num > 0.0 { num } else { -num - 1f64 }
+}
+
+fn not_fake_abs2(num: f64) -> f64 {
+ if num > 0.0 { num + 1.0 } else { -(num + 1.0) }
+}
+
+fn not_fake_abs3(num1: f64, num2: f64) -> f64 {
+ if num1 > 0.0 { num2 } else { -num2 }
+}
+
+fn not_fake_abs4(a: A) -> f64 {
+ if a.a > 0.0 { a.b } else { -a.b }
+}
+
+fn not_fake_abs5(a: A) -> f64 {
+ if a.a > 0.0 { a.a } else { -a.b }
+}
+
+fn main() {
+ fake_abs1(5.0);
+ fake_abs2(5.0);
+ fake_abs3(A { a: 5.0, b: 5.0 });
+ fake_abs4(5.0);
+ fake_abs5(A { a: 5.0, b: 5.0 });
+ fake_nabs1(5.0);
+ fake_nabs2(5.0);
+ fake_nabs3(A { a: 5.0, b: 5.0 });
+ not_fake_abs1(5.0);
+ not_fake_abs2(5.0);
+ not_fake_abs3(5.0, 5.0);
+ not_fake_abs4(A { a: 5.0, b: 5.0 });
+ not_fake_abs5(A { a: 5.0, b: 5.0 });
+}
--- /dev/null
+// run-rustfix
++#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
++/// Allow suboptimal ops in constant context
++pub const fn in_const_context(num: f64) -> f64 {
++ if num >= 0.0 { num } else { -num }
++}
++
+struct A {
+ a: f64,
+ b: f64,
+}
+
+fn fake_abs1(num: f64) -> f64 {
+ if num >= 0.0 { num } else { -num }
+}
+
+fn fake_abs2(num: f64) -> f64 {
+ if 0.0 < num { num } else { -num }
+}
+
+fn fake_abs3(a: A) -> f64 {
+ if a.a > 0.0 { a.a } else { -a.a }
+}
+
+fn fake_abs4(num: f64) -> f64 {
+ if 0.0 >= num { -num } else { num }
+}
+
+fn fake_abs5(a: A) -> f64 {
+ if a.a < 0.0 { -a.a } else { a.a }
+}
+
+fn fake_nabs1(num: f64) -> f64 {
+ if num < 0.0 { num } else { -num }
+}
+
+fn fake_nabs2(num: f64) -> f64 {
+ if 0.0 >= num { num } else { -num }
+}
+
+fn fake_nabs3(a: A) -> A {
+ A {
+ a: if a.a >= 0.0 { -a.a } else { a.a },
+ b: a.b,
+ }
+}
+
+fn not_fake_abs1(num: f64) -> f64 {
+ if num > 0.0 { num } else { -num - 1f64 }
+}
+
+fn not_fake_abs2(num: f64) -> f64 {
+ if num > 0.0 { num + 1.0 } else { -(num + 1.0) }
+}
+
+fn not_fake_abs3(num1: f64, num2: f64) -> f64 {
+ if num1 > 0.0 { num2 } else { -num2 }
+}
+
+fn not_fake_abs4(a: A) -> f64 {
+ if a.a > 0.0 { a.b } else { -a.b }
+}
+
+fn not_fake_abs5(a: A) -> f64 {
+ if a.a > 0.0 { a.a } else { -a.b }
+}
+
+fn main() {
+ fake_abs1(5.0);
+ fake_abs2(5.0);
+ fake_abs3(A { a: 5.0, b: 5.0 });
+ fake_abs4(5.0);
+ fake_abs5(A { a: 5.0, b: 5.0 });
+ fake_nabs1(5.0);
+ fake_nabs2(5.0);
+ fake_nabs3(A { a: 5.0, b: 5.0 });
+ not_fake_abs1(5.0);
+ not_fake_abs2(5.0);
+ not_fake_abs3(5.0, 5.0);
+ not_fake_abs4(A { a: 5.0, b: 5.0 });
+ not_fake_abs5(A { a: 5.0, b: 5.0 });
+}
--- /dev/null
- --> $DIR/floating_point_abs.rs:10:5
+error: manual implementation of `abs` method
- --> $DIR/floating_point_abs.rs:14:5
++ --> $DIR/floating_point_abs.rs:16:5
+ |
+LL | if num >= 0.0 { num } else { -num }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.abs()`
+ |
+ = note: `-D clippy::suboptimal-flops` implied by `-D warnings`
+
+error: manual implementation of `abs` method
- --> $DIR/floating_point_abs.rs:18:5
++ --> $DIR/floating_point_abs.rs:20:5
+ |
+LL | if 0.0 < num { num } else { -num }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.abs()`
+
+error: manual implementation of `abs` method
- --> $DIR/floating_point_abs.rs:22:5
++ --> $DIR/floating_point_abs.rs:24:5
+ |
+LL | if a.a > 0.0 { a.a } else { -a.a }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.a.abs()`
+
+error: manual implementation of `abs` method
- --> $DIR/floating_point_abs.rs:26:5
++ --> $DIR/floating_point_abs.rs:28:5
+ |
+LL | if 0.0 >= num { -num } else { num }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.abs()`
+
+error: manual implementation of `abs` method
- --> $DIR/floating_point_abs.rs:30:5
++ --> $DIR/floating_point_abs.rs:32:5
+ |
+LL | if a.a < 0.0 { -a.a } else { a.a }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.a.abs()`
+
+error: manual implementation of negation of `abs` method
- --> $DIR/floating_point_abs.rs:34:5
++ --> $DIR/floating_point_abs.rs:36:5
+ |
+LL | if num < 0.0 { num } else { -num }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-num.abs()`
+
+error: manual implementation of negation of `abs` method
- --> $DIR/floating_point_abs.rs:39:12
++ --> $DIR/floating_point_abs.rs:40:5
+ |
+LL | if 0.0 >= num { num } else { -num }
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-num.abs()`
+
+error: manual implementation of negation of `abs` method
++ --> $DIR/floating_point_abs.rs:45:12
+ |
+LL | a: if a.a >= 0.0 { -a.a } else { a.a },
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-a.a.abs()`
+
+error: aborting due to 8 previous errors
+
--- /dev/null
+// run-rustfix
++#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
++/// Allow suboptimal_ops in constant context
++pub const fn in_const_context() {
++ let a: f64 = 1234.567;
++ let b: f64 = 45.67834;
++ let c: f64 = 0.0004;
++
++ let _ = a * b + c;
++ let _ = c + a * b;
++}
++
+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
++#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
++/// Allow suboptimal_ops in constant context
++pub const fn in_const_context() {
++ let a: f64 = 1234.567;
++ let b: f64 = 45.67834;
++ let c: f64 = 0.0004;
++
++ let _ = a * b + c;
++ let _ = c + a * b;
++}
++
+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
- --> $DIR/floating_point_mul_add.rs:10:13
+error: multiply and add expressions can be calculated more efficiently and accurately
- --> $DIR/floating_point_mul_add.rs:11:13
++ --> $DIR/floating_point_mul_add.rs:21: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:12:13
++ --> $DIR/floating_point_mul_add.rs:22: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:13:13
++ --> $DIR/floating_point_mul_add.rs:23: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:15:13
++ --> $DIR/floating_point_mul_add.rs:24: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:16:13
++ --> $DIR/floating_point_mul_add.rs:26: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:17:13
++ --> $DIR/floating_point_mul_add.rs:27: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:19:13
++ --> $DIR/floating_point_mul_add.rs:28: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:20:13
++ --> $DIR/floating_point_mul_add.rs:30: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:22:13
++ --> $DIR/floating_point_mul_add.rs:31: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:33:13
+ |
+LL | let _ = (a * a + b).sqrt();
+ | ^^^^^^^^^^^ help: consider using: `a.mul_add(a, b)`
+
+error: aborting due to 10 previous errors
+
--- /dev/null
+// run-rustfix
++#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
++/// Allow suboptimal_flops in constant context
++pub const fn const_context() {
++ let x = 3f32;
++ let _ = x * 180f32 / std::f32::consts::PI;
++}
++
+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
+// run-rustfix
++#![feature(const_fn_floating_point_arithmetic)]
+#![warn(clippy::suboptimal_flops)]
+
++/// Allow suboptimal_flops in constant context
++pub const fn const_context() {
++ let x = 3f32;
++ let _ = x * 180f32 / std::f32::consts::PI;
++}
++
+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
- --> $DIR/floating_point_rad.rs:6:13
+error: conversion to degrees can be done more accurately
- --> $DIR/floating_point_rad.rs:7:13
++ --> $DIR/floating_point_rad.rs:13: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:14:13
+ |
+LL | let _ = x * std::f32::consts::PI / 180f32;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.to_radians()`
+
+error: aborting due to 2 previous errors
+
--- /dev/null
+#![warn(clippy::if_then_some_else_none)]
+#![feature(custom_inner_attributes)]
+
+fn main() {
+ // Should issue an error.
+ let _ = if foo() {
+ println!("true!");
+ Some("foo")
+ } else {
+ None
+ };
+
+ // Should issue an error when macros are used.
+ let _ = if matches!(true, true) {
+ println!("true!");
+ Some(matches!(true, false))
+ } else {
+ None
+ };
+
+ // Should issue an error. Binary expression `o < 32` should be parenthesized.
+ let x = Some(5);
+ let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
+
+ // Should issue an error. Unary expression `!x` should be parenthesized.
+ let x = true;
+ let _ = if !x { Some(0) } else { None };
+
+ // Should not issue an error since the `else` block has a statement besides `None`.
+ let _ = if foo() {
+ println!("true!");
+ Some("foo")
+ } else {
+ eprintln!("false...");
+ None
+ };
+
+ // Should not issue an error since there are more than 2 blocks in the if-else chain.
+ let _ = if foo() {
+ println!("foo true!");
+ Some("foo")
+ } else if bar() {
+ println!("bar true!");
+ Some("bar")
+ } else {
+ None
+ };
+
+ let _ = if foo() {
+ println!("foo true!");
+ Some("foo")
+ } else {
+ bar().then(|| {
+ println!("bar true!");
+ "bar"
+ })
+ };
+
+ // Should not issue an error since the `then` block has `None`, not `Some`.
+ let _ = if foo() { None } else { Some("foo is false") };
+
+ // Should not issue an error since the `else` block doesn't use `None` directly.
+ let _ = if foo() { Some("foo is true") } else { into_none() };
+
+ // Should not issue an error since the `then` block doesn't use `Some` directly.
+ let _ = if foo() { into_some("foo") } else { None };
+}
+
+fn _msrv_1_49() {
+ #![clippy::msrv = "1.49"]
+ // `bool::then` was stabilized in 1.50. Do not lint this
+ let _ = if foo() {
+ println!("true!");
+ Some(149)
+ } else {
+ None
+ };
+}
+
+fn _msrv_1_50() {
+ #![clippy::msrv = "1.50"]
+ let _ = if foo() {
+ println!("true!");
+ Some(150)
+ } else {
+ None
+ };
+}
+
+fn foo() -> bool {
+ unimplemented!()
+}
+
+fn bar() -> bool {
+ unimplemented!()
+}
+
+fn into_some<T>(v: T) -> Option<T> {
+ Some(v)
+}
+
+fn into_none<T>() -> Option<T> {
+ None
+}
++
++// Should not warn
++fn f(b: bool, v: Option<()>) -> Option<()> {
++ if b {
++ v?; // This is a potential early return, is not equivalent with `bool::then`
++
++ Some(())
++ } else {
++ None
++ }
++}
--- /dev/null
--- /dev/null
++#![deny(clippy::index_refutable_slice)]
++
++enum SomeEnum<T> {
++ One(T),
++ Two(T),
++ Three(T),
++ Four(T),
++}
++
++fn lintable_examples() {
++ // Try with reference
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice) = slice {
++ println!("{}", slice[0]);
++ }
++
++ // Try with copy
++ let slice: Option<[u32; 3]> = Some([1, 2, 3]);
++ if let Some(slice) = slice {
++ println!("{}", slice[0]);
++ }
++
++ // Try with long slice and small indices
++ let slice: Option<[u32; 9]> = Some([1, 2, 3, 4, 5, 6, 7, 8, 9]);
++ if let Some(slice) = slice {
++ println!("{}", slice[2]);
++ println!("{}", slice[0]);
++ }
++
++ // Multiple bindings
++ let slice_wrapped: SomeEnum<[u32; 3]> = SomeEnum::One([5, 6, 7]);
++ if let SomeEnum::One(slice) | SomeEnum::Three(slice) = slice_wrapped {
++ println!("{}", slice[0]);
++ }
++
++ // Two lintable slices in one if let
++ let a_wrapped: SomeEnum<[u32; 3]> = SomeEnum::One([9, 5, 1]);
++ let b_wrapped: Option<[u32; 2]> = Some([4, 6]);
++ if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
++ println!("{} -> {}", a[2], b[1]);
++ }
++
++ // This requires the slice values to be borrowed as the slice values can only be
++ // borrowed and `String` doesn't implement copy
++ let slice: Option<[String; 2]> = Some([String::from("1"), String::from("2")]);
++ if let Some(ref slice) = slice {
++ println!("{:?}", slice[1]);
++ }
++ println!("{:?}", slice);
++
++ // This should not suggest using the `ref` keyword as the scrutinee is already
++ // a reference
++ let slice: Option<[String; 2]> = Some([String::from("1"), String::from("2")]);
++ if let Some(slice) = &slice {
++ println!("{:?}", slice[0]);
++ }
++ println!("{:?}", slice);
++}
++
++fn slice_index_above_limit() {
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++
++ if let Some(slice) = slice {
++ // Would cause a panic, IDK
++ println!("{}", slice[7]);
++ }
++}
++
++fn slice_is_used() {
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice) = slice {
++ println!("{:?}", slice.len());
++ }
++
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice) = slice {
++ println!("{:?}", slice.to_vec());
++ }
++
++ let opt: Option<[String; 2]> = Some([String::from("Hello"), String::from("world")]);
++ if let Some(slice) = opt {
++ if !slice.is_empty() {
++ println!("first: {}", slice[0]);
++ }
++ }
++}
++
++/// The slice is used by an external function and should therefore not be linted
++fn check_slice_as_arg() {
++ fn is_interesting<T>(slice: &[T; 2]) -> bool {
++ !slice.is_empty()
++ }
++
++ let slice_wrapped: Option<[String; 2]> = Some([String::from("Hello"), String::from("world")]);
++ if let Some(slice) = &slice_wrapped {
++ if is_interesting(slice) {
++ println!("This is interesting {}", slice[0]);
++ }
++ }
++ println!("{:?}", slice_wrapped);
++}
++
++fn check_slice_in_struct() {
++ #[derive(Debug)]
++ struct Wrapper<'a> {
++ inner: Option<&'a [String]>,
++ is_awesome: bool,
++ }
++
++ impl<'a> Wrapper<'a> {
++ fn is_super_awesome(&self) -> bool {
++ self.is_awesome
++ }
++ }
++
++ let inner = &[String::from("New"), String::from("World")];
++ let wrap = Wrapper {
++ inner: Some(inner),
++ is_awesome: true,
++ };
++
++ // Test 1: Field access
++ if let Some(slice) = wrap.inner {
++ if wrap.is_awesome {
++ println!("This is awesome! {}", slice[0]);
++ }
++ }
++
++ // Test 2: function access
++ if let Some(slice) = wrap.inner {
++ if wrap.is_super_awesome() {
++ println!("This is super awesome! {}", slice[0]);
++ }
++ }
++ println!("Complete wrap: {:?}", wrap);
++}
++
++/// This would be a nice additional feature to have in the future, but adding it
++/// now would make the PR too large. This is therefore only a test that we don't
++/// lint cases we can't make a reasonable suggestion for
++fn mutable_slice_index() {
++ // Mut access
++ let mut slice: Option<[String; 1]> = Some([String::from("Penguin")]);
++ if let Some(ref mut slice) = slice {
++ slice[0] = String::from("Mr. Penguin");
++ }
++ println!("Use after modification: {:?}", slice);
++
++ // Mut access on reference
++ let mut slice: Option<[String; 1]> = Some([String::from("Cat")]);
++ if let Some(slice) = &mut slice {
++ slice[0] = String::from("Lord Meow Meow");
++ }
++ println!("Use after modification: {:?}", slice);
++}
++
++/// The lint will ignore bindings with sub patterns as it would be hard
++/// to build correct suggestions for these instances :)
++fn binding_with_sub_pattern() {
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice @ [_, _, _]) = slice {
++ println!("{:?}", slice[2]);
++ }
++}
++
++fn main() {}
--- /dev/null
--- /dev/null
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:13:17
++ |
++LL | if let Some(slice) = slice {
++ | ^^^^^
++ |
++note: the lint level is defined here
++ --> $DIR/if_let_slice_binding.rs:1:9
++ |
++LL | #![deny(clippy::index_refutable_slice)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++help: try using a slice pattern here
++ |
++LL | if let Some([slice_0, ..]) = slice {
++ | ~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{}", slice_0);
++ | ~~~~~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:19:17
++ |
++LL | if let Some(slice) = slice {
++ | ^^^^^
++ |
++help: try using a slice pattern here
++ |
++LL | if let Some([slice_0, ..]) = slice {
++ | ~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{}", slice_0);
++ | ~~~~~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:25:17
++ |
++LL | if let Some(slice) = slice {
++ | ^^^^^
++ |
++help: try using a slice pattern here
++ |
++LL | if let Some([slice_0, _, slice_2, ..]) = slice {
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL ~ println!("{}", slice_2);
++LL ~ println!("{}", slice_0);
++ |
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:32:26
++ |
++LL | if let SomeEnum::One(slice) | SomeEnum::Three(slice) = slice_wrapped {
++ | ^^^^^
++ |
++help: try using a slice pattern here
++ |
++LL | if let SomeEnum::One([slice_0, ..]) | SomeEnum::Three([slice_0, ..]) = slice_wrapped {
++ | ~~~~~~~~~~~~~ ~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{}", slice_0);
++ | ~~~~~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:39:29
++ |
++LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
++ | ^
++ |
++help: try using a slice pattern here
++ |
++LL | if let (SomeEnum::Three([_, _, a_2, ..]), Some(b)) = (a_wrapped, b_wrapped) {
++ | ~~~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{} -> {}", a_2, b[1]);
++ | ~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:39:38
++ |
++LL | if let (SomeEnum::Three(a), Some(b)) = (a_wrapped, b_wrapped) {
++ | ^
++ |
++help: try using a slice pattern here
++ |
++LL | if let (SomeEnum::Three(a), Some([_, b_1, ..])) = (a_wrapped, b_wrapped) {
++ | ~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{} -> {}", a[2], b_1);
++ | ~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:46:21
++ |
++LL | if let Some(ref slice) = slice {
++ | ^^^^^
++ |
++help: try using a slice pattern here
++ |
++LL | if let Some([_, ref slice_1, ..]) = slice {
++ | ~~~~~~~~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{:?}", slice_1);
++ | ~~~~~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:54:17
++ |
++LL | if let Some(slice) = &slice {
++ | ^^^^^
++ |
++help: try using a slice pattern here
++ |
++LL | if let Some([slice_0, ..]) = &slice {
++ | ~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{:?}", slice_0);
++ | ~~~~~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:123:17
++ |
++LL | if let Some(slice) = wrap.inner {
++ | ^^^^^
++ |
++help: try using a slice pattern here
++ |
++LL | if let Some([slice_0, ..]) = wrap.inner {
++ | ~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("This is awesome! {}", slice_0);
++ | ~~~~~~~
++
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/if_let_slice_binding.rs:130:17
++ |
++LL | if let Some(slice) = wrap.inner {
++ | ^^^^^
++ |
++help: try using a slice pattern here
++ |
++LL | if let Some([slice_0, ..]) = wrap.inner {
++ | ~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("This is super awesome! {}", slice_0);
++ | ~~~~~~~
++
++error: aborting due to 10 previous errors
++
--- /dev/null
--- /dev/null
++#![deny(clippy::index_refutable_slice)]
++
++extern crate if_chain;
++use if_chain::if_chain;
++
++macro_rules! if_let_slice_macro {
++ () => {
++ // This would normally be linted
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice) = slice {
++ println!("{}", slice[0]);
++ }
++ };
++}
++
++fn main() {
++ // Don't lint this
++ if_let_slice_macro!();
++
++ // Do lint this
++ if_chain! {
++ let slice: Option<&[u32]> = Some(&[1, 2, 3]);
++ if let Some(slice) = slice;
++ then {
++ println!("{}", slice[0]);
++ }
++ }
++}
--- /dev/null
--- /dev/null
++error: this binding can be a slice pattern to avoid indexing
++ --> $DIR/slice_indexing_in_macro.rs:23:21
++ |
++LL | if let Some(slice) = slice;
++ | ^^^^^
++ |
++note: the lint level is defined here
++ --> $DIR/slice_indexing_in_macro.rs:1:9
++ |
++LL | #![deny(clippy::index_refutable_slice)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++help: try using a slice pattern here
++ |
++LL | if let Some([slice_0, ..]) = slice;
++ | ~~~~~~~~~~~~~
++help: and replace the index expressions here
++ |
++LL | println!("{}", slice_0);
++ | ~~~~~~~
++
++error: aborting due to previous error
++
--- /dev/null
+// run-rustfix
+
+#![allow(unused)]
+
+use std::collections::HashSet;
+use std::collections::VecDeque;
+
+fn main() {
+ let v = [1, 2, 3, 4, 5];
+ let v2: Vec<isize> = v.to_vec();
+ let v3: HashSet<isize> = v.iter().cloned().collect();
+ let v4: VecDeque<isize> = v.iter().cloned().collect();
+
+ // Handle macro expansion in suggestion
+ let _: Vec<isize> = vec![1, 2, 3].to_vec();
+
+ // Issue #3704
+ unsafe {
+ let _: Vec<u8> = std::ffi::CStr::from_ptr(std::ptr::null())
+ .to_bytes().to_vec();
+ }
+
+ // Issue #6808
+ let arr: [u8; 64] = [0; 64];
+ let _: Vec<_> = arr.to_vec();
++
++ // Issue #6703
++ let _: Vec<isize> = v.to_vec();
+}
--- /dev/null
+// run-rustfix
+
+#![allow(unused)]
+
+use std::collections::HashSet;
+use std::collections::VecDeque;
+
+fn main() {
+ let v = [1, 2, 3, 4, 5];
+ let v2: Vec<isize> = v.iter().cloned().collect();
+ let v3: HashSet<isize> = v.iter().cloned().collect();
+ let v4: VecDeque<isize> = v.iter().cloned().collect();
+
+ // Handle macro expansion in suggestion
+ let _: Vec<isize> = vec![1, 2, 3].iter().cloned().collect();
+
+ // Issue #3704
+ unsafe {
+ let _: Vec<u8> = std::ffi::CStr::from_ptr(std::ptr::null())
+ .to_bytes()
+ .iter()
+ .cloned()
+ .collect();
+ }
+
+ // Issue #6808
+ let arr: [u8; 64] = [0; 64];
+ let _: Vec<_> = arr.iter().cloned().collect();
++
++ // Issue #6703
++ let _: Vec<isize> = v.iter().copied().collect();
+}
--- /dev/null
- error: aborting due to 4 previous errors
+error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable
+ --> $DIR/iter_cloned_collect.rs:10:27
+ |
+LL | let v2: Vec<isize> = v.iter().cloned().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()`
+ |
+ = note: `-D clippy::iter-cloned-collect` implied by `-D warnings`
+
+error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable
+ --> $DIR/iter_cloned_collect.rs:15:38
+ |
+LL | let _: Vec<isize> = vec![1, 2, 3].iter().cloned().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()`
+
+error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable
+ --> $DIR/iter_cloned_collect.rs:20:24
+ |
+LL | .to_bytes()
+ | ________________________^
+LL | | .iter()
+LL | | .cloned()
+LL | | .collect();
+ | |______________________^ help: try: `.to_vec()`
+
+error: called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable
+ --> $DIR/iter_cloned_collect.rs:28:24
+ |
+LL | let _: Vec<_> = arr.iter().cloned().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()`
+
++error: called `iter().copied().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and more readable
++ --> $DIR/iter_cloned_collect.rs:31:26
++ |
++LL | let _: Vec<isize> = v.iter().copied().collect();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.to_vec()`
++
++error: aborting due to 5 previous errors
+
--- /dev/null
- clippy::branches_sharing_code
+#![allow(
+ unused_variables,
+ unused_assignments,
+ clippy::similar_names,
+ clippy::blacklisted_name,
++ clippy::branches_sharing_code,
++ clippy::needless_late_init
+)]
+#![warn(clippy::useless_let_if_seq)]
+
+fn f() -> bool {
+ true
+}
+fn g(x: i32) -> i32 {
+ x + 1
+}
+
+fn issue985() -> i32 {
+ let mut x = 42;
+ if f() {
+ x = g(x);
+ }
+
+ x
+}
+
+fn issue985_alt() -> i32 {
+ let mut x = 42;
+ if f() {
+ f();
+ } else {
+ x = g(x);
+ }
+
+ x
+}
+
+#[allow(clippy::manual_strip)]
+fn issue975() -> String {
+ let mut udn = "dummy".to_string();
+ if udn.starts_with("uuid:") {
+ udn = String::from(&udn[5..]);
+ }
+ udn
+}
+
+fn early_return() -> u8 {
+ // FIXME: we could extend the lint to include such cases:
+ let foo;
+
+ if f() {
+ return 42;
+ } else {
+ foo = 0;
+ }
+
+ foo
+}
+
+fn main() {
+ early_return();
+ issue975();
+ issue985();
+ issue985_alt();
+
+ let mut foo = 0;
+ if f() {
+ foo = 42;
+ }
+
+ let mut bar = 0;
+ if f() {
+ f();
+ bar = 42;
+ } else {
+ f();
+ }
+
+ let quz;
+ if f() {
+ quz = 42;
+ } else {
+ quz = 0;
+ }
+
+ // `toto` is used several times
+ let mut toto;
+ if f() {
+ toto = 42;
+ } else {
+ for i in &[1, 2] {
+ toto = *i;
+ }
+
+ toto = 2;
+ }
+
+ // found in libcore, the inner if is not a statement but the block's expr
+ let mut ch = b'x';
+ if f() {
+ ch = b'*';
+ if f() {
+ ch = b'?';
+ }
+ }
+
+ // baz needs to be mut
+ let mut baz = 0;
+ if f() {
+ baz = 42;
+ }
+
+ baz = 1337;
+
+ // issue 3043 - types with interior mutability should not trigger this lint
+ use std::cell::Cell;
+ let mut val = Cell::new(1);
+ if true {
+ val = Cell::new(2);
+ }
+ println!("{}", val.get());
+}
--- /dev/null
- --> $DIR/let_if_seq.rs:65:5
+error: `if _ { .. } else { .. }` is an expression
- --> $DIR/let_if_seq.rs:70:5
++ --> $DIR/let_if_seq.rs:66:5
+ |
+LL | / let mut foo = 0;
+LL | | if f() {
+LL | | foo = 42;
+LL | | }
+ | |_____^ help: it is more idiomatic to write: `let <mut> foo = if f() { 42 } else { 0 };`
+ |
+ = note: `-D clippy::useless-let-if-seq` implied by `-D warnings`
+ = note: you might not need `mut` at all
+
+error: `if _ { .. } else { .. }` is an expression
- --> $DIR/let_if_seq.rs:78:5
++ --> $DIR/let_if_seq.rs:71:5
+ |
+LL | / let mut bar = 0;
+LL | | if f() {
+LL | | f();
+LL | | bar = 42;
+LL | | } else {
+LL | | f();
+LL | | }
+ | |_____^ help: it is more idiomatic to write: `let <mut> bar = if f() { ..; 42 } else { ..; 0 };`
+ |
+ = note: you might not need `mut` at all
+
+error: `if _ { .. } else { .. }` is an expression
- --> $DIR/let_if_seq.rs:107:5
++ --> $DIR/let_if_seq.rs:79:5
+ |
+LL | / let quz;
+LL | | if f() {
+LL | | quz = 42;
+LL | | } else {
+LL | | quz = 0;
+LL | | }
+ | |_____^ help: it is more idiomatic to write: `let quz = if f() { 42 } else { 0 };`
+
+error: `if _ { .. } else { .. }` is an expression
++ --> $DIR/let_if_seq.rs:108:5
+ |
+LL | / let mut baz = 0;
+LL | | if f() {
+LL | | baz = 42;
+LL | | }
+ | |_____^ help: it is more idiomatic to write: `let <mut> baz = if f() { 42 } else { 0 };`
+ |
+ = note: you might not need `mut` at all
+
+error: aborting due to 4 previous errors
+
--- /dev/null
+#![warn(clippy::let_underscore_lock)]
+
++extern crate parking_lot;
++
+fn main() {
+ let m = std::sync::Mutex::new(());
+ let rw = std::sync::RwLock::new(());
+
+ let _ = m.lock();
+ let _ = rw.read();
+ let _ = rw.write();
+ let _ = m.try_lock();
+ let _ = rw.try_read();
+ let _ = rw.try_write();
++
++ use parking_lot::{lock_api::RawMutex, Mutex, RwLock};
++
++ let p_m: Mutex<()> = Mutex::const_new(RawMutex::INIT, ());
++ let _ = p_m.lock();
++
++ let p_m1 = Mutex::new(0);
++ let _ = p_m1.lock();
++
++ let p_rw = RwLock::new(0);
++ let _ = p_rw.read();
++ let _ = p_rw.write();
+}
--- /dev/null
- --> $DIR/let_underscore_lock.rs:7:5
+error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:8:5
++ --> $DIR/let_underscore_lock.rs:9:5
+ |
+LL | let _ = m.lock();
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::let-underscore-lock` implied by `-D warnings`
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
+error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:9:5
++ --> $DIR/let_underscore_lock.rs:10:5
+ |
+LL | let _ = rw.read();
+ | ^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
+error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:10:5
++ --> $DIR/let_underscore_lock.rs:11:5
+ |
+LL | let _ = rw.write();
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
+error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:11:5
++ --> $DIR/let_underscore_lock.rs:12:5
+ |
+LL | let _ = m.try_lock();
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
+error: non-binding let on a synchronization lock
- --> $DIR/let_underscore_lock.rs:12:5
++ --> $DIR/let_underscore_lock.rs:13:5
+ |
+LL | let _ = rw.try_read();
+ | ^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
+error: non-binding let on a synchronization lock
- error: aborting due to 6 previous errors
++ --> $DIR/let_underscore_lock.rs:14:5
+ |
+LL | let _ = rw.try_write();
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
+
++error: non-binding let on a synchronization lock
++ --> $DIR/let_underscore_lock.rs:19:5
++ |
++LL | let _ = p_m.lock();
++ | ^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
++
++error: non-binding let on a synchronization lock
++ --> $DIR/let_underscore_lock.rs:22:5
++ |
++LL | let _ = p_m1.lock();
++ | ^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
++
++error: non-binding let on a synchronization lock
++ --> $DIR/let_underscore_lock.rs:25:5
++ |
++LL | let _ = p_rw.read();
++ | ^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
++
++error: non-binding let on a synchronization lock
++ --> $DIR/let_underscore_lock.rs:26:5
++ |
++LL | let _ = p_rw.write();
++ | ^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: consider using an underscore-prefixed named binding or dropping explicitly with `std::mem::drop`
++
++error: aborting due to 10 previous errors
+
--- /dev/null
+// revisions: edition2018 edition2021
+// [edition2018] edition:2018
+// [edition2021] edition:2021
+// run-rustfix
++
+#![warn(clippy::manual_assert)]
++#![allow(clippy::nonminimal_bool)]
+
+fn main() {
+ let a = vec![1, 2, 3];
+ let c = Some(2);
+ if !a.is_empty()
+ && a.len() == 3
+ && c != None
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ {
+ panic!("qaqaq{:?}", a);
+ }
+ assert!(a.is_empty(), "qaqaq{:?}", a);
+ assert!(a.is_empty(), "qwqwq");
+ if a.len() == 3 {
+ println!("qwq");
+ println!("qwq");
+ println!("qwq");
+ }
+ if let Some(b) = c {
+ panic!("orz {}", b);
+ }
+ if a.len() == 3 {
+ panic!("qaqaq");
+ } else {
+ println!("qwq");
+ }
+ let b = vec![1, 2, 3];
+ assert!(!b.is_empty(), "panic1");
+ assert!(!(b.is_empty() && a.is_empty()), "panic2");
+ assert!(!(a.is_empty() && !b.is_empty()), "panic3");
+ assert!(!(b.is_empty() || a.is_empty()), "panic4");
+ assert!(!(a.is_empty() || !b.is_empty()), "panic5");
+}
--- /dev/null
- --> $DIR/manual_assert.rs:22:5
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:25:5
++ --> $DIR/manual_assert.rs:24:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qaqaq{:?}", a);
+LL | | }
+ | |_____^ help: try: `assert!(a.is_empty(), "qaqaq{:?}", a);`
+ |
+ = note: `-D clippy::manual-assert` implied by `-D warnings`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:42:5
++ --> $DIR/manual_assert.rs:27:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qwqwq");
+LL | | }
+ | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:45:5
++ --> $DIR/manual_assert.rs:44:5
+ |
+LL | / if b.is_empty() {
+LL | | panic!("panic1");
+LL | | }
+ | |_____^ help: try: `assert!(!b.is_empty(), "panic1");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:48:5
++ --> $DIR/manual_assert.rs:47:5
+ |
+LL | / if b.is_empty() && a.is_empty() {
+LL | | panic!("panic2");
+LL | | }
+ | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:51:5
++ --> $DIR/manual_assert.rs:50:5
+ |
+LL | / if a.is_empty() && !b.is_empty() {
+LL | | panic!("panic3");
+LL | | }
+ | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:54:5
++ --> $DIR/manual_assert.rs:53:5
+ |
+LL | / if b.is_empty() || a.is_empty() {
+LL | | panic!("panic4");
+LL | | }
+ | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");`
+
+error: only a `panic!` in `if`-then statement
++ --> $DIR/manual_assert.rs:56:5
+ |
+LL | / if a.is_empty() || !b.is_empty() {
+LL | | panic!("panic5");
+LL | | }
+ | |_____^ help: try: `assert!(!(a.is_empty() || !b.is_empty()), "panic5");`
+
+error: aborting due to 7 previous errors
+
--- /dev/null
+// revisions: edition2018 edition2021
+// [edition2018] edition:2018
+// [edition2021] edition:2021
+// run-rustfix
++
+#![warn(clippy::manual_assert)]
++#![allow(clippy::nonminimal_bool)]
+
+fn main() {
+ let a = vec![1, 2, 3];
+ let c = Some(2);
+ if !a.is_empty()
+ && a.len() == 3
+ && c != None
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ {
+ panic!("qaqaq{:?}", a);
+ }
+ assert!(a.is_empty(), "qaqaq{:?}", a);
+ assert!(a.is_empty(), "qwqwq");
+ if a.len() == 3 {
+ println!("qwq");
+ println!("qwq");
+ println!("qwq");
+ }
+ if let Some(b) = c {
+ panic!("orz {}", b);
+ }
+ if a.len() == 3 {
+ panic!("qaqaq");
+ } else {
+ println!("qwq");
+ }
+ let b = vec![1, 2, 3];
+ assert!(!b.is_empty(), "panic1");
+ assert!(!(b.is_empty() && a.is_empty()), "panic2");
+ assert!(!(a.is_empty() && !b.is_empty()), "panic3");
+ assert!(!(b.is_empty() || a.is_empty()), "panic4");
+ assert!(!(a.is_empty() || !b.is_empty()), "panic5");
+}
--- /dev/null
- --> $DIR/manual_assert.rs:22:5
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:25:5
++ --> $DIR/manual_assert.rs:24:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qaqaq{:?}", a);
+LL | | }
+ | |_____^ help: try: `assert!(a.is_empty(), "qaqaq{:?}", a);`
+ |
+ = note: `-D clippy::manual-assert` implied by `-D warnings`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:42:5
++ --> $DIR/manual_assert.rs:27:5
+ |
+LL | / if !a.is_empty() {
+LL | | panic!("qwqwq");
+LL | | }
+ | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:45:5
++ --> $DIR/manual_assert.rs:44:5
+ |
+LL | / if b.is_empty() {
+LL | | panic!("panic1");
+LL | | }
+ | |_____^ help: try: `assert!(!b.is_empty(), "panic1");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:48:5
++ --> $DIR/manual_assert.rs:47:5
+ |
+LL | / if b.is_empty() && a.is_empty() {
+LL | | panic!("panic2");
+LL | | }
+ | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:51:5
++ --> $DIR/manual_assert.rs:50:5
+ |
+LL | / if a.is_empty() && !b.is_empty() {
+LL | | panic!("panic3");
+LL | | }
+ | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");`
+
+error: only a `panic!` in `if`-then statement
- --> $DIR/manual_assert.rs:54:5
++ --> $DIR/manual_assert.rs:53:5
+ |
+LL | / if b.is_empty() || a.is_empty() {
+LL | | panic!("panic4");
+LL | | }
+ | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");`
+
+error: only a `panic!` in `if`-then statement
++ --> $DIR/manual_assert.rs:56:5
+ |
+LL | / if a.is_empty() || !b.is_empty() {
+LL | | panic!("panic5");
+LL | | }
+ | |_____^ help: try: `assert!(!(a.is_empty() || !b.is_empty()), "panic5");`
+
+error: aborting due to 7 previous errors
+
--- /dev/null
+// revisions: edition2018 edition2021
+// [edition2018] edition:2018
+// [edition2021] edition:2021
+// run-rustfix
++
+#![warn(clippy::manual_assert)]
++#![allow(clippy::nonminimal_bool)]
+
+fn main() {
+ let a = vec![1, 2, 3];
+ let c = Some(2);
+ if !a.is_empty()
+ && a.len() == 3
+ && c != None
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ {
+ panic!("qaqaq{:?}", a);
+ }
+ assert!(a.is_empty(), "qaqaq{:?}", a);
+ assert!(a.is_empty(), "qwqwq");
+ if a.len() == 3 {
+ println!("qwq");
+ println!("qwq");
+ println!("qwq");
+ }
+ if let Some(b) = c {
+ panic!("orz {}", b);
+ }
+ if a.len() == 3 {
+ panic!("qaqaq");
+ } else {
+ println!("qwq");
+ }
+ let b = vec![1, 2, 3];
+ assert!(!b.is_empty(), "panic1");
+ assert!(!(b.is_empty() && a.is_empty()), "panic2");
+ assert!(!(a.is_empty() && !b.is_empty()), "panic3");
+ assert!(!(b.is_empty() || a.is_empty()), "panic4");
+ assert!(!(a.is_empty() || !b.is_empty()), "panic5");
+}
--- /dev/null
+// revisions: edition2018 edition2021
+// [edition2018] edition:2018
+// [edition2021] edition:2021
+// run-rustfix
++
+#![warn(clippy::manual_assert)]
++#![allow(clippy::nonminimal_bool)]
+
+fn main() {
+ let a = vec![1, 2, 3];
+ let c = Some(2);
+ if !a.is_empty()
+ && a.len() == 3
+ && c != None
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ && !a.is_empty()
+ && a.len() == 3
+ {
+ panic!("qaqaq{:?}", a);
+ }
+ if !a.is_empty() {
+ panic!("qaqaq{:?}", a);
+ }
+ if !a.is_empty() {
+ panic!("qwqwq");
+ }
+ if a.len() == 3 {
+ println!("qwq");
+ println!("qwq");
+ println!("qwq");
+ }
+ if let Some(b) = c {
+ panic!("orz {}", b);
+ }
+ if a.len() == 3 {
+ panic!("qaqaq");
+ } else {
+ println!("qwq");
+ }
+ let b = vec![1, 2, 3];
+ if b.is_empty() {
+ panic!("panic1");
+ }
+ if b.is_empty() && a.is_empty() {
+ panic!("panic2");
+ }
+ if a.is_empty() && !b.is_empty() {
+ panic!("panic3");
+ }
+ if b.is_empty() || a.is_empty() {
+ panic!("panic4");
+ }
+ if a.is_empty() || !b.is_empty() {
+ panic!("panic5");
+ }
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::manual_map)]
+#![allow(clippy::toplevel_ref_arg)]
+
+fn main() {
+ // Lint. `y` is declared within the arm, so it isn't captured by the map closure
+ let _ = Some(0).map(|x| {
+ let y = (String::new(), String::new());
+ (x, y.0)
+ });
+
+ // Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
+ // closure
+ let s = Some(String::new());
+ let _ = match &s {
+ Some(x) => Some((x.clone(), s)),
+ None => None,
+ };
+
+ // Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
+ // closure
+ let s = Some(String::new());
+ let _ = match &s {
+ Some(x) => Some({
+ let clone = x.clone();
+ let s = || s;
+ (clone, s())
+ }),
+ None => None,
+ };
+
+ // Don't lint. `s` is borrowed until partway through the arm, but needs to be captured as a mutable
+ // reference by the map closure
+ let mut s = Some(String::new());
+ let _ = match &s {
+ Some(x) => Some({
+ let clone = x.clone();
+ let ref mut s = s;
+ (clone, s)
+ }),
+ None => None,
+ };
+
+ // Lint. `s` is captured by reference, so no lifetime issues.
+ let s = Some(String::new());
+ let _ = s.as_ref().map(|x| {
+ if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
+ });
++
++ // Issue #7820
++ unsafe fn f(x: u32) -> u32 {
++ x
++ }
++ unsafe {
++ let _ = Some(0).map(|x| f(x));
++ }
++ let _ = Some(0).map(|x| unsafe { f(x) });
++ let _ = Some(0).map(|x| unsafe { f(x) });
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::manual_map)]
+#![allow(clippy::toplevel_ref_arg)]
+
+fn main() {
+ // Lint. `y` is declared within the arm, so it isn't captured by the map closure
+ let _ = match Some(0) {
+ Some(x) => Some({
+ let y = (String::new(), String::new());
+ (x, y.0)
+ }),
+ None => None,
+ };
+
+ // Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
+ // closure
+ let s = Some(String::new());
+ let _ = match &s {
+ Some(x) => Some((x.clone(), s)),
+ None => None,
+ };
+
+ // Don't lint. `s` is borrowed until partway through the arm, but needs to be captured by the map
+ // closure
+ let s = Some(String::new());
+ let _ = match &s {
+ Some(x) => Some({
+ let clone = x.clone();
+ let s = || s;
+ (clone, s())
+ }),
+ None => None,
+ };
+
+ // Don't lint. `s` is borrowed until partway through the arm, but needs to be captured as a mutable
+ // reference by the map closure
+ let mut s = Some(String::new());
+ let _ = match &s {
+ Some(x) => Some({
+ let clone = x.clone();
+ let ref mut s = s;
+ (clone, s)
+ }),
+ None => None,
+ };
+
+ // Lint. `s` is captured by reference, so no lifetime issues.
+ let s = Some(String::new());
+ let _ = match &s {
+ Some(x) => Some({
+ if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
+ }),
+ None => None,
+ };
++
++ // Issue #7820
++ unsafe fn f(x: u32) -> u32 {
++ x
++ }
++ unsafe {
++ let _ = match Some(0) {
++ Some(x) => Some(f(x)),
++ None => None,
++ };
++ }
++ let _ = match Some(0) {
++ Some(x) => unsafe { Some(f(x)) },
++ None => None,
++ };
++ let _ = match Some(0) {
++ Some(x) => Some(unsafe { f(x) }),
++ None => None,
++ };
+}
--- /dev/null
- error: aborting due to 2 previous errors
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option_2.rs:8:13
+ |
+LL | let _ = match Some(0) {
+ | _____________^
+LL | | Some(x) => Some({
+LL | | let y = (String::new(), String::new());
+LL | | (x, y.0)
+LL | | }),
+LL | | None => None,
+LL | | };
+ | |_____^
+ |
+ = note: `-D clippy::manual-map` implied by `-D warnings`
+help: try this
+ |
+LL ~ let _ = Some(0).map(|x| {
+LL + let y = (String::new(), String::new());
+LL + (x, y.0)
+LL ~ });
+ |
+
+error: manual implementation of `Option::map`
+ --> $DIR/manual_map_option_2.rs:50:13
+ |
+LL | let _ = match &s {
+ | _____________^
+LL | | Some(x) => Some({
+LL | | if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
+LL | | }),
+LL | | None => None,
+LL | | };
+ | |_____^
+ |
+help: try this
+ |
+LL ~ let _ = s.as_ref().map(|x| {
+LL + if let Some(ref s) = s { (x.clone(), s) } else { panic!() }
+LL ~ });
+ |
+
++error: manual implementation of `Option::map`
++ --> $DIR/manual_map_option_2.rs:62:17
++ |
++LL | let _ = match Some(0) {
++ | _________________^
++LL | | Some(x) => Some(f(x)),
++LL | | None => None,
++LL | | };
++ | |_________^ help: try this: `Some(0).map(|x| f(x))`
++
++error: manual implementation of `Option::map`
++ --> $DIR/manual_map_option_2.rs:67:13
++ |
++LL | let _ = match Some(0) {
++ | _____________^
++LL | | Some(x) => unsafe { Some(f(x)) },
++LL | | None => None,
++LL | | };
++ | |_____^ help: try this: `Some(0).map(|x| unsafe { f(x) })`
++
++error: manual implementation of `Option::map`
++ --> $DIR/manual_map_option_2.rs:71:13
++ |
++LL | let _ = match Some(0) {
++ | _____________^
++LL | | Some(x) => Some(unsafe { f(x) }),
++LL | | None => None,
++LL | | };
++ | |_____^ help: try this: `Some(0).map(|x| unsafe { f(x) })`
++
++error: aborting due to 5 previous errors
+
--- /dev/null
- #![allow(clippy::iter_skip_next, clippy::iter_nth_zero)]
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::manual_split_once)]
- let _ = "key=value".rsplit_once('=').unwrap().1;
- let _ = "key=value".rsplit_once('=').map_or("key=value", |x| x.0);
++#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::needless_splitn)]
+
+extern crate itertools;
+
+#[allow(unused_imports)]
+use itertools::Itertools;
+
+fn main() {
+ let _ = Some("key=value".split_once('=').map_or("key=value", |x| x.0));
+ let _ = "key=value".splitn(2, '=').nth(2);
+ let _ = "key=value".split_once('=').map_or("key=value", |x| x.0);
+ let _ = "key=value".split_once('=').map_or("key=value", |x| x.0);
+ let _ = "key=value".split_once('=').unwrap().1;
+ let _ = "key=value".split_once('=').unwrap().1;
+ let (_, _) = "key=value".split_once('=').unwrap();
+
+ let s = String::from("key=value");
+ let _ = s.split_once('=').map_or(&*s, |x| x.0);
+
+ let s = Box::<str>::from("key=value");
+ let _ = s.split_once('=').map_or(&*s, |x| x.0);
+
+ let s = &"key=value";
+ let _ = s.split_once('=').map_or(*s, |x| x.0);
+
+ fn _f(s: &str) -> Option<&str> {
+ let _ = s.split_once("key=value").map_or(s, |x| x.0);
+ let _ = s.split_once("key=value")?.1;
+ let _ = s.split_once("key=value")?.1;
+ None
+ }
+
+ // Don't lint, slices don't have `split_once`
+ let _ = [0, 1, 2].splitn(2, |&x| x == 1).nth(1).unwrap();
+
+ // `rsplitn` gives the results in the reverse order of `rsplit_once`
++ let _ = "key=value".rsplitn(2, '=').next().unwrap();
++ let _ = "key=value".rsplit_once('=').unwrap().0;
+ let _ = "key=value".rsplit_once('=').map(|x| x.1);
+ let (_, _) = "key=value".rsplit_once('=').map(|(x, y)| (y, x)).unwrap();
+}
+
+fn _msrv_1_51() {
+ #![clippy::msrv = "1.51"]
+ // `str::split_once` was stabilized in 1.16. Do not lint this
+ let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+}
+
+fn _msrv_1_52() {
+ #![clippy::msrv = "1.52"]
+ let _ = "key=value".split_once('=').unwrap().1;
+}
--- /dev/null
- #![allow(clippy::iter_skip_next, clippy::iter_nth_zero)]
+// run-rustfix
+
+#![feature(custom_inner_attributes)]
+#![warn(clippy::manual_split_once)]
++#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::needless_splitn)]
+
+extern crate itertools;
+
+#[allow(unused_imports)]
+use itertools::Itertools;
+
+fn main() {
+ let _ = "key=value".splitn(2, '=').next();
+ let _ = "key=value".splitn(2, '=').nth(2);
+ let _ = "key=value".splitn(2, '=').next().unwrap();
+ let _ = "key=value".splitn(2, '=').nth(0).unwrap();
+ let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+ let _ = "key=value".splitn(2, '=').skip(1).next().unwrap();
+ let (_, _) = "key=value".splitn(2, '=').next_tuple().unwrap();
+
+ let s = String::from("key=value");
+ let _ = s.splitn(2, '=').next().unwrap();
+
+ let s = Box::<str>::from("key=value");
+ let _ = s.splitn(2, '=').nth(0).unwrap();
+
+ let s = &"key=value";
+ let _ = s.splitn(2, '=').skip(0).next().unwrap();
+
+ fn _f(s: &str) -> Option<&str> {
+ let _ = s.splitn(2, "key=value").next()?;
+ let _ = s.splitn(2, "key=value").nth(1)?;
+ let _ = s.splitn(2, "key=value").skip(1).next()?;
+ None
+ }
+
+ // Don't lint, slices don't have `split_once`
+ let _ = [0, 1, 2].splitn(2, |&x| x == 1).nth(1).unwrap();
+
+ // `rsplitn` gives the results in the reverse order of `rsplit_once`
+ let _ = "key=value".rsplitn(2, '=').next().unwrap();
+ let _ = "key=value".rsplitn(2, '=').nth(1).unwrap();
+ let _ = "key=value".rsplitn(2, '=').nth(0);
+ let (_, _) = "key=value".rsplitn(2, '=').next_tuple().unwrap();
+}
+
+fn _msrv_1_51() {
+ #![clippy::msrv = "1.51"]
+ // `str::split_once` was stabilized in 1.16. Do not lint this
+ let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+}
+
+fn _msrv_1_52() {
+ #![clippy::msrv = "1.52"]
+ let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+}
--- /dev/null
- error: manual implementation of `rsplit_once`
- --> $DIR/manual_split_once.rs:41:13
- |
- LL | let _ = "key=value".rsplitn(2, '=').next().unwrap();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').unwrap().1`
-
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:13:13
+ |
+LL | let _ = "key=value".splitn(2, '=').next();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `Some("key=value".split_once('=').map_or("key=value", |x| x.0))`
+ |
+ = note: `-D clippy::manual-split-once` implied by `-D warnings`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:15:13
+ |
+LL | let _ = "key=value".splitn(2, '=').next().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').map_or("key=value", |x| x.0)`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:16:13
+ |
+LL | let _ = "key=value".splitn(2, '=').nth(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').map_or("key=value", |x| x.0)`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:17:13
+ |
+LL | let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:18:13
+ |
+LL | let _ = "key=value".splitn(2, '=').skip(1).next().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:19:18
+ |
+LL | let (_, _) = "key=value".splitn(2, '=').next_tuple().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=')`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:22:13
+ |
+LL | let _ = s.splitn(2, '=').next().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(&*s, |x| x.0)`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:25:13
+ |
+LL | let _ = s.splitn(2, '=').nth(0).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(&*s, |x| x.0)`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:28:13
+ |
+LL | let _ = s.splitn(2, '=').skip(0).next().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once('=').map_or(*s, |x| x.0)`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:31:17
+ |
+LL | let _ = s.splitn(2, "key=value").next()?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value").map_or(s, |x| x.0)`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:32:17
+ |
+LL | let _ = s.splitn(2, "key=value").nth(1)?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value")?.1`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:33:17
+ |
+LL | let _ = s.splitn(2, "key=value").skip(1).next()?;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s.split_once("key=value")?.1`
+
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').map_or("key=value", |x| x.0)`
+error: manual implementation of `rsplit_once`
+ --> $DIR/manual_split_once.rs:42:13
+ |
+LL | let _ = "key=value".rsplitn(2, '=').nth(1).unwrap();
- error: aborting due to 17 previous errors
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').unwrap().0`
+
+error: manual implementation of `rsplit_once`
+ --> $DIR/manual_split_once.rs:43:13
+ |
+LL | let _ = "key=value".rsplitn(2, '=').nth(0);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').map(|x| x.1)`
+
+error: manual implementation of `rsplit_once`
+ --> $DIR/manual_split_once.rs:44:18
+ |
+LL | let (_, _) = "key=value".rsplitn(2, '=').next_tuple().unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".rsplit_once('=').map(|(x, y)| (y, x))`
+
+error: manual implementation of `split_once`
+ --> $DIR/manual_split_once.rs:55:13
+ |
+LL | let _ = "key=value".splitn(2, '=').nth(1).unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `"key=value".split_once('=').unwrap().1`
+
++error: aborting due to 16 previous errors
+
--- /dev/null
+#![feature(exclusive_range_pattern)]
+#![feature(half_open_range_patterns)]
+#![warn(clippy::match_overlapping_arm)]
+#![allow(clippy::redundant_pattern_matching)]
+#![allow(clippy::if_same_then_else, clippy::equatable_if_let)]
+
+/// Tests for match_overlapping_arm
+
+fn overlapping() {
+ const FOO: u64 = 2;
+
+ match 42 {
+ 0..=10 => println!("0..=10"),
+ 0..=11 => println!("0..=11"),
+ _ => (),
+ }
+
+ match 42 {
+ 0..=5 => println!("0..=5"),
+ 6..=7 => println!("6..=7"),
+ FOO..=11 => println!("FOO..=11"),
+ _ => (),
+ }
+
+ match 42 {
+ 2 => println!("2"),
+ 0..=5 => println!("0..=5"),
+ _ => (),
+ }
+
+ match 42 {
+ 2 => println!("2"),
+ 0..=2 => println!("0..=2"),
+ _ => (),
+ }
+
+ match 42 {
+ 0..=10 => println!("0..=10"),
+ 11..=50 => println!("11..=50"),
+ _ => (),
+ }
+
+ match 42 {
+ 2 => println!("2"),
+ 0..2 => println!("0..2"),
+ _ => (),
+ }
+
+ match 42 {
+ 0..10 => println!("0..10"),
+ 10..50 => println!("10..50"),
+ _ => (),
+ }
+
+ match 42 {
+ 0..11 => println!("0..11"),
+ 0..=11 => println!("0..=11"),
+ _ => (),
+ }
+
+ match 42 {
+ 5..7 => println!("5..7"),
+ 0..10 => println!("0..10"),
+ _ => (),
+ }
+
+ match 42 {
+ 5..10 => println!("5..10"),
+ 0..=10 => println!("0..=10"),
+ _ => (),
+ }
+
+ match 42 {
+ 0..14 => println!("0..14"),
+ 5..10 => println!("5..10"),
+ _ => (),
+ }
+
+ match 42 {
+ 5..14 => println!("5..14"),
+ 0..=10 => println!("0..=10"),
+ _ => (),
+ }
+
+ match 42 {
+ 0..7 => println!("0..7"),
+ 0..=10 => println!("0..=10"),
+ _ => (),
+ }
+
+ match 42 {
+ 3.. => println!("3.."),
+ 0.. => println!("0.."),
+ _ => (),
+ }
+
+ match 42 {
+ ..=23 => println!("..=23"),
+ ..26 => println!("..26"),
+ _ => (),
+ }
+
++ // Issue #7816 - overlap after included range
++ match 42 {
++ 5..=10 => (),
++ 0..=20 => (),
++ 21..=30 => (),
++ 21..=40 => (),
++ _ => (),
++ }
++
+ // Issue #7829
+ match 0 {
+ -1..=1 => (),
+ -2..=2 => (),
+ _ => (),
+ }
+
++ // Only warn about the first if there are multiple overlaps
++ match 42u128 {
++ 0..=0x0000_0000_0000_00ff => (),
++ 0..=0x0000_0000_0000_ffff => (),
++ 0..=0x0000_0000_ffff_ffff => (),
++ 0..=0xffff_ffff_ffff_ffff => (),
++ _ => (),
++ }
++
+ if let None = Some(42) {
+ // nothing
+ } else if let None = Some(42) {
+ // another nothing :-)
+ }
+}
+
+fn main() {}
--- /dev/null
- error: aborting due to 6 previous errors
+error: some ranges overlap
+ --> $DIR/match_overlapping_arm.rs:13:9
+ |
+LL | 0..=10 => println!("0..=10"),
+ | ^^^^^^
+ |
+ = note: `-D clippy::match-overlapping-arm` implied by `-D warnings`
+note: overlaps with this
+ --> $DIR/match_overlapping_arm.rs:14:9
+ |
+LL | 0..=11 => println!("0..=11"),
+ | ^^^^^^
+
+error: some ranges overlap
+ --> $DIR/match_overlapping_arm.rs:19:9
+ |
+LL | 0..=5 => println!("0..=5"),
+ | ^^^^^
+ |
+note: overlaps with this
+ --> $DIR/match_overlapping_arm.rs:21:9
+ |
+LL | FOO..=11 => println!("FOO..=11"),
+ | ^^^^^^^^
+
+error: some ranges overlap
+ --> $DIR/match_overlapping_arm.rs:56:9
+ |
+LL | 0..11 => println!("0..11"),
+ | ^^^^^
+ |
+note: overlaps with this
+ --> $DIR/match_overlapping_arm.rs:57:9
+ |
+LL | 0..=11 => println!("0..=11"),
+ | ^^^^^^
+
+error: some ranges overlap
+ --> $DIR/match_overlapping_arm.rs:81:9
+ |
+LL | 0..=10 => println!("0..=10"),
+ | ^^^^^^
+ |
+note: overlaps with this
+ --> $DIR/match_overlapping_arm.rs:80:9
+ |
+LL | 5..14 => println!("5..14"),
+ | ^^^^^
+
+error: some ranges overlap
+ --> $DIR/match_overlapping_arm.rs:86:9
+ |
+LL | 0..7 => println!("0..7"),
+ | ^^^^
+ |
+note: overlaps with this
+ --> $DIR/match_overlapping_arm.rs:87:9
+ |
+LL | 0..=10 => println!("0..=10"),
+ | ^^^^^^
+
+error: some ranges overlap
+ --> $DIR/match_overlapping_arm.rs:98:9
+ |
+LL | ..=23 => println!("..=23"),
+ | ^^^^^
+ |
+note: overlaps with this
+ --> $DIR/match_overlapping_arm.rs:99:9
+ |
+LL | ..26 => println!("..26"),
+ | ^^^^
+
++error: some ranges overlap
++ --> $DIR/match_overlapping_arm.rs:107:9
++ |
++LL | 21..=30 => (),
++ | ^^^^^^^
++ |
++note: overlaps with this
++ --> $DIR/match_overlapping_arm.rs:108:9
++ |
++LL | 21..=40 => (),
++ | ^^^^^^^
++
++error: some ranges overlap
++ --> $DIR/match_overlapping_arm.rs:121:9
++ |
++LL | 0..=0x0000_0000_0000_00ff => (),
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++note: overlaps with this
++ --> $DIR/match_overlapping_arm.rs:122:9
++ |
++LL | 0..=0x0000_0000_0000_ffff => (),
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^
++
++error: aborting due to 8 previous errors
+
--- /dev/null
- let x;
- x = 2usize;
+#![warn(clippy::all)]
+
+use std::cmp::max as my_max;
+use std::cmp::min as my_min;
+use std::cmp::{max, min};
+
+const LARGE: usize = 3;
+
+struct NotOrd(u64);
+
+impl NotOrd {
+ fn min(self, x: u64) -> NotOrd {
+ NotOrd(x)
+ }
+
+ fn max(self, x: u64) -> NotOrd {
+ NotOrd(x)
+ }
+}
+
+fn main() {
- let s;
- s = "Hello";
-
++ let x = 2usize;
+ min(1, max(3, x));
+ min(max(3, x), 1);
+ max(min(x, 1), 3);
+ max(3, min(x, 1));
+
+ my_max(3, my_min(x, 1));
+
+ min(3, max(1, x)); // ok, could be 1, 2 or 3 depending on x
+
+ min(1, max(LARGE, x)); // no error, we don't lookup consts here
+
+ let y = 2isize;
+ min(max(y, -1), 3);
+
++ let s = "Hello";
+ min("Apple", max("Zoo", s));
+ max(min(s, "Apple"), "Zoo");
+
+ max("Apple", min(s, "Zoo")); // ok
+
+ let f = 3f32;
+ x.min(1).max(3);
+ x.max(3).min(1);
+ f.max(3f32).min(1f32);
+
+ x.max(1).min(3); // ok
+ x.min(3).max(1); // ok
+ f.min(3f32).max(1f32); // ok
+
+ max(x.min(1), 3);
+ min(x.max(1), 3); // ok
+
+ s.max("Zoo").min("Apple");
+ s.min("Apple").max("Zoo");
+
+ s.min("Zoo").max("Apple"); // ok
+
+ let not_ord = NotOrd(1);
+ not_ord.min(1).max(3); // ok
+}
--- /dev/null
- --> $DIR/min_max.rs:24:5
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:25:5
++ --> $DIR/min_max.rs:23:5
+ |
+LL | min(1, max(3, x));
+ | ^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::min-max` implied by `-D warnings`
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:26:5
++ --> $DIR/min_max.rs:24:5
+ |
+LL | min(max(3, x), 1);
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:27:5
++ --> $DIR/min_max.rs:25:5
+ |
+LL | max(min(x, 1), 3);
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:29:5
++ --> $DIR/min_max.rs:26:5
+ |
+LL | max(3, min(x, 1));
+ | ^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:41:5
++ --> $DIR/min_max.rs:28:5
+ |
+LL | my_max(3, my_min(x, 1));
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:42:5
++ --> $DIR/min_max.rs:38:5
+ |
+LL | min("Apple", max("Zoo", s));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:47:5
++ --> $DIR/min_max.rs:39:5
+ |
+LL | max(min(s, "Apple"), "Zoo");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:48:5
++ --> $DIR/min_max.rs:44:5
+ |
+LL | x.min(1).max(3);
+ | ^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:49:5
++ --> $DIR/min_max.rs:45:5
+ |
+LL | x.max(3).min(1);
+ | ^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:55:5
++ --> $DIR/min_max.rs:46:5
+ |
+LL | f.max(3f32).min(1f32);
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:58:5
++ --> $DIR/min_max.rs:52:5
+ |
+LL | max(x.min(1), 3);
+ | ^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
- --> $DIR/min_max.rs:59:5
++ --> $DIR/min_max.rs:55:5
+ |
+LL | s.max("Zoo").min("Apple");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this `min`/`max` combination leads to constant result
++ --> $DIR/min_max.rs:56:5
+ |
+LL | s.min("Apple").max("Zoo");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 13 previous errors
+
--- /dev/null
- mod meets_msrv {
+#![allow(clippy::redundant_clone)]
+#![feature(custom_inner_attributes)]
+#![clippy::msrv = "1.0.0"]
+
+use std::ops::{Deref, RangeFrom};
+
+fn approx_const() {
+ let log2_10 = 3.321928094887362;
+ let log10_2 = 0.301029995663981;
+}
+
+fn cloned_instead_of_copied() {
+ let _ = [1].iter().cloned();
+}
+
+fn option_as_ref_deref() {
+ let mut opt = Some(String::from("123"));
+
+ let _ = opt.as_ref().map(String::as_str);
+ let _ = opt.as_ref().map(|x| x.as_str());
+ let _ = opt.as_mut().map(String::as_mut_str);
+ let _ = opt.as_mut().map(|x| x.as_mut_str());
+}
+
+fn match_like_matches() {
+ let _y = match Some(5) {
+ Some(0) => true,
+ _ => false,
+ };
+}
+
+fn match_same_arms() {
+ match (1, 2, 3) {
+ (1, .., 3) => 42,
+ (.., 3) => 42, //~ ERROR match arms have same body
+ _ => 0,
+ };
+}
+
+fn match_same_arms2() {
+ let _ = match Some(42) {
+ Some(_) => 24,
+ None => 24, //~ ERROR match arms have same body
+ };
+}
+
+pub fn manual_strip_msrv() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+}
+
+pub fn redundant_fieldnames() {
+ let start = 0;
+ let _ = RangeFrom { start: start };
+}
+
+pub fn redundant_static_lifetime() {
+ const VAR_ONE: &'static str = "Test constant #1";
+}
+
+pub fn checked_conversion() {
+ let value: i64 = 42;
+ let _ = value <= (u32::max_value() as i64) && value >= 0;
+ let _ = value <= (u32::MAX as i64) && value >= 0;
+}
+
+pub struct FromOverInto(String);
+
+impl Into<FromOverInto> for String {
+ fn into(self) -> FromOverInto {
+ FromOverInto(self)
+ }
+}
+
+pub fn filter_map_next() {
+ let a = ["1", "lol", "3", "NaN", "5"];
+
+ #[rustfmt::skip]
+ let _: Option<u32> = vec![1, 2, 3, 4, 5, 6]
+ .into_iter()
+ .filter_map(|x| {
+ if x == 2 {
+ Some(x * 2)
+ } else {
+ None
+ }
+ })
+ .next();
+}
+
+#[allow(clippy::no_effect)]
+#[allow(clippy::short_circuit_statement)]
+#[allow(clippy::unnecessary_operation)]
+pub fn manual_range_contains() {
+ let x = 5;
+ x >= 8 && x < 12;
+}
+
+pub fn use_self() {
+ struct Foo {}
+
+ impl Foo {
+ fn new() -> Foo {
+ Foo {}
+ }
+ fn test() -> Foo {
+ Foo::new()
+ }
+ }
+}
+
+fn replace_with_default() {
+ let mut s = String::from("foo");
+ let _ = std::mem::replace(&mut s, String::default());
+}
+
+fn map_unwrap_or() {
+ let opt = Some(1);
+
+ // Check for `option.map(_).unwrap_or(_)` use.
+ // Single line case.
+ let _ = opt
+ .map(|x| x + 1)
+ // Should lint even though this call is on a separate line.
+ .unwrap_or(0);
+}
+
+// Could be const
+fn missing_const_for_fn() -> i32 {
+ 1
+}
+
+fn unnest_or_patterns() {
+ struct TS(u8, u8);
+ if let TS(0, x) | TS(1, x) = TS(0, 0) {}
+}
+
++#[cfg_attr(rustfmt, rustfmt_skip)]
++fn deprecated_cfg_attr() {}
++
++#[warn(clippy::cast_lossless)]
++fn int_from_bool() -> u8 {
++ true as u8
++}
++
+fn main() {
+ filter_map_next();
+ checked_conversion();
+ redundant_fieldnames();
+ redundant_static_lifetime();
+ option_as_ref_deref();
+ match_like_matches();
+ match_same_arms();
+ match_same_arms2();
+ manual_strip_msrv();
+ manual_range_contains();
+ use_self();
+ replace_with_default();
+ map_unwrap_or();
+ missing_const_for_fn();
+ unnest_or_patterns();
++ int_from_bool();
+}
+
- #![clippy::msrv = "1.45.0"]
++mod just_under_msrv {
+ #![feature(custom_inner_attributes)]
- mod just_under_msrv {
++ #![clippy::msrv = "1.44.0"]
+
+ fn main() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+ }
+}
+
- #![clippy::msrv = "1.46.0"]
++mod meets_msrv {
+ #![feature(custom_inner_attributes)]
- #![clippy::msrv = "1.44.0"]
++ #![clippy::msrv = "1.45.0"]
+
+ fn main() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+ }
+}
+
+mod just_above_msrv {
+ #![feature(custom_inner_attributes)]
++ #![clippy::msrv = "1.46.0"]
+
+ fn main() {
+ let s = "hello, world!";
+ if s.starts_with("hello, ") {
+ assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ }
+ }
+}
--- /dev/null
- --> $DIR/min_rust_version_attr.rs:165:24
+error: stripping a prefix manually
- --> $DIR/min_rust_version_attr.rs:164:9
++ --> $DIR/min_rust_version_attr.rs:186:24
+ |
+LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::manual-strip` implied by `-D warnings`
+note: the prefix was tested here
- --> $DIR/min_rust_version_attr.rs:177:24
++ --> $DIR/min_rust_version_attr.rs:185:9
+ |
+LL | if s.starts_with("hello, ") {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: try using the `strip_prefix` method
+ |
+LL ~ if let Some(<stripped>) = s.strip_prefix("hello, ") {
+LL ~ assert_eq!(<stripped>.to_uppercase(), "WORLD!");
+ |
+
+error: stripping a prefix manually
- --> $DIR/min_rust_version_attr.rs:176:9
++ --> $DIR/min_rust_version_attr.rs:198:24
+ |
+LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+note: the prefix was tested here
++ --> $DIR/min_rust_version_attr.rs:197:9
+ |
+LL | if s.starts_with("hello, ") {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: try using the `strip_prefix` method
+ |
+LL ~ if let Some(<stripped>) = s.strip_prefix("hello, ") {
+LL ~ assert_eq!(<stripped>.to_uppercase(), "WORLD!");
+ |
+
+error: aborting due to 2 previous errors
+
--- /dev/null
- #[allow(unused_variables)]
+// run-rustfix
+
+#[warn(clippy::all, clippy::needless_borrow)]
- &&a // FIXME: this should lint, too
++#[allow(unused_variables, clippy::unnecessary_mut_passed)]
+fn main() {
+ let a = 5;
++ let ref_a = &a;
+ let _ = x(&a); // no warning
+ let _ = x(&a); // warn
+
+ let mut b = 5;
+ mut_ref(&mut b); // no warning
+ mut_ref(&mut b); // warn
+
+ let s = &String::from("hi");
+ let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not
+ let g_val = g(&Vec::new()); // should not error, because `&Vec<T>` derefs to `&[T]`
+ let vec = Vec::new();
+ let vec_val = g(&vec); // should not error, because `&Vec<T>` derefs to `&[T]`
+ h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait`
+ let garbl = match 42 {
+ 44 => &a,
+ 45 => {
+ println!("foo");
++ &a
+ },
+ 46 => &a,
++ 47 => {
++ println!("foo");
++ loop {
++ println!("{}", a);
++ if a == 25 {
++ break ref_a;
++ }
++ }
++ },
+ _ => panic!(),
+ };
++
++ let _ = x(&a);
++ let _ = x(&a);
++ let _ = x(&mut b);
++ let _ = x(ref_a);
++ {
++ let b = &mut b;
++ x(b);
++ }
+}
+
+#[allow(clippy::needless_borrowed_reference)]
+fn x(y: &i32) -> i32 {
+ *y
+}
+
+fn mut_ref(y: &mut i32) {
+ *y = 5;
+}
+
+fn f<T: Copy>(y: &T) -> T {
+ *y
+}
+
+fn g(y: &[u8]) -> u8 {
+ y[0]
+}
+
+trait Trait {}
+
+impl<'a> Trait for &'a str {}
+
+fn h(_: &dyn Trait) {}
--- /dev/null
- #[allow(unused_variables)]
+// run-rustfix
+
+#[warn(clippy::all, clippy::needless_borrow)]
- &&a // FIXME: this should lint, too
++#[allow(unused_variables, clippy::unnecessary_mut_passed)]
+fn main() {
+ let a = 5;
++ let ref_a = &a;
+ let _ = x(&a); // no warning
+ let _ = x(&&a); // warn
+
+ let mut b = 5;
+ mut_ref(&mut b); // no warning
+ mut_ref(&mut &mut b); // warn
+
+ let s = &String::from("hi");
+ let s_ident = f(&s); // should not error, because `&String` implements Copy, but `String` does not
+ let g_val = g(&Vec::new()); // should not error, because `&Vec<T>` derefs to `&[T]`
+ let vec = Vec::new();
+ let vec_val = g(&vec); // should not error, because `&Vec<T>` derefs to `&[T]`
+ h(&"foo"); // should not error, because the `&&str` is required, due to `&Trait`
+ let garbl = match 42 {
+ 44 => &a,
+ 45 => {
+ println!("foo");
++ &&a
+ },
+ 46 => &&a,
++ 47 => {
++ println!("foo");
++ loop {
++ println!("{}", a);
++ if a == 25 {
++ break &ref_a;
++ }
++ }
++ },
+ _ => panic!(),
+ };
++
++ let _ = x(&&&a);
++ let _ = x(&mut &&a);
++ let _ = x(&&&mut b);
++ let _ = x(&&ref_a);
++ {
++ let b = &mut b;
++ x(&b);
++ }
+}
+
+#[allow(clippy::needless_borrowed_reference)]
+fn x(y: &i32) -> i32 {
+ *y
+}
+
+fn mut_ref(y: &mut i32) {
+ *y = 5;
+}
+
+fn f<T: Copy>(y: &T) -> T {
+ *y
+}
+
+fn g(y: &[u8]) -> u8 {
+ y[0]
+}
+
+trait Trait {}
+
+impl<'a> Trait for &'a str {}
+
+fn h(_: &dyn Trait) {}
--- /dev/null
- --> $DIR/needless_borrow.rs:8:15
+error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler
- --> $DIR/needless_borrow.rs:12:13
++ --> $DIR/needless_borrow.rs:9:15
+ |
+LL | let _ = x(&&a); // warn
+ | ^^^ help: change this to: `&a`
+ |
+ = note: `-D clippy::needless-borrow` implied by `-D warnings`
+
+error: this expression borrows a reference (`&mut i32`) that is immediately dereferenced by the compiler
- --> $DIR/needless_borrow.rs:26:15
++ --> $DIR/needless_borrow.rs:13:13
+ |
+LL | mut_ref(&mut &mut b); // warn
+ | ^^^^^^^^^^^ help: change this to: `&mut b`
+
+error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler
- error: aborting due to 3 previous errors
++ --> $DIR/needless_borrow.rs:25:13
++ |
++LL | &&a
++ | ^^^ help: change this to: `&a`
++
++error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler
++ --> $DIR/needless_borrow.rs:27:15
+ |
+LL | 46 => &&a,
+ | ^^^ help: change this to: `&a`
+
++error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler
++ --> $DIR/needless_borrow.rs:33:27
++ |
++LL | break &ref_a;
++ | ^^^^^^ help: change this to: `ref_a`
++
++error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler
++ --> $DIR/needless_borrow.rs:40:15
++ |
++LL | let _ = x(&&&a);
++ | ^^^^ help: change this to: `&a`
++
++error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler
++ --> $DIR/needless_borrow.rs:41:15
++ |
++LL | let _ = x(&mut &&a);
++ | ^^^^^^^^ help: change this to: `&a`
++
++error: this expression borrows a reference (`&mut i32`) that is immediately dereferenced by the compiler
++ --> $DIR/needless_borrow.rs:42:15
++ |
++LL | let _ = x(&&&mut b);
++ | ^^^^^^^^ help: change this to: `&mut b`
++
++error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler
++ --> $DIR/needless_borrow.rs:43:15
++ |
++LL | let _ = x(&&ref_a);
++ | ^^^^^^^ help: change this to: `ref_a`
++
++error: this expression borrows a reference (`&mut i32`) that is immediately dereferenced by the compiler
++ --> $DIR/needless_borrow.rs:46:11
++ |
++LL | x(&b);
++ | ^^ help: change this to: `b`
++
++error: aborting due to 10 previous errors
+
--- /dev/null
+use std::collections::{BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
+
+fn main() {
+ let sample = [1; 5];
+ let indirect_iter = sample.iter().collect::<Vec<_>>();
+ indirect_iter.into_iter().map(|x| (x, x + 1)).collect::<HashMap<_, _>>();
+ let indirect_len = sample.iter().collect::<VecDeque<_>>();
+ indirect_len.len();
+ let indirect_empty = sample.iter().collect::<VecDeque<_>>();
+ indirect_empty.is_empty();
+ let indirect_contains = sample.iter().collect::<VecDeque<_>>();
+ indirect_contains.contains(&&5);
+ let indirect_negative = sample.iter().collect::<Vec<_>>();
+ indirect_negative.len();
+ indirect_negative
+ .into_iter()
+ .map(|x| (*x, *x + 1))
+ .collect::<HashMap<_, _>>();
+
+ // #6202
+ let a = "a".to_string();
+ let sample = vec![a.clone(), "b".to_string(), "c".to_string()];
+ let non_copy_contains = sample.into_iter().collect::<Vec<_>>();
+ non_copy_contains.contains(&a);
+
+ // Fix #5991
+ let vec_a = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+ let vec_b = vec_a.iter().collect::<Vec<_>>();
+ if vec_b.len() > 3 {}
+ let other_vec = vec![1, 3, 12, 4, 16, 2];
+ let we_got_the_same_numbers = other_vec.iter().filter(|item| vec_b.contains(item)).collect::<Vec<_>>();
+
+ // Fix #6297
+ let sample = [1; 5];
+ let multiple_indirect = sample.iter().collect::<Vec<_>>();
+ let sample2 = vec![2, 3];
+ if multiple_indirect.is_empty() {
+ // do something
+ } else {
+ let found = sample2
+ .iter()
+ .filter(|i| multiple_indirect.iter().any(|s| **s % **i == 0))
+ .collect::<Vec<_>>();
+ }
+}
+
+mod issue7110 {
+ // #7110 - lint for type annotation cases
+ use super::*;
+
+ fn lint_vec(string: &str) -> usize {
+ let buffer: Vec<&str> = string.split('/').collect();
+ buffer.len()
+ }
+ fn lint_vec_deque() -> usize {
+ let sample = [1; 5];
+ let indirect_len: VecDeque<_> = sample.iter().collect();
+ indirect_len.len()
+ }
+ fn lint_linked_list() -> usize {
+ let sample = [1; 5];
+ let indirect_len: LinkedList<_> = sample.iter().collect();
+ indirect_len.len()
+ }
+ fn lint_binary_heap() -> usize {
+ let sample = [1; 5];
+ let indirect_len: BinaryHeap<_> = sample.iter().collect();
+ indirect_len.len()
+ }
+ fn dont_lint(string: &str) -> usize {
+ let buffer: Vec<&str> = string.split('/').collect();
+ for buff in &buffer {
+ println!("{}", buff);
+ }
+ buffer.len()
+ }
+}
+
++mod issue7975 {
++ use super::*;
++
++ fn direct_mapping_with_used_mutable_reference() -> Vec<()> {
++ let test_vec: Vec<()> = vec![];
++ let mut vec_2: Vec<()> = vec![];
++ let mut_ref = &mut vec_2;
++ let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
++ collected_vec.into_iter().map(|_| mut_ref.push(())).collect()
++ }
++
++ fn indirectly_mapping_with_used_mutable_reference() -> Vec<()> {
++ let test_vec: Vec<()> = vec![];
++ let mut vec_2: Vec<()> = vec![];
++ let mut_ref = &mut vec_2;
++ let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
++ let iter = collected_vec.into_iter();
++ iter.map(|_| mut_ref.push(())).collect()
++ }
++
++ fn indirect_collect_after_indirect_mapping_with_used_mutable_reference() -> Vec<()> {
++ let test_vec: Vec<()> = vec![];
++ let mut vec_2: Vec<()> = vec![];
++ let mut_ref = &mut vec_2;
++ let collected_vec: Vec<_> = test_vec.into_iter().map(|_| mut_ref.push(())).collect();
++ let iter = collected_vec.into_iter();
++ let mapped_iter = iter.map(|_| mut_ref.push(()));
++ mapped_iter.collect()
++ }
++}
++
+fn allow_test() {
+ #[allow(clippy::needless_collect)]
+ let v = [1].iter().collect::<Vec<_>>();
+ v.into_iter().collect::<HashSet<_>>();
+}
--- /dev/null
--- /dev/null
++#![allow(unused)]
++
++fn main() {
++ let a;
++ let n = 1;
++ match n {
++ 1 => a = "one",
++ _ => {
++ a = "two";
++ },
++ }
++
++ let b;
++ if n == 3 {
++ b = "four";
++ } else {
++ b = "five"
++ }
++
++ let c;
++ if let Some(n) = Some(5) {
++ c = n;
++ } else {
++ c = -50;
++ }
++
++ let d;
++ if true {
++ let temp = 5;
++ d = temp;
++ } else {
++ d = 15;
++ }
++
++ let e;
++ if true {
++ e = format!("{} {}", a, b);
++ } else {
++ e = format!("{}", c);
++ }
++
++ println!("{}", a);
++}
++
++async fn in_async() -> &'static str {
++ async fn f() -> &'static str {
++ "one"
++ }
++
++ let a;
++ let n = 1;
++ match n {
++ 1 => a = f().await,
++ _ => {
++ a = "two";
++ },
++ }
++
++ a
++}
++
++const fn in_const() -> &'static str {
++ const fn f() -> &'static str {
++ "one"
++ }
++
++ let a;
++ let n = 1;
++ match n {
++ 1 => a = f(),
++ _ => {
++ a = "two";
++ },
++ }
++
++ a
++}
++
++fn does_not_lint() {
++ let z;
++ if false {
++ z = 1;
++ }
++
++ let x;
++ let y;
++ if true {
++ x = 1;
++ } else {
++ y = 1;
++ }
++
++ let mut x;
++ if true {
++ x = 5;
++ x = 10 / x;
++ } else {
++ x = 2;
++ }
++
++ let x;
++ let _ = match 1 {
++ 1 => x = 10,
++ _ => x = 20,
++ };
++
++ // using tuples would be possible, but not always preferable
++ let x;
++ let y;
++ if true {
++ x = 1;
++ y = 2;
++ } else {
++ x = 3;
++ y = 4;
++ }
++
++ // could match with a smarter heuristic to avoid multiple assignments
++ let x;
++ if true {
++ let mut y = 5;
++ y = 6;
++ x = y;
++ } else {
++ x = 2;
++ }
++
++ let (x, y);
++ if true {
++ x = 1;
++ } else {
++ x = 2;
++ }
++ y = 3;
++
++ macro_rules! assign {
++ ($i:ident) => {
++ $i = 1;
++ };
++ }
++ let x;
++ assign!(x);
++
++ let x;
++ if true {
++ assign!(x);
++ } else {
++ x = 2;
++ }
++
++ macro_rules! in_macro {
++ () => {
++ let x;
++ x = 1;
++
++ let x;
++ if true {
++ x = 1;
++ } else {
++ x = 2;
++ }
++ };
++ }
++ in_macro!();
++
++ println!("{}", x);
++}
--- /dev/null
--- /dev/null
++error: unneeded late initalization
++ --> $DIR/needless_late_init.rs:4:5
++ |
++LL | let a;
++ | ^^^^^^
++ |
++ = note: `-D clippy::needless-late-init` implied by `-D warnings`
++help: declare `a` here
++ |
++LL | let a = match n {
++ | +++++++
++help: remove the assignments from the `match` arms
++ |
++LL ~ 1 => "one",
++LL | _ => {
++LL ~ "two"
++ |
++help: add a semicolon after the `match` expression
++ |
++LL | };
++ | +
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init.rs:13:5
++ |
++LL | let b;
++ | ^^^^^^
++ |
++help: declare `b` here
++ |
++LL | let b = if n == 3 {
++ | +++++++
++help: remove the assignments from the branches
++ |
++LL ~ "four"
++LL | } else {
++LL ~ "five"
++ |
++help: add a semicolon after the `if` expression
++ |
++LL | };
++ | +
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init.rs:20:5
++ |
++LL | let c;
++ | ^^^^^^
++ |
++help: declare `c` here
++ |
++LL | let c = if let Some(n) = Some(5) {
++ | +++++++
++help: remove the assignments from the branches
++ |
++LL ~ n
++LL | } else {
++LL ~ -50
++ |
++help: add a semicolon after the `if` expression
++ |
++LL | };
++ | +
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init.rs:27:5
++ |
++LL | let d;
++ | ^^^^^^
++ |
++help: declare `d` here
++ |
++LL | let d = if true {
++ | +++++++
++help: remove the assignments from the branches
++ |
++LL ~ temp
++LL | } else {
++LL ~ 15
++ |
++help: add a semicolon after the `if` expression
++ |
++LL | };
++ | +
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init.rs:35:5
++ |
++LL | let e;
++ | ^^^^^^
++ |
++help: declare `e` here
++ |
++LL | let e = if true {
++ | +++++++
++help: remove the assignments from the branches
++ |
++LL ~ format!("{} {}", a, b)
++LL | } else {
++LL ~ format!("{}", c)
++ |
++help: add a semicolon after the `if` expression
++ |
++LL | };
++ | +
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init.rs:50:5
++ |
++LL | let a;
++ | ^^^^^^
++ |
++help: declare `a` here
++ |
++LL | let a = match n {
++ | +++++++
++help: remove the assignments from the `match` arms
++ |
++LL ~ 1 => f().await,
++LL | _ => {
++LL ~ "two"
++ |
++help: add a semicolon after the `match` expression
++ |
++LL | };
++ | +
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init.rs:67:5
++ |
++LL | let a;
++ | ^^^^^^
++ |
++help: declare `a` here
++ |
++LL | let a = match n {
++ | +++++++
++help: remove the assignments from the `match` arms
++ |
++LL ~ 1 => f(),
++LL | _ => {
++LL ~ "two"
++ |
++help: add a semicolon after the `match` expression
++ |
++LL | };
++ | +
++
++error: aborting due to 7 previous errors
++
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(unused, clippy::assign_op_pattern)]
++
++fn main() {
++
++ let a = "zero";
++
++
++
++ let b = 1;
++ let c = 2;
++
++
++ let d: usize = 1;
++
++
++ let mut e = 1;
++ e = 2;
++
++
++ let f = match 1 {
++ 1 => "three",
++ _ => return,
++ }; // has semi
++
++
++ let g: usize = if true {
++ 5
++ } else {
++ panic!();
++ };
++
++
++ let h = format!("{}", e);
++
++ println!("{}", a);
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![allow(unused, clippy::assign_op_pattern)]
++
++fn main() {
++ let a;
++ a = "zero";
++
++ let b;
++ let c;
++ b = 1;
++ c = 2;
++
++ let d: usize;
++ d = 1;
++
++ let mut e;
++ e = 1;
++ e = 2;
++
++ let f;
++ match 1 {
++ 1 => f = "three",
++ _ => return,
++ }; // has semi
++
++ let g: usize;
++ if true {
++ g = 5;
++ } else {
++ panic!();
++ }
++
++ let h;
++ h = format!("{}", e);
++
++ println!("{}", a);
++}
--- /dev/null
--- /dev/null
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:6:5
++ |
++LL | let a;
++ | ^^^^^^
++ |
++ = note: `-D clippy::needless-late-init` implied by `-D warnings`
++help: declare `a` here
++ |
++LL | let a = "zero";
++ | ~~~~~
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:9:5
++ |
++LL | let b;
++ | ^^^^^^
++ |
++help: declare `b` here
++ |
++LL | let b = 1;
++ | ~~~~~
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:10:5
++ |
++LL | let c;
++ | ^^^^^^
++ |
++help: declare `c` here
++ |
++LL | let c = 2;
++ | ~~~~~
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:14:5
++ |
++LL | let d: usize;
++ | ^^^^^^^^^^^^^
++ |
++help: declare `d` here
++ |
++LL | let d: usize = 1;
++ | ~~~~~~~~~~~~
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:17:5
++ |
++LL | let mut e;
++ | ^^^^^^^^^^
++ |
++help: declare `e` here
++ |
++LL | let mut e = 1;
++ | ~~~~~~~~~
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:21:5
++ |
++LL | let f;
++ | ^^^^^^
++ |
++help: declare `f` here
++ |
++LL | let f = match 1 {
++ | +++++++
++help: remove the assignments from the `match` arms
++ |
++LL | 1 => "three",
++ | ~~~~~~~
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:27:5
++ |
++LL | let g: usize;
++ | ^^^^^^^^^^^^^
++ |
++help: declare `g` here
++ |
++LL | let g: usize = if true {
++ | ++++++++++++++
++help: remove the assignments from the branches
++ |
++LL | 5
++ |
++help: add a semicolon after the `if` expression
++ |
++LL | };
++ | +
++
++error: unneeded late initalization
++ --> $DIR/needless_late_init_fixable.rs:34:5
++ |
++LL | let h;
++ | ^^^^^^
++ |
++help: declare `h` here
++ |
++LL | let h = format!("{}", e);
++ | ~~~~~
++
++error: aborting due to 8 previous errors
++
--- /dev/null
- | ^^^^^^^^^^^^^^^ help: try: `to.magic`
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:23:12
+ |
+LL | return Some(to.magic?);
- | ^^^^^^^^^^^^^^^ help: try: `to.magic`
++ | ^^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `to.magic`
+ |
+ = note: `-D clippy::needless-question-mark` implied by `-D warnings`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:31:12
+ |
+LL | return Some(to.magic?)
- | ^^^^^^^^^^^^^^^ help: try: `to.magic`
++ | ^^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `to.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:36:5
+ |
+LL | Some(to.magic?)
- | ^^^^^^^^^^^^^^ help: try: `t.magic`
++ | ^^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `to.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:41:21
+ |
+LL | to.and_then(|t| Some(t.magic?))
- | ^^^^^^^^^^^^^^ help: try: `t.magic`
++ | ^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `t.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:50:9
+ |
+LL | Some(t.magic?)
- | ^^^^^^^^^^^^^ help: try: `tr.magic`
++ | ^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `t.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:55:12
+ |
+LL | return Ok(tr.magic?);
- | ^^^^^^^^^^^^^ help: try: `tr.magic`
++ | ^^^^^^^^^^^^^ help: try removing question mark and `Ok()`: `tr.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:62:12
+ |
+LL | return Ok(tr.magic?)
- | ^^^^^^^^^^^^^ help: try: `tr.magic`
++ | ^^^^^^^^^^^^^ help: try removing question mark and `Ok()`: `tr.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:66:5
+ |
+LL | Ok(tr.magic?)
- | ^^^^^^^^^^^^ help: try: `t.magic`
++ | ^^^^^^^^^^^^^ help: try removing question mark and `Ok()`: `tr.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:70:21
+ |
+LL | tr.and_then(|t| Ok(t.magic?))
- | ^^^^^^^^^^^^ help: try: `t.magic`
++ | ^^^^^^^^^^^^ help: try removing question mark and `Ok()`: `t.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:78:9
+ |
+LL | Ok(t.magic?)
- | ^^^^^^^^^^^^ help: try: `t.magic`
++ | ^^^^^^^^^^^^ help: try removing question mark and `Ok()`: `t.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:85:16
+ |
+LL | return Ok(t.magic?);
- | ^^^^^^^^^^^^^^^^^^ help: try: `Some($expr)`
++ | ^^^^^^^^^^^^ help: try removing question mark and `Ok()`: `t.magic`
+
+error: question mark operator is useless here
+ --> $DIR/needless_question_mark.rs:120:27
+ |
+LL | || -> Option<_> { Some(Some($expr)?) }()
++ | ^^^^^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `Some($expr)`
+...
+LL | let _x = some_and_qmark_in_macro!(x?);
+ | ---------------------------- in this macro invocation
+ |
+ = note: this error originates in the macro `some_and_qmark_in_macro` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 12 previous errors
+
--- /dev/null
+// run-rustfix
+
+#![feature(let_else)]
+#![allow(unused)]
+#![allow(
+ clippy::if_same_then_else,
+ clippy::single_match,
+ clippy::needless_bool,
+ clippy::equatable_if_let
+)]
+#![warn(clippy::needless_return)]
+
+macro_rules! the_answer {
+ () => {
+ 42
+ };
+}
+
+fn test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ true
+}
+
+fn test_no_semicolon() -> bool {
+ true
+}
+
+fn test_if_block() -> bool {
+ if true {
+ true
+ } else {
+ false
+ }
+}
+
+fn test_match(x: bool) -> bool {
+ match x {
+ true => false,
+ false => {
+ true
+ },
+ }
+}
+
+fn test_closure() {
+ let _ = || {
+ true
+ };
+ let _ = || true;
+}
+
+fn test_macro_call() -> i32 {
+ return the_answer!();
+}
+
+fn test_void_fun() {
+
+}
+
+fn test_void_if_fun(b: bool) {
+ if b {
+
+ } else {
+
+ }
+}
+
+fn test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => {},
+ }
+}
+
+fn read_line() -> String {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ return stdin.lock().lines().next().unwrap().unwrap();
+}
+
+fn borrows_but_not_last(value: bool) -> String {
+ if value {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ let _a = stdin.lock().lines().next().unwrap().unwrap();
+ String::from("test")
+ } else {
+ String::new()
+ }
+}
+
+macro_rules! needed_return {
+ ($e:expr) => {
+ if $e > 3 {
+ return;
+ }
+ };
+}
+
+fn test_return_in_macro() {
+ // This will return and the macro below won't be executed. Removing the `return` from the macro
+ // will change semantics.
+ needed_return!(10);
+ needed_return!(0);
+}
+
+mod issue6501 {
++ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn foo(bar: Result<(), ()>) {
+ bar.unwrap_or_else(|_| {})
+ }
+
+ fn test_closure() {
+ let _ = || {
+
+ };
+ let _ = || {};
+ }
+
+ struct Foo;
+ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn bar(res: Result<Foo, u8>) -> Foo {
+ res.unwrap_or_else(|_| Foo)
+ }
+}
+
+async fn async_test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ true
+}
+
+async fn async_test_no_semicolon() -> bool {
+ true
+}
+
+async fn async_test_if_block() -> bool {
+ if true {
+ true
+ } else {
+ false
+ }
+}
+
+async fn async_test_match(x: bool) -> bool {
+ match x {
+ true => false,
+ false => {
+ true
+ },
+ }
+}
+
+async fn async_test_closure() {
+ let _ = || {
+ true
+ };
+ let _ = || true;
+}
+
+async fn async_test_macro_call() -> i32 {
+ return the_answer!();
+}
+
+async fn async_test_void_fun() {
+
+}
+
+async fn async_test_void_if_fun(b: bool) {
+ if b {
+
+ } else {
+
+ }
+}
+
+async fn async_test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => {},
+ }
+}
+
+async fn async_read_line() -> String {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ return stdin.lock().lines().next().unwrap().unwrap();
+}
+
+async fn async_borrows_but_not_last(value: bool) -> String {
+ if value {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ let _a = stdin.lock().lines().next().unwrap().unwrap();
+ String::from("test")
+ } else {
+ String::new()
+ }
+}
+
+async fn async_test_return_in_macro() {
+ needed_return!(10);
+ needed_return!(0);
+}
+
+fn let_else() {
+ let Some(1) = Some(1) else { return };
+}
+
+fn main() {}
--- /dev/null
+// run-rustfix
+
+#![feature(let_else)]
+#![allow(unused)]
+#![allow(
+ clippy::if_same_then_else,
+ clippy::single_match,
+ clippy::needless_bool,
+ clippy::equatable_if_let
+)]
+#![warn(clippy::needless_return)]
+
+macro_rules! the_answer {
+ () => {
+ 42
+ };
+}
+
+fn test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ return true;
+}
+
+fn test_no_semicolon() -> bool {
+ return true;
+}
+
+fn test_if_block() -> bool {
+ if true {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+fn test_match(x: bool) -> bool {
+ match x {
+ true => return false,
+ false => {
+ return true;
+ },
+ }
+}
+
+fn test_closure() {
+ let _ = || {
+ return true;
+ };
+ let _ = || return true;
+}
+
+fn test_macro_call() -> i32 {
+ return the_answer!();
+}
+
+fn test_void_fun() {
+ return;
+}
+
+fn test_void_if_fun(b: bool) {
+ if b {
+ return;
+ } else {
+ return;
+ }
+}
+
+fn test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => return,
+ }
+}
+
+fn read_line() -> String {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ return stdin.lock().lines().next().unwrap().unwrap();
+}
+
+fn borrows_but_not_last(value: bool) -> String {
+ if value {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ let _a = stdin.lock().lines().next().unwrap().unwrap();
+ return String::from("test");
+ } else {
+ return String::new();
+ }
+}
+
+macro_rules! needed_return {
+ ($e:expr) => {
+ if $e > 3 {
+ return;
+ }
+ };
+}
+
+fn test_return_in_macro() {
+ // This will return and the macro below won't be executed. Removing the `return` from the macro
+ // will change semantics.
+ needed_return!(10);
+ needed_return!(0);
+}
+
+mod issue6501 {
++ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn foo(bar: Result<(), ()>) {
+ bar.unwrap_or_else(|_| return)
+ }
+
+ fn test_closure() {
+ let _ = || {
+ return;
+ };
+ let _ = || return;
+ }
+
+ struct Foo;
+ #[allow(clippy::unnecessary_lazy_evaluations)]
+ fn bar(res: Result<Foo, u8>) -> Foo {
+ res.unwrap_or_else(|_| return Foo)
+ }
+}
+
+async fn async_test_end_of_fn() -> bool {
+ if true {
+ // no error!
+ return true;
+ }
+ return true;
+}
+
+async fn async_test_no_semicolon() -> bool {
+ return true;
+}
+
+async fn async_test_if_block() -> bool {
+ if true {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+async fn async_test_match(x: bool) -> bool {
+ match x {
+ true => return false,
+ false => {
+ return true;
+ },
+ }
+}
+
+async fn async_test_closure() {
+ let _ = || {
+ return true;
+ };
+ let _ = || return true;
+}
+
+async fn async_test_macro_call() -> i32 {
+ return the_answer!();
+}
+
+async fn async_test_void_fun() {
+ return;
+}
+
+async fn async_test_void_if_fun(b: bool) {
+ if b {
+ return;
+ } else {
+ return;
+ }
+}
+
+async fn async_test_void_match(x: u32) {
+ match x {
+ 0 => (),
+ _ => return,
+ }
+}
+
+async fn async_read_line() -> String {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ return stdin.lock().lines().next().unwrap().unwrap();
+}
+
+async fn async_borrows_but_not_last(value: bool) -> String {
+ if value {
+ use std::io::BufRead;
+ let stdin = ::std::io::stdin();
+ let _a = stdin.lock().lines().next().unwrap().unwrap();
+ return String::from("test");
+ } else {
+ return String::new();
+ }
+}
+
+async fn async_test_return_in_macro() {
+ needed_return!(10);
+ needed_return!(0);
+}
+
+fn let_else() {
+ let Some(1) = Some(1) else { return };
+}
+
+fn main() {}
--- /dev/null
- --> $DIR/needless_return.rs:112:32
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:24:5
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+ |
+ = note: `-D clippy::needless-return` implied by `-D warnings`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:28:5
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:33:9
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:35:9
+ |
+LL | return false;
+ | ^^^^^^^^^^^^^ help: remove `return`: `false`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:41:17
+ |
+LL | true => return false,
+ | ^^^^^^^^^^^^ help: remove `return`: `false`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:43:13
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:50:9
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:52:16
+ |
+LL | let _ = || return true;
+ | ^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:60:5
+ |
+LL | return;
+ | ^^^^^^^ help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:65:9
+ |
+LL | return;
+ | ^^^^^^^ help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:67:9
+ |
+LL | return;
+ | ^^^^^^^ help: remove `return`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:74:14
+ |
+LL | _ => return,
+ | ^^^^^^ help: replace `return` with an empty block: `{}`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:89:9
+ |
+LL | return String::from("test");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")`
+
+error: unneeded `return` statement
+ --> $DIR/needless_return.rs:91:9
+ |
+LL | return String::new();
+ | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:117:13
++ --> $DIR/needless_return.rs:113:32
+ |
+LL | bar.unwrap_or_else(|_| return)
+ | ^^^^^^ help: replace `return` with an empty block: `{}`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:119:20
++ --> $DIR/needless_return.rs:118:13
+ |
+LL | return;
+ | ^^^^^^^ help: remove `return`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:125:32
++ --> $DIR/needless_return.rs:120:20
+ |
+LL | let _ = || return;
+ | ^^^^^^ help: replace `return` with an empty block: `{}`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:134:5
++ --> $DIR/needless_return.rs:126:32
+ |
+LL | res.unwrap_or_else(|_| return Foo)
+ | ^^^^^^^^^^ help: remove `return`: `Foo`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:138:5
++ --> $DIR/needless_return.rs:135:5
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:143:9
++ --> $DIR/needless_return.rs:139:5
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:145:9
++ --> $DIR/needless_return.rs:144:9
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:151:17
++ --> $DIR/needless_return.rs:146:9
+ |
+LL | return false;
+ | ^^^^^^^^^^^^^ help: remove `return`: `false`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:153:13
++ --> $DIR/needless_return.rs:152:17
+ |
+LL | true => return false,
+ | ^^^^^^^^^^^^ help: remove `return`: `false`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:160:9
++ --> $DIR/needless_return.rs:154:13
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:162:16
++ --> $DIR/needless_return.rs:161:9
+ |
+LL | return true;
+ | ^^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:170:5
++ --> $DIR/needless_return.rs:163:16
+ |
+LL | let _ = || return true;
+ | ^^^^^^^^^^^ help: remove `return`: `true`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:175:9
++ --> $DIR/needless_return.rs:171:5
+ |
+LL | return;
+ | ^^^^^^^ help: remove `return`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:177:9
++ --> $DIR/needless_return.rs:176:9
+ |
+LL | return;
+ | ^^^^^^^ help: remove `return`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:184:14
++ --> $DIR/needless_return.rs:178:9
+ |
+LL | return;
+ | ^^^^^^^ help: remove `return`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:199:9
++ --> $DIR/needless_return.rs:185:14
+ |
+LL | _ => return,
+ | ^^^^^^ help: replace `return` with an empty block: `{}`
+
+error: unneeded `return` statement
- --> $DIR/needless_return.rs:201:9
++ --> $DIR/needless_return.rs:200:9
+ |
+LL | return String::from("test");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")`
+
+error: unneeded `return` statement
++ --> $DIR/needless_return.rs:202:9
+ |
+LL | return String::new();
+ | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()`
+
+error: aborting due to 32 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++// edition:2018
++
++#![feature(custom_inner_attributes)]
++#![warn(clippy::needless_splitn)]
++#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::manual_split_once)]
++
++extern crate itertools;
++
++#[allow(unused_imports)]
++use itertools::Itertools;
++
++fn main() {
++ let str = "key=value=end";
++ let _ = str.split('=').next();
++ let _ = str.split('=').nth(0);
++ let _ = str.splitn(2, '=').nth(1);
++ let (_, _) = str.splitn(2, '=').next_tuple().unwrap();
++ let (_, _) = str.split('=').next_tuple().unwrap();
++ let _: Vec<&str> = str.splitn(3, '=').collect();
++
++ let _ = str.rsplit('=').next();
++ let _ = str.rsplit('=').nth(0);
++ let _ = str.rsplitn(2, '=').nth(1);
++ let (_, _) = str.rsplitn(2, '=').next_tuple().unwrap();
++ let (_, _) = str.rsplit('=').next_tuple().unwrap();
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++// edition:2018
++
++#![feature(custom_inner_attributes)]
++#![warn(clippy::needless_splitn)]
++#![allow(clippy::iter_skip_next, clippy::iter_nth_zero, clippy::manual_split_once)]
++
++extern crate itertools;
++
++#[allow(unused_imports)]
++use itertools::Itertools;
++
++fn main() {
++ let str = "key=value=end";
++ let _ = str.splitn(2, '=').next();
++ let _ = str.splitn(2, '=').nth(0);
++ let _ = str.splitn(2, '=').nth(1);
++ let (_, _) = str.splitn(2, '=').next_tuple().unwrap();
++ let (_, _) = str.splitn(3, '=').next_tuple().unwrap();
++ let _: Vec<&str> = str.splitn(3, '=').collect();
++
++ let _ = str.rsplitn(2, '=').next();
++ let _ = str.rsplitn(2, '=').nth(0);
++ let _ = str.rsplitn(2, '=').nth(1);
++ let (_, _) = str.rsplitn(2, '=').next_tuple().unwrap();
++ let (_, _) = str.rsplitn(3, '=').next_tuple().unwrap();
++}
--- /dev/null
--- /dev/null
++error: unnecessary use of `splitn`
++ --> $DIR/needless_splitn.rs:15:13
++ |
++LL | let _ = str.splitn(2, '=').next();
++ | ^^^^^^^^^^^^^^^^^^ help: try this: `str.split('=')`
++ |
++ = note: `-D clippy::needless-splitn` implied by `-D warnings`
++
++error: unnecessary use of `splitn`
++ --> $DIR/needless_splitn.rs:16:13
++ |
++LL | let _ = str.splitn(2, '=').nth(0);
++ | ^^^^^^^^^^^^^^^^^^ help: try this: `str.split('=')`
++
++error: unnecessary use of `splitn`
++ --> $DIR/needless_splitn.rs:19:18
++ |
++LL | let (_, _) = str.splitn(3, '=').next_tuple().unwrap();
++ | ^^^^^^^^^^^^^^^^^^ help: try this: `str.split('=')`
++
++error: unnecessary use of `rsplitn`
++ --> $DIR/needless_splitn.rs:22:13
++ |
++LL | let _ = str.rsplitn(2, '=').next();
++ | ^^^^^^^^^^^^^^^^^^^ help: try this: `str.rsplit('=')`
++
++error: unnecessary use of `rsplitn`
++ --> $DIR/needless_splitn.rs:23:13
++ |
++LL | let _ = str.rsplitn(2, '=').nth(0);
++ | ^^^^^^^^^^^^^^^^^^^ help: try this: `str.rsplit('=')`
++
++error: unnecessary use of `rsplitn`
++ --> $DIR/needless_splitn.rs:26:18
++ |
++LL | let (_, _) = str.rsplitn(3, '=').next_tuple().unwrap();
++ | ^^^^^^^^^^^^^^^^^^^ help: try this: `str.rsplit('=')`
++
++error: aborting due to 6 previous errors
++
--- /dev/null
- #![feature(box_syntax)]
++#![feature(box_syntax, fn_traits, unboxed_closures)]
+#![warn(clippy::no_effect_underscore_binding)]
+#![allow(dead_code)]
+#![allow(path_statements)]
+#![allow(clippy::deref_addrof)]
+#![allow(clippy::redundant_field_names)]
+#![feature(untagged_unions)]
+
+struct Unit;
+struct Tuple(i32);
+struct Struct {
+ field: i32,
+}
+enum Enum {
+ Tuple(i32),
+ Struct { field: i32 },
+}
+struct DropUnit;
+impl Drop for DropUnit {
+ fn drop(&mut self) {}
+}
+struct DropStruct {
+ field: i32,
+}
+impl Drop for DropStruct {
+ fn drop(&mut self) {}
+}
+struct DropTuple(i32);
+impl Drop for DropTuple {
+ fn drop(&mut self) {}
+}
+enum DropEnum {
+ Tuple(i32),
+ Struct { field: i32 },
+}
+impl Drop for DropEnum {
+ fn drop(&mut self) {}
+}
+struct FooString {
+ s: String,
+}
+union Union {
+ a: u8,
+ b: f64,
+}
+
+fn get_number() -> i32 {
+ 0
+}
+fn get_struct() -> Struct {
+ Struct { field: 0 }
+}
+fn get_drop_struct() -> DropStruct {
+ DropStruct { field: 0 }
+}
+
+unsafe fn unsafe_fn() -> i32 {
+ 0
+}
+
++struct GreetStruct1;
++
++impl FnOnce<(&str,)> for GreetStruct1 {
++ type Output = ();
++
++ extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output {
++ println!("hello {}", who);
++ }
++}
++
++struct GreetStruct2();
++
++impl FnOnce<(&str,)> for GreetStruct2 {
++ type Output = ();
++
++ extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output {
++ println!("hello {}", who);
++ }
++}
++
++struct GreetStruct3 {}
++
++impl FnOnce<(&str,)> for GreetStruct3 {
++ type Output = ();
++
++ extern "rust-call" fn call_once(self, (who,): (&str,)) -> Self::Output {
++ println!("hello {}", who);
++ }
++}
++
+fn main() {
+ let s = get_struct();
+ let s2 = get_struct();
+
+ 0;
+ s2;
+ Unit;
+ Tuple(0);
+ Struct { field: 0 };
+ Struct { ..s };
+ Union { a: 0 };
+ Enum::Tuple(0);
+ Enum::Struct { field: 0 };
+ 5 + 6;
+ *&42;
+ &6;
+ (5, 6, 7);
+ box 42;
+ ..;
+ 5..;
+ ..5;
+ 5..6;
+ 5..=6;
+ [42, 55];
+ [42, 55][1];
+ (42, 55).1;
+ [42; 55];
+ [42; 55][13];
+ let mut x = 0;
+ || x += 5;
+ let s: String = "foo".into();
+ FooString { s: s };
+ let _unused = 1;
+ let _penguin = || println!("Some helpful closure");
+ let _duck = Struct { field: 0 };
+ let _cat = [2, 4, 6, 8][2];
+
+ #[allow(clippy::no_effect)]
+ 0;
+
+ // Do not warn
+ get_number();
+ unsafe { unsafe_fn() };
+ let _used = get_struct();
+ let _x = vec![1];
+ DropUnit;
+ DropStruct { field: 0 };
+ DropTuple(0);
+ DropEnum::Tuple(0);
+ DropEnum::Struct { field: 0 };
++ GreetStruct1("world");
++ GreetStruct2()("world");
++ GreetStruct3 {}("world");
+}
--- /dev/null
- --> $DIR/no_effect.rs:65:5
+error: statement with no effect
- --> $DIR/no_effect.rs:66:5
++ --> $DIR/no_effect.rs:95:5
+ |
+LL | 0;
+ | ^^
+ |
+ = note: `-D clippy::no-effect` implied by `-D warnings`
+
+error: statement with no effect
- --> $DIR/no_effect.rs:67:5
++ --> $DIR/no_effect.rs:96:5
+ |
+LL | s2;
+ | ^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:68:5
++ --> $DIR/no_effect.rs:97:5
+ |
+LL | Unit;
+ | ^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:69:5
++ --> $DIR/no_effect.rs:98:5
+ |
+LL | Tuple(0);
+ | ^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:70:5
++ --> $DIR/no_effect.rs:99:5
+ |
+LL | Struct { field: 0 };
+ | ^^^^^^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:71:5
++ --> $DIR/no_effect.rs:100:5
+ |
+LL | Struct { ..s };
+ | ^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:72:5
++ --> $DIR/no_effect.rs:101:5
+ |
+LL | Union { a: 0 };
+ | ^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:73:5
++ --> $DIR/no_effect.rs:102:5
+ |
+LL | Enum::Tuple(0);
+ | ^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:74:5
++ --> $DIR/no_effect.rs:103:5
+ |
+LL | Enum::Struct { field: 0 };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:75:5
++ --> $DIR/no_effect.rs:104:5
+ |
+LL | 5 + 6;
+ | ^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:76:5
++ --> $DIR/no_effect.rs:105:5
+ |
+LL | *&42;
+ | ^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:77:5
++ --> $DIR/no_effect.rs:106:5
+ |
+LL | &6;
+ | ^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:78:5
++ --> $DIR/no_effect.rs:107:5
+ |
+LL | (5, 6, 7);
+ | ^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:79:5
++ --> $DIR/no_effect.rs:108:5
+ |
+LL | box 42;
+ | ^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:80:5
++ --> $DIR/no_effect.rs:109:5
+ |
+LL | ..;
+ | ^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:81:5
++ --> $DIR/no_effect.rs:110:5
+ |
+LL | 5..;
+ | ^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:82:5
++ --> $DIR/no_effect.rs:111:5
+ |
+LL | ..5;
+ | ^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:83:5
++ --> $DIR/no_effect.rs:112:5
+ |
+LL | 5..6;
+ | ^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:84:5
++ --> $DIR/no_effect.rs:113:5
+ |
+LL | 5..=6;
+ | ^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:85:5
++ --> $DIR/no_effect.rs:114:5
+ |
+LL | [42, 55];
+ | ^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:86:5
++ --> $DIR/no_effect.rs:115:5
+ |
+LL | [42, 55][1];
+ | ^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:87:5
++ --> $DIR/no_effect.rs:116:5
+ |
+LL | (42, 55).1;
+ | ^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:88:5
++ --> $DIR/no_effect.rs:117:5
+ |
+LL | [42; 55];
+ | ^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:90:5
++ --> $DIR/no_effect.rs:118:5
+ |
+LL | [42; 55][13];
+ | ^^^^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:92:5
++ --> $DIR/no_effect.rs:120:5
+ |
+LL | || x += 5;
+ | ^^^^^^^^^^
+
+error: statement with no effect
- --> $DIR/no_effect.rs:93:5
++ --> $DIR/no_effect.rs:122:5
+ |
+LL | FooString { s: s };
+ | ^^^^^^^^^^^^^^^^^^^
+
+error: binding to `_` prefixed variable with no side-effect
- --> $DIR/no_effect.rs:94:5
++ --> $DIR/no_effect.rs:123:5
+ |
+LL | let _unused = 1;
+ | ^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::no-effect-underscore-binding` implied by `-D warnings`
+
+error: binding to `_` prefixed variable with no side-effect
- --> $DIR/no_effect.rs:95:5
++ --> $DIR/no_effect.rs:124:5
+ |
+LL | let _penguin = || println!("Some helpful closure");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: binding to `_` prefixed variable with no side-effect
- --> $DIR/no_effect.rs:96:5
++ --> $DIR/no_effect.rs:125:5
+ |
+LL | let _duck = Struct { field: 0 };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: binding to `_` prefixed variable with no side-effect
++ --> $DIR/no_effect.rs:126:5
+ |
+LL | let _cat = [2, 4, 6, 8][2];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 30 previous errors
+
--- /dev/null
+#![warn(clippy::non_send_fields_in_send_ty)]
+#![feature(extern_types)]
+
+use std::cell::UnsafeCell;
+use std::ptr::NonNull;
+use std::rc::Rc;
+use std::sync::{Arc, Mutex, MutexGuard};
+
+// disrustor / RUSTSEC-2020-0150
+pub struct RingBuffer<T> {
+ data: Vec<UnsafeCell<T>>,
+ capacity: usize,
+ mask: usize,
+}
+
+unsafe impl<T> Send for RingBuffer<T> {}
+
+// noise_search / RUSTSEC-2020-0141
+pub struct MvccRwLock<T> {
+ raw: *const T,
+ lock: Mutex<Box<T>>,
+}
+
+unsafe impl<T> Send for MvccRwLock<T> {}
+
+// async-coap / RUSTSEC-2020-0124
+pub struct ArcGuard<RC, T> {
+ inner: T,
+ head: Arc<RC>,
+}
+
+unsafe impl<RC, T: Send> Send for ArcGuard<RC, T> {}
+
+// rusb / RUSTSEC-2020-0098
+extern "C" {
+ type libusb_device_handle;
+}
+
+pub trait UsbContext {
+ // some user trait that does not guarantee `Send`
+}
+
+pub struct DeviceHandle<T: UsbContext> {
+ context: T,
+ handle: NonNull<libusb_device_handle>,
+}
+
+unsafe impl<T: UsbContext> Send for DeviceHandle<T> {}
+
+// Other basic tests
+pub struct NoGeneric {
+ rc_is_not_send: Rc<String>,
+}
+
+unsafe impl Send for NoGeneric {}
+
+pub struct MultiField<T> {
+ field1: T,
+ field2: T,
+ field3: T,
+}
+
+unsafe impl<T> Send for MultiField<T> {}
+
+pub enum MyOption<T> {
+ MySome(T),
+ MyNone,
+}
+
+unsafe impl<T> Send for MyOption<T> {}
+
++// Test types that contain `NonNull` instead of raw pointers (#8045)
++pub struct WrappedNonNull(UnsafeCell<NonNull<()>>);
++
++unsafe impl Send for WrappedNonNull {}
++
+// Multiple type parameters
+pub struct MultiParam<A, B> {
+ vec: Vec<(A, B)>,
+}
+
+unsafe impl<A, B> Send for MultiParam<A, B> {}
+
+// Tests for raw pointer heuristic
+extern "C" {
+ type NonSend;
+}
+
+pub struct HeuristicTest {
+ // raw pointers are allowed
+ field1: Vec<*const NonSend>,
+ field2: [*const NonSend; 3],
+ field3: (*const NonSend, *const NonSend, *const NonSend),
+ // not allowed when it contains concrete `!Send` field
+ field4: (*const NonSend, Rc<u8>),
+ // nested raw pointer is also allowed
+ field5: Vec<Vec<*const NonSend>>,
+}
+
+unsafe impl Send for HeuristicTest {}
+
+// Test attributes
+#[allow(clippy::non_send_fields_in_send_ty)]
+pub struct AttrTest1<T>(T);
+
+pub struct AttrTest2<T> {
+ #[allow(clippy::non_send_fields_in_send_ty)]
+ field: T,
+}
+
+pub enum AttrTest3<T> {
+ #[allow(clippy::non_send_fields_in_send_ty)]
+ Enum1(T),
+ Enum2(T),
+}
+
+unsafe impl<T> Send for AttrTest1<T> {}
+unsafe impl<T> Send for AttrTest2<T> {}
+unsafe impl<T> Send for AttrTest3<T> {}
+
+// Multiple non-overlapping `Send` for a single type
+pub struct Complex<A, B> {
+ field1: A,
+ field2: B,
+}
+
+unsafe impl<P> Send for Complex<P, u32> {}
+
+// `MutexGuard` is non-Send
+unsafe impl<Q: Send> Send for Complex<Q, MutexGuard<'static, bool>> {}
+
+fn main() {}
--- /dev/null
- --> $DIR/non_send_fields_in_send_ty.rs:77:1
+error: this implementation is unsound, as some fields in `RingBuffer<T>` are `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:16:1
+ |
+LL | unsafe impl<T> Send for RingBuffer<T> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::non-send-fields-in-send-ty` implied by `-D warnings`
+note: the type of field `data` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:11:5
+ |
+LL | data: Vec<UnsafeCell<T>>,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ = help: add bounds on type parameter `T` that satisfy `Vec<UnsafeCell<T>>: Send`
+
+error: this implementation is unsound, as some fields in `MvccRwLock<T>` are `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:24:1
+ |
+LL | unsafe impl<T> Send for MvccRwLock<T> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `lock` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:21:5
+ |
+LL | lock: Mutex<Box<T>>,
+ | ^^^^^^^^^^^^^^^^^^^
+ = help: add bounds on type parameter `T` that satisfy `Mutex<Box<T>>: Send`
+
+error: this implementation is unsound, as some fields in `ArcGuard<RC, T>` are `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:32:1
+ |
+LL | unsafe impl<RC, T: Send> Send for ArcGuard<RC, T> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `head` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:29:5
+ |
+LL | head: Arc<RC>,
+ | ^^^^^^^^^^^^^
+ = help: add bounds on type parameter `RC` that satisfy `Arc<RC>: Send`
+
+error: this implementation is unsound, as some fields in `DeviceHandle<T>` are `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:48:1
+ |
+LL | unsafe impl<T: UsbContext> Send for DeviceHandle<T> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `context` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:44:5
+ |
+LL | context: T,
+ | ^^^^^^^^^^
+ = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `NoGeneric` are `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:55:1
+ |
+LL | unsafe impl Send for NoGeneric {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `rc_is_not_send` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:52:5
+ |
+LL | rc_is_not_send: Rc<String>,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ = help: use a thread-safe type that implements `Send`
+
+error: this implementation is unsound, as some fields in `MultiField<T>` are `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:63:1
+ |
+LL | unsafe impl<T> Send for MultiField<T> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `field1` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:58:5
+ |
+LL | field1: T,
+ | ^^^^^^^^^
+ = help: add `T: Send` bound in `Send` impl
+note: the type of field `field2` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:59:5
+ |
+LL | field2: T,
+ | ^^^^^^^^^
+ = help: add `T: Send` bound in `Send` impl
+note: the type of field `field3` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:60:5
+ |
+LL | field3: T,
+ | ^^^^^^^^^
+ = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `MyOption<T>` are `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:70:1
+ |
+LL | unsafe impl<T> Send for MyOption<T> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `0` is `!Send`
+ --> $DIR/non_send_fields_in_send_ty.rs:66:12
+ |
+LL | MySome(T),
+ | ^
+ = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `MultiParam<A, B>` are `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:74:5
++ --> $DIR/non_send_fields_in_send_ty.rs:82:1
+ |
+LL | unsafe impl<A, B> Send for MultiParam<A, B> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `vec` is `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:95:1
++ --> $DIR/non_send_fields_in_send_ty.rs:79:5
+ |
+LL | vec: Vec<(A, B)>,
+ | ^^^^^^^^^^^^^^^^
+ = help: add bounds on type parameters `A, B` that satisfy `Vec<(A, B)>: Send`
+
+error: this implementation is unsound, as some fields in `HeuristicTest` are `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:90:5
++ --> $DIR/non_send_fields_in_send_ty.rs:100:1
+ |
+LL | unsafe impl Send for HeuristicTest {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `field4` is `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:114:1
++ --> $DIR/non_send_fields_in_send_ty.rs:95:5
+ |
+LL | field4: (*const NonSend, Rc<u8>),
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ = help: use a thread-safe type that implements `Send`
+
+error: this implementation is unsound, as some fields in `AttrTest3<T>` are `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:109:11
++ --> $DIR/non_send_fields_in_send_ty.rs:119:1
+ |
+LL | unsafe impl<T> Send for AttrTest3<T> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `0` is `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:122:1
++ --> $DIR/non_send_fields_in_send_ty.rs:114:11
+ |
+LL | Enum2(T),
+ | ^
+ = help: add `T: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `Complex<P, u32>` are `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:118:5
++ --> $DIR/non_send_fields_in_send_ty.rs:127:1
+ |
+LL | unsafe impl<P> Send for Complex<P, u32> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `field1` is `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:125:1
++ --> $DIR/non_send_fields_in_send_ty.rs:123:5
+ |
+LL | field1: A,
+ | ^^^^^^^^^
+ = help: add `P: Send` bound in `Send` impl
+
+error: this implementation is unsound, as some fields in `Complex<Q, MutexGuard<'static, bool>>` are `!Send`
- --> $DIR/non_send_fields_in_send_ty.rs:119:5
++ --> $DIR/non_send_fields_in_send_ty.rs:130:1
+ |
+LL | unsafe impl<Q: Send> Send for Complex<Q, MutexGuard<'static, bool>> {}
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: the type of field `field2` is `!Send`
++ --> $DIR/non_send_fields_in_send_ty.rs:124:5
+ |
+LL | field2: B,
+ | ^^^^^^^^^
+ = help: use a thread-safe type that implements `Send`
+
+error: aborting due to 12 previous errors
+
--- /dev/null
--- /dev/null
++#![warn(clippy::octal_escapes)]
++
++fn main() {
++ let _bad1 = "\033[0m";
++ let _bad2 = b"\033[0m";
++ let _bad3 = "\\\033[0m";
++ // maximum 3 digits (\012 is the escape)
++ let _bad4 = "\01234567";
++ let _bad5 = "\0\03";
++ let _bad6 = "Text-\055\077-MoreText";
++ let _bad7 = "EvenMoreText-\01\02-ShortEscapes";
++ let _bad8 = "锈\01锈";
++ let _bad9 = "锈\011锈";
++
++ let _good1 = "\\033[0m";
++ let _good2 = "\0\\0";
++ let _good3 = "\0\0";
++ let _good4 = "X\0\0X";
++ let _good5 = "锈\0锈";
++}
--- /dev/null
--- /dev/null
++error: octal-looking escape in string literal
++ --> $DIR/octal_escapes.rs:4:17
++ |
++LL | let _bad1 = "/033[0m";
++ | ^^^^^^^^^
++ |
++ = note: `-D clippy::octal-escapes` implied by `-D warnings`
++ = help: octal escapes are not supported, `/0` is always a null character
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad1 = "/x1b[0m";
++ | ~~~~~~~~~
++help: if the null character is intended, disambiguate using
++ |
++LL | let _bad1 = "/x0033[0m";
++ | ~~~~~~~~~~~
++
++error: octal-looking escape in byte string literal
++ --> $DIR/octal_escapes.rs:5:17
++ |
++LL | let _bad2 = b"/033[0m";
++ | ^^^^^^^^^^
++ |
++ = help: octal escapes are not supported, `/0` is always a null byte
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad2 = b"/x1b[0m";
++ | ~~~~~~~~~~
++help: if the null byte is intended, disambiguate using
++ |
++LL | let _bad2 = b"/x0033[0m";
++ | ~~~~~~~~~~~~
++
++error: octal-looking escape in string literal
++ --> $DIR/octal_escapes.rs:6:17
++ |
++LL | let _bad3 = "//033[0m";
++ | ^^^^^^^^^^^
++ |
++ = help: octal escapes are not supported, `/0` is always a null character
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad3 = "//x1b[0m";
++ | ~~~~~~~~~~~
++help: if the null character is intended, disambiguate using
++ |
++LL | let _bad3 = "//x0033[0m";
++ | ~~~~~~~~~~~~~
++
++error: octal-looking escape in string literal
++ --> $DIR/octal_escapes.rs:8:17
++ |
++LL | let _bad4 = "/01234567";
++ | ^^^^^^^^^^^
++ |
++ = help: octal escapes are not supported, `/0` is always a null character
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad4 = "/x0a34567";
++ | ~~~~~~~~~~~
++help: if the null character is intended, disambiguate using
++ |
++LL | let _bad4 = "/x001234567";
++ | ~~~~~~~~~~~~~
++
++error: octal-looking escape in string literal
++ --> $DIR/octal_escapes.rs:10:17
++ |
++LL | let _bad6 = "Text-/055/077-MoreText";
++ | ^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: octal escapes are not supported, `/0` is always a null character
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad6 = "Text-/x2d/x3f-MoreText";
++ | ~~~~~~~~~~~~~~~~~~~~~~~~
++help: if the null character is intended, disambiguate using
++ |
++LL | let _bad6 = "Text-/x0055/x0077-MoreText";
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++error: octal-looking escape in string literal
++ --> $DIR/octal_escapes.rs:11:17
++ |
++LL | let _bad7 = "EvenMoreText-/01/02-ShortEscapes";
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++ = help: octal escapes are not supported, `/0` is always a null character
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad7 = "EvenMoreText-/x01/x02-ShortEscapes";
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++help: if the null character is intended, disambiguate using
++ |
++LL | let _bad7 = "EvenMoreText-/x001/x002-ShortEscapes";
++ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++
++error: octal-looking escape in string literal
++ --> $DIR/octal_escapes.rs:12:17
++ |
++LL | let _bad8 = "锈/01锈";
++ | ^^^^^^^^^
++ |
++ = help: octal escapes are not supported, `/0` is always a null character
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad8 = "锈/x01锈";
++ | ~~~~~~~~~~
++help: if the null character is intended, disambiguate using
++ |
++LL | let _bad8 = "锈/x001锈";
++ | ~~~~~~~~~~~
++
++error: octal-looking escape in string literal
++ --> $DIR/octal_escapes.rs:13:17
++ |
++LL | let _bad9 = "锈/011锈";
++ | ^^^^^^^^^^
++ |
++ = help: octal escapes are not supported, `/0` is always a null character
++help: if an octal escape was intended, use the hexadecimal representation instead
++ |
++LL | let _bad9 = "锈/x09锈";
++ | ~~~~~~~~~~
++help: if the null character is intended, disambiguate using
++ |
++LL | let _bad9 = "锈/x0011锈";
++ | ~~~~~~~~~~~~
++
++error: aborting due to 8 previous errors
++
--- /dev/null
+// aux-build:macro_rules.rs
+#![warn(clippy::option_env_unwrap)]
++#![allow(clippy::map_flatten)]
+
+#[macro_use]
+extern crate macro_rules;
+
+macro_rules! option_env_unwrap {
+ ($env: expr) => {
+ option_env!($env).unwrap()
+ };
+ ($env: expr, $message: expr) => {
+ option_env!($env).expect($message)
+ };
+}
+
+fn main() {
+ let _ = option_env!("PATH").unwrap();
+ let _ = option_env!("PATH").expect("environment variable PATH isn't set");
+ let _ = option_env_unwrap!("PATH");
+ let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set");
+ let _ = option_env_unwrap_external!("PATH");
+ let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set");
+}
--- /dev/null
- --> $DIR/option_env_unwrap.rs:17:13
+error: this will panic at run-time if the environment variable doesn't exist at compile-time
- --> $DIR/option_env_unwrap.rs:18:13
++ --> $DIR/option_env_unwrap.rs:18:13
+ |
+LL | let _ = option_env!("PATH").unwrap();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::option-env-unwrap` implied by `-D warnings`
+ = help: consider using the `env!` macro instead
+
+error: this will panic at run-time if the environment variable doesn't exist at compile-time
- --> $DIR/option_env_unwrap.rs:9:9
++ --> $DIR/option_env_unwrap.rs:19:13
+ |
+LL | let _ = option_env!("PATH").expect("environment variable PATH isn't set");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using the `env!` macro instead
+
+error: this will panic at run-time if the environment variable doesn't exist at compile-time
- --> $DIR/option_env_unwrap.rs:12:9
++ --> $DIR/option_env_unwrap.rs:10:9
+ |
+LL | option_env!($env).unwrap()
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | let _ = option_env_unwrap!("PATH");
+ | -------------------------- in this macro invocation
+ |
+ = help: consider using the `env!` macro instead
+ = note: this error originates in the macro `option_env_unwrap` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: this will panic at run-time if the environment variable doesn't exist at compile-time
- --> $DIR/option_env_unwrap.rs:21:13
++ --> $DIR/option_env_unwrap.rs:13:9
+ |
+LL | option_env!($env).expect($message)
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL | let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set");
+ | ----------------------------------------------------------------- in this macro invocation
+ |
+ = help: consider using the `env!` macro instead
+ = note: this error originates in the macro `option_env_unwrap` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: this will panic at run-time if the environment variable doesn't exist at compile-time
- --> $DIR/option_env_unwrap.rs:22:13
++ --> $DIR/option_env_unwrap.rs:22:13
+ |
+LL | let _ = option_env_unwrap_external!("PATH");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using the `env!` macro instead
+ = note: this error originates in the macro `option_env_unwrap_external` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: this will panic at run-time if the environment variable doesn't exist at compile-time
++ --> $DIR/option_env_unwrap.rs:23:13
+ |
+LL | let _ = option_env_unwrap_external!("PATH", "environment variable PATH isn't set");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = help: consider using the `env!` macro instead
+ = note: this error originates in the macro `option_env_unwrap_external` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 6 previous errors
+
--- /dev/null
- #![warn(clippy::option_filter_map)]
+// run-rustfix
- fn odds_out(x: i32) -> Option<i32> {
- if x % 2 == 0 { Some(x) } else { None }
- }
++#![warn(clippy::option_filter_map)]
++#![allow(clippy::map_flatten)]
+
+fn main() {
+ let _ = Some(Some(1)).flatten();
+ let _ = Some(Some(1)).flatten();
+ let _ = Some(1).map(odds_out).flatten();
+ let _ = Some(1).map(odds_out).flatten();
+
+ let _ = vec![Some(1)].into_iter().flatten();
+ let _ = vec![Some(1)].into_iter().flatten();
+ let _ = vec![1]
+ .into_iter()
+ .map(odds_out)
+ .flatten();
+ let _ = vec![1]
+ .into_iter()
+ .map(odds_out)
+ .flatten();
+}
++
++fn odds_out(x: i32) -> Option<i32> {
++ if x % 2 == 0 { Some(x) } else { None }
++}
--- /dev/null
- #![warn(clippy::option_filter_map)]
+// run-rustfix
- fn odds_out(x: i32) -> Option<i32> {
- if x % 2 == 0 { Some(x) } else { None }
- }
++#![warn(clippy::option_filter_map)]
++#![allow(clippy::map_flatten)]
+
+fn main() {
+ let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap);
+ let _ = Some(Some(1)).filter(|o| o.is_some()).map(|o| o.unwrap());
+ let _ = Some(1).map(odds_out).filter(Option::is_some).map(Option::unwrap);
+ let _ = Some(1).map(odds_out).filter(|o| o.is_some()).map(|o| o.unwrap());
+
+ let _ = vec![Some(1)].into_iter().filter(Option::is_some).map(Option::unwrap);
+ let _ = vec![Some(1)].into_iter().filter(|o| o.is_some()).map(|o| o.unwrap());
+ let _ = vec![1]
+ .into_iter()
+ .map(odds_out)
+ .filter(Option::is_some)
+ .map(Option::unwrap);
+ let _ = vec![1]
+ .into_iter()
+ .map(odds_out)
+ .filter(|o| o.is_some())
+ .map(|o| o.unwrap());
+}
++
++fn odds_out(x: i32) -> Option<i32> {
++ if x % 2 == 0 { Some(x) } else { None }
++}
--- /dev/null
- --> $DIR/option_filter_map.rs:8:27
+error: `filter` for `Some` followed by `unwrap`
- --> $DIR/option_filter_map.rs:9:27
++ --> $DIR/option_filter_map.rs:6:27
+ |
+LL | let _ = Some(Some(1)).filter(Option::is_some).map(Option::unwrap);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
+ |
+ = note: `-D clippy::option-filter-map` implied by `-D warnings`
+
+error: `filter` for `Some` followed by `unwrap`
- --> $DIR/option_filter_map.rs:10:35
++ --> $DIR/option_filter_map.rs:7:27
+ |
+LL | let _ = Some(Some(1)).filter(|o| o.is_some()).map(|o| o.unwrap());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
+
+error: `filter` for `Some` followed by `unwrap`
- --> $DIR/option_filter_map.rs:11:35
++ --> $DIR/option_filter_map.rs:8:35
+ |
+LL | let _ = Some(1).map(odds_out).filter(Option::is_some).map(Option::unwrap);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
+
+error: `filter` for `Some` followed by `unwrap`
- --> $DIR/option_filter_map.rs:13:39
++ --> $DIR/option_filter_map.rs:9:35
+ |
+LL | let _ = Some(1).map(odds_out).filter(|o| o.is_some()).map(|o| o.unwrap());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
+
+error: `filter` for `Some` followed by `unwrap`
- --> $DIR/option_filter_map.rs:14:39
++ --> $DIR/option_filter_map.rs:11:39
+ |
+LL | let _ = vec![Some(1)].into_iter().filter(Option::is_some).map(Option::unwrap);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
+
+error: `filter` for `Some` followed by `unwrap`
- --> $DIR/option_filter_map.rs:18:10
++ --> $DIR/option_filter_map.rs:12:39
+ |
+LL | let _ = vec![Some(1)].into_iter().filter(|o| o.is_some()).map(|o| o.unwrap());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `flatten` instead: `flatten()`
+
+error: `filter` for `Some` followed by `unwrap`
- --> $DIR/option_filter_map.rs:23:10
++ --> $DIR/option_filter_map.rs:16:10
+ |
+LL | .filter(Option::is_some)
+ | __________^
+LL | | .map(Option::unwrap);
+ | |____________________________^ help: consider using `flatten` instead: `flatten()`
+
+error: `filter` for `Some` followed by `unwrap`
++ --> $DIR/option_filter_map.rs:21:10
+ |
+LL | .filter(|o| o.is_some())
+ | __________^
+LL | | .map(|o| o.unwrap());
+ | |____________________________^ help: consider using `flatten` instead: `flatten()`
+
+error: aborting due to 8 previous errors
+
--- /dev/null
- let _ = Some(0).map_or_else(|| s.len(), |x| s.len() + x);
+// run-rustfix
+#![warn(clippy::option_if_let_else)]
+#![allow(clippy::redundant_closure, clippy::ref_option_ref, clippy::equatable_if_let)]
+
+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 if let Some(x) = string {
+ Some((true, x))
+ } else {
+ Some((false, ""))
+ }
+}
+
+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 impure_else(arg: Option<i32>) {
+ let side_effect = || {
+ println!("return 1");
+ 1
+ };
+ let _ = arg.map_or_else(|| side_effect(), |x| x);
+}
+
+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
+}
+
++// #7973
++fn pattern_to_vec(pattern: &str) -> Vec<String> {
++ pattern
++ .trim_matches('/')
++ .split('/')
++ .flat_map(|s| {
++ s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])
++ })
++ .collect::<Vec<_>>()
++}
++
+fn main() {
+ let optional = Some(5);
+ let _ = optional.map_or(5, |x| x + 2);
+ let _ = bad1(None);
+ let _ = else_if_option(None);
+ unop_bad(&None, None);
+ let _ = longer_body(None);
+ test_map_or_else(None);
+ let _ = negative_tests(None);
+ let _ = impure_else(None);
+
+ let _ = Some(0).map_or(0, |x| loop {
+ if x == 0 {
+ break x;
+ }
+ });
+
+ // #7576
+ const fn _f(x: Option<u32>) -> u32 {
+ // Don't lint, `map_or` isn't const
+ if let Some(x) = x { x } else { 10 }
+ }
+
+ // #5822
+ let s = String::new();
+ // Don't lint, `Some` branch consumes `s`, but else branch uses `s`
+ let _ = if let Some(x) = Some(0) {
+ let s = s;
+ s.len() + x
+ } else {
+ s.len()
+ };
+
+ let s = String::new();
+ // Lint, both branches immutably borrow `s`.
++ let _ = Some(0).map_or(s.len(), |x| s.len() + x);
+
+ let s = String::new();
+ // Lint, `Some` branch consumes `s`, but else branch doesn't use `s`.
+ let _ = Some(0).map_or(1, |x| {
+ let s = s;
+ s.len() + x
+ });
+
+ let s = Some(String::new());
+ // Don't lint, `Some` branch borrows `s`, but else branch consumes `s`
+ let _ = if let Some(x) = &s {
+ x.len()
+ } else {
+ let _s = s;
+ 10
+ };
+
+ let mut s = Some(String::new());
+ // Don't lint, `Some` branch mutably borrows `s`, but else branch also borrows `s`
+ let _ = if let Some(x) = &mut s {
+ x.push_str("test");
+ x.len()
+ } else {
+ let _s = &s;
+ 10
+ };
+
+ async fn _f1(x: u32) -> u32 {
+ x
+ }
+
+ async fn _f2() {
+ // Don't lint. `await` can't be moved into a closure.
+ let _ = if let Some(x) = Some(0) { _f1(x).await } else { 0 };
+ }
++
++ let _ = pattern_to_vec("hello world");
+}
--- /dev/null
+// run-rustfix
+#![warn(clippy::option_if_let_else)]
+#![allow(clippy::redundant_closure, clippy::ref_option_ref, clippy::equatable_if_let)]
+
+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 impure_else(arg: Option<i32>) {
+ let side_effect = || {
+ println!("return 1");
+ 1
+ };
+ let _ = if let Some(x) = arg {
+ x
+ } else {
+ // map_or_else must be suggested
+ side_effect()
+ };
+}
+
+fn test_map_or_else(arg: Option<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
+}
+
++// #7973
++fn pattern_to_vec(pattern: &str) -> Vec<String> {
++ pattern
++ .trim_matches('/')
++ .split('/')
++ .flat_map(|s| {
++ if let Some(idx) = s.find('.') {
++ vec![s[..idx].to_string(), s[idx..].to_string()]
++ } else {
++ vec![s.to_string()]
++ }
++ })
++ .collect::<Vec<_>>()
++}
++
+fn main() {
+ let optional = Some(5);
+ let _ = if let Some(x) = optional { x + 2 } else { 5 };
+ let _ = bad1(None);
+ let _ = else_if_option(None);
+ unop_bad(&None, None);
+ let _ = longer_body(None);
+ test_map_or_else(None);
+ let _ = negative_tests(None);
+ let _ = impure_else(None);
+
+ let _ = if let Some(x) = Some(0) {
+ loop {
+ if x == 0 {
+ break x;
+ }
+ }
+ } else {
+ 0
+ };
+
+ // #7576
+ const fn _f(x: Option<u32>) -> u32 {
+ // Don't lint, `map_or` isn't const
+ if let Some(x) = x { x } else { 10 }
+ }
+
+ // #5822
+ let s = String::new();
+ // Don't lint, `Some` branch consumes `s`, but else branch uses `s`
+ let _ = if let Some(x) = Some(0) {
+ let s = s;
+ s.len() + x
+ } else {
+ s.len()
+ };
+
+ let s = String::new();
+ // Lint, both branches immutably borrow `s`.
+ let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() };
+
+ let s = String::new();
+ // Lint, `Some` branch consumes `s`, but else branch doesn't use `s`.
+ let _ = if let Some(x) = Some(0) {
+ let s = s;
+ s.len() + x
+ } else {
+ 1
+ };
+
+ let s = Some(String::new());
+ // Don't lint, `Some` branch borrows `s`, but else branch consumes `s`
+ let _ = if let Some(x) = &s {
+ x.len()
+ } else {
+ let _s = s;
+ 10
+ };
+
+ let mut s = Some(String::new());
+ // Don't lint, `Some` branch mutably borrows `s`, but else branch also borrows `s`
+ let _ = if let Some(x) = &mut s {
+ x.push_str("test");
+ x.len()
+ } else {
+ let _s = &s;
+ 10
+ };
+
+ async fn _f1(x: u32) -> u32 {
+ x
+ }
+
+ async fn _f2() {
+ // Don't lint. `await` can't be moved into a closure.
+ let _ = if let Some(x) = Some(0) { _f1(x).await } else { 0 };
+ }
++
++ let _ = pattern_to_vec("hello world");
+}
--- /dev/null
- --> $DIR/option_if_let_else.rs:99:13
+error: use Option::map_or instead of an if let/else
+ --> $DIR/option_if_let_else.rs:6: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:24: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:25: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:26: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:32: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:33: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:39: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:48: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:61:13
+ |
+LL | let _ = if let Some(x) = arg {
+ | _____________^
+LL | | x
+LL | | } else {
+LL | | // map_or_else must be suggested
+LL | | side_effect()
+LL | | };
+ | |_____^ help: try: `arg.map_or_else(|| side_effect(), |x| x)`
+
+error: use Option::map_or_else instead of an if let/else
+ --> $DIR/option_if_let_else.rs:70: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_else instead of an if let/else
++ --> $DIR/option_if_let_else.rs:103:13
++ |
++LL | / if let Some(idx) = s.find('.') {
++LL | | vec![s[..idx].to_string(), s[idx..].to_string()]
++LL | | } else {
++LL | | vec![s.to_string()]
++LL | | }
++ | |_____________^ help: try: `s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])`
++
+error: use Option::map_or instead of an if let/else
- --> $DIR/option_if_let_else.rs:108:13
++ --> $DIR/option_if_let_else.rs:114:13
+ |
+LL | let _ = if let Some(x) = optional { x + 2 } else { 5 };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)`
+
+error: use Option::map_or instead of an if let/else
- error: use Option::map_or_else instead of an if let/else
- --> $DIR/option_if_let_else.rs:136:13
++ --> $DIR/option_if_let_else.rs:123:13
+ |
+LL | let _ = if let Some(x) = Some(0) {
+ | _____________^
+LL | | loop {
+LL | | if x == 0 {
+LL | | break x;
+... |
+LL | | 0
+LL | | };
+ | |_____^
+ |
+help: try
+ |
+LL ~ let _ = Some(0).map_or(0, |x| loop {
+LL + if x == 0 {
+LL + break x;
+LL + }
+LL ~ });
+ |
+
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or_else(|| s.len(), |x| s.len() + x)`
++error: use Option::map_or instead of an if let/else
++ --> $DIR/option_if_let_else.rs:151:13
+ |
+LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() };
- --> $DIR/option_if_let_else.rs:140:13
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)`
+
+error: use Option::map_or instead of an if let/else
- error: aborting due to 14 previous errors
++ --> $DIR/option_if_let_else.rs:155:13
+ |
+LL | let _ = if let Some(x) = Some(0) {
+ | _____________^
+LL | | let s = s;
+LL | | s.len() + x
+LL | | } else {
+LL | | 1
+LL | | };
+ | |_____^
+ |
+help: try
+ |
+LL ~ let _ = Some(0).map_or(1, |x| {
+LL + let s = s;
+LL + s.len() + x
+LL ~ });
+ |
+
++error: aborting due to 15 previous errors
+
--- /dev/null
- let _ = opt.and_then(|x| Some(x + 1));
+// run-rustfix
+
+#![allow(clippy::bind_instead_of_map)]
+
+fn main() {
+ let opt = Some(1);
++ let r: Result<i32, i32> = Ok(1);
++ let bar = |_| Some(1);
+
+ // Check `OPTION_MAP_OR_NONE`.
+ // Single line case.
- let _ = opt.and_then(|x| {
- Some(x + 1)
- });
++ let _: Option<i32> = opt.map(|x| x + 1);
+ // Multi-line case.
+ #[rustfmt::skip]
++ let _: Option<i32> = opt.map(|x| x + 1);
++ // function returning `Option`
++ let _: Option<i32> = opt.and_then(bar);
++ let _: Option<i32> = opt.and_then(|x| {
++ let offset = 0;
++ let height = x;
++ Some(offset + height)
++ });
++
++ // Check `RESULT_MAP_OR_INTO_OPTION`.
++ let _: Option<i32> = r.ok();
+}
--- /dev/null
- let _ = opt.map_or(None, |x| Some(x + 1));
+// run-rustfix
+
+#![allow(clippy::bind_instead_of_map)]
+
+fn main() {
+ let opt = Some(1);
++ let r: Result<i32, i32> = Ok(1);
++ let bar = |_| Some(1);
+
+ // Check `OPTION_MAP_OR_NONE`.
+ // Single line case.
- let _ = opt.map_or(None, |x| {
++ let _: Option<i32> = opt.map_or(None, |x| Some(x + 1));
+ // Multi-line case.
+ #[rustfmt::skip]
++ let _: Option<i32> = opt.map_or(None, |x| {
+ Some(x + 1)
+ });
++ // function returning `Option`
++ let _: Option<i32> = opt.map_or(None, bar);
++ let _: Option<i32> = opt.map_or(None, |x| {
++ let offset = 0;
++ let height = x;
++ Some(offset + height)
++ });
++
++ // Check `RESULT_MAP_OR_INTO_OPTION`.
++ let _: Option<i32> = r.map_or(None, Some);
+}
--- /dev/null
- error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead
- --> $DIR/option_map_or_none.rs:10:13
++error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `map(..)` instead
++ --> $DIR/option_map_or_none.rs:12:26
+ |
- LL | let _ = opt.map_or(None, |x| Some(x + 1));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `opt.and_then(|x| Some(x + 1))`
++LL | let _: Option<i32> = opt.map_or(None, |x| Some(x + 1));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `map` instead: `opt.map(|x| x + 1)`
+ |
+ = note: `-D clippy::option-map-or-none` implied by `-D warnings`
+
- error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead
- --> $DIR/option_map_or_none.rs:13:13
++error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `map(..)` instead
++ --> $DIR/option_map_or_none.rs:15:26
+ |
- LL | let _ = opt.map_or(None, |x| {
- | _____________^
++LL | let _: Option<i32> = opt.map_or(None, |x| {
++ | __________________________^
+LL | | Some(x + 1)
+LL | | });
- | |_________________________^
++ | |_________________________^ help: try using `map` instead: `opt.map(|x| x + 1)`
++
++error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead
++ --> $DIR/option_map_or_none.rs:19:26
++ |
++LL | let _: Option<i32> = opt.map_or(None, bar);
++ | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `opt.and_then(bar)`
++
++error: called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling `and_then(..)` instead
++ --> $DIR/option_map_or_none.rs:20:26
++ |
++LL | let _: Option<i32> = opt.map_or(None, |x| {
++ | __________________________^
++LL | | let offset = 0;
++LL | | let height = x;
++LL | | Some(offset + height)
++LL | | });
++ | |______^
+ |
+help: try using `and_then` instead
+ |
- LL ~ let _ = opt.and_then(|x| {
- LL + Some(x + 1)
- LL ~ });
++LL ~ let _: Option<i32> = opt.and_then(|x| {
++LL + let offset = 0;
++LL + let height = x;
++LL + Some(offset + height)
++LL ~ });
++ |
++
++error: called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling `ok()` instead
++ --> $DIR/option_map_or_none.rs:27:26
++ |
++LL | let _: Option<i32> = r.map_or(None, Some);
++ | ^^^^^^^^^^^^^^^^^^^^ help: try using `ok` instead: `r.ok()`
+ |
++ = note: `-D clippy::result-map-or-into-option` implied by `-D warnings`
+
- error: aborting due to 2 previous errors
++error: aborting due to 5 previous errors
+
--- /dev/null
- with_const_fn.unwrap_or_else(|| Duration::from_secs(5));
+// run-rustfix
+
+#![warn(clippy::or_fun_call)]
+#![allow(dead_code)]
+#![allow(clippy::unnecessary_wraps)]
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::time::Duration;
+
+/// Checks implementation of the `OR_FUN_CALL` lint.
+fn or_fun_call() {
+ struct Foo;
+
+ impl Foo {
+ fn new() -> Foo {
+ Foo
+ }
+ }
+
+ struct FakeDefault;
+ impl FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ impl Default for FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ enum Enum {
+ A(i32),
+ }
+
+ fn make<T>() -> T {
+ unimplemented!();
+ }
+
+ let with_enum = Some(Enum::A(1));
+ with_enum.unwrap_or(Enum::A(5));
+
+ let with_const_fn = Some(Duration::from_secs(1));
- map.entry(42).or_insert_with(String::new);
++ with_const_fn.unwrap_or(Duration::from_secs(5));
+
+ let with_constructor = Some(vec![1]);
+ with_constructor.unwrap_or_else(make);
+
+ let with_new = Some(vec![1]);
+ with_new.unwrap_or_default();
+
+ let with_const_args = Some(vec![1]);
+ with_const_args.unwrap_or_else(|| Vec::with_capacity(12));
+
+ let with_err: Result<_, ()> = Ok(vec![1]);
+ with_err.unwrap_or_else(|_| make());
+
+ let with_err_args: Result<_, ()> = Ok(vec![1]);
+ with_err_args.unwrap_or_else(|_| Vec::with_capacity(12));
+
+ let with_default_trait = Some(1);
+ with_default_trait.unwrap_or_default();
+
+ let with_default_type = Some(1);
+ with_default_type.unwrap_or_default();
+
+ let self_default = None::<FakeDefault>;
+ self_default.unwrap_or_else(<FakeDefault>::default);
+
+ let real_default = None::<FakeDefault>;
+ real_default.unwrap_or_default();
+
+ let with_vec = Some(vec![1]);
+ with_vec.unwrap_or_default();
+
+ let without_default = Some(Foo);
+ without_default.unwrap_or_else(Foo::new);
+
+ let mut map = HashMap::<u64, String>::new();
- map_vec.entry(42).or_insert_with(Vec::new);
++ map.entry(42).or_insert(String::new());
+
+ let mut map_vec = HashMap::<u64, Vec<i32>>::new();
- btree.entry(42).or_insert_with(String::new);
++ map_vec.entry(42).or_insert(vec![]);
+
+ let mut btree = BTreeMap::<u64, String>::new();
- btree_vec.entry(42).or_insert_with(Vec::new);
++ btree.entry(42).or_insert(String::new());
+
+ let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new();
- .or_else(|| Some(Bar(b, Duration::from_secs(2))));
++ btree_vec.entry(42).or_insert(vec![]);
+
+ let stringy = Some(String::from(""));
+ let _ = stringy.unwrap_or_else(|| "".to_owned());
+
+ let opt = Some(1);
+ let hello = "Hello";
+ let _ = opt.ok_or(format!("{} world.", hello));
+
+ // index
+ let map = HashMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or_else(|| map[&1]);
+ let map = BTreeMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or_else(|| map[&1]);
+ // don't lint index vec
+ let vec = vec![1];
+ let _ = Some(1).unwrap_or(vec[1]);
+}
+
+struct Foo(u8);
+struct Bar(String, Duration);
+#[rustfmt::skip]
+fn test_or_with_ctors() {
+ let opt = Some(1);
+ let opt_opt = Some(Some(1));
+ // we also test for const promotion, this makes sure we don't hit that
+ let two = 2;
+
+ let _ = opt_opt.unwrap_or(Some(2));
+ let _ = opt_opt.unwrap_or(Some(two));
+ let _ = opt.ok_or(Some(2));
+ let _ = opt.ok_or(Some(two));
+ let _ = opt.ok_or(Foo(2));
+ let _ = opt.ok_or(Foo(two));
+ let _ = opt.or(Some(2));
+ let _ = opt.or(Some(two));
+
+ let _ = Some("a".to_string()).or_else(|| Some("b".to_string()));
+
+ let b = "b".to_string();
+ let _ = Some(Bar("a".to_string(), Duration::from_secs(1)))
- let mut s = "test".to_owned();
- None.unwrap_or_else(|| s.as_mut_vec());
++ .or(Some(Bar(b, Duration::from_secs(2))));
+
+ let vec = vec!["foo"];
+ let _ = opt.ok_or(vec.len());
+
+ let array = ["foo"];
+ let _ = opt.ok_or(array.len());
+
+ let slice = &["foo"][..];
+ let _ = opt.ok_or(slice.len());
+
+ let string = "foo";
+ let _ = opt.ok_or(string.len());
+}
+
+// Issue 4514 - early return
+fn f() -> Option<()> {
+ let a = Some(1);
+ let b = 1i32;
+
+ let _ = a.unwrap_or(b.checked_mul(3)?.min(240));
+
+ Some(())
+}
+
+mod issue6675 {
++ unsafe fn ptr_to_ref<'a, T>(p: *const T) -> &'a T {
++ #[allow(unused)]
++ let x = vec![0; 1000]; // future-proofing, make this function expensive.
++ &*p
++ }
++
+ unsafe fn foo() {
- let mut s = "test".to_owned();
- None.unwrap_or_else(|| unsafe { s.as_mut_vec() });
++ let s = "test".to_owned();
++ let s = &s as *const _;
++ None.unwrap_or_else(|| ptr_to_ref(s));
+ }
+
+ fn bar() {
- None.unwrap_or_else(|| unsafe { s.as_mut_vec() });
++ let s = "test".to_owned();
++ let s = &s as *const _;
++ None.unwrap_or_else(|| unsafe { ptr_to_ref(s) });
+ #[rustfmt::skip]
++ None.unwrap_or_else(|| unsafe { ptr_to_ref(s) });
+ }
+}
+
+fn main() {}
--- /dev/null
- let mut s = "test".to_owned();
- None.unwrap_or(s.as_mut_vec());
+// run-rustfix
+
+#![warn(clippy::or_fun_call)]
+#![allow(dead_code)]
+#![allow(clippy::unnecessary_wraps)]
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::time::Duration;
+
+/// Checks implementation of the `OR_FUN_CALL` lint.
+fn or_fun_call() {
+ struct Foo;
+
+ impl Foo {
+ fn new() -> Foo {
+ Foo
+ }
+ }
+
+ struct FakeDefault;
+ impl FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ impl Default for FakeDefault {
+ fn default() -> Self {
+ FakeDefault
+ }
+ }
+
+ enum Enum {
+ A(i32),
+ }
+
+ fn make<T>() -> T {
+ unimplemented!();
+ }
+
+ let with_enum = Some(Enum::A(1));
+ with_enum.unwrap_or(Enum::A(5));
+
+ let with_const_fn = Some(Duration::from_secs(1));
+ with_const_fn.unwrap_or(Duration::from_secs(5));
+
+ let with_constructor = Some(vec![1]);
+ with_constructor.unwrap_or(make());
+
+ let with_new = Some(vec![1]);
+ with_new.unwrap_or(Vec::new());
+
+ let with_const_args = Some(vec![1]);
+ with_const_args.unwrap_or(Vec::with_capacity(12));
+
+ let with_err: Result<_, ()> = Ok(vec![1]);
+ with_err.unwrap_or(make());
+
+ let with_err_args: Result<_, ()> = Ok(vec![1]);
+ with_err_args.unwrap_or(Vec::with_capacity(12));
+
+ let with_default_trait = Some(1);
+ with_default_trait.unwrap_or(Default::default());
+
+ let with_default_type = Some(1);
+ with_default_type.unwrap_or(u64::default());
+
+ let self_default = None::<FakeDefault>;
+ self_default.unwrap_or(<FakeDefault>::default());
+
+ let real_default = None::<FakeDefault>;
+ real_default.unwrap_or(<FakeDefault as Default>::default());
+
+ let with_vec = Some(vec![1]);
+ with_vec.unwrap_or(vec![]);
+
+ let without_default = Some(Foo);
+ without_default.unwrap_or(Foo::new());
+
+ let mut map = HashMap::<u64, String>::new();
+ map.entry(42).or_insert(String::new());
+
+ let mut map_vec = HashMap::<u64, Vec<i32>>::new();
+ map_vec.entry(42).or_insert(vec![]);
+
+ let mut btree = BTreeMap::<u64, String>::new();
+ btree.entry(42).or_insert(String::new());
+
+ let mut btree_vec = BTreeMap::<u64, Vec<i32>>::new();
+ btree_vec.entry(42).or_insert(vec![]);
+
+ let stringy = Some(String::from(""));
+ let _ = stringy.unwrap_or("".to_owned());
+
+ let opt = Some(1);
+ let hello = "Hello";
+ let _ = opt.ok_or(format!("{} world.", hello));
+
+ // index
+ let map = HashMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or(map[&1]);
+ let map = BTreeMap::<u64, u64>::new();
+ let _ = Some(1).unwrap_or(map[&1]);
+ // don't lint index vec
+ let vec = vec![1];
+ let _ = Some(1).unwrap_or(vec[1]);
+}
+
+struct Foo(u8);
+struct Bar(String, Duration);
+#[rustfmt::skip]
+fn test_or_with_ctors() {
+ let opt = Some(1);
+ let opt_opt = Some(Some(1));
+ // we also test for const promotion, this makes sure we don't hit that
+ let two = 2;
+
+ let _ = opt_opt.unwrap_or(Some(2));
+ let _ = opt_opt.unwrap_or(Some(two));
+ let _ = opt.ok_or(Some(2));
+ let _ = opt.ok_or(Some(two));
+ let _ = opt.ok_or(Foo(2));
+ let _ = opt.ok_or(Foo(two));
+ let _ = opt.or(Some(2));
+ let _ = opt.or(Some(two));
+
+ let _ = Some("a".to_string()).or(Some("b".to_string()));
+
+ let b = "b".to_string();
+ let _ = Some(Bar("a".to_string(), Duration::from_secs(1)))
+ .or(Some(Bar(b, Duration::from_secs(2))));
+
+ let vec = vec!["foo"];
+ let _ = opt.ok_or(vec.len());
+
+ let array = ["foo"];
+ let _ = opt.ok_or(array.len());
+
+ let slice = &["foo"][..];
+ let _ = opt.ok_or(slice.len());
+
+ let string = "foo";
+ let _ = opt.ok_or(string.len());
+}
+
+// Issue 4514 - early return
+fn f() -> Option<()> {
+ let a = Some(1);
+ let b = 1i32;
+
+ let _ = a.unwrap_or(b.checked_mul(3)?.min(240));
+
+ Some(())
+}
+
+mod issue6675 {
++ unsafe fn ptr_to_ref<'a, T>(p: *const T) -> &'a T {
++ #[allow(unused)]
++ let x = vec![0; 1000]; // future-proofing, make this function expensive.
++ &*p
++ }
++
+ unsafe fn foo() {
- let mut s = "test".to_owned();
- None.unwrap_or(unsafe { s.as_mut_vec() });
++ let s = "test".to_owned();
++ let s = &s as *const _;
++ None.unwrap_or(ptr_to_ref(s));
+ }
+
+ fn bar() {
- None.unwrap_or( unsafe { s.as_mut_vec() } );
++ let s = "test".to_owned();
++ let s = &s as *const _;
++ None.unwrap_or(unsafe { ptr_to_ref(s) });
+ #[rustfmt::skip]
++ None.unwrap_or( unsafe { ptr_to_ref(s) } );
+ }
+}
+
+fn main() {}
--- /dev/null
- error: use of `unwrap_or` followed by a function call
- --> $DIR/or_fun_call.rs:46:19
- |
- LL | with_const_fn.unwrap_or(Duration::from_secs(5));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Duration::from_secs(5))`
- |
- = note: `-D clippy::or-fun-call` implied by `-D warnings`
-
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:49:22
+ |
+LL | with_constructor.unwrap_or(make());
+ | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(make)`
++ |
++ = note: `-D clippy::or-fun-call` implied by `-D warnings`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:52:5
+ |
+LL | with_new.unwrap_or(Vec::new());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_new.unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:55:21
+ |
+LL | with_const_args.unwrap_or(Vec::with_capacity(12));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| Vec::with_capacity(12))`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:58:14
+ |
+LL | with_err.unwrap_or(make());
+ | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| make())`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:61:19
+ |
+LL | with_err_args.unwrap_or(Vec::with_capacity(12));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| Vec::with_capacity(12))`
+
+error: use of `unwrap_or` followed by a call to `default`
+ --> $DIR/or_fun_call.rs:64:5
+ |
+LL | with_default_trait.unwrap_or(Default::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_trait.unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a call to `default`
+ --> $DIR/or_fun_call.rs:67:5
+ |
+LL | with_default_type.unwrap_or(u64::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_type.unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:70:18
+ |
+LL | self_default.unwrap_or(<FakeDefault>::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(<FakeDefault>::default)`
+
+error: use of `unwrap_or` followed by a call to `default`
+ --> $DIR/or_fun_call.rs:73:5
+ |
+LL | real_default.unwrap_or(<FakeDefault as Default>::default());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `real_default.unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a call to `new`
+ --> $DIR/or_fun_call.rs:76:5
+ |
+LL | with_vec.unwrap_or(vec![]);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_vec.unwrap_or_default()`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:79:21
+ |
+LL | without_default.unwrap_or(Foo::new());
+ | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(Foo::new)`
+
- error: use of `or_insert` followed by a function call
- --> $DIR/or_fun_call.rs:82:19
- |
- LL | map.entry(42).or_insert(String::new());
- | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)`
-
- error: use of `or_insert` followed by a function call
- --> $DIR/or_fun_call.rs:85:23
- |
- LL | map_vec.entry(42).or_insert(vec![]);
- | ^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(Vec::new)`
-
- error: use of `or_insert` followed by a function call
- --> $DIR/or_fun_call.rs:88:21
- |
- LL | btree.entry(42).or_insert(String::new());
- | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(String::new)`
-
- error: use of `or_insert` followed by a function call
- --> $DIR/or_fun_call.rs:91:25
- |
- LL | btree_vec.entry(42).or_insert(vec![]);
- | ^^^^^^^^^^^^^^^^^ help: try this: `or_insert_with(Vec::new)`
-
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:94:21
+ |
+LL | let _ = stringy.unwrap_or("".to_owned());
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| "".to_owned())`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:102:21
+ |
+LL | let _ = Some(1).unwrap_or(map[&1]);
+ | ^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| map[&1])`
+
+error: use of `unwrap_or` followed by a function call
+ --> $DIR/or_fun_call.rs:104:21
+ |
+LL | let _ = Some(1).unwrap_or(map[&1]);
+ | ^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| map[&1])`
+
+error: use of `or` followed by a function call
+ --> $DIR/or_fun_call.rs:128:35
+ |
+LL | let _ = Some("a".to_string()).or(Some("b".to_string()));
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some("b".to_string()))`
+
- error: use of `or` followed by a function call
- --> $DIR/or_fun_call.rs:132:10
- |
- LL | .or(Some(Bar(b, Duration::from_secs(2))));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `or_else(|| Some(Bar(b, Duration::from_secs(2))))`
-
+error: use of `unwrap_or` followed by a function call
- --> $DIR/or_fun_call.rs:160:14
++ --> $DIR/or_fun_call.rs:167:14
+ |
- LL | None.unwrap_or(s.as_mut_vec());
- | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| s.as_mut_vec())`
++LL | None.unwrap_or(ptr_to_ref(s));
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| ptr_to_ref(s))`
+
+error: use of `unwrap_or` followed by a function call
- --> $DIR/or_fun_call.rs:165:14
++ --> $DIR/or_fun_call.rs:173:14
+ |
- LL | None.unwrap_or(unsafe { s.as_mut_vec() });
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { s.as_mut_vec() })`
++LL | None.unwrap_or(unsafe { ptr_to_ref(s) });
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })`
+
+error: use of `unwrap_or` followed by a function call
- --> $DIR/or_fun_call.rs:167:14
++ --> $DIR/or_fun_call.rs:175:14
+ |
- LL | None.unwrap_or( unsafe { s.as_mut_vec() } );
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { s.as_mut_vec() })`
++LL | None.unwrap_or( unsafe { ptr_to_ref(s) } );
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })`
+
- error: aborting due to 24 previous errors
++error: aborting due to 18 previous errors
+
--- /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(_) => (),
+ _ => (),
+ }
++
++ const FOO: &str = "foo";
++
++ fn foo(s: &str) -> i32 {
++ match s {
++ FOO => 1,
++ _ => 0,
++ }
++ }
+}
--- /dev/null
+// non rustfixable, see redundant_closure_call_fixable.rs
+
+#![warn(clippy::redundant_closure_call)]
++#![allow(clippy::needless_late_init)]
+
+fn main() {
+ let mut i = 1;
+
+ // don't lint here, the closure is used more than once
+ let closure = |i| i + 1;
+ i = closure(3);
+ i = closure(4);
+
+ // lint here
+ let redun_closure = || 1;
+ i = redun_closure();
+
+ // shadowed closures are supported, lint here
+ let shadowed_closure = || 1;
+ i = shadowed_closure();
+ let shadowed_closure = || 2;
+ i = shadowed_closure();
+
+ // don't lint here
+ let shadowed_closure = || 2;
+ i = shadowed_closure();
+ i = shadowed_closure();
+
+ // Fix FP in #5916
+ let mut x;
+ let create = || 2 * 2;
+ x = create();
+ fun(move || {
+ x = create();
+ })
+}
+
+fn fun<T: 'static + FnMut()>(mut f: T) {
+ f();
+}
--- /dev/null
- --> $DIR/redundant_closure_call_late.rs:15:5
+error: closure called just once immediately after it was declared
- --> $DIR/redundant_closure_call_late.rs:19:5
++ --> $DIR/redundant_closure_call_late.rs:16:5
+ |
+LL | i = redun_closure();
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::redundant-closure-call` implied by `-D warnings`
+
+error: closure called just once immediately after it was declared
- --> $DIR/redundant_closure_call_late.rs:21:5
++ --> $DIR/redundant_closure_call_late.rs:20:5
+ |
+LL | i = shadowed_closure();
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: closure called just once immediately after it was declared
++ --> $DIR/redundant_closure_call_late.rs:22:5
+ |
+LL | i = shadowed_closure();
+ | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 3 previous errors
+
--- /dev/null
- #![allow(clippy::needless_return, clippy::if_same_then_else)]
+#![warn(clippy::redundant_else)]
++#![allow(clippy::needless_return, clippy::if_same_then_else, clippy::needless_late_init)]
+
+fn main() {
+ loop {
+ // break
+ if foo() {
+ println!("Love your neighbor;");
+ break;
+ } else {
+ println!("yet don't pull down your hedge.");
+ }
+ // continue
+ if foo() {
+ println!("He that lies down with Dogs,");
+ continue;
+ } else {
+ println!("shall rise up with fleas.");
+ }
+ // match block
+ if foo() {
+ match foo() {
+ 1 => break,
+ _ => return,
+ }
+ } else {
+ println!("You may delay, but time will not.");
+ }
+ }
+ // else if
+ if foo() {
+ return;
+ } else if foo() {
+ return;
+ } else {
+ println!("A fat kitchen makes a lean will.");
+ }
+ // let binding outside of block
+ let _ = {
+ if foo() {
+ return;
+ } else {
+ 1
+ }
+ };
+ // else if with let binding outside of block
+ let _ = {
+ if foo() {
+ return;
+ } else if foo() {
+ return;
+ } else {
+ 2
+ }
+ };
+ // inside if let
+ let _ = if let Some(1) = foo() {
+ let _ = 1;
+ if foo() {
+ return;
+ } else {
+ 1
+ }
+ } else {
+ 1
+ };
+
+ //
+ // non-lint cases
+ //
+
+ // sanity check
+ if foo() {
+ let _ = 1;
+ } else {
+ println!("Who is wise? He that learns from every one.");
+ }
+ // else if without else
+ if foo() {
+ return;
+ } else if foo() {
+ foo()
+ };
+ // nested if return
+ if foo() {
+ if foo() {
+ return;
+ }
+ } else {
+ foo()
+ };
+ // match with non-breaking branch
+ if foo() {
+ match foo() {
+ 1 => foo(),
+ _ => return,
+ }
+ } else {
+ println!("Three may keep a secret, if two of them are dead.");
+ }
+ // let binding
+ let _ = if foo() {
+ return;
+ } else {
+ 1
+ };
+ // assign
+ let mut a;
+ a = if foo() {
+ return;
+ } else {
+ 1
+ };
+ // assign-op
+ a += if foo() {
+ return;
+ } else {
+ 1
+ };
+ // if return else if else
+ if foo() {
+ return;
+ } else if foo() {
+ 1
+ } else {
+ 2
+ };
+ // if else if return else
+ if foo() {
+ 1
+ } else if foo() {
+ return;
+ } else {
+ 2
+ };
+ // else if with let binding
+ let _ = if foo() {
+ return;
+ } else if foo() {
+ return;
+ } else {
+ 2
+ };
+ // inside function call
+ Box::new(if foo() {
+ return;
+ } else {
+ 1
+ });
+}
+
+fn foo<T>() -> T {
+ unimplemented!("I'm not Santa Claus")
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all)]
+#![warn(clippy::redundant_pattern_matching)]
+#![allow(
+ unused_must_use,
+ clippy::needless_bool,
+ clippy::match_like_matches_macro,
+ clippy::equatable_if_let,
+ clippy::if_same_then_else
+)]
+
+fn main() {
+ if None::<()>.is_none() {}
+
+ if Some(42).is_some() {}
+
+ if Some(42).is_some() {
+ foo();
+ } else {
+ bar();
+ }
+
+ while Some(42).is_some() {}
+
+ while Some(42).is_none() {}
+
+ while None::<()>.is_none() {}
+
+ let mut v = vec![1, 2, 3];
+ while v.pop().is_some() {
+ foo();
+ }
+
+ if None::<i32>.is_none() {}
+
+ if Some(42).is_some() {}
+
+ Some(42).is_some();
+
+ None::<()>.is_none();
+
+ let _ = None::<()>.is_none();
+
+ let opt = Some(false);
+ let _ = if opt.is_some() { true } else { false };
+
+ issue6067();
+
+ let _ = if gen_opt().is_some() {
+ 1
+ } else if gen_opt().is_none() {
+ 2
+ } else {
+ 3
+ };
+}
+
+fn gen_opt() -> Option<()> {
+ None
+}
+
+fn foo() {}
+
+fn bar() {}
+
+// Methods that are unstable const should not be suggested within a const context, see issue #5697.
+// However, in Rust 1.48.0 the methods `is_some` and `is_none` of `Option` were stabilized as const,
+// so the following should be linted.
+const fn issue6067() {
+ if Some(42).is_some() {}
+
+ if None::<()>.is_none() {}
+
+ while Some(42).is_some() {}
+
+ while None::<()>.is_none() {}
+
+ Some(42).is_some();
+
+ None::<()>.is_none();
+}
++
++#[allow(clippy::deref_addrof, dead_code)]
++fn issue7921() {
++ if (&None::<()>).is_none() {}
++ if (&None::<()>).is_none() {}
++}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all)]
+#![warn(clippy::redundant_pattern_matching)]
+#![allow(
+ unused_must_use,
+ clippy::needless_bool,
+ clippy::match_like_matches_macro,
+ clippy::equatable_if_let,
+ clippy::if_same_then_else
+)]
+
+fn main() {
+ if let None = None::<()> {}
+
+ if let Some(_) = Some(42) {}
+
+ if let Some(_) = Some(42) {
+ foo();
+ } else {
+ bar();
+ }
+
+ while let Some(_) = Some(42) {}
+
+ while let None = Some(42) {}
+
+ while let None = None::<()> {}
+
+ let mut v = vec![1, 2, 3];
+ while let Some(_) = v.pop() {
+ foo();
+ }
+
+ if None::<i32>.is_none() {}
+
+ if Some(42).is_some() {}
+
+ match Some(42) {
+ Some(_) => true,
+ None => false,
+ };
+
+ match None::<()> {
+ Some(_) => false,
+ None => true,
+ };
+
+ let _ = match None::<()> {
+ Some(_) => false,
+ None => true,
+ };
+
+ let opt = Some(false);
+ let _ = if let Some(_) = opt { true } else { false };
+
+ issue6067();
+
+ let _ = if let Some(_) = gen_opt() {
+ 1
+ } else if let None = gen_opt() {
+ 2
+ } else {
+ 3
+ };
+}
+
+fn gen_opt() -> Option<()> {
+ None
+}
+
+fn foo() {}
+
+fn bar() {}
+
+// Methods that are unstable const should not be suggested within a const context, see issue #5697.
+// However, in Rust 1.48.0 the methods `is_some` and `is_none` of `Option` were stabilized as const,
+// so the following should be linted.
+const fn issue6067() {
+ if let Some(_) = Some(42) {}
+
+ if let None = None::<()> {}
+
+ while let Some(_) = Some(42) {}
+
+ while let None = None::<()> {}
+
+ match Some(42) {
+ Some(_) => true,
+ None => false,
+ };
+
+ match None::<()> {
+ Some(_) => false,
+ None => true,
+ };
+}
++
++#[allow(clippy::deref_addrof, dead_code)]
++fn issue7921() {
++ if let None = *(&None::<()>) {}
++ if let None = *&None::<()> {}
++}
--- /dev/null
- error: aborting due to 19 previous errors
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:14:12
+ |
+LL | if let None = None::<()> {}
+ | -------^^^^------------- help: try this: `if None::<()>.is_none()`
+ |
+ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:16:12
+ |
+LL | if let Some(_) = Some(42) {}
+ | -------^^^^^^^----------- help: try this: `if Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:18:12
+ |
+LL | if let Some(_) = Some(42) {
+ | -------^^^^^^^----------- help: try this: `if Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:24:15
+ |
+LL | while let Some(_) = Some(42) {}
+ | ----------^^^^^^^----------- help: try this: `while Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:26:15
+ |
+LL | while let None = Some(42) {}
+ | ----------^^^^----------- help: try this: `while Some(42).is_none()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:28:15
+ |
+LL | while let None = None::<()> {}
+ | ----------^^^^------------- help: try this: `while None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:31:15
+ |
+LL | while let Some(_) = v.pop() {
+ | ----------^^^^^^^---------- help: try this: `while v.pop().is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:39:5
+ |
+LL | / match Some(42) {
+LL | | Some(_) => true,
+LL | | None => false,
+LL | | };
+ | |_____^ help: try this: `Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:44:5
+ |
+LL | / match None::<()> {
+LL | | Some(_) => false,
+LL | | None => true,
+LL | | };
+ | |_____^ help: try this: `None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:49:13
+ |
+LL | let _ = match None::<()> {
+ | _____________^
+LL | | Some(_) => false,
+LL | | None => true,
+LL | | };
+ | |_____^ help: try this: `None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:55:20
+ |
+LL | let _ = if let Some(_) = opt { true } else { false };
+ | -------^^^^^^^------ help: try this: `if opt.is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:59:20
+ |
+LL | let _ = if let Some(_) = gen_opt() {
+ | -------^^^^^^^------------ help: try this: `if gen_opt().is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:61:19
+ |
+LL | } else if let None = gen_opt() {
+ | -------^^^^------------ help: try this: `if gen_opt().is_none()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:80:12
+ |
+LL | if let Some(_) = Some(42) {}
+ | -------^^^^^^^----------- help: try this: `if Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:82:12
+ |
+LL | if let None = None::<()> {}
+ | -------^^^^------------- help: try this: `if None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:84:15
+ |
+LL | while let Some(_) = Some(42) {}
+ | ----------^^^^^^^----------- help: try this: `while Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:86:15
+ |
+LL | while let None = None::<()> {}
+ | ----------^^^^------------- help: try this: `while None::<()>.is_none()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_option.rs:88:5
+ |
+LL | / match Some(42) {
+LL | | Some(_) => true,
+LL | | None => false,
+LL | | };
+ | |_____^ help: try this: `Some(42).is_some()`
+
+error: redundant pattern matching, consider using `is_none()`
+ --> $DIR/redundant_pattern_matching_option.rs:93:5
+ |
+LL | / match None::<()> {
+LL | | Some(_) => false,
+LL | | None => true,
+LL | | };
+ | |_____^ help: try this: `None::<()>.is_none()`
+
++error: redundant pattern matching, consider using `is_none()`
++ --> $DIR/redundant_pattern_matching_option.rs:101:12
++ |
++LL | if let None = *(&None::<()>) {}
++ | -------^^^^----------------- help: try this: `if (&None::<()>).is_none()`
++
++error: redundant pattern matching, consider using `is_none()`
++ --> $DIR/redundant_pattern_matching_option.rs:102:12
++ |
++LL | if let None = *&None::<()> {}
++ | -------^^^^--------------- help: try this: `if (&None::<()>).is_none()`
++
++error: aborting due to 21 previous errors
+
--- /dev/null
- while r#try!(result_opt()).is_some() {}
- if r#try!(result_opt()).is_some() {}
+// run-rustfix
+
+#![warn(clippy::all)]
+#![warn(clippy::redundant_pattern_matching)]
+#![allow(
+ unused_must_use,
+ clippy::needless_bool,
+ clippy::match_like_matches_macro,
+ clippy::unnecessary_wraps,
+ deprecated,
+ clippy::if_same_then_else
+)]
+
+fn main() {
+ let result: Result<usize, usize> = Err(5);
+ if result.is_ok() {}
+
+ 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() {}
+
+ if Ok::<i32, i32>(42).is_ok() {}
+
+ if Err::<i32, i32>(42).is_err() {}
+
+ 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();
+
+ let _ = if Ok::<usize, ()>(4).is_ok() { true } else { false };
+
+ issue5504();
+ issue6067();
+ issue6065();
+
+ let _ = if gen_res().is_ok() {
+ 1
+ } else if gen_res().is_err() {
+ 2
+ } else {
+ 3
+ };
+}
+
+fn gen_res() -> Result<(), ()> {
+ Ok(())
+}
+
+macro_rules! m {
+ () => {
+ Some(42u32)
+ };
+}
+
+fn issue5504() {
+ fn result_opt() -> Result<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() {}
+}
+
+fn issue6065() {
+ macro_rules! if_let_in_macro {
+ ($pat:pat, $x:expr) => {
+ if let Some($pat) = $x {}
+ };
+ }
+
+ // shouldn't be linted
+ if_let_in_macro!(_, Some(42));
+}
+
+// Methods that are unstable const should not be suggested within a const context, see issue #5697.
+// However, in Rust 1.48.0 the methods `is_ok` and `is_err` of `Result` were stabilized as const,
+// so the following should be linted.
+const fn issue6067() {
+ if Ok::<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();
+}
--- /dev/null
- | ----------^^^^^^^----------------------- help: try this: `while r#try!(result_opt()).is_some()`
+error: redundant pattern matching, consider using `is_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:16:12
+ |
+LL | if let Ok(_) = &result {}
+ | -------^^^^^---------- help: try this: `if result.is_ok()`
+ |
+ = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
+
+error: redundant pattern matching, consider using `is_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:18:12
+ |
+LL | if let Ok(_) = Ok::<i32, i32>(42) {}
+ | -------^^^^^--------------------- help: try this: `if Ok::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
+ --> $DIR/redundant_pattern_matching_result.rs:20: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_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:22: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_result.rs:24: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_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:34: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_result.rs:39: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_result.rs:44: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_result.rs:49: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_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:54: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_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:60:20
+ |
+LL | let _ = if let Ok(_) = gen_res() {
+ | -------^^^^^------------ help: try this: `if gen_res().is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
+ --> $DIR/redundant_pattern_matching_result.rs:62:19
+ |
+LL | } else if let Err(_) = gen_res() {
+ | -------^^^^^^------------ help: try this: `if gen_res().is_err()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_result.rs:85:19
+ |
+LL | while let Some(_) = r#try!(result_opt()) {}
- | -------^^^^^^^----------------------- help: try this: `if r#try!(result_opt()).is_some()`
++ | ----------^^^^^^^----------------------- help: try this: `while (r#try!(result_opt())).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_result.rs:86:16
+ |
+LL | if let Some(_) = r#try!(result_opt()) {}
++ | -------^^^^^^^----------------------- help: try this: `if (r#try!(result_opt())).is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_result.rs:92:12
+ |
+LL | if let Some(_) = m!() {}
+ | -------^^^^^^^------- help: try this: `if m!().is_some()`
+
+error: redundant pattern matching, consider using `is_some()`
+ --> $DIR/redundant_pattern_matching_result.rs:93:15
+ |
+LL | while let Some(_) = m!() {}
+ | ----------^^^^^^^------- help: try this: `while m!().is_some()`
+
+error: redundant pattern matching, consider using `is_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:111:12
+ |
+LL | if let Ok(_) = Ok::<i32, i32>(42) {}
+ | -------^^^^^--------------------- help: try this: `if Ok::<i32, i32>(42).is_ok()`
+
+error: redundant pattern matching, consider using `is_err()`
+ --> $DIR/redundant_pattern_matching_result.rs:113: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_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:115: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_result.rs:117: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_ok()`
+ --> $DIR/redundant_pattern_matching_result.rs:119: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_result.rs:124:5
+ |
+LL | / match Err::<i32, i32>(42) {
+LL | | Ok(_) => false,
+LL | | Err(_) => true,
+LL | | };
+ | |_____^ help: try this: `Err::<i32, i32>(42).is_err()`
+
+error: aborting due to 22 previous errors
+
--- /dev/null
+//! Test for Clippy lint renames.
+// run-rustfix
+
+#![allow(dead_code)]
+// allow the new lint name here, to test if the new name works
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::redundant_static_lifetimes)]
++#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::box_collection)]
+#![allow(clippy::blocks_in_if_conditions)]
+#![allow(clippy::map_unwrap_or)]
+#![allow(clippy::unwrap_used)]
+#![allow(clippy::expect_used)]
+#![allow(clippy::for_loops_over_fallibles)]
+#![allow(clippy::useless_conversion)]
+#![allow(clippy::invisible_characters)]
+#![allow(clippy::single_char_add_str)]
+#![allow(clippy::match_result_ok)]
++#![allow(clippy::disallowed_types)]
++#![allow(clippy::disallowed_methods)]
+// uplifted lints
+#![allow(invalid_value)]
+#![allow(array_into_iter)]
+#![allow(unused_labels)]
+#![allow(drop_bounds)]
+#![allow(temporary_cstring_as_ptr)]
+#![allow(non_fmt_panics)]
+#![allow(unknown_lints)]
+#![allow(invalid_atomic_ordering)]
+#![allow(enum_intrinsics_non_enums)]
+// warn for the old lint name here, to test if the renaming worked
+#![warn(clippy::module_name_repetitions)]
+#![warn(clippy::new_without_default)]
+#![warn(clippy::redundant_static_lifetimes)]
+#![warn(clippy::cognitive_complexity)]
+#![warn(clippy::bind_instead_of_map)]
+#![warn(clippy::box_collection)]
+#![warn(clippy::blocks_in_if_conditions)]
+#![warn(clippy::blocks_in_if_conditions)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::map_unwrap_or)]
+#![warn(clippy::unwrap_used)]
+#![warn(clippy::unwrap_used)]
+#![warn(clippy::expect_used)]
+#![warn(clippy::expect_used)]
+#![warn(clippy::for_loops_over_fallibles)]
+#![warn(clippy::for_loops_over_fallibles)]
+#![warn(clippy::useless_conversion)]
+#![warn(clippy::invisible_characters)]
+#![warn(clippy::single_char_add_str)]
+#![warn(clippy::match_result_ok)]
++#![warn(clippy::disallowed_types)]
++#![warn(clippy::disallowed_methods)]
+// uplifted lints
+#![warn(invalid_value)]
+#![warn(array_into_iter)]
+#![warn(unused_labels)]
+#![warn(drop_bounds)]
+#![warn(temporary_cstring_as_ptr)]
+#![warn(non_fmt_panics)]
+#![warn(unknown_lints)]
+#![warn(invalid_atomic_ordering)]
+#![warn(enum_intrinsics_non_enums)]
+
+fn main() {}
--- /dev/null
+//! Test for Clippy lint renames.
+// run-rustfix
+
+#![allow(dead_code)]
+// allow the new lint name here, to test if the new name works
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::redundant_static_lifetimes)]
++#![allow(clippy::cognitive_complexity)]
+#![allow(clippy::bind_instead_of_map)]
+#![allow(clippy::box_collection)]
+#![allow(clippy::blocks_in_if_conditions)]
+#![allow(clippy::map_unwrap_or)]
+#![allow(clippy::unwrap_used)]
+#![allow(clippy::expect_used)]
+#![allow(clippy::for_loops_over_fallibles)]
+#![allow(clippy::useless_conversion)]
+#![allow(clippy::invisible_characters)]
+#![allow(clippy::single_char_add_str)]
+#![allow(clippy::match_result_ok)]
++#![allow(clippy::disallowed_types)]
++#![allow(clippy::disallowed_methods)]
+// uplifted lints
+#![allow(invalid_value)]
+#![allow(array_into_iter)]
+#![allow(unused_labels)]
+#![allow(drop_bounds)]
+#![allow(temporary_cstring_as_ptr)]
+#![allow(non_fmt_panics)]
+#![allow(unknown_lints)]
+#![allow(invalid_atomic_ordering)]
+#![allow(enum_intrinsics_non_enums)]
+// warn for the old lint name here, to test if the renaming worked
+#![warn(clippy::stutter)]
+#![warn(clippy::new_without_default_derive)]
+#![warn(clippy::const_static_lifetime)]
+#![warn(clippy::cyclomatic_complexity)]
+#![warn(clippy::option_and_then_some)]
+#![warn(clippy::box_vec)]
+#![warn(clippy::block_in_if_condition_expr)]
+#![warn(clippy::block_in_if_condition_stmt)]
+#![warn(clippy::option_map_unwrap_or)]
+#![warn(clippy::option_map_unwrap_or_else)]
+#![warn(clippy::result_map_unwrap_or_else)]
+#![warn(clippy::option_unwrap_used)]
+#![warn(clippy::result_unwrap_used)]
+#![warn(clippy::option_expect_used)]
+#![warn(clippy::result_expect_used)]
+#![warn(clippy::for_loop_over_option)]
+#![warn(clippy::for_loop_over_result)]
+#![warn(clippy::identity_conversion)]
+#![warn(clippy::zero_width_space)]
+#![warn(clippy::single_char_push_str)]
+#![warn(clippy::if_let_some_result)]
++#![warn(clippy::disallowed_type)]
++#![warn(clippy::disallowed_method)]
+// uplifted lints
+#![warn(clippy::invalid_ref)]
+#![warn(clippy::into_iter_on_array)]
+#![warn(clippy::unused_label)]
+#![warn(clippy::drop_bounds)]
+#![warn(clippy::temporary_cstring_as_ptr)]
+#![warn(clippy::panic_params)]
+#![warn(clippy::unknown_clippy_lints)]
+#![warn(clippy::invalid_atomic_ordering)]
+#![warn(clippy::mem_discriminant_non_enum)]
+
+fn main() {}
--- /dev/null
- --> $DIR/rename.rs:31:9
+error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions`
- --> $DIR/rename.rs:32:9
++ --> $DIR/rename.rs:34:9
+ |
+LL | #![warn(clippy::stutter)]
+ | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions`
+ |
+ = note: `-D renamed-and-removed-lints` implied by `-D warnings`
+
+error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default`
- --> $DIR/rename.rs:33:9
++ --> $DIR/rename.rs:35:9
+ |
+LL | #![warn(clippy::new_without_default_derive)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default`
+
+error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes`
- --> $DIR/rename.rs:34:9
++ --> $DIR/rename.rs:36:9
+ |
+LL | #![warn(clippy::const_static_lifetime)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes`
+
+error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity`
- --> $DIR/rename.rs:35:9
++ --> $DIR/rename.rs:37:9
+ |
+LL | #![warn(clippy::cyclomatic_complexity)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity`
+
+error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map`
- --> $DIR/rename.rs:36:9
++ --> $DIR/rename.rs:38:9
+ |
+LL | #![warn(clippy::option_and_then_some)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map`
+
+error: lint `clippy::box_vec` has been renamed to `clippy::box_collection`
- --> $DIR/rename.rs:37:9
++ --> $DIR/rename.rs:39:9
+ |
+LL | #![warn(clippy::box_vec)]
+ | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection`
+
+error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_if_conditions`
- --> $DIR/rename.rs:38:9
++ --> $DIR/rename.rs:40:9
+ |
+LL | #![warn(clippy::block_in_if_condition_expr)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions`
+
+error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_if_conditions`
- --> $DIR/rename.rs:39:9
++ --> $DIR/rename.rs:41:9
+ |
+LL | #![warn(clippy::block_in_if_condition_stmt)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions`
+
+error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:40:9
++ --> $DIR/rename.rs:42:9
+ |
+LL | #![warn(clippy::option_map_unwrap_or)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:41:9
++ --> $DIR/rename.rs:43:9
+ |
+LL | #![warn(clippy::option_map_unwrap_or_else)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or`
- --> $DIR/rename.rs:42:9
++ --> $DIR/rename.rs:44:9
+ |
+LL | #![warn(clippy::result_map_unwrap_or_else)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or`
+
+error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used`
- --> $DIR/rename.rs:43:9
++ --> $DIR/rename.rs:45:9
+ |
+LL | #![warn(clippy::option_unwrap_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used`
+
+error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used`
- --> $DIR/rename.rs:44:9
++ --> $DIR/rename.rs:46:9
+ |
+LL | #![warn(clippy::result_unwrap_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used`
+
+error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used`
- --> $DIR/rename.rs:45:9
++ --> $DIR/rename.rs:47:9
+ |
+LL | #![warn(clippy::option_expect_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used`
+
+error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used`
- --> $DIR/rename.rs:46:9
++ --> $DIR/rename.rs:48:9
+ |
+LL | #![warn(clippy::result_expect_used)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used`
+
+error: lint `clippy::for_loop_over_option` has been renamed to `clippy::for_loops_over_fallibles`
- --> $DIR/rename.rs:47:9
++ --> $DIR/rename.rs:49:9
+ |
+LL | #![warn(clippy::for_loop_over_option)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::for_loops_over_fallibles`
+
+error: lint `clippy::for_loop_over_result` has been renamed to `clippy::for_loops_over_fallibles`
- --> $DIR/rename.rs:48:9
++ --> $DIR/rename.rs:50:9
+ |
+LL | #![warn(clippy::for_loop_over_result)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::for_loops_over_fallibles`
+
+error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion`
- --> $DIR/rename.rs:49:9
++ --> $DIR/rename.rs:51:9
+ |
+LL | #![warn(clippy::identity_conversion)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion`
+
+error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters`
- --> $DIR/rename.rs:50:9
++ --> $DIR/rename.rs:52:9
+ |
+LL | #![warn(clippy::zero_width_space)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters`
+
+error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str`
- --> $DIR/rename.rs:51:9
++ --> $DIR/rename.rs:53:9
+ |
+LL | #![warn(clippy::single_char_push_str)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str`
+
+error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok`
- --> $DIR/rename.rs:53:9
++ --> $DIR/rename.rs:54:9
+ |
+LL | #![warn(clippy::if_let_some_result)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok`
+
++error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types`
++ --> $DIR/rename.rs:55:9
++ |
++LL | #![warn(clippy::disallowed_type)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types`
++
++error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods`
++ --> $DIR/rename.rs:56:9
++ |
++LL | #![warn(clippy::disallowed_method)]
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods`
++
+error: lint `clippy::invalid_ref` has been renamed to `invalid_value`
- --> $DIR/rename.rs:54:9
++ --> $DIR/rename.rs:58:9
+ |
+LL | #![warn(clippy::invalid_ref)]
+ | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value`
+
+error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter`
- --> $DIR/rename.rs:55:9
++ --> $DIR/rename.rs:59:9
+ |
+LL | #![warn(clippy::into_iter_on_array)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter`
+
+error: lint `clippy::unused_label` has been renamed to `unused_labels`
- --> $DIR/rename.rs:56:9
++ --> $DIR/rename.rs:60:9
+ |
+LL | #![warn(clippy::unused_label)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels`
+
+error: lint `clippy::drop_bounds` has been renamed to `drop_bounds`
- --> $DIR/rename.rs:57:9
++ --> $DIR/rename.rs:61:9
+ |
+LL | #![warn(clippy::drop_bounds)]
+ | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds`
+
+error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr`
- --> $DIR/rename.rs:58:9
++ --> $DIR/rename.rs:62:9
+ |
+LL | #![warn(clippy::temporary_cstring_as_ptr)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr`
+
+error: lint `clippy::panic_params` has been renamed to `non_fmt_panics`
- --> $DIR/rename.rs:59:9
++ --> $DIR/rename.rs:63:9
+ |
+LL | #![warn(clippy::panic_params)]
+ | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics`
+
+error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints`
- --> $DIR/rename.rs:60:9
++ --> $DIR/rename.rs:64:9
+ |
+LL | #![warn(clippy::unknown_clippy_lints)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints`
+
+error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering`
- --> $DIR/rename.rs:61:9
++ --> $DIR/rename.rs:65:9
+ |
+LL | #![warn(clippy::invalid_atomic_ordering)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering`
+
+error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums`
- error: aborting due to 30 previous errors
++ --> $DIR/rename.rs:66:9
+ |
+LL | #![warn(clippy::mem_discriminant_non_enum)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums`
+
++error: aborting due to 32 previous errors
+
--- /dev/null
- error: method's name is same to an existing method in a trait
++error: method's name is the same as an existing method in a trait
+ --> $DIR/same_name_method.rs:20:13
+ |
+LL | fn foo() {}
+ | ^^^^^^^^^^^
+ |
+ = note: `-D clippy::same-name-method` implied by `-D warnings`
+note: existing `foo` defined here
+ --> $DIR/same_name_method.rs:24:13
+ |
+LL | fn foo() {}
+ | ^^^^^^^^^^^
+
- error: method's name is same to an existing method in a trait
++error: method's name is the same as an existing method in a trait
+ --> $DIR/same_name_method.rs:44:13
+ |
+LL | fn foo() {}
+ | ^^^^^^^^^^^
+ |
+note: existing `foo` defined here
+ --> $DIR/same_name_method.rs:48:13
+ |
+LL | fn foo() {}
+ | ^^^^^^^^^^^
+
- error: method's name is same to an existing method in a trait
++error: method's name is the same as an existing method in a trait
+ --> $DIR/same_name_method.rs:58:13
+ |
+LL | fn foo() {}
+ | ^^^^^^^^^^^
+ |
+note: existing `foo` defined here
+ --> $DIR/same_name_method.rs:61:9
+ |
+LL | impl T1 for S {}
+ | ^^^^^^^^^^^^^^^^
+
- error: method's name is same to an existing method in a trait
++error: method's name is the same as an existing method in a trait
+ --> $DIR/same_name_method.rs:70:13
+ |
+LL | fn foo() {}
+ | ^^^^^^^^^^^
+ |
+note: existing `foo` defined here
+ --> $DIR/same_name_method.rs:73:9
+ |
+LL | impl T1 for S {}
+ | ^^^^^^^^^^^^^^^^
+
- error: method's name is same to an existing method in a trait
++error: method's name is the same as an existing method in a trait
+ --> $DIR/same_name_method.rs:34:13
+ |
+LL | fn clone() {}
+ | ^^^^^^^^^^^^^
+ |
+note: existing `clone` defined here
+ --> $DIR/same_name_method.rs:30:18
+ |
+LL | #[derive(Clone)]
+ | ^^^^^
+ = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 5 previous errors
+
--- /dev/null
+// aux-build:option_helpers.rs
+#![warn(clippy::search_is_some)]
+#![allow(dead_code)]
+extern crate option_helpers;
+use option_helpers::IteratorFalsePositives;
+
+#[rustfmt::skip]
+fn main() {
+ let v = vec![3, 2, 1, 0, -1, -2, -3];
+ let y = &&42;
+
+
+ // Check `find().is_some()`, multi-line case.
+ let _ = v.iter().find(|&x| {
+ *x < 0
+ }
+ ).is_some();
+
+ // Check `position().is_some()`, multi-line case.
+ let _ = v.iter().position(|&x| {
+ x < 0
+ }
+ ).is_some();
+
+ // Check `rposition().is_some()`, multi-line case.
+ let _ = v.iter().rposition(|&x| {
+ x < 0
+ }
+ ).is_some();
+
+ // Check that we don't lint if the caller is not an `Iterator` or string
+ let falsepos = IteratorFalsePositives { foo: 0 };
+ let _ = falsepos.find().is_some();
+ let _ = falsepos.position().is_some();
+ let _ = falsepos.rposition().is_some();
+ // check that we don't lint if `find()` is called with
+ // `Pattern` that is not a string
+ let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_some();
++
++ let some_closure = |x: &u32| *x == 0;
++ let _ = (0..1).find(some_closure).is_some();
+}
+
+#[rustfmt::skip]
+fn is_none() {
+ let v = vec![3, 2, 1, 0, -1, -2, -3];
+ let y = &&42;
+
+
+ // Check `find().is_none()`, multi-line case.
+ let _ = v.iter().find(|&x| {
+ *x < 0
+ }
+ ).is_none();
+
+ // Check `position().is_none()`, multi-line case.
+ let _ = v.iter().position(|&x| {
+ x < 0
+ }
+ ).is_none();
+
+ // Check `rposition().is_none()`, multi-line case.
+ let _ = v.iter().rposition(|&x| {
+ x < 0
+ }
+ ).is_none();
+
+ // Check that we don't lint if the caller is not an `Iterator` or string
+ let falsepos = IteratorFalsePositives { foo: 0 };
+ let _ = falsepos.find().is_none();
+ let _ = falsepos.position().is_none();
+ let _ = falsepos.rposition().is_none();
+ // check that we don't lint if `find()` is called with
+ // `Pattern` that is not a string
+ let _ = "hello world".find(|c: char| c == 'o' || c == 'l').is_none();
++
++ let some_closure = |x: &u32| *x == 0;
++ let _ = (0..1).find(some_closure).is_none();
+}
--- /dev/null
- --> $DIR/search_is_some.rs:48:13
+error: called `is_some()` after searching an `Iterator` with `find`
+ --> $DIR/search_is_some.rs:14:13
+ |
+LL | let _ = v.iter().find(|&x| {
+ | _____________^
+LL | | *x < 0
+LL | | }
+LL | | ).is_some();
+ | |______________________________^
+ |
+ = note: `-D clippy::search-is-some` implied by `-D warnings`
+ = help: this is more succinctly expressed by calling `any()`
+
+error: called `is_some()` after searching an `Iterator` with `position`
+ --> $DIR/search_is_some.rs:20:13
+ |
+LL | let _ = v.iter().position(|&x| {
+ | _____________^
+LL | | x < 0
+LL | | }
+LL | | ).is_some();
+ | |______________________________^
+ |
+ = help: this is more succinctly expressed by calling `any()`
+
+error: called `is_some()` after searching an `Iterator` with `rposition`
+ --> $DIR/search_is_some.rs:26:13
+ |
+LL | let _ = v.iter().rposition(|&x| {
+ | _____________^
+LL | | x < 0
+LL | | }
+LL | | ).is_some();
+ | |______________________________^
+ |
+ = help: this is more succinctly expressed by calling `any()`
+
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some.rs:41:20
++ |
++LL | let _ = (0..1).find(some_closure).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(some_closure)`
++
+error: called `is_none()` after searching an `Iterator` with `find`
- --> $DIR/search_is_some.rs:54:13
++ --> $DIR/search_is_some.rs:51:13
+ |
+LL | let _ = v.iter().find(|&x| {
+ | _____________^
+LL | | *x < 0
+LL | | }
+LL | | ).is_none();
+ | |______________________________^
+ |
+ = help: this is more succinctly expressed by calling `any()` with negation
+
+error: called `is_none()` after searching an `Iterator` with `position`
- --> $DIR/search_is_some.rs:60:13
++ --> $DIR/search_is_some.rs:57:13
+ |
+LL | let _ = v.iter().position(|&x| {
+ | _____________^
+LL | | x < 0
+LL | | }
+LL | | ).is_none();
+ | |______________________________^
+ |
+ = help: this is more succinctly expressed by calling `any()` with negation
+
+error: called `is_none()` after searching an `Iterator` with `rposition`
- error: aborting due to 6 previous errors
++ --> $DIR/search_is_some.rs:63:13
+ |
+LL | let _ = v.iter().rposition(|&x| {
+ | _____________^
+LL | | x < 0
+LL | | }
+LL | | ).is_none();
+ | |______________________________^
+ |
+ = help: this is more succinctly expressed by calling `any()` with negation
+
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some.rs:78:13
++ |
++LL | let _ = (0..1).find(some_closure).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!(0..1).any(some_closure)`
++
++error: aborting due to 8 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++#![allow(dead_code)]
++#![warn(clippy::search_is_some)]
++
++fn main() {
++ let v = vec![3, 2, 1, 0, -1, -2, -3];
++ let y = &&42;
++
++ // Check `find().is_none()`, single-line case.
++ let _ = !v.iter().any(|x| *x < 0);
++ let _ = !(0..1).any(|x| **y == x); // one dereference less
++ let _ = !(0..1).any(|x| x == 0);
++ let _ = !v.iter().any(|x| *x == 0);
++ let _ = !(4..5).any(|x| x == 1 || x == 3 || x == 5);
++ let _ = !(1..3).any(|x| [1, 2, 3].contains(&x));
++ let _ = !(1..3).any(|x| x == 0 || [1, 2, 3].contains(&x));
++ let _ = !(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0);
++ let _ = !(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1);
++
++ // Check `position().is_none()`, single-line case.
++ let _ = !v.iter().any(|&x| x < 0);
++
++ // Check `rposition().is_none()`, single-line case.
++ let _ = !v.iter().any(|&x| x < 0);
++
++ let s1 = String::from("hello world");
++ let s2 = String::from("world");
++
++ // caller of `find()` is a `&`static str`
++ let _ = !"hello world".contains("world");
++ let _ = !"hello world".contains(&s2);
++ let _ = !"hello world".contains(&s2[2..]);
++ // caller of `find()` is a `String`
++ let _ = !s1.contains("world");
++ let _ = !s1.contains(&s2);
++ let _ = !s1.contains(&s2[2..]);
++ // caller of `find()` is slice of `String`
++ let _ = !s1[2..].contains("world");
++ let _ = !s1[2..].contains(&s2);
++ let _ = !s1[2..].contains(&s2[2..]);
++}
++
++#[allow(clippy::clone_on_copy, clippy::map_clone)]
++mod issue7392 {
++ struct Player {
++ hand: Vec<usize>,
++ }
++ fn filter() {
++ let p = Player {
++ hand: vec![1, 2, 3, 4, 5],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|c| !filter_hand.iter().any(|cc| c == &cc))
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ struct PlayerTuple {
++ hand: Vec<(usize, char)>,
++ }
++ fn filter_tuple() {
++ let p = PlayerTuple {
++ hand: vec![(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|(c, _)| !filter_hand.iter().any(|cc| c == cc))
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ fn field_projection() {
++ struct Foo {
++ foo: i32,
++ bar: u32,
++ }
++ let vfoo = vec![Foo { foo: 1, bar: 2 }];
++ let _ = !vfoo.iter().any(|v| v.foo == 1 && v.bar == 2);
++
++ let vfoo = vec![(42, Foo { foo: 1, bar: 2 })];
++ let _ = !vfoo
++ .iter().any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2);
++ }
++
++ fn index_projection() {
++ let vfoo = vec![[0, 1, 2, 3]];
++ let _ = !vfoo.iter().any(|a| a[0] == 42);
++ }
++
++ #[allow(clippy::match_like_matches_macro)]
++ fn slice_projection() {
++ let vfoo = vec![[0, 1, 2, 3, 0, 1, 2, 3]];
++ let _ = !vfoo.iter().any(|sub| sub[1..4].len() == 3);
++ }
++
++ fn please(x: &u32) -> bool {
++ *x == 9
++ }
++
++ fn deref_enough(x: u32) -> bool {
++ x == 78
++ }
++
++ fn arg_no_deref(x: &&u32) -> bool {
++ **x == 78
++ }
++
++ fn more_projections() {
++ let x = 19;
++ let ppx: &u32 = &x;
++ let _ = ![ppx].iter().any(|ppp_x: &&u32| please(ppp_x));
++ let _ = ![String::from("Hey hey")].iter().any(|s| s.len() == 2);
++
++ let v = vec![3, 2, 1, 0];
++ let _ = !v.iter().any(|x| deref_enough(*x));
++ let _ = !v.iter().any(|x: &u32| deref_enough(*x));
++
++ #[allow(clippy::redundant_closure)]
++ let _ = !v.iter().any(|x| arg_no_deref(&x));
++ #[allow(clippy::redundant_closure)]
++ let _ = !v.iter().any(|x: &u32| arg_no_deref(&x));
++ }
++
++ fn field_index_projection() {
++ struct FooDouble {
++ bar: Vec<Vec<i32>>,
++ }
++ struct Foo {
++ bar: Vec<i32>,
++ }
++ struct FooOuter {
++ inner: Foo,
++ inner_double: FooDouble,
++ }
++ let vfoo = vec![FooOuter {
++ inner: Foo { bar: vec![0, 1, 2, 3] },
++ inner_double: FooDouble {
++ bar: vec![vec![0, 1, 2, 3]],
++ },
++ }];
++ let _ = !vfoo
++ .iter().any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2);
++ }
++
++ fn index_field_projection() {
++ struct Foo {
++ bar: i32,
++ }
++ struct FooOuter {
++ inner: Vec<Foo>,
++ }
++ let vfoo = vec![FooOuter {
++ inner: vec![Foo { bar: 0 }],
++ }];
++ let _ = !vfoo.iter().any(|v| v.inner[0].bar == 2);
++ }
++
++ fn double_deref_index_projection() {
++ let vfoo = vec![&&[0, 1, 2, 3]];
++ let _ = !vfoo.iter().any(|x| (**x)[0] == 9);
++ }
++
++ fn method_call_by_ref() {
++ struct Foo {
++ bar: u32,
++ }
++ impl Foo {
++ pub fn by_ref(&self, x: &u32) -> bool {
++ *x == self.bar
++ }
++ }
++ let vfoo = vec![Foo { bar: 1 }];
++ let _ = !vfoo.iter().any(|v| v.by_ref(&v.bar));
++ }
++
++ fn ref_bindings() {
++ let _ = ![&(&1, 2), &(&3, 4), &(&5, 4)].iter().any(|(&x, y)| x == *y);
++ let _ = ![&(&1, 2), &(&3, 4), &(&5, 4)].iter().any(|(&x, y)| x == *y);
++ }
++
++ fn test_string_1(s: &str) -> bool {
++ s.is_empty()
++ }
++
++ fn test_u32_1(s: &u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn test_u32_2(s: u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn projection_in_args_test() {
++ // Index projections
++ let lst = &[String::from("Hello"), String::from("world")];
++ let v: Vec<&[String]> = vec![lst];
++ let _ = !v.iter().any(|s| s[0].is_empty());
++ let _ = !v.iter().any(|s| test_string_1(&s[0]));
++
++ // Field projections
++ struct FieldProjection<'a> {
++ field: &'a u32,
++ }
++ let field = 123456789;
++ let instance = FieldProjection { field: &field };
++ let v = vec![instance];
++ let _ = !v.iter().any(|fp| fp.field.is_power_of_two());
++ let _ = !v.iter().any(|fp| test_u32_1(fp.field));
++ let _ = !v.iter().any(|fp| test_u32_2(*fp.field));
++ }
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++#![allow(dead_code)]
++#![warn(clippy::search_is_some)]
++
++fn main() {
++ let v = vec![3, 2, 1, 0, -1, -2, -3];
++ let y = &&42;
++
++ // Check `find().is_none()`, single-line case.
++ let _ = v.iter().find(|&x| *x < 0).is_none();
++ let _ = (0..1).find(|x| **y == *x).is_none(); // one dereference less
++ let _ = (0..1).find(|x| *x == 0).is_none();
++ let _ = v.iter().find(|x| **x == 0).is_none();
++ let _ = (4..5).find(|x| *x == 1 || *x == 3 || *x == 5).is_none();
++ let _ = (1..3).find(|x| [1, 2, 3].contains(x)).is_none();
++ let _ = (1..3).find(|x| *x == 0 || [1, 2, 3].contains(x)).is_none();
++ let _ = (1..3).find(|x| [1, 2, 3].contains(x) || *x == 0).is_none();
++ let _ = (1..3)
++ .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1)
++ .is_none();
++
++ // Check `position().is_none()`, single-line case.
++ let _ = v.iter().position(|&x| x < 0).is_none();
++
++ // Check `rposition().is_none()`, single-line case.
++ let _ = v.iter().rposition(|&x| x < 0).is_none();
++
++ let s1 = String::from("hello world");
++ let s2 = String::from("world");
++
++ // caller of `find()` is a `&`static str`
++ let _ = "hello world".find("world").is_none();
++ let _ = "hello world".find(&s2).is_none();
++ let _ = "hello world".find(&s2[2..]).is_none();
++ // caller of `find()` is a `String`
++ let _ = s1.find("world").is_none();
++ let _ = s1.find(&s2).is_none();
++ let _ = s1.find(&s2[2..]).is_none();
++ // caller of `find()` is slice of `String`
++ let _ = s1[2..].find("world").is_none();
++ let _ = s1[2..].find(&s2).is_none();
++ let _ = s1[2..].find(&s2[2..]).is_none();
++}
++
++#[allow(clippy::clone_on_copy, clippy::map_clone)]
++mod issue7392 {
++ struct Player {
++ hand: Vec<usize>,
++ }
++ fn filter() {
++ let p = Player {
++ hand: vec![1, 2, 3, 4, 5],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|c| filter_hand.iter().find(|cc| c == cc).is_none())
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ struct PlayerTuple {
++ hand: Vec<(usize, char)>,
++ }
++ fn filter_tuple() {
++ let p = PlayerTuple {
++ hand: vec![(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_none())
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ fn field_projection() {
++ struct Foo {
++ foo: i32,
++ bar: u32,
++ }
++ let vfoo = vec![Foo { foo: 1, bar: 2 }];
++ let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_none();
++
++ let vfoo = vec![(42, Foo { foo: 1, bar: 2 })];
++ let _ = vfoo
++ .iter()
++ .find(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2)
++ .is_none();
++ }
++
++ fn index_projection() {
++ let vfoo = vec![[0, 1, 2, 3]];
++ let _ = vfoo.iter().find(|a| a[0] == 42).is_none();
++ }
++
++ #[allow(clippy::match_like_matches_macro)]
++ fn slice_projection() {
++ let vfoo = vec![[0, 1, 2, 3, 0, 1, 2, 3]];
++ let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_none();
++ }
++
++ fn please(x: &u32) -> bool {
++ *x == 9
++ }
++
++ fn deref_enough(x: u32) -> bool {
++ x == 78
++ }
++
++ fn arg_no_deref(x: &&u32) -> bool {
++ **x == 78
++ }
++
++ fn more_projections() {
++ let x = 19;
++ let ppx: &u32 = &x;
++ let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_none();
++ let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_none();
++
++ let v = vec![3, 2, 1, 0];
++ let _ = v.iter().find(|x| deref_enough(**x)).is_none();
++ let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_none();
++
++ #[allow(clippy::redundant_closure)]
++ let _ = v.iter().find(|x| arg_no_deref(x)).is_none();
++ #[allow(clippy::redundant_closure)]
++ let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_none();
++ }
++
++ fn field_index_projection() {
++ struct FooDouble {
++ bar: Vec<Vec<i32>>,
++ }
++ struct Foo {
++ bar: Vec<i32>,
++ }
++ struct FooOuter {
++ inner: Foo,
++ inner_double: FooDouble,
++ }
++ let vfoo = vec![FooOuter {
++ inner: Foo { bar: vec![0, 1, 2, 3] },
++ inner_double: FooDouble {
++ bar: vec![vec![0, 1, 2, 3]],
++ },
++ }];
++ let _ = vfoo
++ .iter()
++ .find(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2)
++ .is_none();
++ }
++
++ fn index_field_projection() {
++ struct Foo {
++ bar: i32,
++ }
++ struct FooOuter {
++ inner: Vec<Foo>,
++ }
++ let vfoo = vec![FooOuter {
++ inner: vec![Foo { bar: 0 }],
++ }];
++ let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_none();
++ }
++
++ fn double_deref_index_projection() {
++ let vfoo = vec![&&[0, 1, 2, 3]];
++ let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_none();
++ }
++
++ fn method_call_by_ref() {
++ struct Foo {
++ bar: u32,
++ }
++ impl Foo {
++ pub fn by_ref(&self, x: &u32) -> bool {
++ *x == self.bar
++ }
++ }
++ let vfoo = vec![Foo { bar: 1 }];
++ let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_none();
++ }
++
++ fn ref_bindings() {
++ let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|(&x, y)| x == *y).is_none();
++ let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_none();
++ }
++
++ fn test_string_1(s: &String) -> bool {
++ s.is_empty()
++ }
++
++ fn test_u32_1(s: &u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn test_u32_2(s: u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn projection_in_args_test() {
++ // Index projections
++ let lst = &[String::from("Hello"), String::from("world")];
++ let v: Vec<&[String]> = vec![lst];
++ let _ = v.iter().find(|s| s[0].is_empty()).is_none();
++ let _ = v.iter().find(|s| test_string_1(&s[0])).is_none();
++
++ // Field projections
++ struct FieldProjection<'a> {
++ field: &'a u32,
++ }
++ let field = 123456789;
++ let instance = FieldProjection { field: &field };
++ let v = vec![instance];
++ let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_none();
++ let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_none();
++ let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_none();
++ }
++}
--- /dev/null
--- /dev/null
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:10:13
++ |
++LL | let _ = v.iter().find(|&x| *x < 0).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|x| *x < 0)`
++ |
++ = note: `-D clippy::search-is-some` implied by `-D warnings`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:11:13
++ |
++LL | let _ = (0..1).find(|x| **y == *x).is_none(); // one dereference less
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!(0..1).any(|x| **y == x)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:12:13
++ |
++LL | let _ = (0..1).find(|x| *x == 0).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!(0..1).any(|x| x == 0)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:13:13
++ |
++LL | let _ = v.iter().find(|x| **x == 0).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|x| *x == 0)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:14:13
++ |
++LL | let _ = (4..5).find(|x| *x == 1 || *x == 3 || *x == 5).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!(4..5).any(|x| x == 1 || x == 3 || x == 5)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:15:13
++ |
++LL | let _ = (1..3).find(|x| [1, 2, 3].contains(x)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!(1..3).any(|x| [1, 2, 3].contains(&x))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:16:13
++ |
++LL | let _ = (1..3).find(|x| *x == 0 || [1, 2, 3].contains(x)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!(1..3).any(|x| x == 0 || [1, 2, 3].contains(&x))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:17:13
++ |
++LL | let _ = (1..3).find(|x| [1, 2, 3].contains(x) || *x == 0).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:18:13
++ |
++LL | let _ = (1..3)
++ | _____________^
++LL | | .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1)
++LL | | .is_none();
++ | |__________________^ help: use `!_.any()` instead: `!(1..3).any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1)`
++
++error: called `is_none()` after searching an `Iterator` with `position`
++ --> $DIR/search_is_some_fixable_none.rs:23:13
++ |
++LL | let _ = v.iter().position(|&x| x < 0).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|&x| x < 0)`
++
++error: called `is_none()` after searching an `Iterator` with `rposition`
++ --> $DIR/search_is_some_fixable_none.rs:26:13
++ |
++LL | let _ = v.iter().rposition(|&x| x < 0).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|&x| x < 0)`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:32:13
++ |
++LL | let _ = "hello world".find("world").is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!"hello world".contains("world")`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:33:13
++ |
++LL | let _ = "hello world".find(&s2).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!"hello world".contains(&s2)`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:34:13
++ |
++LL | let _ = "hello world".find(&s2[2..]).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!"hello world".contains(&s2[2..])`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:36:13
++ |
++LL | let _ = s1.find("world").is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!s1.contains("world")`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:37:13
++ |
++LL | let _ = s1.find(&s2).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!s1.contains(&s2)`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:38:13
++ |
++LL | let _ = s1.find(&s2[2..]).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!s1.contains(&s2[2..])`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:40:13
++ |
++LL | let _ = s1[2..].find("world").is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!s1[2..].contains("world")`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:41:13
++ |
++LL | let _ = s1[2..].find(&s2).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!s1[2..].contains(&s2)`
++
++error: called `is_none()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_none.rs:42:13
++ |
++LL | let _ = s1[2..].find(&s2[2..]).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.contains()` instead: `!s1[2..].contains(&s2[2..])`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:58:25
++ |
++LL | .filter(|c| filter_hand.iter().find(|cc| c == cc).is_none())
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!filter_hand.iter().any(|cc| c == &cc)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:74:30
++ |
++LL | .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_none())
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!filter_hand.iter().any(|cc| c == cc)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:85:17
++ |
++LL | let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!vfoo.iter().any(|v| v.foo == 1 && v.bar == 2)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:88:17
++ |
++LL | let _ = vfoo
++ | _________________^
++LL | | .iter()
++LL | | .find(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2)
++LL | | .is_none();
++ | |______________________^
++ |
++help: use `!_.any()` instead
++ |
++LL ~ let _ = !vfoo
++LL ~ .iter().any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2);
++ |
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:96:17
++ |
++LL | let _ = vfoo.iter().find(|a| a[0] == 42).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!vfoo.iter().any(|a| a[0] == 42)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:102:17
++ |
++LL | let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!vfoo.iter().any(|sub| sub[1..4].len() == 3)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:120:17
++ |
++LL | let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `![ppx].iter().any(|ppp_x: &&u32| please(ppp_x))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:121:17
++ |
++LL | let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `![String::from("Hey hey")].iter().any(|s| s.len() == 2)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:124:17
++ |
++LL | let _ = v.iter().find(|x| deref_enough(**x)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|x| deref_enough(*x))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:125:17
++ |
++LL | let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|x: &u32| deref_enough(*x))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:128:17
++ |
++LL | let _ = v.iter().find(|x| arg_no_deref(x)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|x| arg_no_deref(&x))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:130:17
++ |
++LL | let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|x: &u32| arg_no_deref(&x))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:150:17
++ |
++LL | let _ = vfoo
++ | _________________^
++LL | | .iter()
++LL | | .find(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2)
++LL | | .is_none();
++ | |______________________^
++ |
++help: use `!_.any()` instead
++ |
++LL ~ let _ = !vfoo
++LL ~ .iter().any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2);
++ |
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:166:17
++ |
++LL | let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!vfoo.iter().any(|v| v.inner[0].bar == 2)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:171:17
++ |
++LL | let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!vfoo.iter().any(|x| (**x)[0] == 9)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:184:17
++ |
++LL | let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!vfoo.iter().any(|v| v.by_ref(&v.bar))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:188:17
++ |
++LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|(&x, y)| x == *y).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `![&(&1, 2), &(&3, 4), &(&5, 4)].iter().any(|(&x, y)| x == *y)`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:189:17
++ |
++LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `![&(&1, 2), &(&3, 4), &(&5, 4)].iter().any(|(&x, y)| x == *y)`
++
++error: writing `&String` instead of `&str` involves a new object where a slice will do
++ --> $DIR/search_is_some_fixable_none.rs:192:25
++ |
++LL | fn test_string_1(s: &String) -> bool {
++ | ^^^^^^^ help: change this to: `&str`
++ |
++ = note: `-D clippy::ptr-arg` implied by `-D warnings`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:208:17
++ |
++LL | let _ = v.iter().find(|s| s[0].is_empty()).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|s| s[0].is_empty())`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:209:17
++ |
++LL | let _ = v.iter().find(|s| test_string_1(&s[0])).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|s| test_string_1(&s[0]))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:218:17
++ |
++LL | let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|fp| fp.field.is_power_of_two())`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:219:17
++ |
++LL | let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|fp| test_u32_1(fp.field))`
++
++error: called `is_none()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_none.rs:220:17
++ |
++LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_none();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|fp| test_u32_2(*fp.field))`
++
++error: aborting due to 44 previous errors
++
--- /dev/null
--- /dev/null
++// run-rustfix
++#![allow(dead_code)]
++#![warn(clippy::search_is_some)]
++
++fn main() {
++ let v = vec![3, 2, 1, 0, -1, -2, -3];
++ let y = &&42;
++
++ // Check `find().is_some()`, single-line case.
++ let _ = v.iter().any(|x| *x < 0);
++ let _ = (0..1).any(|x| **y == x); // one dereference less
++ let _ = (0..1).any(|x| x == 0);
++ let _ = v.iter().any(|x| *x == 0);
++ let _ = (4..5).any(|x| x == 1 || x == 3 || x == 5);
++ let _ = (1..3).any(|x| [1, 2, 3].contains(&x));
++ let _ = (1..3).any(|x| x == 0 || [1, 2, 3].contains(&x));
++ let _ = (1..3).any(|x| [1, 2, 3].contains(&x) || x == 0);
++ let _ = (1..3)
++ .any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1);
++
++ // Check `position().is_some()`, single-line case.
++ let _ = v.iter().any(|&x| x < 0);
++
++ // Check `rposition().is_some()`, single-line case.
++ let _ = v.iter().any(|&x| x < 0);
++
++ let s1 = String::from("hello world");
++ let s2 = String::from("world");
++ // caller of `find()` is a `&`static str`
++ let _ = "hello world".contains("world");
++ let _ = "hello world".contains(&s2);
++ let _ = "hello world".contains(&s2[2..]);
++ // caller of `find()` is a `String`
++ let _ = s1.contains("world");
++ let _ = s1.contains(&s2);
++ let _ = s1.contains(&s2[2..]);
++ // caller of `find()` is slice of `String`
++ let _ = s1[2..].contains("world");
++ let _ = s1[2..].contains(&s2);
++ let _ = s1[2..].contains(&s2[2..]);
++}
++
++#[allow(clippy::clone_on_copy, clippy::map_clone)]
++mod issue7392 {
++ struct Player {
++ hand: Vec<usize>,
++ }
++ fn filter() {
++ let p = Player {
++ hand: vec![1, 2, 3, 4, 5],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|c| filter_hand.iter().any(|cc| c == &cc))
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ struct PlayerTuple {
++ hand: Vec<(usize, char)>,
++ }
++ fn filter_tuple() {
++ let p = PlayerTuple {
++ hand: vec![(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|(c, _)| filter_hand.iter().any(|cc| c == cc))
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ fn field_projection() {
++ struct Foo {
++ foo: i32,
++ bar: u32,
++ }
++ let vfoo = vec![Foo { foo: 1, bar: 2 }];
++ let _ = vfoo.iter().any(|v| v.foo == 1 && v.bar == 2);
++
++ let vfoo = vec![(42, Foo { foo: 1, bar: 2 })];
++ let _ = vfoo
++ .iter()
++ .any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2);
++ }
++
++ fn index_projection() {
++ let vfoo = vec![[0, 1, 2, 3]];
++ let _ = vfoo.iter().any(|a| a[0] == 42);
++ }
++
++ #[allow(clippy::match_like_matches_macro)]
++ fn slice_projection() {
++ let vfoo = vec![[0, 1, 2, 3, 0, 1, 2, 3]];
++ let _ = vfoo.iter().any(|sub| sub[1..4].len() == 3);
++ }
++
++ fn please(x: &u32) -> bool {
++ *x == 9
++ }
++
++ fn deref_enough(x: u32) -> bool {
++ x == 78
++ }
++
++ fn arg_no_deref(x: &&u32) -> bool {
++ **x == 78
++ }
++
++ fn more_projections() {
++ let x = 19;
++ let ppx: &u32 = &x;
++ let _ = [ppx].iter().any(|ppp_x: &&u32| please(ppp_x));
++ let _ = [String::from("Hey hey")].iter().any(|s| s.len() == 2);
++
++ let v = vec![3, 2, 1, 0];
++ let _ = v.iter().any(|x| deref_enough(*x));
++ let _ = v.iter().any(|x: &u32| deref_enough(*x));
++
++ #[allow(clippy::redundant_closure)]
++ let _ = v.iter().any(|x| arg_no_deref(&x));
++ #[allow(clippy::redundant_closure)]
++ let _ = v.iter().any(|x: &u32| arg_no_deref(&x));
++ }
++
++ fn field_index_projection() {
++ struct FooDouble {
++ bar: Vec<Vec<i32>>,
++ }
++ struct Foo {
++ bar: Vec<i32>,
++ }
++ struct FooOuter {
++ inner: Foo,
++ inner_double: FooDouble,
++ }
++ let vfoo = vec![FooOuter {
++ inner: Foo { bar: vec![0, 1, 2, 3] },
++ inner_double: FooDouble {
++ bar: vec![vec![0, 1, 2, 3]],
++ },
++ }];
++ let _ = vfoo
++ .iter()
++ .any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2);
++ }
++
++ fn index_field_projection() {
++ struct Foo {
++ bar: i32,
++ }
++ struct FooOuter {
++ inner: Vec<Foo>,
++ }
++ let vfoo = vec![FooOuter {
++ inner: vec![Foo { bar: 0 }],
++ }];
++ let _ = vfoo.iter().any(|v| v.inner[0].bar == 2);
++ }
++
++ fn double_deref_index_projection() {
++ let vfoo = vec![&&[0, 1, 2, 3]];
++ let _ = vfoo.iter().any(|x| (**x)[0] == 9);
++ }
++
++ fn method_call_by_ref() {
++ struct Foo {
++ bar: u32,
++ }
++ impl Foo {
++ pub fn by_ref(&self, x: &u32) -> bool {
++ *x == self.bar
++ }
++ }
++ let vfoo = vec![Foo { bar: 1 }];
++ let _ = vfoo.iter().any(|v| v.by_ref(&v.bar));
++ }
++
++ fn ref_bindings() {
++ let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().any(|(&x, y)| x == *y);
++ let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().any(|(&x, y)| x == *y);
++ }
++
++ fn test_string_1(s: &str) -> bool {
++ s.is_empty()
++ }
++
++ fn test_u32_1(s: &u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn test_u32_2(s: u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn projection_in_args_test() {
++ // Index projections
++ let lst = &[String::from("Hello"), String::from("world")];
++ let v: Vec<&[String]> = vec![lst];
++ let _ = v.iter().any(|s| s[0].is_empty());
++ let _ = v.iter().any(|s| test_string_1(&s[0]));
++
++ // Field projections
++ struct FieldProjection<'a> {
++ field: &'a u32,
++ }
++ let field = 123456789;
++ let instance = FieldProjection { field: &field };
++ let v = vec![instance];
++ let _ = v.iter().any(|fp| fp.field.is_power_of_two());
++ let _ = v.iter().any(|fp| test_u32_1(fp.field));
++ let _ = v.iter().any(|fp| test_u32_2(*fp.field));
++ }
++}
--- /dev/null
--- /dev/null
++// run-rustfix
++#![allow(dead_code)]
++#![warn(clippy::search_is_some)]
++
++fn main() {
++ let v = vec![3, 2, 1, 0, -1, -2, -3];
++ let y = &&42;
++
++ // Check `find().is_some()`, single-line case.
++ let _ = v.iter().find(|&x| *x < 0).is_some();
++ let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
++ let _ = (0..1).find(|x| *x == 0).is_some();
++ let _ = v.iter().find(|x| **x == 0).is_some();
++ let _ = (4..5).find(|x| *x == 1 || *x == 3 || *x == 5).is_some();
++ let _ = (1..3).find(|x| [1, 2, 3].contains(x)).is_some();
++ let _ = (1..3).find(|x| *x == 0 || [1, 2, 3].contains(x)).is_some();
++ let _ = (1..3).find(|x| [1, 2, 3].contains(x) || *x == 0).is_some();
++ let _ = (1..3)
++ .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1)
++ .is_some();
++
++ // Check `position().is_some()`, single-line case.
++ let _ = v.iter().position(|&x| x < 0).is_some();
++
++ // Check `rposition().is_some()`, single-line case.
++ let _ = v.iter().rposition(|&x| x < 0).is_some();
++
++ let s1 = String::from("hello world");
++ let s2 = String::from("world");
++ // caller of `find()` is a `&`static str`
++ let _ = "hello world".find("world").is_some();
++ let _ = "hello world".find(&s2).is_some();
++ let _ = "hello world".find(&s2[2..]).is_some();
++ // caller of `find()` is a `String`
++ let _ = s1.find("world").is_some();
++ let _ = s1.find(&s2).is_some();
++ let _ = s1.find(&s2[2..]).is_some();
++ // caller of `find()` is slice of `String`
++ let _ = s1[2..].find("world").is_some();
++ let _ = s1[2..].find(&s2).is_some();
++ let _ = s1[2..].find(&s2[2..]).is_some();
++}
++
++#[allow(clippy::clone_on_copy, clippy::map_clone)]
++mod issue7392 {
++ struct Player {
++ hand: Vec<usize>,
++ }
++ fn filter() {
++ let p = Player {
++ hand: vec![1, 2, 3, 4, 5],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|c| filter_hand.iter().find(|cc| c == cc).is_some())
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ struct PlayerTuple {
++ hand: Vec<(usize, char)>,
++ }
++ fn filter_tuple() {
++ let p = PlayerTuple {
++ hand: vec![(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')],
++ };
++ let filter_hand = vec![5];
++ let _ = p
++ .hand
++ .iter()
++ .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_some())
++ .map(|c| c.clone())
++ .collect::<Vec<_>>();
++ }
++
++ fn field_projection() {
++ struct Foo {
++ foo: i32,
++ bar: u32,
++ }
++ let vfoo = vec![Foo { foo: 1, bar: 2 }];
++ let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_some();
++
++ let vfoo = vec![(42, Foo { foo: 1, bar: 2 })];
++ let _ = vfoo
++ .iter()
++ .find(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2)
++ .is_some();
++ }
++
++ fn index_projection() {
++ let vfoo = vec![[0, 1, 2, 3]];
++ let _ = vfoo.iter().find(|a| a[0] == 42).is_some();
++ }
++
++ #[allow(clippy::match_like_matches_macro)]
++ fn slice_projection() {
++ let vfoo = vec![[0, 1, 2, 3, 0, 1, 2, 3]];
++ let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_some();
++ }
++
++ fn please(x: &u32) -> bool {
++ *x == 9
++ }
++
++ fn deref_enough(x: u32) -> bool {
++ x == 78
++ }
++
++ fn arg_no_deref(x: &&u32) -> bool {
++ **x == 78
++ }
++
++ fn more_projections() {
++ let x = 19;
++ let ppx: &u32 = &x;
++ let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_some();
++ let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_some();
++
++ let v = vec![3, 2, 1, 0];
++ let _ = v.iter().find(|x| deref_enough(**x)).is_some();
++ let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_some();
++
++ #[allow(clippy::redundant_closure)]
++ let _ = v.iter().find(|x| arg_no_deref(x)).is_some();
++ #[allow(clippy::redundant_closure)]
++ let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_some();
++ }
++
++ fn field_index_projection() {
++ struct FooDouble {
++ bar: Vec<Vec<i32>>,
++ }
++ struct Foo {
++ bar: Vec<i32>,
++ }
++ struct FooOuter {
++ inner: Foo,
++ inner_double: FooDouble,
++ }
++ let vfoo = vec![FooOuter {
++ inner: Foo { bar: vec![0, 1, 2, 3] },
++ inner_double: FooDouble {
++ bar: vec![vec![0, 1, 2, 3]],
++ },
++ }];
++ let _ = vfoo
++ .iter()
++ .find(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2)
++ .is_some();
++ }
++
++ fn index_field_projection() {
++ struct Foo {
++ bar: i32,
++ }
++ struct FooOuter {
++ inner: Vec<Foo>,
++ }
++ let vfoo = vec![FooOuter {
++ inner: vec![Foo { bar: 0 }],
++ }];
++ let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_some();
++ }
++
++ fn double_deref_index_projection() {
++ let vfoo = vec![&&[0, 1, 2, 3]];
++ let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_some();
++ }
++
++ fn method_call_by_ref() {
++ struct Foo {
++ bar: u32,
++ }
++ impl Foo {
++ pub fn by_ref(&self, x: &u32) -> bool {
++ *x == self.bar
++ }
++ }
++ let vfoo = vec![Foo { bar: 1 }];
++ let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_some();
++ }
++
++ fn ref_bindings() {
++ let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|(&x, y)| x == *y).is_some();
++ let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_some();
++ }
++
++ fn test_string_1(s: &String) -> bool {
++ s.is_empty()
++ }
++
++ fn test_u32_1(s: &u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn test_u32_2(s: u32) -> bool {
++ s.is_power_of_two()
++ }
++
++ fn projection_in_args_test() {
++ // Index projections
++ let lst = &[String::from("Hello"), String::from("world")];
++ let v: Vec<&[String]> = vec![lst];
++ let _ = v.iter().find(|s| s[0].is_empty()).is_some();
++ let _ = v.iter().find(|s| test_string_1(&s[0])).is_some();
++
++ // Field projections
++ struct FieldProjection<'a> {
++ field: &'a u32,
++ }
++ let field = 123456789;
++ let instance = FieldProjection { field: &field };
++ let v = vec![instance];
++ let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_some();
++ let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_some();
++ let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_some();
++ }
++}
--- /dev/null
--- /dev/null
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:10:22
++ |
++LL | let _ = v.iter().find(|&x| *x < 0).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| *x < 0)`
++ |
++ = note: `-D clippy::search-is-some` implied by `-D warnings`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:11:20
++ |
++LL | let _ = (0..1).find(|x| **y == *x).is_some(); // one dereference less
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| **y == x)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:12:20
++ |
++LL | let _ = (0..1).find(|x| *x == 0).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| x == 0)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:13:22
++ |
++LL | let _ = v.iter().find(|x| **x == 0).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| *x == 0)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:14:20
++ |
++LL | let _ = (4..5).find(|x| *x == 1 || *x == 3 || *x == 5).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| x == 1 || x == 3 || x == 5)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:15:20
++ |
++LL | let _ = (1..3).find(|x| [1, 2, 3].contains(x)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| [1, 2, 3].contains(&x))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:16:20
++ |
++LL | let _ = (1..3).find(|x| *x == 0 || [1, 2, 3].contains(x)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| x == 0 || [1, 2, 3].contains(&x))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:17:20
++ |
++LL | let _ = (1..3).find(|x| [1, 2, 3].contains(x) || *x == 0).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| [1, 2, 3].contains(&x) || x == 0)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:19:10
++ |
++LL | .find(|x| [1, 2, 3].contains(x) || *x == 0 || [4, 5, 6].contains(x) || *x == -1)
++ | __________^
++LL | | .is_some();
++ | |__________________^ help: use `any()` instead: `any(|x| [1, 2, 3].contains(&x) || x == 0 || [4, 5, 6].contains(&x) || x == -1)`
++
++error: called `is_some()` after searching an `Iterator` with `position`
++ --> $DIR/search_is_some_fixable_some.rs:23:22
++ |
++LL | let _ = v.iter().position(|&x| x < 0).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|&x| x < 0)`
++
++error: called `is_some()` after searching an `Iterator` with `rposition`
++ --> $DIR/search_is_some_fixable_some.rs:26:22
++ |
++LL | let _ = v.iter().rposition(|&x| x < 0).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|&x| x < 0)`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:31:27
++ |
++LL | let _ = "hello world".find("world").is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains("world")`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:32:27
++ |
++LL | let _ = "hello world".find(&s2).is_some();
++ | ^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2)`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:33:27
++ |
++LL | let _ = "hello world".find(&s2[2..]).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2[2..])`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:35:16
++ |
++LL | let _ = s1.find("world").is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains("world")`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:36:16
++ |
++LL | let _ = s1.find(&s2).is_some();
++ | ^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2)`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:37:16
++ |
++LL | let _ = s1.find(&s2[2..]).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2[2..])`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:39:21
++ |
++LL | let _ = s1[2..].find("world").is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains("world")`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:40:21
++ |
++LL | let _ = s1[2..].find(&s2).is_some();
++ | ^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2)`
++
++error: called `is_some()` after calling `find()` on a string
++ --> $DIR/search_is_some_fixable_some.rs:41:21
++ |
++LL | let _ = s1[2..].find(&s2[2..]).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `contains()` instead: `contains(&s2[2..])`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:57:44
++ |
++LL | .filter(|c| filter_hand.iter().find(|cc| c == cc).is_some())
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|cc| c == &cc)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:73:49
++ |
++LL | .filter(|(c, _)| filter_hand.iter().find(|cc| c == *cc).is_some())
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|cc| c == cc)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:84:29
++ |
++LL | let _ = vfoo.iter().find(|v| v.foo == 1 && v.bar == 2).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|v| v.foo == 1 && v.bar == 2)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:89:14
++ |
++LL | .find(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2)
++ | ______________^
++LL | | .is_some();
++ | |______________________^ help: use `any()` instead: `any(|(i, v)| *i == 42 && v.foo == 1 && v.bar == 2)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:95:29
++ |
++LL | let _ = vfoo.iter().find(|a| a[0] == 42).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|a| a[0] == 42)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:101:29
++ |
++LL | let _ = vfoo.iter().find(|sub| sub[1..4].len() == 3).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|sub| sub[1..4].len() == 3)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:119:30
++ |
++LL | let _ = [ppx].iter().find(|ppp_x: &&&u32| please(**ppp_x)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|ppp_x: &&u32| please(ppp_x))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:120:50
++ |
++LL | let _ = [String::from("Hey hey")].iter().find(|s| s.len() == 2).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|s| s.len() == 2)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:123:26
++ |
++LL | let _ = v.iter().find(|x| deref_enough(**x)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| deref_enough(*x))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:124:26
++ |
++LL | let _ = v.iter().find(|x: &&u32| deref_enough(**x)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x: &u32| deref_enough(*x))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:127:26
++ |
++LL | let _ = v.iter().find(|x| arg_no_deref(x)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| arg_no_deref(&x))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:129:26
++ |
++LL | let _ = v.iter().find(|x: &&u32| arg_no_deref(x)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x: &u32| arg_no_deref(&x))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:151:14
++ |
++LL | .find(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2)
++ | ______________^
++LL | | .is_some();
++ | |______________________^ help: use `any()` instead: `any(|v| v.inner_double.bar[0][0] == 2 && v.inner.bar[0] == 2)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:165:29
++ |
++LL | let _ = vfoo.iter().find(|v| v.inner[0].bar == 2).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|v| v.inner[0].bar == 2)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:170:29
++ |
++LL | let _ = vfoo.iter().find(|x| (**x)[0] == 9).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x| (**x)[0] == 9)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:183:29
++ |
++LL | let _ = vfoo.iter().find(|v| v.by_ref(&v.bar)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|v| v.by_ref(&v.bar))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:187:55
++ |
++LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|(&x, y)| x == *y).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|(&x, y)| x == *y)`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:188:55
++ |
++LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|(&x, y)| x == *y)`
++
++error: writing `&String` instead of `&str` involves a new object where a slice will do
++ --> $DIR/search_is_some_fixable_some.rs:191:25
++ |
++LL | fn test_string_1(s: &String) -> bool {
++ | ^^^^^^^ help: change this to: `&str`
++ |
++ = note: `-D clippy::ptr-arg` implied by `-D warnings`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:207:26
++ |
++LL | let _ = v.iter().find(|s| s[0].is_empty()).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|s| s[0].is_empty())`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:208:26
++ |
++LL | let _ = v.iter().find(|s| test_string_1(&s[0])).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|s| test_string_1(&s[0]))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:217:26
++ |
++LL | let _ = v.iter().find(|fp| fp.field.is_power_of_two()).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|fp| fp.field.is_power_of_two())`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:218:26
++ |
++LL | let _ = v.iter().find(|fp| test_u32_1(fp.field)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|fp| test_u32_1(fp.field))`
++
++error: called `is_some()` after searching an `Iterator` with `find`
++ --> $DIR/search_is_some_fixable_some.rs:219:26
++ |
++LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_some();
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|fp| test_u32_2(*fp.field))`
++
++error: aborting due to 44 previous errors
++
--- /dev/null
+#![warn(clippy::semicolon_if_nothing_returned)]
+#![allow(clippy::redundant_closure)]
+#![feature(label_break_value)]
++#![feature(let_else)]
+
+fn get_unit() {}
+
+// the functions below trigger the lint
+fn main() {
+ println!("Hello")
+}
+
+fn hello() {
+ get_unit()
+}
+
+fn basic101(x: i32) {
+ let y: i32;
+ y = x + 1
+}
+
+#[rustfmt::skip]
+fn closure_error() {
+ let _d = || {
+ hello()
+ };
+}
+
+#[rustfmt::skip]
+fn unsafe_checks_error() {
+ use std::mem::MaybeUninit;
+ use std::ptr;
+
+ let mut s = MaybeUninit::<String>::uninit();
+ let _d = || unsafe {
+ ptr::drop_in_place(s.as_mut_ptr())
+ };
+}
+
+// this is fine
+fn print_sum(a: i32, b: i32) {
+ println!("{}", a + b);
+ assert_eq!(true, false);
+}
+
+fn foo(x: i32) {
+ let y: i32;
+ if x < 1 {
+ y = 4;
+ } else {
+ y = 5;
+ }
+}
+
+fn bar(x: i32) {
+ let y: i32;
+ match x {
+ 1 => y = 4,
+ _ => y = 32,
+ }
+}
+
+fn foobar(x: i32) {
+ let y: i32;
+ 'label: {
+ y = x + 1;
+ }
+}
+
+fn loop_test(x: i32) {
+ let y: i32;
+ for &ext in &["stdout", "stderr", "fixed"] {
+ println!("{}", ext);
+ }
+}
+
+fn closure() {
+ let _d = || hello();
+}
+
+#[rustfmt::skip]
+fn closure_block() {
+ let _d = || { hello() };
+}
+
+unsafe fn some_unsafe_op() {}
+unsafe fn some_other_unsafe_fn() {}
+
+fn do_something() {
+ unsafe { some_unsafe_op() };
+
+ unsafe { some_other_unsafe_fn() };
+}
+
+fn unsafe_checks() {
+ use std::mem::MaybeUninit;
+ use std::ptr;
+
+ let mut s = MaybeUninit::<String>::uninit();
+ let _d = || unsafe { ptr::drop_in_place(s.as_mut_ptr()) };
+}
+
+// Issue #7768
+#[rustfmt::skip]
+fn macro_with_semicolon() {
+ macro_rules! repro {
+ () => {
+ while false {
+ }
+ };
+ }
+ repro!();
+}
++
++fn function_returning_option() -> Option<i32> {
++ Some(1)
++}
++
++// No warning
++fn let_else_stmts() {
++ let Some(x) = function_returning_option() else { return; };
++}
--- /dev/null
- --> $DIR/semicolon_if_nothing_returned.rs:9:5
+error: consider adding a `;` to the last statement for consistent formatting
- --> $DIR/semicolon_if_nothing_returned.rs:13:5
++ --> $DIR/semicolon_if_nothing_returned.rs:10:5
+ |
+LL | println!("Hello")
+ | ^^^^^^^^^^^^^^^^^ help: add a `;` here: `println!("Hello");`
+ |
+ = note: `-D clippy::semicolon-if-nothing-returned` implied by `-D warnings`
+
+error: consider adding a `;` to the last statement for consistent formatting
- --> $DIR/semicolon_if_nothing_returned.rs:18:5
++ --> $DIR/semicolon_if_nothing_returned.rs:14:5
+ |
+LL | get_unit()
+ | ^^^^^^^^^^ help: add a `;` here: `get_unit();`
+
+error: consider adding a `;` to the last statement for consistent formatting
- --> $DIR/semicolon_if_nothing_returned.rs:24:9
++ --> $DIR/semicolon_if_nothing_returned.rs:19:5
+ |
+LL | y = x + 1
+ | ^^^^^^^^^ help: add a `;` here: `y = x + 1;`
+
+error: consider adding a `;` to the last statement for consistent formatting
- --> $DIR/semicolon_if_nothing_returned.rs:35:9
++ --> $DIR/semicolon_if_nothing_returned.rs:25:9
+ |
+LL | hello()
+ | ^^^^^^^ help: add a `;` here: `hello();`
+
+error: consider adding a `;` to the last statement for consistent formatting
++ --> $DIR/semicolon_if_nothing_returned.rs:36:9
+ |
+LL | ptr::drop_in_place(s.as_mut_ptr())
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add a `;` here: `ptr::drop_in_place(s.as_mut_ptr());`
+
+error: aborting due to 5 previous errors
+
--- /dev/null
+#![warn(clippy::shadow_same, clippy::shadow_reuse, clippy::shadow_unrelated)]
+
+fn shadow_same() {
+ let x = 1;
+ let x = x;
+ let mut x = &x;
+ let x = &mut x;
+ let x = *x;
+}
+
+fn shadow_reuse() -> Option<()> {
+ let x = ([[0]], ());
+ let x = x.0;
+ let x = x[0];
+ let [x] = x;
+ let x = Some(x);
+ let x = foo(x);
+ let x = || x;
+ let x = Some(1).map(|_| x)?;
+ let y = 1;
+ let y = match y {
+ 1 => 2,
+ _ => 3,
+ };
+ None
+}
+
+fn shadow_unrelated() {
+ let x = 1;
+ let x = 2;
+}
+
+fn syntax() {
+ fn f(x: u32) {
+ let x = 1;
+ }
+ let x = 1;
+ match Some(1) {
+ Some(1) => {},
+ Some(x) => {
+ let x = 1;
+ },
+ _ => {},
+ }
+ if let Some(x) = Some(1) {}
+ while let Some(x) = Some(1) {}
+ let _ = |[x]: [u32; 1]| {
+ let x = 1;
+ };
+}
+
+fn negative() {
+ match Some(1) {
+ Some(x) if x == 1 => {},
+ Some(x) => {},
+ None => {},
+ }
+ match [None, Some(1)] {
+ [Some(x), None] | [None, Some(x)] => {},
+ _ => {},
+ }
+ if let Some(x) = Some(1) {
+ let y = 1;
+ } else {
+ let x = 1;
+ let y = 1;
+ }
+ let x = 1;
+ #[allow(clippy::shadow_unrelated)]
+ let x = 1;
+}
+
+fn foo<T>(_: T) {}
+
+fn question_mark() -> Option<()> {
+ let val = 1;
+ // `?` expands with a `val` binding
+ None?;
+ None
+}
+
++pub async fn foo1(_a: i32) {}
++
++pub async fn foo2(_a: i32, _b: i64) {
++ let _b = _a;
++}
++
+fn main() {}
--- /dev/null
- error: aborting due to 20 previous errors
+error: `x` is shadowed by itself in `x`
+ --> $DIR/shadow.rs:5:9
+ |
+LL | let x = x;
+ | ^
+ |
+ = note: `-D clippy::shadow-same` implied by `-D warnings`
+note: previous binding is here
+ --> $DIR/shadow.rs:4:9
+ |
+LL | let x = 1;
+ | ^
+
+error: `mut x` is shadowed by itself in `&x`
+ --> $DIR/shadow.rs:6:13
+ |
+LL | let mut x = &x;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:5:9
+ |
+LL | let x = x;
+ | ^
+
+error: `x` is shadowed by itself in `&mut x`
+ --> $DIR/shadow.rs:7:9
+ |
+LL | let x = &mut x;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:6:9
+ |
+LL | let mut x = &x;
+ | ^^^^^
+
+error: `x` is shadowed by itself in `*x`
+ --> $DIR/shadow.rs:8:9
+ |
+LL | let x = *x;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:7:9
+ |
+LL | let x = &mut x;
+ | ^
+
+error: `x` is shadowed
+ --> $DIR/shadow.rs:13:9
+ |
+LL | let x = x.0;
+ | ^
+ |
+ = note: `-D clippy::shadow-reuse` implied by `-D warnings`
+note: previous binding is here
+ --> $DIR/shadow.rs:12:9
+ |
+LL | let x = ([[0]], ());
+ | ^
+
+error: `x` is shadowed
+ --> $DIR/shadow.rs:14:9
+ |
+LL | let x = x[0];
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:13:9
+ |
+LL | let x = x.0;
+ | ^
+
+error: `x` is shadowed
+ --> $DIR/shadow.rs:15:10
+ |
+LL | let [x] = x;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:14:9
+ |
+LL | let x = x[0];
+ | ^
+
+error: `x` is shadowed
+ --> $DIR/shadow.rs:16:9
+ |
+LL | let x = Some(x);
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:15:10
+ |
+LL | let [x] = x;
+ | ^
+
+error: `x` is shadowed
+ --> $DIR/shadow.rs:17:9
+ |
+LL | let x = foo(x);
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:16:9
+ |
+LL | let x = Some(x);
+ | ^
+
+error: `x` is shadowed
+ --> $DIR/shadow.rs:18:9
+ |
+LL | let x = || x;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:17:9
+ |
+LL | let x = foo(x);
+ | ^
+
+error: `x` is shadowed
+ --> $DIR/shadow.rs:19:9
+ |
+LL | let x = Some(1).map(|_| x)?;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:18:9
+ |
+LL | let x = || x;
+ | ^
+
+error: `y` is shadowed
+ --> $DIR/shadow.rs:21:9
+ |
+LL | let y = match y {
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:20:9
+ |
+LL | let y = 1;
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:30:9
+ |
+LL | let x = 2;
+ | ^
+ |
+ = note: `-D clippy::shadow-unrelated` implied by `-D warnings`
+note: previous binding is here
+ --> $DIR/shadow.rs:29:9
+ |
+LL | let x = 1;
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:35:13
+ |
+LL | let x = 1;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:34:10
+ |
+LL | fn f(x: u32) {
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:40:14
+ |
+LL | Some(x) => {
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:37:9
+ |
+LL | let x = 1;
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:41:17
+ |
+LL | let x = 1;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:40:14
+ |
+LL | Some(x) => {
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:45:17
+ |
+LL | if let Some(x) = Some(1) {}
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:37:9
+ |
+LL | let x = 1;
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:46:20
+ |
+LL | while let Some(x) = Some(1) {}
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:37:9
+ |
+LL | let x = 1;
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:47:15
+ |
+LL | let _ = |[x]: [u32; 1]| {
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:37:9
+ |
+LL | let x = 1;
+ | ^
+
+error: `x` shadows a previous, unrelated binding
+ --> $DIR/shadow.rs:48:13
+ |
+LL | let x = 1;
+ | ^
+ |
+note: previous binding is here
+ --> $DIR/shadow.rs:47:15
+ |
+LL | let _ = |[x]: [u32; 1]| {
+ | ^
+
++error: `_b` shadows a previous, unrelated binding
++ --> $DIR/shadow.rs:85:9
++ |
++LL | let _b = _a;
++ | ^^
++ |
++note: previous binding is here
++ --> $DIR/shadow.rs:84:28
++ |
++LL | pub async fn foo2(_a: i32, _b: i64) {
++ | ^^
++
++error: aborting due to 21 previous errors
+
--- /dev/null
- x.replace(";", ",").split(','); // issue #2978
+// run-rustfix
+
+#![allow(unused_must_use)]
+
+use std::collections::HashSet;
+
+fn main() {
+ let x = "foo";
+ x.split('x');
+ x.split("xx");
+ x.split('x');
+
+ let y = "x";
+ x.split(y);
+ x.split('ß');
+ x.split('ℝ');
+ x.split('💣');
+ // Can't use this lint for unicode code points which don't fit in a char
+ x.split("❤️");
++ x.split_inclusive('x');
+ x.contains('x');
+ x.starts_with('x');
+ x.ends_with('x');
+ x.find('x');
+ x.rfind('x');
+ x.rsplit('x');
+ x.split_terminator('x');
+ x.rsplit_terminator('x');
+ x.splitn(2, 'x');
+ x.rsplitn(2, 'x');
++ x.split_once('x');
++ x.rsplit_once('x');
+ x.matches('x');
+ x.rmatches('x');
+ x.match_indices('x');
+ x.rmatch_indices('x');
+ x.trim_start_matches('x');
+ x.trim_end_matches('x');
+ x.strip_prefix('x');
+ x.strip_suffix('x');
++ x.replace('x', "y");
++ x.replacen('x', "y", 3);
+ // Make sure we escape characters correctly.
+ x.split('\n');
+ x.split('\'');
+ x.split('\'');
+
+ let h = HashSet::<String>::new();
+ h.contains("X"); // should not warn
+
++ x.replace(';', ",").split(','); // issue #2978
+ x.starts_with('\x03'); // issue #2996
+
+ // Issue #3204
+ const S: &str = "#";
+ x.find(S);
+
+ // Raw string
+ x.split('a');
+ x.split('a');
+ x.split('a');
+ x.split('\'');
+ x.split('#');
++ // Must escape backslash in raw strings when converting to char #8060
++ x.split('\\');
++ x.split('\\');
+}
--- /dev/null
- x.replace(";", ",").split(","); // issue #2978
+// run-rustfix
+
+#![allow(unused_must_use)]
+
+use std::collections::HashSet;
+
+fn main() {
+ let x = "foo";
+ x.split("x");
+ x.split("xx");
+ x.split('x');
+
+ let y = "x";
+ x.split(y);
+ x.split("ß");
+ x.split("ℝ");
+ x.split("💣");
+ // Can't use this lint for unicode code points which don't fit in a char
+ x.split("❤️");
++ x.split_inclusive("x");
+ x.contains("x");
+ x.starts_with("x");
+ x.ends_with("x");
+ x.find("x");
+ x.rfind("x");
+ x.rsplit("x");
+ x.split_terminator("x");
+ x.rsplit_terminator("x");
+ x.splitn(2, "x");
+ x.rsplitn(2, "x");
++ x.split_once("x");
++ x.rsplit_once("x");
+ x.matches("x");
+ x.rmatches("x");
+ x.match_indices("x");
+ x.rmatch_indices("x");
+ x.trim_start_matches("x");
+ x.trim_end_matches("x");
+ x.strip_prefix("x");
+ x.strip_suffix("x");
++ x.replace("x", "y");
++ x.replacen("x", "y", 3);
+ // Make sure we escape characters correctly.
+ x.split("\n");
+ x.split("'");
+ x.split("\'");
+
+ let h = HashSet::<String>::new();
+ h.contains("X"); // should not warn
+
++ x.replace(';', ",").split(","); // issue #2978
+ x.starts_with("\x03"); // issue #2996
+
+ // Issue #3204
+ const S: &str = "#";
+ x.find(S);
+
+ // Raw string
+ x.split(r"a");
+ x.split(r#"a"#);
+ x.split(r###"a"###);
+ x.split(r###"'"###);
+ x.split(r###"#"###);
++ // Must escape backslash in raw strings when converting to char #8060
++ x.split(r#"\"#);
++ x.split(r"\");
+}
--- /dev/null
- --> $DIR/single_char_pattern.rs:20:16
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:9:13
+ |
+LL | x.split("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+ |
+ = note: `-D clippy::single-char-pattern` implied by `-D warnings`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:15:13
+ |
+LL | x.split("ß");
+ | ^^^ help: try using a `char` instead: `'ß'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:16:13
+ |
+LL | x.split("ℝ");
+ | ^^^ help: try using a `char` instead: `'ℝ'`
+
+error: single-character string constant used as pattern
+ --> $DIR/single_char_pattern.rs:17:13
+ |
+LL | x.split("💣");
+ | ^^^^ help: try using a `char` instead: `'💣'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:21:19
++ --> $DIR/single_char_pattern.rs:20:23
++ |
++LL | x.split_inclusive("x");
++ | ^^^ help: try using a `char` instead: `'x'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:21:16
+ |
+LL | x.contains("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:22:17
++ --> $DIR/single_char_pattern.rs:22:19
+ |
+LL | x.starts_with("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:23:12
++ --> $DIR/single_char_pattern.rs:23:17
+ |
+LL | x.ends_with("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:24:13
++ --> $DIR/single_char_pattern.rs:24:12
+ |
+LL | x.find("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:25:14
++ --> $DIR/single_char_pattern.rs:25:13
+ |
+LL | x.rfind("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:26:24
++ --> $DIR/single_char_pattern.rs:26:14
+ |
+LL | x.rsplit("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:27:25
++ --> $DIR/single_char_pattern.rs:27:24
+ |
+LL | x.split_terminator("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:28:17
++ --> $DIR/single_char_pattern.rs:28:25
+ |
+LL | x.rsplit_terminator("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:29:18
++ --> $DIR/single_char_pattern.rs:29:17
+ |
+LL | x.splitn(2, "x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:30:15
++ --> $DIR/single_char_pattern.rs:30:18
+ |
+LL | x.rsplitn(2, "x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:31:16
++ --> $DIR/single_char_pattern.rs:31:18
++ |
++LL | x.split_once("x");
++ | ^^^ help: try using a `char` instead: `'x'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:32:19
++ |
++LL | x.rsplit_once("x");
++ | ^^^ help: try using a `char` instead: `'x'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:33:15
+ |
+LL | x.matches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:32:21
++ --> $DIR/single_char_pattern.rs:34:16
+ |
+LL | x.rmatches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:33:22
++ --> $DIR/single_char_pattern.rs:35:21
+ |
+LL | x.match_indices("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:34:26
++ --> $DIR/single_char_pattern.rs:36:22
+ |
+LL | x.rmatch_indices("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:35:24
++ --> $DIR/single_char_pattern.rs:37:26
+ |
+LL | x.trim_start_matches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:36:20
++ --> $DIR/single_char_pattern.rs:38:24
+ |
+LL | x.trim_end_matches("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:37:20
++ --> $DIR/single_char_pattern.rs:39:20
+ |
+LL | x.strip_prefix("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:39:13
++ --> $DIR/single_char_pattern.rs:40:20
+ |
+LL | x.strip_suffix("x");
+ | ^^^ help: try using a `char` instead: `'x'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:40:13
++ --> $DIR/single_char_pattern.rs:41:15
++ |
++LL | x.replace("x", "y");
++ | ^^^ help: try using a `char` instead: `'x'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:42:16
++ |
++LL | x.replacen("x", "y", 3);
++ | ^^^ help: try using a `char` instead: `'x'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:44:13
+ |
+LL | x.split("/n");
+ | ^^^^ help: try using a `char` instead: `'/n'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:41:13
++ --> $DIR/single_char_pattern.rs:45:13
+ |
+LL | x.split("'");
+ | ^^^ help: try using a `char` instead: `'/''`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:46:31
++ --> $DIR/single_char_pattern.rs:46:13
+ |
+LL | x.split("/'");
+ | ^^^^ help: try using a `char` instead: `'/''`
+
+error: single-character string constant used as pattern
- LL | x.replace(";", ",").split(","); // issue #2978
++ --> $DIR/single_char_pattern.rs:51:31
+ |
- --> $DIR/single_char_pattern.rs:47:19
++LL | x.replace(';', ",").split(","); // issue #2978
+ | ^^^ help: try using a `char` instead: `','`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:54:13
++ --> $DIR/single_char_pattern.rs:52:19
+ |
+LL | x.starts_with("/x03"); // issue #2996
+ | ^^^^^^ help: try using a `char` instead: `'/x03'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:55:13
++ --> $DIR/single_char_pattern.rs:59:13
+ |
+LL | x.split(r"a");
+ | ^^^^ help: try using a `char` instead: `'a'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:56:13
++ --> $DIR/single_char_pattern.rs:60:13
+ |
+LL | x.split(r#"a"#);
+ | ^^^^^^ help: try using a `char` instead: `'a'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:57:13
++ --> $DIR/single_char_pattern.rs:61:13
+ |
+LL | x.split(r###"a"###);
+ | ^^^^^^^^^^ help: try using a `char` instead: `'a'`
+
+error: single-character string constant used as pattern
- --> $DIR/single_char_pattern.rs:58:13
++ --> $DIR/single_char_pattern.rs:62:13
+ |
+LL | x.split(r###"'"###);
+ | ^^^^^^^^^^ help: try using a `char` instead: `'/''`
+
+error: single-character string constant used as pattern
- error: aborting due to 32 previous errors
++ --> $DIR/single_char_pattern.rs:63:13
+ |
+LL | x.split(r###"#"###);
+ | ^^^^^^^^^^ help: try using a `char` instead: `'#'`
+
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:65:13
++ |
++LL | x.split(r#"/"#);
++ | ^^^^^^ help: try using a `char` instead: `'/'`
++
++error: single-character string constant used as pattern
++ --> $DIR/single_char_pattern.rs:66:13
++ |
++LL | x.split(r"/");
++ | ^^^^ help: try using a `char` instead: `'/'`
++
++error: aborting due to 39 previous errors
+
--- /dev/null
--- /dev/null
++// run-rustfix
++
++#![warn(clippy::strlen_on_c_strings)]
++#![allow(dead_code)]
++#![feature(rustc_private)]
++extern crate libc;
++
++#[allow(unused)]
++use libc::strlen;
++use std::ffi::{CStr, CString};
++
++fn main() {
++ // CString
++ let cstring = CString::new("foo").expect("CString::new failed");
++ let _ = cstring.as_bytes().len();
++
++ // CStr
++ let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed");
++ let _ = cstr.to_bytes().len();
++
++ let _ = cstr.to_bytes().len();
++
++ let pcstr: *const &CStr = &cstr;
++ let _ = unsafe { (*pcstr).to_bytes().len() };
++
++ unsafe fn unsafe_identity<T>(x: T) -> T {
++ x
++ }
++ let _ = unsafe { unsafe_identity(cstr).to_bytes().len() };
++ let _ = unsafe { unsafe_identity(cstr) }.to_bytes().len();
++
++ let f: unsafe fn(_) -> _ = unsafe_identity;
++ let _ = unsafe { f(cstr).to_bytes().len() };
++}
--- /dev/null
- let len = unsafe { libc::strlen(cstring.as_ptr()) };
++// run-rustfix
++
+#![warn(clippy::strlen_on_c_strings)]
+#![allow(dead_code)]
+#![feature(rustc_private)]
+extern crate libc;
+
++#[allow(unused)]
++use libc::strlen;
+use std::ffi::{CStr, CString};
+
+fn main() {
+ // CString
+ let cstring = CString::new("foo").expect("CString::new failed");
- let len = unsafe { libc::strlen(cstr.as_ptr()) };
++ let _ = unsafe { libc::strlen(cstring.as_ptr()) };
+
+ // CStr
+ let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed");
++ let _ = unsafe { libc::strlen(cstr.as_ptr()) };
++
++ let _ = unsafe { strlen(cstr.as_ptr()) };
++
++ let pcstr: *const &CStr = &cstr;
++ let _ = unsafe { strlen((*pcstr).as_ptr()) };
++
++ unsafe fn unsafe_identity<T>(x: T) -> T {
++ x
++ }
++ let _ = unsafe { strlen(unsafe_identity(cstr).as_ptr()) };
++ let _ = unsafe { strlen(unsafe { unsafe_identity(cstr) }.as_ptr()) };
++
++ let f: unsafe fn(_) -> _ = unsafe_identity;
++ let _ = unsafe { strlen(f(cstr).as_ptr()) };
+}
--- /dev/null
- --> $DIR/strlen_on_c_strings.rs:11:24
+error: using `libc::strlen` on a `CString` or `CStr` value
- LL | let len = unsafe { libc::strlen(cstring.as_ptr()) };
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/strlen_on_c_strings.rs:15:13
+ |
- help: try this (you might also need to get rid of `unsafe` block in some cases):
++LL | let _ = unsafe { libc::strlen(cstring.as_ptr()) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `cstring.as_bytes().len()`
+ |
+ = note: `-D clippy::strlen-on-c-strings` implied by `-D warnings`
- LL | let len = unsafe { cstring.as_bytes().len() };
- | ~~~~~~~~~~~~~~~~~~~~~~~~
++
++error: using `libc::strlen` on a `CString` or `CStr` value
++ --> $DIR/strlen_on_c_strings.rs:19:13
++ |
++LL | let _ = unsafe { libc::strlen(cstr.as_ptr()) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `cstr.to_bytes().len()`
++
++error: using `libc::strlen` on a `CString` or `CStr` value
++ --> $DIR/strlen_on_c_strings.rs:21:13
++ |
++LL | let _ = unsafe { strlen(cstr.as_ptr()) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `cstr.to_bytes().len()`
++
++error: using `libc::strlen` on a `CString` or `CStr` value
++ --> $DIR/strlen_on_c_strings.rs:24:22
+ |
- --> $DIR/strlen_on_c_strings.rs:15:24
++LL | let _ = unsafe { strlen((*pcstr).as_ptr()) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `(*pcstr).to_bytes().len()`
+
+error: using `libc::strlen` on a `CString` or `CStr` value
- LL | let len = unsafe { libc::strlen(cstr.as_ptr()) };
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ --> $DIR/strlen_on_c_strings.rs:29:22
+ |
- help: try this (you might also need to get rid of `unsafe` block in some cases):
++LL | let _ = unsafe { strlen(unsafe_identity(cstr).as_ptr()) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unsafe_identity(cstr).to_bytes().len()`
++
++error: using `libc::strlen` on a `CString` or `CStr` value
++ --> $DIR/strlen_on_c_strings.rs:30:13
+ |
- LL | let len = unsafe { cstr.to_bytes().len() };
- | ~~~~~~~~~~~~~~~~~~~~~
++LL | let _ = unsafe { strlen(unsafe { unsafe_identity(cstr) }.as_ptr()) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unsafe { unsafe_identity(cstr) }.to_bytes().len()`
++
++error: using `libc::strlen` on a `CString` or `CStr` value
++ --> $DIR/strlen_on_c_strings.rs:33:22
+ |
- error: aborting due to 2 previous errors
++LL | let _ = unsafe { strlen(f(cstr).as_ptr()) };
++ | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `f(cstr).to_bytes().len()`
+
++error: aborting due to 7 previous errors
+
--- /dev/null
+#![warn(clippy::suspicious_splitn)]
++#![allow(clippy::needless_splitn)]
+
+fn main() {
+ let _ = "a,b,c".splitn(3, ',');
+ let _ = [0, 1, 2, 1, 3].splitn(3, |&x| x == 1);
+ let _ = "".splitn(0, ',');
+ let _ = [].splitn(0, |&x: &u32| x == 1);
+
+ let _ = "a,b".splitn(0, ',');
+ let _ = "a,b".rsplitn(0, ',');
+ let _ = "a,b".splitn(1, ',');
+ let _ = [0, 1, 2].splitn(0, |&x| x == 1);
+ let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1);
+ let _ = [0, 1, 2].splitn(1, |&x| x == 1);
+ let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1);
+
+ const X: usize = 0;
+ let _ = "a,b".splitn(X + 1, ',');
+ let _ = "a,b".splitn(X, ',');
+}
--- /dev/null
- --> $DIR/suspicious_splitn.rs:9:13
+error: `splitn` called with `0` splits
- --> $DIR/suspicious_splitn.rs:10:13
++ --> $DIR/suspicious_splitn.rs:10:13
+ |
+LL | let _ = "a,b".splitn(0, ',');
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::suspicious-splitn` implied by `-D warnings`
+ = note: the resulting iterator will always return `None`
+
+error: `rsplitn` called with `0` splits
- --> $DIR/suspicious_splitn.rs:11:13
++ --> $DIR/suspicious_splitn.rs:11:13
+ |
+LL | let _ = "a,b".rsplitn(0, ',');
+ | ^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return `None`
+
+error: `splitn` called with `1` split
- --> $DIR/suspicious_splitn.rs:12:13
++ --> $DIR/suspicious_splitn.rs:12:13
+ |
+LL | let _ = "a,b".splitn(1, ',');
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return the entire string followed by `None`
+
+error: `splitn` called with `0` splits
- --> $DIR/suspicious_splitn.rs:13:13
++ --> $DIR/suspicious_splitn.rs:13:13
+ |
+LL | let _ = [0, 1, 2].splitn(0, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return `None`
+
+error: `splitn_mut` called with `0` splits
- --> $DIR/suspicious_splitn.rs:14:13
++ --> $DIR/suspicious_splitn.rs:14:13
+ |
+LL | let _ = [0, 1, 2].splitn_mut(0, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return `None`
+
+error: `splitn` called with `1` split
- --> $DIR/suspicious_splitn.rs:15:13
++ --> $DIR/suspicious_splitn.rs:15:13
+ |
+LL | let _ = [0, 1, 2].splitn(1, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return the entire slice followed by `None`
+
+error: `rsplitn_mut` called with `1` split
- --> $DIR/suspicious_splitn.rs:18:13
++ --> $DIR/suspicious_splitn.rs:16:13
+ |
+LL | let _ = [0, 1, 2].rsplitn_mut(1, |&x| x == 1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return the entire slice followed by `None`
+
+error: `splitn` called with `1` split
- --> $DIR/suspicious_splitn.rs:19:13
++ --> $DIR/suspicious_splitn.rs:19:13
+ |
+LL | let _ = "a,b".splitn(X + 1, ',');
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return the entire string followed by `None`
+
+error: `splitn` called with `0` splits
++ --> $DIR/suspicious_splitn.rs:20:13
+ |
+LL | let _ = "a,b".splitn(X, ',');
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: the resulting iterator will always return `None`
+
+error: aborting due to 9 previous errors
+
--- /dev/null
+#![warn(clippy::undocumented_unsafe_blocks)]
+
+// Valid comments
+
+fn nested_local() {
+ let _ = {
+ let _ = {
+ // Safety:
+ let _ = unsafe {};
+ };
+ };
+}
+
+fn deep_nest() {
+ let _ = {
+ let _ = {
+ // Safety:
+ let _ = unsafe {};
+
+ // Safety:
+ unsafe {};
+
+ let _ = {
+ let _ = {
+ let _ = {
+ let _ = {
+ let _ = {
+ // Safety:
+ let _ = unsafe {};
+
+ // Safety:
+ unsafe {};
+ };
+ };
+ };
+
+ // Safety:
+ unsafe {};
+ };
+ };
+ };
+
+ // Safety:
+ unsafe {};
+ };
+
+ // Safety:
+ unsafe {};
+}
+
+fn local_tuple_expression() {
+ // Safety:
+ let _ = (42, unsafe {});
+}
+
+fn line_comment() {
+ // Safety:
+ unsafe {}
+}
+
+fn line_comment_newlines() {
+ // Safety:
+
+ unsafe {}
+}
+
+fn line_comment_empty() {
+ // Safety:
+ //
+ //
+ //
+ unsafe {}
+}
+
+fn line_comment_with_extras() {
+ // This is a description
+ // Safety:
+ unsafe {}
+}
+
+fn block_comment() {
+ /* Safety: */
+ unsafe {}
+}
+
+fn block_comment_newlines() {
+ /* Safety: */
+
+ unsafe {}
+}
+
+#[rustfmt::skip]
+fn inline_block_comment() {
+ /* Safety: */unsafe {}
+}
+
+fn block_comment_with_extras() {
+ /* This is a description
+ * Safety:
+ */
+ unsafe {}
+}
+
+fn block_comment_terminator_same_line() {
+ /* This is a description
+ * Safety: */
+ unsafe {}
+}
+
+fn buried_safety() {
+ // Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+ // incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
+ // ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+ // reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+ // occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
+ // laborum. Safety:
+ // Tellus elementum sagittis vitae et leo duis ut diam quam. Sit amet nulla facilisi
+ // morbi tempus iaculis urna. Amet luctus venenatis lectus magna. At quis risus sed vulputate odio
+ // ut. Luctus venenatis lectus magna fringilla urna. Tortor id aliquet lectus proin nibh nisl
+ // condimentum id venenatis. Vulputate dignissim suspendisse in est ante in nibh mauris cursus.
+ unsafe {}
+}
+
+fn safety_with_prepended_text() {
+ // This is a test. Safety:
+ unsafe {}
+}
+
+fn local_line_comment() {
+ // Safety:
+ let _ = unsafe {};
+}
+
+fn local_block_comment() {
+ /* Safety: */
+ let _ = unsafe {};
+}
+
+fn comment_array() {
+ // Safety:
+ let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+}
+
+fn comment_tuple() {
+ // Safety:
+ let _ = (42, unsafe {}, "test", unsafe {});
+}
+
+fn comment_unary() {
+ // Safety:
+ let _ = *unsafe { &42 };
+}
+
+#[allow(clippy::match_single_binding)]
+fn comment_match() {
+ // Safety:
+ let _ = match unsafe {} {
+ _ => {},
+ };
+}
+
+fn comment_addr_of() {
+ // Safety:
+ let _ = &unsafe {};
+}
+
+fn comment_repeat() {
+ // Safety:
+ let _ = [unsafe {}; 5];
+}
+
+fn comment_macro_call() {
+ macro_rules! t {
+ ($b:expr) => {
+ $b
+ };
+ }
+
+ t!(
+ // Safety:
+ unsafe {}
+ );
+}
+
+fn comment_macro_def() {
+ macro_rules! t {
+ () => {
+ // Safety:
+ unsafe {}
+ };
+ }
+
+ t!();
+}
+
+fn non_ascii_comment() {
+ // ॐ᧻໒ Safety: ௵∰
+ unsafe {};
+}
+
+fn local_commented_block() {
+ let _ =
+ // Safety:
+ unsafe {};
+}
+
+fn local_nest() {
+ // Safety:
+ let _ = [(42, unsafe {}, unsafe {}), (52, unsafe {}, unsafe {})];
+}
+
+// Invalid comments
+
+fn no_comment() {
+ unsafe {}
+}
+
+fn no_comment_array() {
+ let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+}
+
+fn no_comment_tuple() {
+ let _ = (42, unsafe {}, "test", unsafe {});
+}
+
+fn no_comment_unary() {
+ let _ = *unsafe { &42 };
+}
+
+#[allow(clippy::match_single_binding)]
+fn no_comment_match() {
+ let _ = match unsafe {} {
+ _ => {},
+ };
+}
+
+fn no_comment_addr_of() {
+ let _ = &unsafe {};
+}
+
+fn no_comment_repeat() {
+ let _ = [unsafe {}; 5];
+}
+
+fn local_no_comment() {
+ let _ = unsafe {};
+}
+
+fn no_comment_macro_call() {
+ macro_rules! t {
+ ($b:expr) => {
+ $b
+ };
+ }
+
+ t!(unsafe {});
+}
+
+fn no_comment_macro_def() {
+ macro_rules! t {
+ () => {
+ unsafe {}
+ };
+ }
+
+ t!();
+}
+
+fn trailing_comment() {
+ unsafe {} // Safety:
+}
+
+fn internal_comment() {
+ unsafe {
+ // Safety:
+ }
+}
+
+fn interference() {
+ // Safety
+
+ let _ = 42;
+
+ unsafe {};
+}
+
++pub fn print_binary_tree() {
++ println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
++}
++
+fn main() {}
--- /dev/null
- error: aborting due to 13 previous errors
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:215:5
+ |
+LL | unsafe {}
+ | ^^^^^^^^^
+ |
+ = note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + unsafe {}
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:219:5
+ |
+LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:223:5
+ |
+LL | let _ = (42, unsafe {}, "test", unsafe {});
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + let _ = (42, unsafe {}, "test", unsafe {});
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:227:5
+ |
+LL | let _ = *unsafe { &42 };
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + let _ = *unsafe { &42 };
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:232:5
+ |
+LL | let _ = match unsafe {} {
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + let _ = match unsafe {} {
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:238:5
+ |
+LL | let _ = &unsafe {};
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + let _ = &unsafe {};
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:242:5
+ |
+LL | let _ = [unsafe {}; 5];
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + let _ = [unsafe {}; 5];
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:246:5
+ |
+LL | let _ = unsafe {};
+ | ^^^^^^^^^^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + let _ = unsafe {};
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:256:8
+ |
+LL | t!(unsafe {});
+ | ^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ t!(// Safety: ...
+LL ~ unsafe {});
+ |
+
+error: unsafe block in macro expansion missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:262:13
+ |
+LL | unsafe {}
+ | ^^^^^^^^^
+...
+LL | t!();
+ | ---- in this macro invocation
+ |
+ = help: consider adding a safety comment in the macro definition
+ = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:270:5
+ |
+LL | unsafe {} // Safety:
+ | ^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL ~ unsafe {} // Safety:
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:274:5
+ |
+LL | unsafe {
+ | ^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL + unsafe {
+ |
+
+error: unsafe block missing a safety comment
+ --> $DIR/undocumented_unsafe_blocks.rs:284:5
+ |
+LL | unsafe {};
+ | ^^^^^^^^^
+ |
+help: consider adding a safety comment
+ |
+LL ~ // Safety: ...
+LL ~ unsafe {};
+ |
+
++error: unsafe block missing a safety comment
++ --> $DIR/undocumented_unsafe_blocks.rs:288:20
++ |
++LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
++ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++ |
++help: consider adding a safety comment
++ |
++LL ~ println!("{}", // Safety: ...
++LL ~ unsafe { String::from_utf8_unchecked(vec![]) });
++ |
++
++error: aborting due to 14 previous errors
+
--- /dev/null
+#[warn(clippy::invisible_characters)]
+fn zero() {
+ print!("Here >< is a ZWS, and another");
+ print!("This\u{200B}is\u{200B}fine");
+ print!("Here >< is a SHY, and another");
+ print!("This\u{ad}is\u{ad}fine");
+ print!("Here >< is a WJ, and another");
+ print!("This\u{2060}is\u{2060}fine");
+}
+
+#[warn(clippy::unicode_not_nfc)]
+fn canon() {
+ print!("̀àh?");
+ print!("a\u{0300}h?"); // also ok
+}
+
+#[warn(clippy::non_ascii_literal)]
+fn uni() {
+ print!("Üben!");
+ print!("\u{DC}ben!"); // this is ok
+}
+
++// issue 8013
++#[warn(clippy::non_ascii_literal)]
++fn single_quote() {
++ const _EMPTY_BLOCK: char = '▱';
++ const _FULL_BLOCK: char = '▰';
++}
++
+fn main() {
+ zero();
+ uni();
+ canon();
++ single_quote();
+}
--- /dev/null
- error: aborting due to 5 previous errors
+error: invisible character detected
+ --> $DIR/unicode.rs:3:12
+ |
+LL | print!("Here >< is a ZWS, and another");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{200B}< is a ZWS, and /u{200B}another"`
+ |
+ = note: `-D clippy::invisible-characters` implied by `-D warnings`
+
+error: invisible character detected
+ --> $DIR/unicode.rs:5:12
+ |
+LL | print!("Here >< is a SHY, and another");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{AD}< is a SHY, and /u{AD}another"`
+
+error: invisible character detected
+ --> $DIR/unicode.rs:7:12
+ |
+LL | print!("Here >< is a WJ, and another");
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider replacing the string with: `"Here >/u{2060}< is a WJ, and /u{2060}another"`
+
+error: non-NFC Unicode sequence detected
+ --> $DIR/unicode.rs:13:12
+ |
+LL | print!("̀àh?");
+ | ^^^^^ help: consider replacing the string with: `"̀àh?"`
+ |
+ = note: `-D clippy::unicode-not-nfc` implied by `-D warnings`
+
+error: literal non-ASCII character detected
+ --> $DIR/unicode.rs:19:12
+ |
+LL | print!("Üben!");
+ | ^^^^^^^ help: consider replacing the string with: `"/u{dc}ben!"`
+ |
+ = note: `-D clippy::non-ascii-literal` implied by `-D warnings`
+
++error: literal non-ASCII character detected
++ --> $DIR/unicode.rs:26:32
++ |
++LL | const _EMPTY_BLOCK: char = '▱';
++ | ^^^ help: consider replacing the string with: `'/u{25b1}'`
++
++error: literal non-ASCII character detected
++ --> $DIR/unicode.rs:27:31
++ |
++LL | const _FULL_BLOCK: char = '▰';
++ | ^^^ help: consider replacing the string with: `'/u{25b0}'`
++
++error: aborting due to 7 previous errors
+
--- /dev/null
+[relabel]
+allow-unauthenticated = [
+ "A-*", "C-*", "E-*", "I-*", "L-*", "P-*", "S-*", "T-*",
+ "good-first-issue"
+]
+
+[assign]
++
++# Allows shortcuts like `@rustbot ready`
++#
++# See https://github.com/rust-lang/triagebot/wiki/Shortcuts
++[shortcut]
--- /dev/null
- <ul class="list-group lint-docs" ng-class="{collapse: true, in: open[lint.id]}">
+<!DOCTYPE html>
+<!--
+Welcome to a Clippy's lint list, at least the source code of it. If you are
+interested in contributing to this website checkout `util/gh-pages/index.html`
+inside the rust-clippy repository.
+
+Otherwise, have a great day =^.^=
+-->
+<html lang="en">
+<head>
+ <meta charset="UTF-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+
+ <title>Clippy Lints</title>
+
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css"/>
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/styles/github.min.css"/>
+
+ <!-- The files are not copied over into the Clippy project since they use the MPL-2.0 License -->
+ <link rel="stylesheet" href="https://rust-lang.github.io/mdBook/css/variables.css"/>
+ <link id="styleHighlight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/highlight.css">
+ <link id="styleNight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/tomorrow-night.css" disabled="true">
+ <link id="styleAyu" rel="stylesheet" href="https://rust-lang.github.io/mdBook/ayu-highlight.css" disabled="true">
+ <style>
+ blockquote { font-size: 1em; }
+ [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; }
+
+ .form-inline .checkbox { margin-right: 0.6em }
+
+ .panel-heading { cursor: pointer; }
+
+ .panel-title { display: flex; flex-wrap: wrap;}
+ .panel-title .label { display: inline-block; }
+
+ .panel-title-name { flex: 1; min-width: 400px;}
+ .panel-title-name span { vertical-align: bottom; }
+
+ .panel .panel-title-name .anchor { display: none; }
+ .panel:hover .panel-title-name .anchor { display: inline;}
+
+ .label {
+ padding-top: 0.3em;
+ padding-bottom: 0.3em;
+ }
+
+ .label-lint-group {
+ min-width: 8em;
+ }
+ .label-lint-level {
+ min-width: 4em;
+ }
+
+ .label-lint-level-allow {
+ background-color: #5cb85c;
+ }
+ .label-lint-level-warn {
+ background-color: #f0ad4e;
+ }
+ .label-lint-level-deny {
+ background-color: #d9534f;
+ }
+ .label-lint-level-none {
+ background-color: #777777;
+ opacity: 0.5;
+ }
+
+ .label-group-deprecated {
+ opacity: 0.5;
+ }
+
+ .label-doc-folding {
+ color: #000;
+ background-color: #fff;
+ border: 1px solid var(--theme-popup-border);
+ }
+ .label-doc-folding:hover {
+ background-color: #e6e6e6;
+ }
+
+ .lint-doc-md > h3 {
+ border-top: 1px solid var(--theme-popup-border);
+ padding: 10px 15px;
+ margin: 0 -15px;
+ font-size: 18px;
+ }
+ .lint-doc-md > h3:first-child {
+ border-top: none;
+ padding-top: 0px;
+ }
+
+ @media (max-width:749px) {
+ .lint-additional-info-container {
+ display: flex;
+ flex-flow: column;
+ }
+ .lint-additional-info-item + .lint-additional-info-item {
+ border-top: 1px solid var(--theme-popup-border);
+ }
+ }
+ @media (min-width:750px) {
+ .lint-additional-info-container {
+ display: flex;
+ flex-flow: row;
+ }
+ .lint-additional-info-item + .lint-additional-info-item {
+ border-left: 1px solid var(--theme-popup-border);
+ }
+ }
+
+ .lint-additional-info-item {
+ display: inline-flex;
+ min-width: 200px;
+ flex-grow: 1;
+ padding: 9px 5px 5px 15px;
+ }
+
+ .label-applicability {
+ background-color: #777777;
+ margin: auto 5px;
+ }
++
++ .label-version {
++ background-color: #777777;
++ margin: auto 5px;
++ font-family: monospace;
++ }
+ </style>
+ <style>
+ /* Expanding the mdBoom theme*/
+ .light {
+ --inline-code-bg: #f6f7f6;
+ }
+ .rust {
+ --inline-code-bg: #f6f7f6;
+ }
+ .coal {
+ --inline-code-bg: #1d1f21;
+ }
+ .navy {
+ --inline-code-bg: #1d1f21;
+ }
+ .ayu {
+ --inline-code-bg: #191f26;
+ }
+
+ /* Applying the mdBook theme */
+ .theme-icon {
+ position: absolute;
+ text-align: center;
+ width: 2em;
+ height: 2em;
+ margin: 0.7em;
+ line-height: 2em;
+ border: solid 1px var(--icons);
+ border-radius: 5px;
+ user-select: none;
+ cursor: pointer;
+ }
+ .theme-icon:hover {
+ background: var(--theme-hover);
+ }
+ .theme-choice {
+ position: absolute;
+ margin-top: calc(2em + 0.7em);
+ margin-left: 0.7em;
+ list-style: none;
+ border: 1px solid var(--theme-popup-border);
+ border-radius: 5px;
+ color: var(--fg);
+ background: var(--theme-popup-bg);
+ padding: 0 0;
+ }
+ .theme-choice > li {
+ padding: 5px 10px;
+ font-size: 0.8em;
+ user-select: none;
+ cursor: pointer;
+ }
+ .theme-choice > li:hover {
+ background: var(--theme-hover);
+ }
+
+ .alert {
+ color: var(--fg);
+ background: var(--theme-hover);
+ border: 1px solid var(--theme-popup-border);
+ }
+ .page-header {
+ border-color: var(--theme-popup-border);
+ }
+ .panel-default > .panel-heading {
+ background: var(--theme-hover);
+ color: var(--fg);
+ border: 1px solid var(--theme-popup-border);
+ }
+ .panel-default > .panel-heading:hover {
+ filter: brightness(90%);
+ }
+ .list-group-item {
+ background: 0%;
+ border: 1px solid var(--theme-popup-border);
+ }
+ .panel, pre, hr {
+ background: var(--bg);
+ border: 1px solid var(--theme-popup-border);
+ }
+
+ #filter-label, #filter-clear {
+ background: var(--searchbar-bg);
+ color: var(--searchbar-fg);
+ border-color: var(--theme-popup-border);
+ filter: brightness(95%);
+ }
+ #filter-label:hover, #filter-clear:hover {
+ filter: brightness(90%);
+ }
+ #filter-input {
+ background: var(--searchbar-bg);
+ color: var(--searchbar-fg);
+ border-color: var(--theme-popup-border);
+ }
+
+ #filter-input::-webkit-input-placeholder,
+ #filter-input::-moz-placeholder {
+ color: var(--searchbar-fg);
+ opacity: 30%;
+ }
+
+ :not(pre) > code {
+ color: var(--inline-code-color);
+ background-color: var(--inline-code-bg);
+ }
+ html {
+ scrollbar-color: var(--scrollbar) var(--bg);
+ }
+ body {
+ background: var(--bg);
+ color: var(--fg);
+ }
+
+ </style>
+</head>
+<body>
+ <div id="theme-icon" class="theme-icon">🖌</div>
+ <ul id="theme-menu" class="theme-choice" style="display: none;">
+ <li id="light">Light</li>
+ <li id="rust">Rust</li>
+ <li id="coal">Coal</li>
+ <li id="navy">Navy</li>
+ <li id="ayu">Ayu</li>
+ </ul>
+
+ <div class="container" ng-app="clippy" ng-controller="lintList">
+ <div class="page-header">
+ <h1>Clippy Lints</h1>
+ </div>
+
+ <noscript>
+ <div class="alert alert-danger" role="alert">
+ Sorry, this site only works with JavaScript! :(
+ </div>
+ </noscript>
+
+ <div ng-cloak>
+
+ <div class="alert alert-info" role="alert" ng-if="loading">
+ Loading…
+ </div>
+ <div class="alert alert-danger" role="alert" ng-if="error">
+ Error loading lints!
+ </div>
+
+ <div class="panel panel-default" ng-show="data">
+ <div class="panel-body row filter-panel">
+ <div class="col-md-6 form-inline">
+ <div class="form-group form-group-lg">
+ <p class="h4">
+ Lint levels
+ <a href="https://doc.rust-lang.org/rustc/lints/levels.html">(?)</a>
+ </p>
+ <div class="checkbox" ng-repeat="(level, enabled) in levels">
+ <label class="text-capitalize">
+ <input type="checkbox" ng-model="levels[level]" />
+ {{level}}
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6 form-inline">
+ <div class="form-group form-group-lg">
+ <p class="h4">
+ Lint groups
+ <a href="https://github.com/rust-lang/rust-clippy/#clippy">(?)</a>
+ </p>
+ <div class="checkbox" ng-repeat="(group, enabled) in groups">
+ <label class="text-capitalize">
+ <input type="checkbox" ng-model="groups[group]" />
+ {{group}}
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="panel-body row">
+ <div class="col-md-12 form-horizontal">
+ <div class="input-group">
+ <label class="input-group-addon" id="filter-label" for="filter-input">Filter:</label>
+ <input type="text" class="form-control" placeholder="Keywords or search string" id="filter-input" ng-model="search" ng-model-options="{debounce: 50}"/>
+ <span class="input-group-btn">
+ <button id="filter-clear" class="btn" type="button" ng-click="search = ''">
+ Clear
+ </button>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- The order of the filters should be from most likely to remove a lint to least likely to improve performance. -->
+ <article class="panel panel-default" id="{{lint.id}}" ng-repeat="lint in data | filter:bySearch | filter:byGroups | filter:byLevels">
+ <header class="panel-heading" ng-click="open[lint.id] = !open[lint.id]">
+ <h2 class="panel-title">
+ <div class="panel-title-name">
+ <span>{{lint.id}}</span>
+ <a href="#{{lint.id}}" class="anchor label label-default" ng-click="open[lint.id] = true; $event.stopPropagation()">¶</a>
+ </div>
+
+ <div class="panel-title-addons">
+ <span class="label label-lint-group label-default label-group-{{lint.group}}">{{lint.group}}</span>
+
+ <span class="label label-lint-level label-lint-level-{{lint.level}}">{{lint.level}}</span>
+
+
+ <span class="label label-doc-folding" ng-show="open[lint.id]">−</span>
+ <span class="label label-doc-folding" ng-hide="open[lint.id]">+</span>
+ </div>
+ </h2>
+ </header>
+
- <!-- TODO xFrednet 2021-05-19: Somehow collect and show the version See rust-clippy#6492 -->
++ <div class="list-group lint-docs" ng-class="{collapse: true, in: open[lint.id]}">
+ <div class="list-group-item lint-doc-md" ng-bind-html="lint.docs | markdown"></div>
+ <div class="lint-additional-info-container">
+ <!-- Applicability -->
+ <div class="lint-additional-info-item">
+ <span> Applicability: </span>
+ <span class="label label-default label-applicability">{{lint.applicability.applicability}}</span>
+ <a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.Applicability.html#variants">(?)</a>
+ </div>
- </ul>
++ <!-- Clippy version -->
++ <div class="lint-additional-info-item">
++ <span>{{lint.group == "deprecated" ? "Deprecated" : "Added"}} in: </span>
++ <span class="label label-default label-version">{{lint.version}}</span>
++ </div>
+ <!-- Open related issues -->
+ <div class="lint-additional-info-item">
+ <a href="https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+{{lint.id}}">Related Issues</a>
+ </div>
+ <!-- Jump to source -->
+ <div class="lint-additional-info-item">
+ <a href="https://github.com/rust-lang/rust-clippy/blob/{{docVersion}}/clippy_lints/{{lint.id_span.path}}#L{{lint.id_span.line}}">View Source</a>
+ </div>
+ </div>
++ </div>
+ </article>
+ </div>
+ </div>
+
+ <a href="https://github.com/rust-lang/rust-clippy">
+ <img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
+ </a>
+
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/7.0.0/markdown-it.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/highlight.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.5.0/languages/rust.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.12/angular.min.js"></script>
+ <script>
+ (function () {
+ var md = window.markdownit({
+ html: true,
+ linkify: true,
+ typographer: true,
+ highlight: function (str, lang) {
+ if (lang && hljs.getLanguage(lang)) {
+ try {
+ return '<pre class="hljs"><code>' +
+ hljs.highlight(lang, str, true).value +
+ '</code></pre>';
+ } catch (__) {}
+ }
+
+ return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
+ }
+ });
+
+ function scrollToLint(lintId) {
+ var target = document.getElementById(lintId);
+ if (!target) {
+ return;
+ }
+ target.scrollIntoView();
+ }
+
+ function scrollToLintByURL($scope) {
+ var removeListener = $scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
+ scrollToLint(window.location.hash.slice(1));
+ removeListener();
+ });
+ }
+
+ function selectGroup($scope, selectedGroup) {
+ var groups = $scope.groups;
+ for (var group in groups) {
+ if (groups.hasOwnProperty(group)) {
+ if (group === selectedGroup) {
+ groups[group] = true;
+ } else {
+ groups[group] = false;
+ }
+ }
+ }
+ }
+
+ angular.module("clippy", [])
+ .filter('markdown', function ($sce) {
+ return function (text) {
+ return $sce.trustAsHtml(
+ md.render(text || '')
+ // Oh deer, what a hack :O
+ .replace('<table', '<table class="table"')
+ );
+ };
+ })
+ .directive('onFinishRender', function ($timeout) {
+ return {
+ restrict: 'A',
+ link: function (scope, element, attr) {
+ if (scope.$last === true) {
+ $timeout(function () {
+ scope.$emit(attr.onFinishRender);
+ });
+ }
+ }
+ };
+ })
+ .controller("lintList", function ($scope, $http, $timeout) {
+ // Level filter
+ var LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true};
+ $scope.levels = LEVEL_FILTERS_DEFAULT;
+ $scope.byLevels = function (lint) {
+ return $scope.levels[lint.level];
+ };
+
+ var GROUPS_FILTER_DEFAULT = {
+ cargo: true,
+ complexity: true,
+ correctness: true,
+ deprecated: false,
+ nursery: true,
+ pedantic: true,
+ perf: true,
+ restriction: true,
+ style: true,
+ suspicious: true,
+ };
+ $scope.groups = GROUPS_FILTER_DEFAULT;
+ $scope.byGroups = function (lint) {
+ return $scope.groups[lint.group];
+ };
+
+ $scope.bySearch = function (lint, index, array) {
+ let searchStr = $scope.search;
+ // It can be `null` I haven't missed this value
+ if (searchStr == null || searchStr.length < 3) {
+ return true;
+ }
+ searchStr = searchStr.toLowerCase();
+
+ // Search by id
+ if (lint.id.indexOf(searchStr.replace("-", "_")) !== -1) {
+ return true;
+ }
+
+ // Search the description
+ // The use of `for`-loops instead of `foreach` enables us to return early
+ let terms = searchStr.split(" ");
+ let docsLowerCase = lint.docs.toLowerCase();
+ for (index = 0; index < terms.length; index++) {
+ // This is more likely and will therefor be checked first
+ if (docsLowerCase.indexOf(terms[index]) !== -1) {
+ continue;
+ }
+
+ if (lint.id.indexOf(terms[index]) !== -1) {
+ continue;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ // Get data
+ $scope.open = {};
+ $scope.loading = true;
+ // This will be used to jump into the source code of the version that this documentation is for.
+ $scope.docVersion = window.location.pathname.split('/')[2] || "master";
+
+ if (window.location.hash.length > 1) {
+ $scope.search = window.location.hash.slice(1);
+ $scope.open[window.location.hash.slice(1)] = true;
+ scrollToLintByURL($scope);
+ }
+
+ $http.get('./lints.json')
+ .success(function (data) {
+ $scope.data = data;
+ $scope.loading = false;
+
+ var selectedGroup = getQueryVariable("sel");
+ if (selectedGroup) {
+ selectGroup($scope, selectedGroup.toLowerCase());
+ }
+
+ scrollToLintByURL($scope);
+ })
+ .error(function (data) {
+ $scope.error = data;
+ $scope.loading = false;
+ });
+
+ window.addEventListener('hashchange', function () {
+ // trigger re-render
+ $timeout(function () {
+ $scope.levels = LEVEL_FILTERS_DEFAULT;
+ $scope.search = window.location.hash.slice(1);
+ $scope.open[window.location.hash.slice(1)] = true;
+
+ scrollToLintByURL($scope);
+ });
+ return true;
+ }, false);
+ });
+ })();
+
+ function getQueryVariable(variable) {
+ var query = window.location.search.substring(1);
+ var vars = query.split('&');
+ for (var i = 0; i < vars.length; i++) {
+ var pair = vars[i].split('=');
+ if (decodeURIComponent(pair[0]) == variable) {
+ return decodeURIComponent(pair[1]);
+ }
+ }
+ }
+
+ function setupListeners() {
+ let themeIcon = document.getElementById("theme-icon");
+ let themeMenu = document.getElementById("theme-menu");
+ themeIcon.addEventListener("click", function(e) {
+ if (themeMenu.style.display == "none") {
+ themeMenu.style.display = "block";
+ } else {
+ themeMenu.style.display = "none";
+ }
+ });
+
+ let children = themeMenu.children;
+ for (let index = 0; index < children.length; index++) {
+ let child = children[index];
+ child.addEventListener("click", function(e) {
+ setTheme(child.id, true);
+ });
+ }
+ }
+
+ setupListeners();
+
+ function setTheme(theme, store) {
+ let enableHighlight = false;
+ let enableNight = false;
+ let enableAyu = false;
+
+ if (theme == "ayu") {
+ enableAyu = true;
+ } else if (theme == "coal" || theme == "navy") {
+ enableNight = true;
+ } else if (theme == "rust") {
+ enableHighlight = true;
+ } else {
+ enableHighlight = true;
+ // this makes sure that an unknown theme request gets set to a known one
+ theme = "light";
+ }
+ document.getElementsByTagName("body")[0].className = theme;
+
+ document.getElementById("styleHighlight").disabled = !enableHighlight;
+ document.getElementById("styleNight").disabled = !enableNight;
+ document.getElementById("styleAyu").disabled = !enableAyu;
+
+ if (store) {
+ try {
+ localStorage.setItem('clippy-lint-list-theme', theme);
+ } catch (e) { }
+ }
+ }
+
+ // loading the theme after the initial load
+ setTheme(localStorage.getItem('clippy-lint-list-theme'), false);
+ </script>
+</body>
+</html>