Merge commit '20ce69b9a6' into clippy-subtree-update
This commit is contained in:
parent
2267234673
commit
47bcee4ee2
163 changed files with 4388 additions and 2487 deletions
4
.github/ISSUE_TEMPLATE/new_lint.yml
vendored
4
.github/ISSUE_TEMPLATE/new_lint.yml
vendored
|
|
@ -1,7 +1,5 @@
|
|||
name: New lint suggestion
|
||||
description: |
|
||||
Suggest a new Clippy lint (currently not accepting new lints)
|
||||
Check out the Clippy book for more information about the feature freeze.
|
||||
description: Suggest a new Clippy lint.
|
||||
labels: ["A-lint"]
|
||||
body:
|
||||
- type: markdown
|
||||
|
|
|
|||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -32,10 +32,6 @@ order to get feedback.
|
|||
|
||||
Delete this line and everything above before opening your PR.
|
||||
|
||||
Note that we are currently not taking in new PRs that add new lints. We are in a
|
||||
feature freeze. Check out the book for more information. If you open a
|
||||
feature-adding pull request, its review will be delayed.
|
||||
|
||||
---
|
||||
|
||||
*Please write a short comment explaining your change (or "none" for internal only changes)*
|
||||
|
|
|
|||
45
.github/workflows/feature_freeze.yml
vendored
45
.github/workflows/feature_freeze.yml
vendored
|
|
@ -1,45 +0,0 @@
|
|||
name: Feature freeze check
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'clippy_lints/src/declared_lints.rs'
|
||||
|
||||
jobs:
|
||||
auto-comment:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
# Do not in any case add code that runs anything coming from the content
|
||||
# of the pull request, as malicious code would be able to access the private
|
||||
# GitHub token.
|
||||
steps:
|
||||
- name: Add freeze warning comment
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
COMMENT=$(echo "**Seems that you are trying to add a new lint!**\n\
|
||||
\n\
|
||||
We are currently in a [feature freeze](https://doc.rust-lang.org/nightly/clippy/development/feature_freeze.html), so we are delaying all lint-adding PRs to September 18 and [focusing on bugfixes](https://github.com/rust-lang/rust-clippy/issues/15086).\n\
|
||||
\n\
|
||||
Thanks a lot for your contribution, and sorry for the inconvenience.\n\
|
||||
\n\
|
||||
With ❤ from the Clippy team.\n\
|
||||
\n\
|
||||
@rustbot note Feature-freeze\n\
|
||||
@rustbot blocked\n\
|
||||
@rustbot label +A-lint"
|
||||
)
|
||||
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "Content-Type: application/vnd.github.raw+json" \
|
||||
-X POST \
|
||||
--data "{\"body\":\"${COMMENT}\"}" \
|
||||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -48,3 +48,6 @@ helper.txt
|
|||
|
||||
# mdbook generated output
|
||||
/book/book
|
||||
|
||||
# Remove jujutsu directory from search tools
|
||||
.jj
|
||||
|
|
|
|||
148
CHANGELOG.md
148
CHANGELOG.md
|
|
@ -6,7 +6,152 @@ document.
|
|||
|
||||
## Unreleased / Beta / In Rust Nightly
|
||||
|
||||
[4ef75291...master](https://github.com/rust-lang/rust-clippy/compare/4ef75291...master)
|
||||
[e9b7045...master](https://github.com/rust-lang/rust-clippy/compare/e9b7045...master)
|
||||
|
||||
## Rust 1.90
|
||||
|
||||
Current stable, released 2025-09-18
|
||||
|
||||
[View all 118 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-06-13T15%3A55%3A04Z..2025-07-25T13%3A24%3A00Z+base%3Amaster)
|
||||
|
||||
Note: This Clippy release does not introduce many new lints and is focused entirely on bug fixes — see
|
||||
[#15086](https://github.com/rust-lang/rust-clippy/issues/15086) for more details.
|
||||
|
||||
## New Lints
|
||||
|
||||
* Added [`manual_is_multiple_of`] to `complexity` [#14292](https://github.com/rust-lang/rust-clippy/pull/14292)
|
||||
* Added [`doc_broken_link`] to `pedantic` [#13696](https://github.com/rust-lang/rust-clippy/pull/13696)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Move [`uninlined_format_args`] to `pedantic` (from `style`, now allow-by-default) [#15287](https://github.com/rust-lang/rust-clippy/pull/15287)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`or_fun_call`] now lints `Option::get_or_insert`, `Result::map_or`, `Option/Result::and` methods
|
||||
[#15071](https://github.com/rust-lang/rust-clippy/pull/15071)
|
||||
[#15073](https://github.com/rust-lang/rust-clippy/pull/15073)
|
||||
[#15074](https://github.com/rust-lang/rust-clippy/pull/15074)
|
||||
* [`incompatible_msrv`] now recognizes types exceeding MSRV
|
||||
[#15296](https://github.com/rust-lang/rust-clippy/pull/15296)
|
||||
* [`incompatible_msrv`] now checks the right MSRV when in a `const` context
|
||||
[#15297](https://github.com/rust-lang/rust-clippy/pull/15297)
|
||||
* [`zero_ptr`] now lints in `const` context as well
|
||||
[#15152](https://github.com/rust-lang/rust-clippy/pull/15152)
|
||||
* [`map_identity`],[`flat_map_identity`] now recognizes `|[x, y]| [x, y]` as an identity function
|
||||
[#15229](https://github.com/rust-lang/rust-clippy/pull/15229)
|
||||
* [`exit`] no longer fails on the main function when using `--test` or `--all-targets` flag
|
||||
[#15222](https://github.com/rust-lang/rust-clippy/pull/15222)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`if_then_some_else_none`] fixed FP when require type coercion
|
||||
[#15267](https://github.com/rust-lang/rust-clippy/pull/15267)
|
||||
* [`module_name_repetitions`] fixed FP on exported macros
|
||||
[#15319](https://github.com/rust-lang/rust-clippy/pull/15319)
|
||||
* [`unused_async`] fixed FP on function with `todo!`
|
||||
[#15308](https://github.com/rust-lang/rust-clippy/pull/15308)
|
||||
* [`useless_attribute`] fixed FP when using `#[expect(redundant_imports)]` and similar lint attributes
|
||||
on `use` statements
|
||||
[#15318](https://github.com/rust-lang/rust-clippy/pull/15318)
|
||||
* [`pattern_type_mismatch`] fixed FP in external macro
|
||||
[#15306](https://github.com/rust-lang/rust-clippy/pull/15306)
|
||||
* [`large_enum_variant`] fixed FP by not suggesting `Box` in `no_std` mode
|
||||
[#15241](https://github.com/rust-lang/rust-clippy/pull/15241)
|
||||
* [`missing_inline_in_public_items`] fixed FP on functions with `extern`
|
||||
[#15313](https://github.com/rust-lang/rust-clippy/pull/15313)
|
||||
* [`needless_range_loop`] fixed FP on array literals
|
||||
[#15314](https://github.com/rust-lang/rust-clippy/pull/15314)
|
||||
* [`ptr_as_ptr`] fixed wrong suggestions with turbo fish
|
||||
[#15289](https://github.com/rust-lang/rust-clippy/pull/15289)
|
||||
* [`range_plus_one`], [`range_minus_one`] fixed FP by restricting lint to cases where it is safe
|
||||
to switch the range type
|
||||
[#14432](https://github.com/rust-lang/rust-clippy/pull/14432)
|
||||
* [`ptr_arg`] fixed FP with underscore binding to `&T` or `&mut T` argument
|
||||
[#15105](https://github.com/rust-lang/rust-clippy/pull/15105)
|
||||
* [`unsafe_derive_deserialize`] fixed FP caused by the standard library's `pin!()` macro
|
||||
[#15137](https://github.com/rust-lang/rust-clippy/pull/15137)
|
||||
* [`unused_trait_names`] fixed FP in macros
|
||||
[#14947](https://github.com/rust-lang/rust-clippy/pull/14947)
|
||||
* [`expect_fun_call`] fixed invalid suggestions
|
||||
[#15122](https://github.com/rust-lang/rust-clippy/pull/15122)
|
||||
* [`manual_is_multiple_of`] fixed various false positives
|
||||
[#15205](https://github.com/rust-lang/rust-clippy/pull/15205)
|
||||
* [`manual_assert`] fixed wrong suggestions for macros
|
||||
[#15264](https://github.com/rust-lang/rust-clippy/pull/15264)
|
||||
* [`expect_used`] fixed false negative when meeting `Option::ok().expect`
|
||||
[#15253](https://github.com/rust-lang/rust-clippy/pull/15253)
|
||||
* [`manual_abs_diff`] fixed wrong suggestions behind refs
|
||||
[#15265](https://github.com/rust-lang/rust-clippy/pull/15265)
|
||||
* [`approx_constant`] fixed FP with overly precise literals and non trivially formatted numerals
|
||||
[#15236](https://github.com/rust-lang/rust-clippy/pull/15236)
|
||||
* [`arithmetic_side_effects`] fixed FP on `NonZeroU*.get() - 1`
|
||||
[#15238](https://github.com/rust-lang/rust-clippy/pull/15238)
|
||||
* [`legacy_numeric_constants`] fixed suggestion when call is inside parentheses
|
||||
[#15191](https://github.com/rust-lang/rust-clippy/pull/15191)
|
||||
* [`manual_is_variant_and`] fixed inverted suggestions that could lead to code with different semantics
|
||||
[#15206](https://github.com/rust-lang/rust-clippy/pull/15206)
|
||||
* [`unnecessary_map_or`] fixed FP by not proposing to replace `map_or` call when types wouldn't be correct
|
||||
[#15181](https://github.com/rust-lang/rust-clippy/pull/15181)
|
||||
* [`return_and_then`] fixed FP in case of a partially used expression
|
||||
[#15115](https://github.com/rust-lang/rust-clippy/pull/15115)
|
||||
* [`manual_let_else`] fixed FP with binding subpattern with unused name
|
||||
[#15118](https://github.com/rust-lang/rust-clippy/pull/15118)
|
||||
* [`suboptimal_flops`] fixed FP with ambiguous float types in mul_add suggestions
|
||||
[#15133](https://github.com/rust-lang/rust-clippy/pull/15133)
|
||||
* [`borrow_as_ptr`] fixed FP by not removing cast to trait object pointer
|
||||
[#15145](https://github.com/rust-lang/rust-clippy/pull/15145)
|
||||
* [`unnecessary_operation`] fixed FP by not removing casts if they are useful to type the expression
|
||||
[#15182](https://github.com/rust-lang/rust-clippy/pull/15182)
|
||||
* [`empty_loop`] fixed FP on intrinsic function declaration
|
||||
[#15201](https://github.com/rust-lang/rust-clippy/pull/15201)
|
||||
* [`doc_nested_refdefs`] fixed FP where task lists are reported as refdefs
|
||||
[#15146](https://github.com/rust-lang/rust-clippy/pull/15146)
|
||||
* [`std_instead_of_core`] fixed FP when not all items come from the new crate
|
||||
[#15165](https://github.com/rust-lang/rust-clippy/pull/15165)
|
||||
* [`swap_with_temporary`] fixed FP leading to different semantics being suggested
|
||||
[#15172](https://github.com/rust-lang/rust-clippy/pull/15172)
|
||||
* [`cast_possible_truncation`] fixed FP by not giving suggestions inside const context
|
||||
[#15164](https://github.com/rust-lang/rust-clippy/pull/15164)
|
||||
* [`missing_panics_doc`] fixed FP by allowing unwrap() and expect()s in const-only contexts
|
||||
[#15170](https://github.com/rust-lang/rust-clippy/pull/15170)
|
||||
* [`disallowed_script_idents`] fixed FP on identifiers with `_`
|
||||
[#15123](https://github.com/rust-lang/rust-clippy/pull/15123)
|
||||
* [`wildcard_enum_match_arm`] fixed wrong suggestions with raw identifiers
|
||||
[#15093](https://github.com/rust-lang/rust-clippy/pull/15093)
|
||||
* [`borrow_deref_ref`] fixed FP by not proposing replacing a reborrow when the reborrow is itself mutably borrowed
|
||||
[#14967](https://github.com/rust-lang/rust-clippy/pull/14967)
|
||||
* [`manual_ok_err`] fixed wrong suggestions with references
|
||||
[#15053](https://github.com/rust-lang/rust-clippy/pull/15053)
|
||||
* [`identity_op`] fixed FP when encountering `Default::default()`
|
||||
[#15028](https://github.com/rust-lang/rust-clippy/pull/15028)
|
||||
* [`exhaustive_structs`] fixed FP on structs with default valued field
|
||||
[#15022](https://github.com/rust-lang/rust-clippy/pull/15022)
|
||||
* [`collapsible_else_if`] fixed FP on conditionally compiled stmt
|
||||
[#14906](https://github.com/rust-lang/rust-clippy/pull/14906)
|
||||
* [`missing_const_for_fn`] fixed FP by checking MSRV before emitting lint on function containing
|
||||
non-`Sized` trait bounds
|
||||
[#15080](https://github.com/rust-lang/rust-clippy/pull/15080)
|
||||
* [`question_mark`] fixed FP when else branch of let-else contains `#[cfg]`
|
||||
[#15082](https://github.com/rust-lang/rust-clippy/pull/15082)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`single_match`] fixed ICE with deref patterns and string literals
|
||||
[#15124](https://github.com/rust-lang/rust-clippy/pull/15124)
|
||||
* [`needless_doctest_main`] fixed panic when doctest is invalid
|
||||
[#15052](https://github.com/rust-lang/rust-clippy/pull/15052)
|
||||
* [`zero_sized_map_values`] do not attempt to compute size of a type with escaping lifetimes
|
||||
[#15434](https://github.com/rust-lang/rust-clippy/pull/15434)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`manual_is_variant_and`] improved documentation to include equality comparison patterns
|
||||
[#15239](https://github.com/rust-lang/rust-clippy/pull/15239)
|
||||
* [`uninlined_format_args`] improved documentation with example of how to fix a `{:?}` parameter
|
||||
[#15228](https://github.com/rust-lang/rust-clippy/pull/15228)
|
||||
* [`undocumented_unsafe_blocks`] improved documentation wording
|
||||
[#15213](https://github.com/rust-lang/rust-clippy/pull/15213)
|
||||
|
||||
## Rust 1.89
|
||||
|
||||
|
|
@ -6408,6 +6553,7 @@ Released 2018-09-13
|
|||
[`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_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards
|
||||
[`redundant_iter_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_iter_cloned
|
||||
[`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals
|
||||
[`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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy"
|
||||
version = "0.1.91"
|
||||
version = "0.1.92"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
# Clippy
|
||||
|
||||
[### IMPORTANT NOTE FOR CONTRIBUTORS ================](development/feature_freeze.md)
|
||||
|
||||
----
|
||||
|
||||
[](https://github.com/rust-lang/rust-clippy#license)
|
||||
|
||||
A collection of lints to catch common mistakes and improve your
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
- [GitLab CI](continuous_integration/gitlab.md)
|
||||
- [Travis CI](continuous_integration/travis.md)
|
||||
- [Development](development/README.md)
|
||||
- [IMPORTANT: FEATURE FREEZE](development/feature_freeze.md)
|
||||
- [Basics](development/basics.md)
|
||||
- [Adding Lints](development/adding_lints.md)
|
||||
- [Defining Lints](development/defining_lints.md)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
# Adding a new lint
|
||||
|
||||
[### IMPORTANT NOTE FOR CONTRIBUTORS ================](feature_freeze.md)
|
||||
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
# IMPORTANT: FEATURE FREEZE
|
||||
|
||||
This is a temporary notice.
|
||||
|
||||
From the 26th of June until the 18th of September we will perform a feature freeze. Only bugfix PRs will be reviewed
|
||||
except already open ones. Every feature-adding PR opened in between those dates will be moved into a
|
||||
milestone to be reviewed separately at another time.
|
||||
|
||||
We do this because of the long backlog of bugs that need to be addressed
|
||||
in order to continue being the state-of-the-art linter that Clippy has become known for being.
|
||||
|
||||
## For contributors
|
||||
|
||||
If you are a contributor or are planning to become one, **please do not open a lint-adding PR**, we have lots of open
|
||||
bugs of all levels of difficulty that you can address instead!
|
||||
|
||||
We currently have about 800 lints, each one posing a maintainability challenge that needs to account to every possible
|
||||
use case of the whole ecosystem. Bugs are natural in every software, but the Clippy team considers that Clippy needs a
|
||||
refinement period.
|
||||
|
||||
If you open a PR at this time, we will not review it but push it into a milestone until the refinement period ends,
|
||||
adding additional load into our reviewing schedules.
|
||||
|
||||
## I want to help, what can I do
|
||||
|
||||
Thanks a lot to everyone who wants to help Clippy become better software in this feature freeze period!
|
||||
If you'd like to help, making a bugfix, making sure that it works, and opening a PR is a great step!
|
||||
|
||||
To find things to fix, go to the [tracking issue][tracking_issue], find an issue that you like, go there and claim that
|
||||
issue with `@rustbot claim`.
|
||||
|
||||
As a general metric and always taking into account your skill and knowledge level, you can use this guide:
|
||||
|
||||
- 🟥 [ICEs][search_ice], these are compiler errors that causes Clippy to panic and crash. Usually involves high-level
|
||||
debugging, sometimes interacting directly with the upstream compiler. Difficult to fix but a great challenge that
|
||||
improves a lot developer workflows!
|
||||
|
||||
- 🟧 [Suggestion causes bug][sugg_causes_bug], Clippy suggested code that changed logic in some silent way.
|
||||
Unacceptable, as this may have disastrous consequences. Easier to fix than ICEs
|
||||
|
||||
- 🟨 [Suggestion causes error][sugg_causes_error], Clippy suggested code snippet that caused a compiler error
|
||||
when applied. We need to make sure that Clippy doesn't suggest using a variable twice at the same time or similar
|
||||
easy-to-happen occurrences.
|
||||
|
||||
- 🟩 [False positives][false_positive], a lint should not have fired, the easiest of them all, as this is "just"
|
||||
identifying the root of a false positive and making an exception for those cases.
|
||||
|
||||
Note that false negatives do not have priority unless the case is very clear, as they are a feature-request in a
|
||||
trench coat.
|
||||
|
||||
[search_ice]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc+state%3Aopen+label%3A%22I-ICE%22
|
||||
[sugg_causes_bug]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-bug
|
||||
[sugg_causes_error]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-suggestion-causes-error%20
|
||||
[false_positive]: https://github.com/rust-lang/rust-clippy/issues?q=sort%3Aupdated-desc%20state%3Aopen%20label%3AI-false-positive
|
||||
[tracking_issue]: https://github.com/rust-lang/rust-clippy/issues/15086
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_config"
|
||||
version = "0.1.91"
|
||||
version = "0.1.92"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_lints"
|
||||
version = "0.1.91"
|
||||
version = "0.1.92"
|
||||
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
readme = "README.md"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
|
|||
sym::ambiguous_glob_reexports
|
||||
| sym::dead_code
|
||||
| sym::deprecated
|
||||
| sym::deprecated_in_future
|
||||
| sym::hidden_glob_reexports
|
||||
| sym::unreachable_pub
|
||||
| sym::unused
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Ty, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::IsSuggestable;
|
||||
|
||||
use super::AS_UNDERSCORE;
|
||||
|
||||
|
|
@ -10,15 +10,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ty: &'tc
|
|||
if matches!(ty.kind, TyKind::Infer(())) {
|
||||
span_lint_and_then(cx, AS_UNDERSCORE, expr.span, "using `as _` conversion", |diag| {
|
||||
let ty_resolved = cx.typeck_results().expr_ty(expr);
|
||||
if let ty::Error(_) = ty_resolved.kind() {
|
||||
diag.help("consider giving the type explicitly");
|
||||
} else {
|
||||
if ty_resolved.is_suggestable(cx.tcx, true) {
|
||||
diag.span_suggestion(
|
||||
ty.span,
|
||||
"consider giving the type explicitly",
|
||||
ty_resolved,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
diag.help("consider giving the type explicitly");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
|
|
@ -16,7 +19,14 @@ enum EmitState {
|
|||
LintOnPtrSize(u64),
|
||||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
expr: &Expr<'_>,
|
||||
cast_op: &Expr<'_>,
|
||||
cast_from: Ty<'_>,
|
||||
cast_to: Ty<'_>,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
let (Some(from_nbits), Some(to_nbits)) = (
|
||||
utils::int_ty_to_nbits(cx.tcx, cast_from),
|
||||
utils::int_ty_to_nbits(cx.tcx, cast_to),
|
||||
|
|
@ -85,5 +95,23 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca
|
|||
.note("`usize` and `isize` may be as small as 16 bits on some platforms")
|
||||
.note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types");
|
||||
}
|
||||
|
||||
if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST)
|
||||
&& let Some(cast) = utils::is_signedness_cast(cast_from, cast_to)
|
||||
{
|
||||
let method = match cast {
|
||||
utils::CastTo::Signed => "cast_signed()",
|
||||
utils::CastTo::Unsigned => "cast_unsigned()",
|
||||
};
|
||||
let mut app = Applicability::MaybeIncorrect;
|
||||
let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app);
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
format!("if this is intentional, use `{method}` instead"),
|
||||
format!("{}.{method}", sugg.maybe_paren()),
|
||||
app,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,18 @@ use std::convert::Infallible;
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
|
||||
use clippy_utils::{method_chain_args, sext, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::Symbol;
|
||||
|
||||
use super::CAST_SIGN_LOSS;
|
||||
use super::{CAST_SIGN_LOSS, utils};
|
||||
|
||||
/// A list of methods that can never return a negative value.
|
||||
/// Includes methods that panic rather than returning a negative value.
|
||||
|
|
@ -42,13 +45,33 @@ pub(super) fn check<'cx>(
|
|||
cast_op: &Expr<'_>,
|
||||
cast_from: Ty<'cx>,
|
||||
cast_to: Ty<'_>,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
if should_lint(cx, cast_op, cast_from, cast_to) {
|
||||
span_lint(
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
CAST_SIGN_LOSS,
|
||||
expr.span,
|
||||
format!("casting `{cast_from}` to `{cast_to}` may lose the sign of the value"),
|
||||
|diag| {
|
||||
if msrv.meets(cx, msrvs::INTEGER_SIGN_CAST)
|
||||
&& let Some(cast) = utils::is_signedness_cast(cast_from, cast_to)
|
||||
{
|
||||
let method = match cast {
|
||||
utils::CastTo::Signed => "cast_signed()",
|
||||
utils::CastTo::Unsigned => "cast_unsigned()",
|
||||
};
|
||||
let mut app = Applicability::MaybeIncorrect;
|
||||
let sugg = Sugg::hir_with_context(cx, cast_op, expr.span.ctxt(), "..", &mut app);
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
format!("if this is intentional, use `{method}` instead"),
|
||||
format!("{}.{method}", sugg.maybe_paren()),
|
||||
app,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -890,9 +890,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
if cast_to.is_numeric() {
|
||||
cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span);
|
||||
if cast_from.is_numeric() {
|
||||
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
|
||||
cast_possible_wrap::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
|
||||
cast_precision_loss::check(cx, expr, cast_from, cast_to);
|
||||
cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to);
|
||||
cast_sign_loss::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
|
||||
cast_abs_to_unsigned::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv);
|
||||
cast_nan_to_int::check(cx, expr, cast_from_expr, cast_from, cast_to);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,3 +60,18 @@ pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 {
|
|||
neg_bits.max(pos_bits).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) enum CastTo {
|
||||
Signed,
|
||||
Unsigned,
|
||||
}
|
||||
/// Returns `Some` if the type cast is between 2 integral types that differ
|
||||
/// only in signedness, otherwise `None`. The value of `Some` is which
|
||||
/// signedness is casted to.
|
||||
pub(super) fn is_signedness_cast(cast_from: Ty<'_>, cast_to: Ty<'_>) -> Option<CastTo> {
|
||||
match (cast_from.kind(), cast_to.kind()) {
|
||||
(ty::Int(from), ty::Uint(to)) if from.to_unsigned() == *to => Some(CastTo::Unsigned),
|
||||
(ty::Uint(from), ty::Int(to)) if *from == to.to_unsigned() => Some(CastTo::Signed),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -448,10 +448,12 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::methods::OR_THEN_UNWRAP_INFO,
|
||||
crate::methods::PATH_BUF_PUSH_OVERWRITE_INFO,
|
||||
crate::methods::PATH_ENDS_WITH_EXT_INFO,
|
||||
crate::methods::PTR_OFFSET_WITH_CAST_INFO,
|
||||
crate::methods::RANGE_ZIP_WITH_LEN_INFO,
|
||||
crate::methods::READONLY_WRITE_LOCK_INFO,
|
||||
crate::methods::READ_LINE_WITHOUT_TRIM_INFO,
|
||||
crate::methods::REDUNDANT_AS_STR_INFO,
|
||||
crate::methods::REDUNDANT_ITER_CLONED_INFO,
|
||||
crate::methods::REPEAT_ONCE_INFO,
|
||||
crate::methods::RESULT_FILTER_MAP_INFO,
|
||||
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
|
||||
|
|
@ -503,7 +505,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::min_ident_chars::MIN_IDENT_CHARS_INFO,
|
||||
crate::minmax::MIN_MAX_INFO,
|
||||
crate::misc::SHORT_CIRCUIT_STATEMENT_INFO,
|
||||
crate::misc::TOPLEVEL_REF_ARG_INFO,
|
||||
crate::misc::USED_UNDERSCORE_BINDING_INFO,
|
||||
crate::misc::USED_UNDERSCORE_ITEMS_INFO,
|
||||
crate::misc_early::BUILTIN_TYPE_SHADOW_INFO,
|
||||
|
|
@ -625,7 +626,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::ptr::MUT_FROM_REF_INFO,
|
||||
crate::ptr::PTR_ARG_INFO,
|
||||
crate::ptr::PTR_EQ_INFO,
|
||||
crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO,
|
||||
crate::pub_underscore_fields::PUB_UNDERSCORE_FIELDS_INFO,
|
||||
crate::pub_use::PUB_USE_INFO,
|
||||
crate::question_mark::QUESTION_MARK_INFO,
|
||||
|
|
@ -706,6 +706,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
|
|||
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
|
||||
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
|
||||
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
|
||||
crate::toplevel_ref_arg::TOPLEVEL_REF_ARG_INFO,
|
||||
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
|
||||
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
|
||||
crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop};
|
||||
use clippy_utils::ty::{adjust_derefs_manually_drop, implements_trait, is_manually_drop, peel_and_count_ty_refs};
|
||||
use clippy_utils::{
|
||||
DefinedTy, ExprUseNode, expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local,
|
||||
peel_middle_ty_refs,
|
||||
};
|
||||
use rustc_ast::util::parser::ExprPrecedence;
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
|
|
@ -942,7 +941,7 @@ fn report<'tcx>(
|
|||
let (expr_str, expr_is_macro_call) =
|
||||
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
|
||||
let ty = typeck.expr_ty(expr);
|
||||
let (_, ref_count) = peel_middle_ty_refs(ty);
|
||||
let (_, ref_count, _) = peel_and_count_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.
|
||||
|
|
@ -1045,7 +1044,7 @@ fn report<'tcx>(
|
|||
if let ty::Ref(_, dst, _) = data.adjusted_ty.kind()
|
||||
&& dst.is_slice()
|
||||
{
|
||||
let (src, n_src_refs) = peel_middle_ty_refs(ty);
|
||||
let (src, n_src_refs, _) = peel_and_count_ty_refs(ty);
|
||||
if n_src_refs >= 2 && src.is_array() {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,527 +0,0 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
|
||||
use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, paths};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item};
|
||||
use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{
|
||||
self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast,
|
||||
};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints against manual `PartialEq` implementations for types with a derived `Hash`
|
||||
/// implementation.
|
||||
///
|
||||
/// ### 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 DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
correctness,
|
||||
"deriving `Hash` but implementing `PartialEq` explicitly"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord`
|
||||
/// or `PartialOrd` implementation.
|
||||
///
|
||||
/// ### 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 behavior, these traits should
|
||||
/// agree and the behavior 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 held 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_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for types that derive `PartialEq` and could implement `Eq`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
|
||||
/// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
|
||||
/// in APIs that require `Eq` types. It also allows structs containing `T` to derive
|
||||
/// `Eq` themselves.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[derive(PartialEq)]
|
||||
/// struct Foo {
|
||||
/// i_am_eq: i32,
|
||||
/// i_am_eq_too: Vec<String>,
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[derive(PartialEq, Eq)]
|
||||
/// struct Foo {
|
||||
/// i_am_eq: i32,
|
||||
/// i_am_eq_too: Vec<String>,
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.63.0"]
|
||||
pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
nursery,
|
||||
"deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Derive => [
|
||||
EXPL_IMPL_CLONE_ON_COPY,
|
||||
DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
DERIVE_ORD_XOR_PARTIAL_ORD,
|
||||
UNSAFE_DERIVE_DESERIALIZE,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ
|
||||
]);
|
||||
|
||||
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(of_trait),
|
||||
..
|
||||
}) = item.kind
|
||||
{
|
||||
let trait_ref = &of_trait.trait_ref;
|
||||
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id());
|
||||
|
||||
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);
|
||||
check_partial_eq_without_eq(cx, item.span, trait_ref, ty);
|
||||
} else {
|
||||
check_copy_clone(cx, item, trait_ref, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint.
|
||||
fn check_hash_peq<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
hash_is_automatically_derived: bool,
|
||||
) {
|
||||
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Hash, def_id)
|
||||
{
|
||||
// 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 = cx.tcx.is_automatically_derived(impl_id);
|
||||
|
||||
if !hash_is_automatically_derived || peq_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.instantiate_identity().args.type_at(1) == ty {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
span,
|
||||
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|
||||
|diag| {
|
||||
if let Some(local_def_id) = impl_id.as_local() {
|
||||
let hir_id = cx.tcx.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: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
ord_is_automatically_derived: bool,
|
||||
) {
|
||||
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
|
||||
&& let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait()
|
||||
&& let Some(def_id) = &trait_ref.trait_def_id()
|
||||
&& *def_id == ord_trait_def_id
|
||||
{
|
||||
// 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 = cx.tcx.is_automatically_derived(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.instantiate_identity().args.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.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: &hir::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 Some(copy_id) = cx.tcx.lang_items().copy_trait() else {
|
||||
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.local_trait_impls(copy_id).iter().any(|&id| {
|
||||
matches!(cx.tcx.type_of(id).instantiate_identity().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;
|
||||
}
|
||||
// `#[repr(packed)]` structs with type/const parameters can't derive `Clone`.
|
||||
// https://github.com/rust-lang/rust-clippy/issues/10188
|
||||
if ty_adt.repr().packed()
|
||||
&& ty_subs
|
||||
.iter()
|
||||
.any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// The presence of `unsafe` fields prevents deriving `Clone` automatically
|
||||
if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) {
|
||||
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: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
) {
|
||||
fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
|
||||
let mut visitor = UnsafeVisitor { cx };
|
||||
walk_item(&mut visitor, item).is_break()
|
||||
}
|
||||
|
||||
if let Some(trait_def_id) = trait_ref.trait_def_id()
|
||||
&& paths::SERDE_DESERIALIZE.matches(cx, trait_def_id)
|
||||
&& let ty::Adt(def, _) = ty.kind()
|
||||
&& let Some(local_def_id) = def.did().as_local()
|
||||
&& let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id)
|
||||
&& !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id)
|
||||
&& 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))
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
UNSAFE_DERIVE_DESERIALIZE,
|
||||
adt_hir_id,
|
||||
item.span,
|
||||
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
|
||||
|diag| {
|
||||
diag.help(
|
||||
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct UnsafeVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
||||
type Result = ControlFlow<()>;
|
||||
type NestedFilter = nested_filter::All;
|
||||
|
||||
fn visit_fn(
|
||||
&mut self,
|
||||
kind: FnKind<'tcx>,
|
||||
decl: &'tcx FnDecl<'_>,
|
||||
body_id: BodyId,
|
||||
_: Span,
|
||||
id: LocalDefId,
|
||||
) -> Self::Result {
|
||||
if let Some(header) = kind.header()
|
||||
&& header.is_unsafe()
|
||||
{
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
walk_fn(self, kind, decl, body_id, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
|
||||
if let ExprKind::Block(block, _) = expr.kind
|
||||
&& block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
|
||||
&& block
|
||||
.span
|
||||
.source_callee()
|
||||
.and_then(|expr| expr.macro_def_id)
|
||||
.is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did))
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
||||
walk_expr(self, expr)
|
||||
}
|
||||
|
||||
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.cx.tcx
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
|
||||
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
if let ty::Adt(adt, args) = ty.kind()
|
||||
&& cx.tcx.visibility(adt.did()).is_public()
|
||||
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
|
||||
&& !has_non_exhaustive_attr(cx.tcx, *adt)
|
||||
&& !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id)
|
||||
&& let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
||||
&& let Some(local_def_id) = adt.did().as_local()
|
||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||
&& adt
|
||||
.all_fields()
|
||||
.map(|f| f.ty(cx.tcx, args))
|
||||
.all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[]))
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
cx.tcx.local_def_id_to_hir_id(local_def_id),
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"you are deriving `PartialEq` and can implement `Eq`",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"consider deriving `Eq` as well",
|
||||
"PartialEq, Eq",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool {
|
||||
tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some()
|
||||
}
|
||||
|
||||
/// Creates the `ParamEnv` used for the given type's derived `Eq` impl.
|
||||
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
|
||||
// Initial map from generic index to param def.
|
||||
// Vec<(param_def, needs_eq)>
|
||||
let mut params = tcx
|
||||
.generics_of(did)
|
||||
.own_params
|
||||
.iter()
|
||||
.map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. })))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ty_predicates = tcx.predicates_of(did).predicates;
|
||||
for (p, _) in ty_predicates {
|
||||
if let ClauseKind::Trait(p) = p.kind().skip_binder()
|
||||
&& p.trait_ref.def_id == eq_trait_id
|
||||
&& let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
|
||||
{
|
||||
// Flag types which already have an `Eq` bound.
|
||||
params[self_ty.index as usize].1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain(
|
||||
params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| {
|
||||
ClauseKind::Trait(TraitPredicate {
|
||||
trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]),
|
||||
polarity: ty::PredicatePolarity::Positive,
|
||||
})
|
||||
.upcast(tcx)
|
||||
}),
|
||||
)));
|
||||
ty::TypingEnv {
|
||||
typing_mode: ty::TypingMode::non_body_analysis(),
|
||||
param_env,
|
||||
}
|
||||
}
|
||||
50
clippy_lints/src/derive/derive_ord_xor_partial_ord.rs
Normal file
50
clippy_lints/src/derive/derive_ord_xor_partial_ord.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use super::DERIVE_ORD_XOR_PARTIAL_ORD;
|
||||
|
||||
/// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint.
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
ord_is_automatically_derived: bool,
|
||||
) {
|
||||
if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord)
|
||||
&& let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait()
|
||||
&& let Some(def_id) = &trait_ref.trait_def_id()
|
||||
&& *def_id == ord_trait_def_id
|
||||
{
|
||||
// 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 = cx.tcx.is_automatically_derived(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.instantiate_identity().args.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.local_def_id_to_hir_id(local_def_id);
|
||||
diag.span_note(cx.tcx.hir_span(hir_id), "`PartialOrd` implemented here");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
87
clippy_lints/src/derive/derive_partial_eq_without_eq.rs
Normal file
87
clippy_lints/src/derive/derive_partial_eq_without_eq.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::has_non_exhaustive_attr;
|
||||
use clippy_utils::ty::implements_trait_with_env;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, ClauseKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast};
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use super::DERIVE_PARTIAL_EQ_WITHOUT_EQ;
|
||||
|
||||
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
if let ty::Adt(adt, args) = ty.kind()
|
||||
&& cx.tcx.visibility(adt.did()).is_public()
|
||||
&& let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq)
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::PartialEq, def_id)
|
||||
&& !has_non_exhaustive_attr(cx.tcx, *adt)
|
||||
&& !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id)
|
||||
&& let typing_env = typing_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id)
|
||||
&& let Some(local_def_id) = adt.did().as_local()
|
||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||
&& adt
|
||||
.all_fields()
|
||||
.map(|f| f.ty(cx.tcx, args))
|
||||
.all(|ty| implements_trait_with_env(cx.tcx, typing_env, ty, eq_trait_def_id, None, &[]))
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
cx.tcx.local_def_id_to_hir_id(local_def_id),
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"you are deriving `PartialEq` and can implement `Eq`",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
span.ctxt().outer_expn_data().call_site,
|
||||
"consider deriving `Eq` as well",
|
||||
"PartialEq, Eq",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: DefId) -> bool {
|
||||
tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some()
|
||||
}
|
||||
|
||||
/// Creates the `ParamEnv` used for the given type's derived `Eq` impl.
|
||||
fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> {
|
||||
// Initial map from generic index to param def.
|
||||
// Vec<(param_def, needs_eq)>
|
||||
let mut params = tcx
|
||||
.generics_of(did)
|
||||
.own_params
|
||||
.iter()
|
||||
.map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. })))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ty_predicates = tcx.predicates_of(did).predicates;
|
||||
for (p, _) in ty_predicates {
|
||||
if let ClauseKind::Trait(p) = p.kind().skip_binder()
|
||||
&& p.trait_ref.def_id == eq_trait_id
|
||||
&& let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
|
||||
{
|
||||
// Flag types which already have an `Eq` bound.
|
||||
params[self_ty.index as usize].1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
let param_env = ParamEnv::new(tcx.mk_clauses_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain(
|
||||
params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| {
|
||||
ClauseKind::Trait(TraitPredicate {
|
||||
trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]),
|
||||
polarity: ty::PredicatePolarity::Positive,
|
||||
})
|
||||
.upcast(tcx)
|
||||
}),
|
||||
)));
|
||||
ty::TypingEnv {
|
||||
typing_mode: ty::TypingMode::non_body_analysis(),
|
||||
param_env,
|
||||
}
|
||||
}
|
||||
49
clippy_lints/src/derive/derived_hash_with_manual_eq.rs
Normal file
49
clippy_lints/src/derive/derived_hash_with_manual_eq.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use super::DERIVED_HASH_WITH_MANUAL_EQ;
|
||||
|
||||
/// Implementation of the `DERIVED_HASH_WITH_MANUAL_EQ` lint.
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
trait_ref: &hir::TraitRef<'_>,
|
||||
ty: Ty<'tcx>,
|
||||
hash_is_automatically_derived: bool,
|
||||
) {
|
||||
if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait()
|
||||
&& let Some(def_id) = trait_ref.trait_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::Hash, def_id)
|
||||
{
|
||||
// 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 = cx.tcx.is_automatically_derived(impl_id);
|
||||
|
||||
if !hash_is_automatically_derived || peq_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.instantiate_identity().args.type_at(1) == ty {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
span,
|
||||
"you are deriving `Hash` but have implemented `PartialEq` explicitly",
|
||||
|diag| {
|
||||
if let Some(local_def_id) = impl_id.as_local() {
|
||||
let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id);
|
||||
diag.span_note(cx.tcx.hir_span(hir_id), "`PartialEq` implemented here");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
65
clippy_lints/src/derive/expl_impl_clone_on_copy.rs
Normal file
65
clippy_lints/src/derive/expl_impl_clone_on_copy.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use rustc_hir::{self as hir, Item};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind, Ty};
|
||||
|
||||
use super::EXPL_IMPL_CLONE_ON_COPY;
|
||||
|
||||
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::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 Some(copy_id) = cx.tcx.lang_items().copy_trait() else {
|
||||
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.local_trait_impls(copy_id).iter().any(|&id| {
|
||||
matches!(cx.tcx.type_of(id).instantiate_identity().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;
|
||||
}
|
||||
// `#[repr(packed)]` structs with type/const parameters can't derive `Clone`.
|
||||
// https://github.com/rust-lang/rust-clippy/issues/10188
|
||||
if ty_adt.repr().packed()
|
||||
&& ty_subs
|
||||
.iter()
|
||||
.any(|arg| matches!(arg.kind(), GenericArgKind::Type(_) | GenericArgKind::Const(_)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// The presence of `unsafe` fields prevents deriving `Clone` automatically
|
||||
if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) {
|
||||
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`",
|
||||
);
|
||||
}
|
||||
215
clippy_lints/src/derive/mod.rs
Normal file
215
clippy_lints/src/derive/mod.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
use rustc_hir::{Impl, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
||||
mod derive_ord_xor_partial_ord;
|
||||
mod derive_partial_eq_without_eq;
|
||||
mod derived_hash_with_manual_eq;
|
||||
mod expl_impl_clone_on_copy;
|
||||
mod unsafe_derive_deserialize;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints against manual `PartialEq` implementations for types with a derived `Hash`
|
||||
/// implementation.
|
||||
///
|
||||
/// ### 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 DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
correctness,
|
||||
"deriving `Hash` but implementing `PartialEq` explicitly"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord`
|
||||
/// or `PartialOrd` implementation.
|
||||
///
|
||||
/// ### 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 behavior, these traits should
|
||||
/// agree and the behavior 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 held 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_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for types that derive `PartialEq` and could implement `Eq`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If a type `T` derives `PartialEq` and all of its members implement `Eq`,
|
||||
/// then `T` can always implement `Eq`. Implementing `Eq` allows `T` to be used
|
||||
/// in APIs that require `Eq` types. It also allows structs containing `T` to derive
|
||||
/// `Eq` themselves.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// #[derive(PartialEq)]
|
||||
/// struct Foo {
|
||||
/// i_am_eq: i32,
|
||||
/// i_am_eq_too: Vec<String>,
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// #[derive(PartialEq, Eq)]
|
||||
/// struct Foo {
|
||||
/// i_am_eq: i32,
|
||||
/// i_am_eq_too: Vec<String>,
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.63.0"]
|
||||
pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
|
||||
nursery,
|
||||
"deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Derive => [
|
||||
EXPL_IMPL_CLONE_ON_COPY,
|
||||
DERIVED_HASH_WITH_MANUAL_EQ,
|
||||
DERIVE_ORD_XOR_PARTIAL_ORD,
|
||||
UNSAFE_DERIVE_DESERIALIZE,
|
||||
DERIVE_PARTIAL_EQ_WITHOUT_EQ
|
||||
]);
|
||||
|
||||
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(of_trait),
|
||||
..
|
||||
}) = item.kind
|
||||
{
|
||||
let trait_ref = &of_trait.trait_ref;
|
||||
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
|
||||
let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id());
|
||||
|
||||
derived_hash_with_manual_eq::check(cx, item.span, trait_ref, ty, is_automatically_derived);
|
||||
derive_ord_xor_partial_ord::check(cx, item.span, trait_ref, ty, is_automatically_derived);
|
||||
|
||||
if is_automatically_derived {
|
||||
unsafe_derive_deserialize::check(cx, item, trait_ref, ty);
|
||||
derive_partial_eq_without_eq::check(cx, item.span, trait_ref, ty);
|
||||
} else {
|
||||
expl_impl_clone_on_copy::check(cx, item, trait_ref, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
clippy_lints/src/derive/unsafe_derive_deserialize.rs
Normal file
93
clippy_lints/src/derive/unsafe_derive_deserialize.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::{is_lint_allowed, paths};
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn, walk_item};
|
||||
use rustc_hir::{self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Item, UnsafeSource};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
use super::UNSAFE_DERIVE_DESERIALIZE;
|
||||
|
||||
/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
|
||||
fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
|
||||
let mut visitor = UnsafeVisitor { cx };
|
||||
walk_item(&mut visitor, item).is_break()
|
||||
}
|
||||
|
||||
if let Some(trait_def_id) = trait_ref.trait_def_id()
|
||||
&& paths::SERDE_DESERIALIZE.matches(cx, trait_def_id)
|
||||
&& let ty::Adt(def, _) = ty.kind()
|
||||
&& let Some(local_def_id) = def.did().as_local()
|
||||
&& let adt_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id)
|
||||
&& !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id)
|
||||
&& 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))
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
UNSAFE_DERIVE_DESERIALIZE,
|
||||
adt_hir_id,
|
||||
item.span,
|
||||
"you are deriving `serde::Deserialize` on a type that has methods using `unsafe`",
|
||||
|diag| {
|
||||
diag.help(
|
||||
"consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct UnsafeVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
|
||||
type Result = ControlFlow<()>;
|
||||
type NestedFilter = nested_filter::All;
|
||||
|
||||
fn visit_fn(
|
||||
&mut self,
|
||||
kind: FnKind<'tcx>,
|
||||
decl: &'tcx FnDecl<'_>,
|
||||
body_id: BodyId,
|
||||
_: Span,
|
||||
id: LocalDefId,
|
||||
) -> Self::Result {
|
||||
if let Some(header) = kind.header()
|
||||
&& header.is_unsafe()
|
||||
{
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
walk_fn(self, kind, decl, body_id, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result {
|
||||
if let ExprKind::Block(block, _) = expr.kind
|
||||
&& block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
|
||||
&& block
|
||||
.span
|
||||
.source_callee()
|
||||
.and_then(|expr| expr.macro_def_id)
|
||||
.is_none_or(|did| !self.cx.tcx.is_diagnostic_item(sym::pin_macro, did))
|
||||
{
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
|
||||
walk_expr(self, expr)
|
||||
}
|
||||
|
||||
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
|
||||
self.cx.tcx
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
use clippy_config::Conf;
|
||||
use clippy_config::types::{DisallowedPath, create_disallowed_map};
|
||||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
|
|
@ -8,7 +7,8 @@ use rustc_data_structures::fx::FxHashSet;
|
|||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::DefIdMap;
|
||||
use rustc_hir::{
|
||||
AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, ImplItemImplKind, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty,
|
||||
AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, ImplItemImplKind, Item, ItemKind, OwnerId, Pat, Path, Stmt,
|
||||
TraitItem, Ty,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
|
|
|||
|
|
@ -197,6 +197,18 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
|
|||
// in a type which is `'static`.
|
||||
// For now ignore all callee types which reference a type parameter.
|
||||
&& !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_)))
|
||||
// Rule out `AsyncFn*`s, because while they can be called as `|x| f(x)`,
|
||||
// they can't be passed directly into a place expecting an `Fn*` (#13892)
|
||||
&& let Ok((closure_kind, _)) = cx
|
||||
.tcx
|
||||
.infer_ctxt()
|
||||
.build(cx.typing_mode())
|
||||
.err_ctxt()
|
||||
.type_implements_fn_trait(
|
||||
cx.param_env,
|
||||
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
|
||||
ty::PredicatePolarity::Positive,
|
||||
)
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
|
|
@ -213,19 +225,10 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx
|
|||
// 'cuz currently nothing changes after deleting this check.
|
||||
local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr)
|
||||
}) {
|
||||
match cx
|
||||
.tcx
|
||||
.infer_ctxt()
|
||||
.build(cx.typing_mode())
|
||||
.err_ctxt()
|
||||
.type_implements_fn_trait(
|
||||
cx.param_env,
|
||||
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
|
||||
ty::PredicatePolarity::Positive,
|
||||
) {
|
||||
match closure_kind {
|
||||
// Mutable closure is used after current expr; we cannot consume it.
|
||||
Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"),
|
||||
Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => {
|
||||
ClosureKind::FnMut => snippet = format!("&mut {snippet}"),
|
||||
ClosureKind::Fn if !callee_ty_raw.is_ref() => {
|
||||
snippet = format!("&{snippet}");
|
||||
},
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use rustc_middle::ty;
|
|||
use rustc_span::def_id::LocalDefId;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::type_is_unsafe_function;
|
||||
use clippy_utils::ty::is_unsafe_fn;
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{iter_input_pats, path_to_local};
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ fn check_raw_ptr<'tcx>(
|
|||
let typeck = cx.tcx.typeck_body(body.id());
|
||||
let _: Option<!> = for_each_expr(cx, body.value, |e| {
|
||||
match e.kind {
|
||||
hir::ExprKind::Call(f, args) if type_is_unsafe_function(cx, typeck.expr_ty(f)) => {
|
||||
hir::ExprKind::Call(f, args) if is_unsafe_fn(cx, typeck.expr_ty(f)) => {
|
||||
for arg in args {
|
||||
check_arg(cx, &raw_ptrs, arg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,20 @@
|
|||
use crate::functions::REF_OPTION;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_trait_impl_item;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::ty::option_arg_ty;
|
||||
use clippy_utils::{is_from_proc_macro, is_trait_impl_item};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{FnDecl, HirId};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind, Mutability, Ty};
|
||||
use rustc_hir::{self as hir, FnDecl, HirId};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::{self, Mutability, Ty};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::{Span, sym};
|
||||
|
||||
fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) {
|
||||
if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind()
|
||||
&& is_type_diagnostic_item(cx, *opt_ty, sym::Option)
|
||||
&& let ty::Adt(_, opt_gen_args) = opt_ty.kind()
|
||||
&& let [gen_arg] = opt_gen_args.as_slice()
|
||||
&& let GenericArgKind::Type(gen_ty) = gen_arg.kind()
|
||||
fn check_ty<'a>(cx: &LateContext<'a>, param: &hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) {
|
||||
if !param.span.in_external_macro(cx.sess().source_map())
|
||||
&& let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind()
|
||||
&& let Some(gen_ty) = option_arg_ty(cx, *opt_ty)
|
||||
&& !gen_ty.is_ref()
|
||||
// Need to gen the original spans, so first parsing mid, and hir parsing afterward
|
||||
&& let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind
|
||||
|
|
@ -27,6 +24,7 @@ fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a
|
|||
args: [hir::GenericArg::Type(opt_ty)],
|
||||
..
|
||||
}) = last.args
|
||||
&& !is_from_proc_macro(cx, param)
|
||||
{
|
||||
let lifetime = snippet(cx, lifetime.ident.span, "..");
|
||||
fixes.push((
|
||||
|
|
@ -67,21 +65,24 @@ fn check_fn_sig<'a>(cx: &LateContext<'a>, decl: &FnDecl<'a>, span: Span, sig: ty
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_fn<'a>(
|
||||
cx: &LateContext<'a>,
|
||||
kind: FnKind<'_>,
|
||||
kind: FnKind<'a>,
|
||||
decl: &FnDecl<'a>,
|
||||
span: Span,
|
||||
hir_id: HirId,
|
||||
def_id: LocalDefId,
|
||||
body: &hir::Body<'_>,
|
||||
body: &hir::Body<'a>,
|
||||
avoid_breaking_exported_api: bool,
|
||||
) {
|
||||
if avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
|
||||
return;
|
||||
}
|
||||
if span.in_external_macro(cx.sess().source_map()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let FnKind::Closure = kind {
|
||||
// Compute the span of the closure parameters + return type if set
|
||||
let span = if let hir::FnRetTy::Return(out_ty) = &decl.output {
|
||||
let inputs_output_span = if let hir::FnRetTy::Return(out_ty) = &decl.output {
|
||||
if decl.inputs.is_empty() {
|
||||
out_ty.span
|
||||
} else {
|
||||
|
|
@ -100,9 +101,18 @@ pub(crate) fn check_fn<'a>(
|
|||
};
|
||||
let sig = args.as_closure().sig().skip_binder();
|
||||
|
||||
check_fn_sig(cx, decl, span, sig);
|
||||
if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) {
|
||||
return;
|
||||
}
|
||||
|
||||
check_fn_sig(cx, decl, inputs_output_span, sig);
|
||||
} else if !is_trait_impl_item(cx, hir_id) {
|
||||
let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
|
||||
|
||||
if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) {
|
||||
return;
|
||||
}
|
||||
|
||||
check_fn_sig(cx, decl, span, sig);
|
||||
}
|
||||
}
|
||||
|
|
@ -112,8 +122,10 @@ pub(super) fn check_trait_item<'a>(
|
|||
trait_item: &hir::TraitItem<'a>,
|
||||
avoid_breaking_exported_api: bool,
|
||||
) {
|
||||
if let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind
|
||||
if !trait_item.span.in_external_macro(cx.sess().source_map())
|
||||
&& let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind
|
||||
&& !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(trait_item.owner_id.def_id))
|
||||
&& !is_from_proc_macro(cx, trait_item)
|
||||
{
|
||||
let def_id = trait_item.owner_id.def_id;
|
||||
let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
|
||||
|
|
|
|||
|
|
@ -78,66 +78,65 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
|
|||
if let ty::Alias(ty::Opaque, AliasTy { def_id, args, .. }) = *ret_ty.kind()
|
||||
&& let Some(future_trait) = cx.tcx.lang_items().future_trait()
|
||||
&& let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send)
|
||||
&& let preds = cx.tcx.explicit_item_self_bounds(def_id)
|
||||
// If is a Future
|
||||
&& preds
|
||||
.iter_instantiated_copied(cx.tcx, args)
|
||||
.filter_map(|(p, _)| p.as_trait_clause())
|
||||
.any(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait)
|
||||
{
|
||||
let preds = cx.tcx.explicit_item_self_bounds(def_id);
|
||||
let is_future = preds.iter_instantiated_copied(cx.tcx, args).any(|(p, _)| {
|
||||
p.as_trait_clause()
|
||||
.is_some_and(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait)
|
||||
let span = decl.output.span();
|
||||
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
|
||||
let ocx = ObligationCtxt::new_with_diagnostics(&infcx);
|
||||
let cause = traits::ObligationCause::misc(span, fn_def_id);
|
||||
ocx.register_bound(cause, cx.param_env, ret_ty, send_trait);
|
||||
let send_errors = ocx.select_all_or_error();
|
||||
|
||||
// Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top
|
||||
// level".
|
||||
// For example, allow errors that `T: Send` can't be proven, but reject `Rc<T>: Send` errors,
|
||||
// which is always unconditionally `!Send` for any possible type `T`.
|
||||
//
|
||||
// We also allow associated type projections if the self type is either itself a projection or a
|
||||
// type parameter.
|
||||
// This is to prevent emitting warnings for e.g. holding a `<Fut as Future>::Output` across await
|
||||
// points, where `Fut` is a type parameter.
|
||||
|
||||
let is_send = send_errors.iter().all(|err| {
|
||||
err.obligation
|
||||
.predicate
|
||||
.as_trait_clause()
|
||||
.map(Binder::skip_binder)
|
||||
.is_some_and(|pred| {
|
||||
pred.def_id() == send_trait
|
||||
&& pred.self_ty().has_param()
|
||||
&& TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true)
|
||||
})
|
||||
});
|
||||
if is_future {
|
||||
let span = decl.output.span();
|
||||
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
|
||||
let ocx = ObligationCtxt::new_with_diagnostics(&infcx);
|
||||
let cause = traits::ObligationCause::misc(span, fn_def_id);
|
||||
ocx.register_bound(cause, cx.param_env, ret_ty, send_trait);
|
||||
let send_errors = ocx.select_all_or_error();
|
||||
|
||||
// Allow errors that try to prove `Send` for types that "mention" a generic parameter at the "top
|
||||
// level".
|
||||
// For example, allow errors that `T: Send` can't be proven, but reject `Rc<T>: Send` errors,
|
||||
// which is always unconditionally `!Send` for any possible type `T`.
|
||||
//
|
||||
// We also allow associated type projections if the self type is either itself a projection or a
|
||||
// type parameter.
|
||||
// This is to prevent emitting warnings for e.g. holding a `<Fut as Future>::Output` across await
|
||||
// points, where `Fut` is a type parameter.
|
||||
|
||||
let is_send = send_errors.iter().all(|err| {
|
||||
err.obligation
|
||||
.predicate
|
||||
.as_trait_clause()
|
||||
.map(Binder::skip_binder)
|
||||
.is_some_and(|pred| {
|
||||
pred.def_id() == send_trait
|
||||
&& pred.self_ty().has_param()
|
||||
&& TyParamAtTopLevelVisitor.visit_ty(pred.self_ty()) == ControlFlow::Break(true)
|
||||
})
|
||||
});
|
||||
|
||||
if !is_send {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FUTURE_NOT_SEND,
|
||||
span,
|
||||
"future cannot be sent between threads safely",
|
||||
|db| {
|
||||
for FulfillmentError { obligation, .. } in send_errors {
|
||||
infcx
|
||||
.err_ctxt()
|
||||
.maybe_note_obligation_cause_for_async_await(db, &obligation);
|
||||
if let PredicateKind::Clause(ClauseKind::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(),
|
||||
));
|
||||
}
|
||||
if !is_send {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FUTURE_NOT_SEND,
|
||||
span,
|
||||
"future cannot be sent between threads safely",
|
||||
|db| {
|
||||
for FulfillmentError { obligation, .. } in send_errors {
|
||||
infcx
|
||||
.err_ctxt()
|
||||
.maybe_note_obligation_cause_for_async_await(db, &obligation);
|
||||
if let PredicateKind::Clause(ClauseKind::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(),
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
|
|
@ -9,7 +10,7 @@ use clippy_utils::comparisons;
|
|||
use clippy_utils::comparisons::Rel;
|
||||
use clippy_utils::consts::{ConstEvalCtxt, FullInt};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -69,13 +70,21 @@ fn numeric_cast_precast_bounds(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<
|
|||
|
||||
fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) {
|
||||
if let ExprKind::Cast(cast_val, _) = expr.kind {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (cast_val_snip, _) = snippet_with_context(
|
||||
cx,
|
||||
cast_val.span,
|
||||
expr.span.ctxt(),
|
||||
"the expression",
|
||||
&mut applicability,
|
||||
);
|
||||
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"),
|
||||
cast_val_snip,
|
||||
if always { "true" } else { "false" },
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -371,9 +371,9 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Le
|
|||
|
||||
match *sig.output().kind() {
|
||||
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
|
||||
ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) {
|
||||
Some(sym::Option) => Some(LenOutput::Option(adt.did())),
|
||||
Some(sym::Result) => Some(LenOutput::Result(adt.did())),
|
||||
ty::Adt(adt, subs) => match cx.tcx.get_diagnostic_name(adt.did()) {
|
||||
Some(sym::Option) => subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())),
|
||||
Some(sym::Result) => subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::source::{IntoSpan, SpanRangeExt};
|
||||
use rustc_ast::{Local, TyKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{LetStmt, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -26,14 +26,14 @@ declare_clippy_lint! {
|
|||
}
|
||||
declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped {
|
||||
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
|
||||
if let Some(ty) = local.ty // Ensure that it has a type defined
|
||||
&& let TyKind::Infer(()) = &ty.kind // that type is '_'
|
||||
impl EarlyLintPass for UnderscoreTyped {
|
||||
fn check_local(&mut self, cx: &EarlyContext<'_>, local: &Local) {
|
||||
if let Some(ty) = &local.ty // Ensure that it has a type defined
|
||||
&& let TyKind::Infer = ty.kind // that type is '_'
|
||||
&& local.span.eq_ctxt(ty.span)
|
||||
&& let sm = cx.tcx.sess.source_map()
|
||||
&& let sm = cx.sess().source_map()
|
||||
&& !local.span.in_external_macro(sm)
|
||||
&& !is_from_proc_macro(cx, ty)
|
||||
&& !is_from_proc_macro(cx, &**ty)
|
||||
{
|
||||
let span_to_remove = sm
|
||||
.span_extend_to_prev_char_before(ty.span, ':', true)
|
||||
|
|
|
|||
|
|
@ -302,7 +302,6 @@ mod permissions_set_readonly_false;
|
|||
mod pointers_in_nomem_asm_block;
|
||||
mod precedence;
|
||||
mod ptr;
|
||||
mod ptr_offset_with_cast;
|
||||
mod pub_underscore_fields;
|
||||
mod pub_use;
|
||||
mod question_mark;
|
||||
|
|
@ -360,6 +359,7 @@ mod temporary_assignment;
|
|||
mod tests_outside_test_module;
|
||||
mod to_digit_is_some;
|
||||
mod to_string_trait_impl;
|
||||
mod toplevel_ref_arg;
|
||||
mod trailing_empty_array;
|
||||
mod trait_bounds;
|
||||
mod transmute;
|
||||
|
|
@ -592,7 +592,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_late_pass(|_| Box::new(unwrap::Unwrap));
|
||||
store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf)));
|
||||
store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf)));
|
||||
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(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf)));
|
||||
|
|
@ -744,7 +743,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
|
||||
store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized));
|
||||
store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock));
|
||||
store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
|
||||
store.register_early_pass(|| Box::new(let_with_type_underscore::UnderscoreTyped));
|
||||
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
|
||||
store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf)));
|
||||
|
|
@ -831,5 +830,6 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
|
|||
store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf)));
|
||||
store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom));
|
||||
store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny));
|
||||
store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -745,7 +745,7 @@ fn report_elidable_impl_lifetimes<'tcx>(
|
|||
impl_: &'tcx Impl<'_>,
|
||||
map: &FxIndexMap<LocalDefId, Vec<Usage>>,
|
||||
) {
|
||||
let single_usages = map
|
||||
let (elidable_lts, usages): (Vec<_>, Vec<_>) = map
|
||||
.iter()
|
||||
.filter_map(|(def_id, usages)| {
|
||||
if let [
|
||||
|
|
@ -762,14 +762,12 @@ fn report_elidable_impl_lifetimes<'tcx>(
|
|||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.unzip();
|
||||
|
||||
if single_usages.is_empty() {
|
||||
if elidable_lts.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (elidable_lts, usages): (Vec<_>, Vec<_>) = single_usages.into_iter().unzip();
|
||||
|
||||
report_elidable_lifetimes(cx, impl_.generics, &elidable_lts, &usages, true);
|
||||
}
|
||||
|
||||
|
|
@ -795,9 +793,7 @@ fn report_elidable_lifetimes(
|
|||
// In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
|
||||
// `Node::GenericParam`.
|
||||
.filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident())
|
||||
.map(|ident| ident.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
.format(", ");
|
||||
|
||||
let elidable_usages: Vec<ElidableUsage> = usages
|
||||
.iter()
|
||||
|
|
@ -860,36 +856,89 @@ fn elision_suggestions(
|
|||
.filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut suggestions = if elidable_lts.len() == explicit_params.len() {
|
||||
if !elidable_lts
|
||||
.iter()
|
||||
.all(|lt| explicit_params.iter().any(|param| param.def_id == *lt))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut suggestions = if elidable_lts.is_empty() {
|
||||
vec![]
|
||||
} else if elidable_lts.len() == explicit_params.len() {
|
||||
// if all the params are elided remove the whole generic block
|
||||
//
|
||||
// fn x<'a>() {}
|
||||
// ^^^^
|
||||
vec![(generics.span, String::new())]
|
||||
} else {
|
||||
elidable_lts
|
||||
.iter()
|
||||
.map(|&id| {
|
||||
let pos = explicit_params.iter().position(|param| param.def_id == id)?;
|
||||
let param = explicit_params.get(pos)?;
|
||||
|
||||
let span = if let Some(next) = explicit_params.get(pos + 1) {
|
||||
// fn x<'prev, 'a, 'next>() {}
|
||||
// ^^^^
|
||||
param.span.until(next.span)
|
||||
match &explicit_params[..] {
|
||||
// no params, nothing to elide
|
||||
[] => unreachable!("handled by `elidable_lts.is_empty()`"),
|
||||
[param] => {
|
||||
if elidable_lts.contains(¶m.def_id) {
|
||||
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
|
||||
} else {
|
||||
// `pos` should be at least 1 here, because the param in position 0 would either have a `next`
|
||||
// param or would have taken the `elidable_lts.len() == explicit_params.len()` branch.
|
||||
let prev = explicit_params.get(pos - 1)?;
|
||||
unreachable!("handled by `elidable_lts.is_empty()`")
|
||||
}
|
||||
},
|
||||
[_, _, ..] => {
|
||||
// Given a list like `<'a, 'b, 'c, 'd, ..>`,
|
||||
//
|
||||
// If there is a cluster of elidable lifetimes at the beginning, say `'a` and `'b`, we should
|
||||
// suggest removing them _and_ the trailing comma. The span for that is `a.span.until(c.span)`:
|
||||
// <'a, 'b, 'c, 'd, ..> => <'a, 'b, 'c, 'd, ..>
|
||||
// ^^ ^^ ^^^^^^^^
|
||||
//
|
||||
// And since we know that `'c` isn't elidable--otherwise it would've been in the cluster--we can go
|
||||
// over all the lifetimes after it, and for each elidable one, add a suggestion spanning the
|
||||
// lifetime itself and the comma before, because each individual suggestion is guaranteed to leave
|
||||
// the list valid:
|
||||
// <.., 'c, 'd, 'e, 'f, 'g, ..> => <.., 'c, 'd, 'e, 'f, 'g, ..>
|
||||
// ^^ ^^ ^^ ^^^^ ^^^^^^^^
|
||||
//
|
||||
// In case there is no such starting cluster, we only need to do the second part of the algorithm:
|
||||
// <'a, 'b, 'c, 'd, 'e, 'f, 'g, ..> => <'a, 'b , 'c, 'd, 'e, 'f, 'g, ..>
|
||||
// ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^
|
||||
|
||||
// fn x<'prev, 'a>() {}
|
||||
// ^^^^
|
||||
param.span.with_lo(prev.span.hi())
|
||||
// Split off the starting cluster
|
||||
// TODO: use `slice::split_once` once stabilized (github.com/rust-lang/rust/issues/112811):
|
||||
// ```
|
||||
// let Some(split) = explicit_params.split_once(|param| !elidable_lts.contains(¶m.def_id)) else {
|
||||
// // there were no lifetime param that couldn't be elided
|
||||
// unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
|
||||
// };
|
||||
// match split { /* .. */ }
|
||||
// ```
|
||||
let Some(split_pos) = explicit_params
|
||||
.iter()
|
||||
.position(|param| !elidable_lts.contains(¶m.def_id))
|
||||
else {
|
||||
// there were no lifetime param that couldn't be elided
|
||||
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
|
||||
};
|
||||
let split = explicit_params
|
||||
.split_at_checked(split_pos)
|
||||
.expect("got `split_pos` from `position` on the same Vec");
|
||||
|
||||
Some((span, String::new()))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?
|
||||
match split {
|
||||
([..], []) => unreachable!("handled by `elidable_lts.len() == explicit_params.len()`"),
|
||||
([], [_]) => unreachable!("handled by `explicit_params.len() == 1`"),
|
||||
(cluster, rest @ [rest_first, ..]) => {
|
||||
// the span for the cluster
|
||||
(cluster.first().map(|fw| fw.span.until(rest_first.span)).into_iter())
|
||||
// the span for the remaining lifetimes (calculations independent of the cluster)
|
||||
.chain(
|
||||
rest.array_windows()
|
||||
.filter(|[_, curr]| elidable_lts.contains(&curr.def_id))
|
||||
.map(|[prev, curr]| curr.span.with_lo(prev.span.hi())),
|
||||
)
|
||||
.map(|sp| (sp, String::new()))
|
||||
.collect()
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
suggestions.extend(usages.iter().map(|&usage| {
|
||||
|
|
|
|||
|
|
@ -138,9 +138,9 @@ fn is_ref_iterable<'tcx>(
|
|||
return Some((AdjustKind::None, self_ty));
|
||||
}
|
||||
|
||||
let res_ty = cx
|
||||
.tcx
|
||||
.erase_and_anonymize_regions(EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)));
|
||||
let res_ty = cx.tcx.erase_and_anonymize_regions(
|
||||
EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)),
|
||||
);
|
||||
let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() {
|
||||
Some(mutbl)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@ pub(super) fn check<'tcx>(
|
|||
for_loop: Option<&ForLoop<'_>>,
|
||||
) {
|
||||
match never_loop_block(cx, block, &mut Vec::new(), loop_id) {
|
||||
NeverLoopResult::Diverging { ref break_spans } => {
|
||||
NeverLoopResult::Diverging {
|
||||
ref break_spans,
|
||||
ref never_spans,
|
||||
} => {
|
||||
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
|
||||
if let Some(ForLoop {
|
||||
arg: iterator,
|
||||
|
|
@ -34,12 +37,16 @@ pub(super) fn check<'tcx>(
|
|||
{
|
||||
// If the block contains a break or continue, or if the loop has a label, `MachineApplicable` is not
|
||||
// appropriate.
|
||||
let app = if !contains_any_break_or_continue(block) && label.is_none() {
|
||||
let mut app = if !contains_any_break_or_continue(block) && label.is_none() {
|
||||
Applicability::MachineApplicable
|
||||
} else {
|
||||
Applicability::Unspecified
|
||||
};
|
||||
|
||||
if !never_spans.is_empty() {
|
||||
app = Applicability::HasPlaceholders;
|
||||
}
|
||||
|
||||
let mut suggestions = vec![(
|
||||
for_span.with_hi(iterator.span.hi()),
|
||||
for_to_if_let_sugg(cx, iterator, pat),
|
||||
|
|
@ -51,6 +58,13 @@ pub(super) fn check<'tcx>(
|
|||
suggestions,
|
||||
app,
|
||||
);
|
||||
|
||||
for span in never_spans {
|
||||
diag.span_help(
|
||||
*span,
|
||||
"this code is unreachable. Consider moving the reachable parts out",
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -77,13 +91,16 @@ fn contains_any_break_or_continue(block: &Block<'_>) -> bool {
|
|||
/// The first two bits of information are in this enum, and the last part is in the
|
||||
/// `local_labels` variable, which contains a list of `(block_id, reachable)` pairs ordered by
|
||||
/// scope.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
enum NeverLoopResult {
|
||||
/// A continue may occur for the main loop.
|
||||
MayContinueMainLoop,
|
||||
/// We have not encountered any main loop continue,
|
||||
/// but we are diverging (subsequent control flow is not reachable)
|
||||
Diverging { break_spans: Vec<Span> },
|
||||
Diverging {
|
||||
break_spans: Vec<Span>,
|
||||
never_spans: Vec<Span>,
|
||||
},
|
||||
/// We have not encountered any main loop continue,
|
||||
/// and subsequent control flow is (possibly) reachable
|
||||
Normal,
|
||||
|
|
@ -128,14 +145,18 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult
|
|||
(
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: mut break_spans1,
|
||||
never_spans: mut never_spans1,
|
||||
},
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: mut break_spans2,
|
||||
never_spans: mut never_spans2,
|
||||
},
|
||||
) => {
|
||||
break_spans1.append(&mut break_spans2);
|
||||
never_spans1.append(&mut never_spans2);
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: break_spans1,
|
||||
never_spans: never_spans1,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -207,6 +228,8 @@ fn all_spans_after_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Vec<Span> {
|
|||
}
|
||||
|
||||
return vec![stmt.span];
|
||||
} else if let Node::Block(_) = cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
return vec![expr.span];
|
||||
}
|
||||
|
||||
vec![]
|
||||
|
|
@ -270,10 +293,13 @@ fn never_loop_expr<'tcx>(
|
|||
ExprKind::Match(e, arms, _) => {
|
||||
let e = never_loop_expr(cx, e, local_labels, main_loop_id);
|
||||
combine_seq(e, || {
|
||||
arms.iter()
|
||||
.fold(NeverLoopResult::Diverging { break_spans: vec![] }, |a, b| {
|
||||
combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id))
|
||||
})
|
||||
arms.iter().fold(
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: vec![],
|
||||
never_spans: vec![],
|
||||
},
|
||||
|a, b| combine_branches(a, never_loop_expr(cx, b.body, local_labels, main_loop_id)),
|
||||
)
|
||||
})
|
||||
},
|
||||
ExprKind::Block(b, _) => {
|
||||
|
|
@ -296,6 +322,7 @@ fn never_loop_expr<'tcx>(
|
|||
} else {
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: all_spans_after_expr(cx, expr),
|
||||
never_spans: vec![],
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -306,7 +333,10 @@ fn never_loop_expr<'tcx>(
|
|||
combine_seq(first, || {
|
||||
// checks if break targets a block instead of a loop
|
||||
mark_block_as_reachable(expr, local_labels);
|
||||
NeverLoopResult::Diverging { break_spans: vec![] }
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: vec![],
|
||||
never_spans: vec![],
|
||||
}
|
||||
})
|
||||
},
|
||||
ExprKind::Break(dest, e) => {
|
||||
|
|
@ -322,11 +352,15 @@ fn never_loop_expr<'tcx>(
|
|||
} else {
|
||||
all_spans_after_expr(cx, expr)
|
||||
},
|
||||
never_spans: vec![],
|
||||
}
|
||||
})
|
||||
},
|
||||
ExprKind::Become(e) => combine_seq(never_loop_expr(cx, e, local_labels, main_loop_id), || {
|
||||
NeverLoopResult::Diverging { break_spans: vec![] }
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: vec![],
|
||||
never_spans: vec![],
|
||||
}
|
||||
}),
|
||||
ExprKind::InlineAsm(asm) => combine_seq_many(asm.operands.iter().map(|(o, _)| match o {
|
||||
InlineAsmOperand::In { expr, .. } | InlineAsmOperand::InOut { expr, .. } => {
|
||||
|
|
@ -356,7 +390,10 @@ fn never_loop_expr<'tcx>(
|
|||
};
|
||||
let result = combine_seq(result, || {
|
||||
if cx.typeck_results().expr_ty(expr).is_never() {
|
||||
NeverLoopResult::Diverging { break_spans: vec![] }
|
||||
NeverLoopResult::Diverging {
|
||||
break_spans: vec![],
|
||||
never_spans: all_spans_after_expr(cx, expr),
|
||||
}
|
||||
} else {
|
||||
NeverLoopResult::Normal
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use clippy_utils::higher::If;
|
|||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::HasSession as _;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{eq_expr_value, peel_blocks, peel_middle_ty_refs, span_contains_comment};
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, peel_and_count_ty_refs};
|
||||
use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -107,7 +107,7 @@ impl ManualAbsDiff {
|
|||
|ty| is_type_diagnostic_item(cx, ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF);
|
||||
|
||||
let a_ty = cx.typeck_results().expr_ty(a).peel_refs();
|
||||
let (b_ty, b_n_refs) = peel_middle_ty_refs(cx.typeck_results().expr_ty(b));
|
||||
let (b_ty, b_n_refs, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(b));
|
||||
|
||||
(a_ty == b_ty && (is_int(a_ty) || is_duration(a_ty))).then_some((a_ty, b_n_refs))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs, sym};
|
||||
use clippy_utils::{is_none_pattern, msrvs, peel_hir_expr_refs, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal};
|
||||
|
|
@ -60,8 +60,8 @@ impl LateLintPass<'_> for ManualOptionAsSlice {
|
|||
}
|
||||
match expr.kind {
|
||||
ExprKind::Match(scrutinee, [arm1, arm2], _) => {
|
||||
if is_none_arm(cx, arm2) && check_arms(cx, arm2, arm1)
|
||||
|| is_none_arm(cx, arm1) && check_arms(cx, arm1, arm2)
|
||||
if is_none_pattern(cx, arm2.pat) && check_arms(cx, arm2, arm1)
|
||||
|| is_none_pattern(cx, arm1.pat) && check_arms(cx, arm1, arm2)
|
||||
{
|
||||
check_as_ref(cx, scrutinee, span, self.msrv);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use clippy_config::Conf;
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::peel_and_count_ty_refs;
|
||||
use clippy_utils::{expr_or_init, is_in_const_context, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
|
|
@ -102,7 +103,7 @@ fn simplify_half<'tcx>(
|
|||
&& let ExprKind::MethodCall(method_path, receiver, [], _) = expr1.kind
|
||||
&& method_path.ident.name == sym::len
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let (receiver_ty, refs_count) = clippy_utils::ty::walk_ptrs_ty_depth(receiver_ty)
|
||||
&& let (receiver_ty, refs_count, _) = peel_and_count_ty_refs(receiver_ty)
|
||||
&& let ty::Slice(ty1) = receiver_ty.kind()
|
||||
// expr2 is `size_of::<T2>()`?
|
||||
&& let ExprKind::Call(func, []) = expr2.kind
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{indent_of, reindent_multiline};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{option_arg_ty, peel_mid_ty_refs_is_mutable};
|
||||
use clippy_utils::ty::{option_arg_ty, peel_and_count_ty_refs};
|
||||
use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res, peel_blocks, span_contains_comment};
|
||||
use rustc_ast::{BindingMode, Mutability};
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -135,15 +135,11 @@ fn apply_lint(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, is_ok
|
|||
let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_paren();
|
||||
|
||||
let scrutinee_ty = cx.typeck_results().expr_ty(scrutinee);
|
||||
let (_, n_ref, mutability) = peel_mid_ty_refs_is_mutable(scrutinee_ty);
|
||||
let prefix = if n_ref > 0 {
|
||||
if mutability == Mutability::Mut {
|
||||
".as_mut()"
|
||||
} else {
|
||||
".as_ref()"
|
||||
}
|
||||
} else {
|
||||
""
|
||||
let (_, _, mutability) = peel_and_count_ty_refs(scrutinee_ty);
|
||||
let prefix = match mutability {
|
||||
Some(Mutability::Mut) => ".as_mut()",
|
||||
Some(Mutability::Not) => ".as_ref()",
|
||||
None => "",
|
||||
};
|
||||
|
||||
let sugg = format!("{scrut}{prefix}.{method}()");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::map_unit_fn::OPTION_MAP_UNIT_FN;
|
|||
use crate::matches::MATCH_AS_REF;
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
|
||||
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, is_unsafe_fn, peel_and_count_ty_refs};
|
||||
use clippy_utils::{
|
||||
CaptureKind, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, is_res_lang_ctor,
|
||||
path_res, path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while,
|
||||
|
|
@ -30,8 +30,9 @@ pub(super) fn check_with<'tcx, F>(
|
|||
where
|
||||
F: Fn(&LateContext<'tcx>, &'tcx Pat<'_>, &'tcx Expr<'_>, SyntaxContext) -> Option<SomeExpr<'tcx>>,
|
||||
{
|
||||
let (scrutinee_ty, ty_ref_count, ty_mutability) =
|
||||
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
|
||||
let (scrutinee_ty, ty_ref_count, ty_mutability) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(scrutinee));
|
||||
let ty_mutability = ty_mutability.unwrap_or(Mutability::Mut);
|
||||
|
||||
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
|
||||
{
|
||||
|
|
@ -191,7 +192,7 @@ fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Ex
|
|||
ExprKind::Call(func, [arg])
|
||||
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()) =>
|
||||
&& !is_unsafe_fn(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
|
||||
{
|
||||
Some(func)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::NEEDLESS_MATCH;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, same_type_modulo_regions};
|
||||
use clippy_utils::{
|
||||
SpanlessEq, eq_expr_value, get_parent_expr_for_hir, higher, is_else_clause, is_res_lang_ctor, over, path_res,
|
||||
peel_blocks_with_stmt,
|
||||
|
|
@ -122,7 +122,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>
|
|||
// Compare match_expr ty with local in `let local = match match_expr {..}`
|
||||
Node::LetStmt(local) => {
|
||||
let results = cx.typeck_results();
|
||||
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
return same_type_modulo_regions(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
},
|
||||
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
|
||||
Node::Item(item) => {
|
||||
|
|
@ -133,7 +133,7 @@ fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>
|
|||
.instantiate_identity()
|
||||
.output()
|
||||
.skip_binder();
|
||||
return same_type_and_consts(output, cx.typeck_results().expr_ty(expr));
|
||||
return same_type_modulo_regions(output, cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
},
|
||||
// check the parent expr for this whole block `{ match match_expr {..} }`
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use super::REST_PAT_IN_FULLY_BOUND_STRUCTS;
|
|||
|
||||
pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) {
|
||||
if !pat.span.from_expansion()
|
||||
&& let PatKind::Struct(QPath::Resolved(_, path), fields, Some(_)) = pat.kind
|
||||
&& let PatKind::Struct(QPath::Resolved(_, path), fields, Some(dotdot)) = pat.kind
|
||||
&& let Some(def_id) = path.res.opt_def_id()
|
||||
&& let ty = cx.tcx.type_of(def_id).instantiate_identity()
|
||||
&& let ty::Adt(def, _) = ty.kind()
|
||||
|
|
@ -15,14 +15,18 @@ pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) {
|
|||
&& fields.len() == def.non_enum_variant().fields.len()
|
||||
&& !def.non_enum_variant().is_field_list_non_exhaustive()
|
||||
{
|
||||
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
REST_PAT_IN_FULLY_BOUND_STRUCTS,
|
||||
pat.span,
|
||||
"unnecessary use of `..` pattern in struct binding. All fields were already bound",
|
||||
|diag| {
|
||||
diag.help("consider removing `..` from this binding");
|
||||
diag.span_suggestion_verbose(
|
||||
dotdot,
|
||||
"consider removing `..` from this binding",
|
||||
"",
|
||||
rustc_errors::Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use clippy_utils::source::{
|
||||
SpanRangeExt, expr_block, snippet, snippet_block_with_context, snippet_with_applicability, snippet_with_context,
|
||||
};
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{
|
||||
is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_middle_ty_refs, peel_n_hir_expr_refs,
|
||||
};
|
||||
use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs};
|
||||
use clippy_utils::{is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_arena::DroplessArena;
|
||||
use rustc_errors::{Applicability, Diag};
|
||||
|
|
@ -133,7 +131,7 @@ fn report_single_pattern(
|
|||
|
||||
let (pat, pat_ref_count) = peel_hir_pat_refs(arm.pat);
|
||||
let (msg, sugg) = if let PatKind::Expr(_) = pat.kind
|
||||
&& let (ty, ty_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(ex))
|
||||
&& let (ty, ty_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(ex))
|
||||
&& let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait()
|
||||
&& let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait()
|
||||
&& (ty.is_integral()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{is_diag_item_method, is_diag_trait_item, peel_middle_ty_refs, sym};
|
||||
use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs};
|
||||
use clippy_utils::{is_diag_item_method, is_diag_trait_item, sym};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -14,7 +14,7 @@ pub fn check(cx: &LateContext<'_>, method_name: Symbol, expr: &hir::Expr<'_>, re
|
|||
&& is_clone_like(cx, method_name, method_def_id)
|
||||
&& let return_type = cx.typeck_results().expr_ty(expr)
|
||||
&& let input_type = cx.typeck_results().expr_ty(recv)
|
||||
&& let (input_type, ref_count) = peel_middle_ty_refs(input_type)
|
||||
&& let (input_type, ref_count, _) = peel_and_count_ty_refs(input_type)
|
||||
&& !(ref_count > 0 && is_diag_trait_item(cx, method_def_id, sym::ToOwned))
|
||||
&& let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did()))
|
||||
&& return_type == input_type
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_lang_item, walk_ptrs_ty_depth};
|
||||
use clippy_utils::ty::{is_type_lang_item, peel_and_count_ty_refs};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -24,7 +24,7 @@ pub fn check(
|
|||
&& let Some(args) = cx.typeck_results().node_args_opt(expr.hir_id)
|
||||
&& let arg_ty = cx.typeck_results().expr_ty_adjusted(receiver)
|
||||
&& let self_ty = args.type_at(0)
|
||||
&& let (deref_self_ty, deref_count) = walk_ptrs_ty_depth(self_ty)
|
||||
&& let (deref_self_ty, deref_count, _) = peel_and_count_ty_refs(self_ty)
|
||||
&& deref_count >= 1
|
||||
&& specializes_tostring(cx, deref_self_ty)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ use rustc_middle::mir::{FakeReadCause, Mutability};
|
|||
use rustc_middle::ty::{self, BorrowKind};
|
||||
use rustc_span::{Symbol, sym};
|
||||
|
||||
use super::ITER_OVEREAGER_CLONED;
|
||||
use crate::redundant_clone::REDUNDANT_CLONE;
|
||||
use super::{ITER_OVEREAGER_CLONED, REDUNDANT_ITER_CLONED};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum Op<'a> {
|
||||
|
|
@ -96,7 +95,7 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
|
||||
let (lint, msg, trailing_clone) = match op {
|
||||
Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_CLONE, "unneeded cloning of iterator items", ""),
|
||||
Op::RmCloned | Op::NeedlessMove(_) => (REDUNDANT_ITER_CLONED, "unneeded cloning of iterator items", ""),
|
||||
Op::LaterCloned | Op::FixClosure(_, _) => (
|
||||
ITER_OVEREAGER_CLONED,
|
||||
"unnecessarily eager cloning of iterator items",
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ mod or_fun_call;
|
|||
mod or_then_unwrap;
|
||||
mod path_buf_push_overwrite;
|
||||
mod path_ends_with_ext;
|
||||
mod ptr_offset_with_cast;
|
||||
mod range_zip_with_len;
|
||||
mod read_line_without_trim;
|
||||
mod readonly_write_lock;
|
||||
|
|
@ -1725,6 +1726,43 @@ declare_clippy_lint! {
|
|||
"Check for offset calculations on raw pointers to zero-sized types"
|
||||
}
|
||||
|
||||
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
|
||||
/// ```no_run
|
||||
/// 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:
|
||||
///
|
||||
/// ```no_run
|
||||
/// 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_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `FileType::is_file()`.
|
||||
|
|
@ -4576,6 +4614,31 @@ declare_clippy_lint! {
|
|||
"hardcoded localhost IP address"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to `Iterator::cloned` where the original value could be used
|
||||
/// instead.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It is not always possible for the compiler to eliminate useless allocations and
|
||||
/// deallocations generated by redundant `clone()`s.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// let x = vec![String::new()];
|
||||
/// let _ = x.iter().cloned().map(|x| x.len());
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// let x = vec![String::new()];
|
||||
/// let _ = x.iter().map(|x| x.len());
|
||||
/// ```
|
||||
#[clippy::version = "1.90.0"]
|
||||
pub REDUNDANT_ITER_CLONED,
|
||||
perf,
|
||||
"detects redundant calls to `Iterator::cloned`"
|
||||
}
|
||||
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
|
|
@ -4665,6 +4728,7 @@ impl_lint_pass!(Methods => [
|
|||
UNINIT_ASSUMED_INIT,
|
||||
MANUAL_SATURATING_ARITHMETIC,
|
||||
ZST_OFFSET,
|
||||
PTR_OFFSET_WITH_CAST,
|
||||
FILETYPE_IS_FILE,
|
||||
OPTION_AS_REF_DEREF,
|
||||
UNNECESSARY_LAZY_EVALUATIONS,
|
||||
|
|
@ -4755,6 +4819,7 @@ impl_lint_pass!(Methods => [
|
|||
IO_OTHER_ERROR,
|
||||
SWAP_WITH_TEMPORARY,
|
||||
IP_CONSTANT,
|
||||
REDUNDANT_ITER_CLONED,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
|
@ -4960,10 +5025,7 @@ impl Methods {
|
|||
// Handle method calls whose receiver and arguments may not come from expansion
|
||||
if let Some((name, recv, args, span, call_span)) = method_call(expr) {
|
||||
match (name, args) {
|
||||
(
|
||||
sym::add | sym::offset | sym::sub | sym::wrapping_offset | sym::wrapping_add | sym::wrapping_sub,
|
||||
[_arg],
|
||||
) => {
|
||||
(sym::add | sym::sub | sym::wrapping_add | sym::wrapping_sub, [_arg]) => {
|
||||
zst_offset::check(cx, expr, recv);
|
||||
},
|
||||
(sym::all, [arg]) => {
|
||||
|
|
@ -5334,6 +5396,11 @@ impl Methods {
|
|||
},
|
||||
_ => iter_nth_zero::check(cx, expr, recv, n_arg),
|
||||
},
|
||||
(sym::offset | sym::wrapping_offset, [arg]) => {
|
||||
zst_offset::check(cx, expr, recv);
|
||||
|
||||
ptr_offset_with_cast::check(cx, name, expr, recv, arg, self.msrv);
|
||||
},
|
||||
(sym::ok_or_else, [arg]) => {
|
||||
unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::expr_custom_deref_adjustment;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, peel_and_count_ty_refs};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, Mutability};
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -10,8 +10,7 @@ use super::MUT_MUTEX_LOCK;
|
|||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) {
|
||||
if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut))
|
||||
&& let (_, ref_depth, Mutability::Mut) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv))
|
||||
&& ref_depth >= 1
|
||||
&& let (_, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(recv))
|
||||
&& let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id)
|
||||
&& let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
|
||||
&& is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Mutex)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::ops::ControlFlow;
|
|||
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
|
||||
use clippy_utils::higher::VecArgs;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
|
|
@ -97,6 +98,12 @@ pub(super) fn check<'tcx>(
|
|||
return false;
|
||||
}
|
||||
|
||||
// `.unwrap_or(vec![])` is as readable as `.unwrap_or_default()`. And if the expression is a
|
||||
// non-empty `Vec`, then it will not be a default value anyway. Bail out in all cases.
|
||||
if call_expr.and_then(|call_expr| VecArgs::hir(cx, call_expr)).is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// needs to target Default::default in particular or be *::new and have a Default impl
|
||||
// available
|
||||
if (is_new(fun) && output_type_implements_default(fun))
|
||||
|
|
|
|||
82
clippy_lints/src/methods/ptr_offset_with_cast.rs
Normal file
82
clippy_lints/src/methods/ptr_offset_with_cast.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::sym;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Symbol;
|
||||
use std::fmt;
|
||||
|
||||
use super::PTR_OFFSET_WITH_CAST;
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
method: Symbol,
|
||||
expr: &Expr<'_>,
|
||||
recv: &Expr<'_>,
|
||||
arg: &Expr<'_>,
|
||||
msrv: Msrv,
|
||||
) {
|
||||
// `pointer::add` and `pointer::wrapping_add` are only stable since 1.26.0. These functions
|
||||
// became const-stable in 1.61.0, the same version that `pointer::offset` became const-stable.
|
||||
if !msrv.meets(cx, msrvs::POINTER_ADD_SUB_METHODS) {
|
||||
return;
|
||||
}
|
||||
|
||||
let method = match method {
|
||||
sym::offset => Method::Offset,
|
||||
sym::wrapping_offset => Method::WrappingOffset,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !cx.typeck_results().expr_ty_adjusted(recv).is_raw_ptr() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the argument to the method call is a cast from usize.
|
||||
let cast_lhs_expr = match arg.kind {
|
||||
ExprKind::Cast(lhs, _) if cx.typeck_results().expr_ty(lhs).is_usize() => lhs,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let ExprKind::MethodCall(method_name, _, _, _) = expr.kind else {
|
||||
return;
|
||||
};
|
||||
|
||||
let msg = format!("use of `{method}` with a `usize` casted to an `isize`");
|
||||
span_lint_and_then(cx, PTR_OFFSET_WITH_CAST, expr.span, msg, |diag| {
|
||||
diag.multipart_suggestion(
|
||||
format!("use `{}` instead", method.suggestion()),
|
||||
vec![
|
||||
(method_name.ident.span, method.suggestion().to_string()),
|
||||
(arg.span.with_lo(cast_lhs_expr.span.hi()), String::new()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,12 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
|
|||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{SpanRangeExt, snippet};
|
||||
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item};
|
||||
use clippy_utils::ty::{
|
||||
get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_and_count_ty_refs,
|
||||
};
|
||||
use clippy_utils::visitors::find_all_ret_expressions;
|
||||
use clippy_utils::{
|
||||
fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, peel_middle_ty_refs,
|
||||
return_ty, sym,
|
||||
fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, is_expr_temporary_value, return_ty, sym,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
|
|
@ -119,8 +120,8 @@ fn check_addr_of_expr(
|
|||
},
|
||||
] = adjustments[..]
|
||||
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
|
||||
&& let (target_ty, n_target_refs) = peel_middle_ty_refs(*target_ty)
|
||||
&& let (receiver_ty, n_receiver_refs) = peel_middle_ty_refs(receiver_ty)
|
||||
&& let (target_ty, n_target_refs, _) = peel_and_count_ty_refs(*target_ty)
|
||||
&& let (receiver_ty, n_receiver_refs, _) = peel_and_count_ty_refs(receiver_ty)
|
||||
// Only flag cases satisfying at least one of the following three conditions:
|
||||
// * the referent and receiver types are distinct
|
||||
// * the referent/receiver type is a copyable array
|
||||
|
|
@ -385,7 +386,7 @@ fn check_other_call_arg<'tcx>(
|
|||
&& let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder()
|
||||
&& let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id)
|
||||
&& let Some(input) = fn_sig.inputs().get(i)
|
||||
&& let (input, n_refs) = peel_middle_ty_refs(*input)
|
||||
&& let (input, n_refs, _) = peel_and_count_ty_refs(*input)
|
||||
&& let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input)
|
||||
&& let Some(sized_def_id) = cx.tcx.lang_items().sized_trait()
|
||||
&& let Some(meta_sized_def_id) = cx.tcx.lang_items().meta_sized_trait()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{implements_trait, should_call_clone_as_function, walk_ptrs_ty_depth};
|
||||
use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs, should_call_clone_as_function};
|
||||
use clippy_utils::{get_parent_expr, is_diag_trait_item, path_to_local_id, peel_blocks, strip_pat_refs};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, LangItem};
|
||||
|
|
@ -50,8 +50,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: Symbo
|
|||
// check if the type after `as_ref` or `as_mut` is the same as before
|
||||
let rcv_ty = cx.typeck_results().expr_ty(recvr);
|
||||
let res_ty = cx.typeck_results().expr_ty(expr);
|
||||
let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty);
|
||||
let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty);
|
||||
let (base_res_ty, res_depth, _) = peel_and_count_ty_refs(res_ty);
|
||||
let (base_rcv_ty, rcv_depth, _) = peel_and_count_ty_refs(rcv_ty);
|
||||
if base_rcv_ty == base_res_ty && rcv_depth >= res_depth {
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
// allow the `as_ref` or `as_mut` if it is followed by another method call
|
||||
|
|
|
|||
|
|
@ -256,7 +256,11 @@ fn is_not_in_trait_impl(cx: &LateContext<'_>, pat: &Pat<'_>, ident: Ident) -> bo
|
|||
}
|
||||
|
||||
fn get_param_name(impl_item: &ImplItem<'_>, cx: &LateContext<'_>, ident: Ident) -> Option<Symbol> {
|
||||
if let ImplItemImplKind::Trait { trait_item_def_id: Ok(trait_item_def_id), .. } = impl_item.impl_kind {
|
||||
if let ImplItemImplKind::Trait {
|
||||
trait_item_def_id: Ok(trait_item_def_id),
|
||||
..
|
||||
} = impl_item.impl_kind
|
||||
{
|
||||
let trait_param_names = cx.tcx.fn_arg_idents(trait_item_def_id);
|
||||
|
||||
let ImplItemKind::Fn(_, body_id) = impl_item.kind else {
|
||||
|
|
|
|||
|
|
@ -1,57 +1,11 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_with_context};
|
||||
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{
|
||||
SpanlessEq, fulfill_or_allowed, get_parent_expr, in_automatically_derived, is_lint_allowed, iter_input_pats,
|
||||
last_path_segment,
|
||||
};
|
||||
use clippy_utils::{SpanlessEq, fulfill_or_allowed, get_parent_expr, in_automatically_derived, last_path_segment};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
BinOpKind, BindingMode, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, QPath, Stmt, StmtKind,
|
||||
};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
|
||||
use crate::ref_patterns::REF_PATTERNS;
|
||||
|
||||
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
|
||||
/// ```no_run
|
||||
/// fn foo(ref _x: u8) {}
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn foo(_x: &u8) {}
|
||||
/// ```
|
||||
#[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
|
||||
|
|
@ -140,79 +94,13 @@ declare_clippy_lint! {
|
|||
}
|
||||
|
||||
declare_lint_pass!(LintPass => [
|
||||
TOPLEVEL_REF_ARG,
|
||||
USED_UNDERSCORE_BINDING,
|
||||
USED_UNDERSCORE_ITEMS,
|
||||
SHORT_CIRCUIT_STATEMENT,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LintPass {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
k: FnKind<'tcx>,
|
||||
decl: &'tcx FnDecl<'_>,
|
||||
body: &'tcx Body<'_>,
|
||||
_: Span,
|
||||
_: LocalDefId,
|
||||
) {
|
||||
if !matches!(k, FnKind::Closure) {
|
||||
for arg in iter_input_pats(decl, body) {
|
||||
if let PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..) = arg.pat.kind
|
||||
&& is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id)
|
||||
&& !arg.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
{
|
||||
span_lint_hir(
|
||||
cx,
|
||||
TOPLEVEL_REF_ARG,
|
||||
arg.hir_id,
|
||||
arg.pat.span,
|
||||
"`ref` directly on a function parameter does not prevent taking ownership of the passed argument. \
|
||||
Consider using a reference type instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
if let StmtKind::Let(local) = stmt.kind
|
||||
&& let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., name, None) = local.pat.kind
|
||||
&& let Some(init) = local.init
|
||||
// Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue.
|
||||
&& is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id)
|
||||
&& !stmt.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
{
|
||||
let ctxt = local.span.ctxt();
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app);
|
||||
let (mutopt, initref) = if mutabl == Mutability::Mut {
|
||||
("mut ", sugg_init.mut_addr())
|
||||
} else {
|
||||
("", sugg_init.addr())
|
||||
};
|
||||
let tyopt = if let Some(ty) = local.ty {
|
||||
let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0;
|
||||
format!(": &{mutopt}{ty_snip}")
|
||||
} 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, ".."),),
|
||||
app,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
if let StmtKind::Semi(expr) = stmt.kind
|
||||
&& let ExprKind::Binary(binop, a, b) = &expr.kind
|
||||
&& matches!(binop.node, BinOpKind::And | BinOpKind::Or)
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
|
|||
AssocContainer::Trait | AssocContainer::TraitImpl(_) => {
|
||||
note_prev_span_then_ret!(self.prev_span, impl_item.span);
|
||||
},
|
||||
AssocContainer::InherentImpl => {}
|
||||
AssocContainer::InherentImpl => {},
|
||||
}
|
||||
|
||||
let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id());
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use clippy_utils::desugar_await;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::visitors::{Descend, Visitable, for_each_expr};
|
||||
use core::ops::ControlFlow::Continue;
|
||||
|
|
@ -97,6 +98,13 @@ fn collect_unsafe_exprs<'tcx>(
|
|||
) {
|
||||
for_each_expr(cx, node, |expr| {
|
||||
match expr.kind {
|
||||
// The `await` itself will desugar to two unsafe calls, but we should ignore those.
|
||||
// Instead, check the expression that is `await`ed
|
||||
_ if let Some(e) = desugar_await(expr) => {
|
||||
collect_unsafe_exprs(cx, e, unsafe_ops);
|
||||
return Continue(Descend::No);
|
||||
},
|
||||
|
||||
ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)),
|
||||
|
||||
ExprKind::Field(e, _) => {
|
||||
|
|
|
|||
|
|
@ -252,7 +252,9 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
|
|||
{
|
||||
(
|
||||
trait_item_id,
|
||||
FnKind::ImplTraitFn(std::ptr::from_ref(cx.tcx.erase_and_anonymize_regions(trait_ref.args)) as usize),
|
||||
FnKind::ImplTraitFn(
|
||||
std::ptr::from_ref(cx.tcx.erase_and_anonymize_regions(trait_ref.args)) as usize
|
||||
),
|
||||
usize::from(sig.decl.implicit_self.has_implicit_self()),
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::same_type_and_consts;
|
||||
use clippy_utils::ty::same_type_modulo_regions;
|
||||
|
||||
use rustc_hir::{BinOpKind, Expr};
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -29,7 +29,7 @@ pub(super) fn check<'tcx>(
|
|||
fn different_types(tck: &TypeckResults<'_>, input: &Expr<'_>, output: &Expr<'_>) -> bool {
|
||||
let input_ty = tck.expr_ty(input).peel_refs();
|
||||
let output_ty = tck.expr_ty(output).peel_refs();
|
||||
!same_type_and_consts(input_ty, output_ty)
|
||||
!same_type_modulo_regions(input_ty, output_ty)
|
||||
}
|
||||
|
||||
fn check_op<'tcx>(
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::sym;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
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
|
||||
/// ```no_run
|
||||
/// 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:
|
||||
///
|
||||
/// ```no_run
|
||||
/// 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 Some((receiver_expr, arg_expr, method)) = expr_as_ptr_offset_call(cx, expr) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Check if the argument to the method call is a cast from usize
|
||||
let Some(cast_lhs_expr) = expr_as_cast_from_usize(cx, arg_expr) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let msg = format!("use of `{method}` with a `usize` casted to an `isize`");
|
||||
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
|
||||
&& 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
|
||||
&& 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(cx: &LateContext<'_>, 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(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
cx.typeck_results().expr_ty(expr).is_raw_ptr()
|
||||
}
|
||||
|
||||
fn build_suggestion(
|
||||
cx: &LateContext<'_>,
|
||||
method: Method,
|
||||
receiver_expr: &Expr<'_>,
|
||||
cast_lhs_expr: &Expr<'_>,
|
||||
) -> Option<String> {
|
||||
let receiver = receiver_expr.span.get_source_text(cx)?;
|
||||
let cast_lhs = cast_lhs_expr.span.get_source_text(cx)?;
|
||||
Some(format!("{receiver}.{}({cast_lhs})", method.suggestion()))
|
||||
}
|
||||
|
||||
#[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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,9 @@ use clippy_utils::source::snippet_with_applicability;
|
|||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::{
|
||||
eq_expr_value, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor,
|
||||
pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
|
||||
span_contains_cfg, span_contains_comment, sym,
|
||||
eq_expr_value, fn_def_id_with_node_args, higher, is_else_clause, is_in_const_context, is_lint_allowed,
|
||||
is_path_lang_item, is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id,
|
||||
peel_blocks, peel_blocks_with_stmt, span_contains_cfg, span_contains_comment, sym,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk};
|
||||
|
|
@ -393,8 +393,8 @@ fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &A
|
|||
&& let ExprKind::Ret(Some(wrapped_ret_expr)) = arm_body.kind
|
||||
&& let ExprKind::Call(ok_ctor, [ret_expr]) = wrapped_ret_expr.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, ok_ctor), ResultErr)
|
||||
// check `...` is `val` from binding
|
||||
&& path_to_local_id(ret_expr, ok_val)
|
||||
// check if `...` is `val` from binding or `val.into()`
|
||||
&& is_local_or_local_into(cx, ret_expr, ok_val)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
|
|
@ -417,6 +417,17 @@ fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &A
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if `expr` is `val` or `val.into()`
|
||||
fn is_local_or_local_into(cx: &LateContext<'_>, expr: &Expr<'_>, val: HirId) -> bool {
|
||||
let is_into_call = fn_def_id_with_node_args(cx, expr)
|
||||
.and_then(|(fn_def_id, _)| cx.tcx.trait_of_assoc(fn_def_id))
|
||||
.is_some_and(|trait_def_id| cx.tcx.is_diagnostic_item(sym::Into, trait_def_id));
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(_, recv, [], _) | ExprKind::Call(_, [recv]) => is_into_call && path_to_local_id(recv, val),
|
||||
_ => path_to_local_id(expr, val),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_arms_are_try<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm1: &Arm<'tcx>, arm2: &Arm<'tcx>) -> bool {
|
||||
(check_arm_is_some_or_ok(cx, mode, arm1) && check_arm_is_none_or_err(cx, mode, arm2))
|
||||
|| (check_arm_is_some_or_ok(cx, mode, arm2) && check_arm_is_none_or_err(cx, mode, arm1))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
||||
use clippy_utils::higher::{VecInitKind, get_vec_init_kind};
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::source::{indent_of, snippet};
|
||||
use clippy_utils::{get_enclosing_block, sym};
|
||||
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -83,10 +83,12 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
|
|||
expr.span,
|
||||
"reading zero byte data to `Vec`",
|
||||
|diag| {
|
||||
let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span);
|
||||
let indent = indent_of(cx, span).unwrap_or(0);
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
span.shrink_to_lo(),
|
||||
"try",
|
||||
format!("{}.resize({len}, 0); {}", ident, snippet(cx, expr.span, "..")),
|
||||
format!("{ident}.resize({len}, 0);\n{}", " ".repeat(indent)),
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
|
|
@ -100,14 +102,15 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
|
|||
expr.span,
|
||||
"reading zero byte data to `Vec`",
|
||||
|diag| {
|
||||
let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span);
|
||||
let indent = indent_of(cx, span).unwrap_or(0);
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
span.shrink_to_lo(),
|
||||
"try",
|
||||
format!(
|
||||
"{}.resize({}, 0); {}",
|
||||
ident,
|
||||
"{ident}.resize({}, 0);\n{}",
|
||||
snippet(cx, e.span, ".."),
|
||||
snippet(cx, expr.span, "..")
|
||||
" ".repeat(indent)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
|
|
@ -130,6 +133,16 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
|
|||
}
|
||||
}
|
||||
|
||||
fn first_stmt_containing_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx hir::Stmt<'tcx>> {
|
||||
cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| {
|
||||
if let hir::Node::Stmt(stmt) = node {
|
||||
Some(stmt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
struct ReadVecVisitor<'tcx> {
|
||||
local_id: HirId,
|
||||
read_zero_expr: Option<&'tcx Expr<'tcx>>,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
|||
use clippy_utils::fn_has_unsatisfiable_preds;
|
||||
use clippy_utils::mir::{LocalUsage, PossibleBorrowerMap, visit_local_usage};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, walk_ptrs_ty_depth};
|
||||
use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, peel_and_count_ty_refs};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, FnDecl, LangItem, def_id};
|
||||
|
|
@ -263,7 +263,7 @@ fn is_call_with_ref_arg<'tcx>(
|
|||
&& args.len() == 1
|
||||
&& let mir::Operand::Move(mir::Place { local, .. }) = &args[0].node
|
||||
&& let ty::FnDef(def_id, _) = *func.ty(mir, cx.tcx).kind()
|
||||
&& let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].node.ty(mir, cx.tcx))
|
||||
&& let (inner_ty, 1, _) = peel_and_count_ty_refs(args[0].node.ty(mir, cx.tcx))
|
||||
&& !is_copy(cx, inner_ty)
|
||||
{
|
||||
Some((def_id, *local, inner_ty, destination.as_local()?))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use clippy_utils::{get_parent_expr, peel_middle_ty_refs};
|
||||
use clippy_utils::ty::{is_type_lang_item, peel_and_count_ty_refs};
|
||||
use rustc_ast::util::parser::ExprPrecedence;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability};
|
||||
|
|
@ -82,8 +82,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
|
|||
&& let ExprKind::Index(indexed, range, _) = addressee.kind
|
||||
&& is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull)
|
||||
{
|
||||
let (expr_ty, expr_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(expr));
|
||||
let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed));
|
||||
let (expr_ty, expr_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(expr));
|
||||
let (indexed_ty, indexed_ref_count, _) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(indexed));
|
||||
let parent_expr = get_parent_expr(cx, expr);
|
||||
let needs_parens_for_prefix =
|
||||
parent_expr.is_some_and(|parent| cx.precedence(parent) > ExprPrecedence::Prefix);
|
||||
|
|
|
|||
|
|
@ -1,513 +0,0 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{
|
||||
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor,
|
||||
leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg,
|
||||
span_find_starting_semi, sym,
|
||||
};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_ast::MetaItemInner;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::ResultErr;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt,
|
||||
StmtKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, Level, LintContext};
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_middle::ty::{self, GenericArgKind, Ty};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::{BytePos, Pos, Span};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Display;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `let`-bindings, which are subsequently
|
||||
/// returned.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It is just extraneous code. Remove it to make your code
|
||||
/// more rusty.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// In the case of some temporaries, e.g. locks, eliding the variable binding could lead
|
||||
/// to deadlocks. See [this issue](https://github.com/rust-lang/rust/issues/37612).
|
||||
/// This could become relevant if the code is later changed to use the code that would have been
|
||||
/// bound without first assigning it to a let-binding.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn foo() -> String {
|
||||
/// let x = String::new();
|
||||
/// x
|
||||
/// }
|
||||
/// ```
|
||||
/// instead, use
|
||||
/// ```no_run
|
||||
/// 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
|
||||
/// ```no_run
|
||||
/// fn foo(x: usize) -> usize {
|
||||
/// return x;
|
||||
/// }
|
||||
/// ```
|
||||
/// simplify to
|
||||
/// ```no_run
|
||||
/// fn foo(x: usize) -> usize {
|
||||
/// x
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub NEEDLESS_RETURN,
|
||||
// This lint requires some special handling in `check_final_expr` for `#[expect]`.
|
||||
// This handling needs to be updated if the group gets changed. This should also
|
||||
// be caught by tests.
|
||||
style,
|
||||
"using a return statement like `return expr;` where an expression would suffice"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for return statements on `Err` paired with the `?` operator.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The `return` is unnecessary.
|
||||
///
|
||||
/// Returns may be used to add attributes to the return expression. Return
|
||||
/// statements with attributes are therefore be accepted by this lint.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// fn foo(x: usize) -> Result<(), Box<dyn Error>> {
|
||||
/// if x == 0 {
|
||||
/// return Err(...)?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
/// simplify to
|
||||
/// ```rust,ignore
|
||||
/// fn foo(x: usize) -> Result<(), Box<dyn Error>> {
|
||||
/// if x == 0 {
|
||||
/// Err(...)?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
/// if paired with `try_err`, use instead:
|
||||
/// ```rust,ignore
|
||||
/// fn foo(x: usize) -> Result<(), Box<dyn Error>> {
|
||||
/// if x == 0 {
|
||||
/// return Err(...);
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.73.0"]
|
||||
pub NEEDLESS_RETURN_WITH_QUESTION_MARK,
|
||||
style,
|
||||
"using a return statement like `return Err(expr)?;` where removing it would suffice"
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum RetReplacement<'tcx> {
|
||||
Empty,
|
||||
Block,
|
||||
Unit,
|
||||
NeedsPar(Cow<'tcx, str>, Applicability),
|
||||
Expr(Cow<'tcx, str>, Applicability),
|
||||
}
|
||||
|
||||
impl RetReplacement<'_> {
|
||||
fn sugg_help(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Empty | Self::Expr(..) => "remove `return`",
|
||||
Self::Block => "replace `return` with an empty block",
|
||||
Self::Unit => "replace `return` with a unit value",
|
||||
Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses",
|
||||
}
|
||||
}
|
||||
|
||||
fn applicability(&self) -> Applicability {
|
||||
match self {
|
||||
Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap,
|
||||
_ => Applicability::MachineApplicable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RetReplacement<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, ""),
|
||||
Self::Block => write!(f, "{{}}"),
|
||||
Self::Unit => write!(f, "()"),
|
||||
Self::NeedsPar(inner, _) => write!(f, "({inner})"),
|
||||
Self::Expr(inner, _) => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]);
|
||||
|
||||
/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed. This
|
||||
/// is the case when the enclosing block expression is coerced to some other type, which only works
|
||||
/// because of the never-ness of `return` expressions
|
||||
fn stmt_needs_never_type(cx: &LateContext<'_>, stmt_hir_id: HirId) -> bool {
|
||||
cx.tcx
|
||||
.hir_parent_iter(stmt_hir_id)
|
||||
.find_map(|(_, node)| if let Node::Expr(expr) = node { Some(expr) } else { None })
|
||||
.is_some_and(|e| {
|
||||
cx.typeck_results()
|
||||
.expr_adjustments(e)
|
||||
.iter()
|
||||
.any(|adjust| adjust.target != cx.tcx.types.unit && matches!(adjust.kind, Adjust::NeverToAny))
|
||||
})
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Return {
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
if !stmt.span.in_external_macro(cx.sess().source_map())
|
||||
&& let StmtKind::Semi(expr) = stmt.kind
|
||||
&& let ExprKind::Ret(Some(ret)) = expr.kind
|
||||
// return Err(...)? desugars to a match
|
||||
// over a Err(...).branch()
|
||||
// which breaks down to a branch call, with the callee being
|
||||
// the constructor of the Err variant
|
||||
&& let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind
|
||||
&& let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind
|
||||
&& let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr)
|
||||
|
||||
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
||||
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id))
|
||||
&& let ItemKind::Fn { body, .. } = item.kind
|
||||
&& let block = cx.tcx.hir_body(body).value
|
||||
&& let ExprKind::Block(block, _) = block.kind
|
||||
&& !is_inside_let_else(cx.tcx, expr)
|
||||
&& let [.., final_stmt] = block.stmts
|
||||
&& final_stmt.hir_id != stmt.hir_id
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
&& !stmt_needs_never_type(cx, stmt.hir_id)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_RETURN_WITH_QUESTION_MARK,
|
||||
expr.span.until(ret.span),
|
||||
"unneeded `return` statement with `?` operator",
|
||||
"remove it",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
// we need both a let-binding stmt and an expr
|
||||
if let Some(retexpr) = block.expr
|
||||
&& let Some(stmt) = block.stmts.iter().last()
|
||||
&& let StmtKind::Let(local) = &stmt.kind
|
||||
&& local.ty.is_none()
|
||||
&& cx.tcx.hir_attrs(local.hir_id).is_empty()
|
||||
&& let Some(initexpr) = &local.init
|
||||
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
|
||||
&& path_to_local_id(retexpr, local_id)
|
||||
&& (cx.sess().edition() >= Edition::Edition2024 || !last_statement_borrows(cx, initexpr))
|
||||
&& !initexpr.span.in_external_macro(cx.sess().source_map())
|
||||
&& !retexpr.span.in_external_macro(cx.sess().source_map())
|
||||
&& !local.span.from_expansion()
|
||||
&& !span_contains_cfg(cx, stmt.span.between(retexpr.span))
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
LET_AND_RETURN,
|
||||
retexpr.hir_id,
|
||||
retexpr.span,
|
||||
"returning the result of a `let` binding from a block",
|
||||
|err| {
|
||||
err.span_label(local.span, "unnecessary `let` binding");
|
||||
|
||||
if let Some(src) = initexpr.span.get_source_text(cx) {
|
||||
let sugg = if binary_expr_needs_parentheses(initexpr) {
|
||||
if has_enclosing_paren(&src) {
|
||||
src.to_owned()
|
||||
} else {
|
||||
format!("({src})")
|
||||
}
|
||||
} else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
|
||||
if has_enclosing_paren(&src) {
|
||||
format!("{src} as _")
|
||||
} else {
|
||||
format!("({src}) as _")
|
||||
}
|
||||
} else {
|
||||
src.to_owned()
|
||||
};
|
||||
err.multipart_suggestion(
|
||||
"return the expression directly",
|
||||
vec![(local.span, String::new()), (retexpr.span, sugg)],
|
||||
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>,
|
||||
sp: Span,
|
||||
_: LocalDefId,
|
||||
) {
|
||||
if sp.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
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, vec![], replacement, None);
|
||||
},
|
||||
FnKind::ItemFn(..) | FnKind::Method(..) => {
|
||||
check_block_return(cx, &body.value.kind, sp, vec![]);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if `expr` is a block, check if there are needless returns in it
|
||||
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) {
|
||||
if let ExprKind::Block(block, _) = expr_kind {
|
||||
if let Some(block_expr) = block.expr {
|
||||
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None);
|
||||
} else if let Some(stmt) = block.stmts.iter().last() {
|
||||
match stmt.kind {
|
||||
StmtKind::Expr(expr) => {
|
||||
check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None);
|
||||
},
|
||||
StmtKind::Semi(semi_expr) => {
|
||||
// Remove ending semicolons and any whitespace ' ' in between.
|
||||
// Without `return`, the suggestion might not compile if the semicolon is retained
|
||||
if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) {
|
||||
let semi_span_to_remove =
|
||||
span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi()));
|
||||
semi_spans.push(semi_span_to_remove);
|
||||
}
|
||||
check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_final_expr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
|
||||
* needless return */
|
||||
replacement: RetReplacement<'tcx>,
|
||||
match_ty_opt: Option<Ty<'_>>,
|
||||
) {
|
||||
let peeled_drop_expr = expr.peel_drop_temps();
|
||||
match &peeled_drop_expr.kind {
|
||||
// simple return is always "bad"
|
||||
ExprKind::Ret(inner) => {
|
||||
// check if expr return nothing
|
||||
let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
|
||||
extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)
|
||||
} else {
|
||||
peeled_drop_expr.span
|
||||
};
|
||||
|
||||
let replacement = if let Some(inner_expr) = inner {
|
||||
// if desugar of `do yeet`, don't lint
|
||||
if let ExprKind::Call(path_expr, [_]) = inner_expr.kind
|
||||
&& let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, ..)) = path_expr.kind
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability);
|
||||
if binary_expr_needs_parentheses(inner_expr) {
|
||||
RetReplacement::NeedsPar(snippet, applicability)
|
||||
} else {
|
||||
RetReplacement::Expr(snippet, applicability)
|
||||
}
|
||||
} else {
|
||||
match match_ty_opt {
|
||||
Some(match_ty) => {
|
||||
match match_ty.kind() {
|
||||
// If the code got till here with
|
||||
// tuple not getting detected before it,
|
||||
// then we are sure it's going to be Unit
|
||||
// type
|
||||
ty::Tuple(_) => RetReplacement::Unit,
|
||||
// We don't want to anything in this case
|
||||
// cause we can't predict what the user would
|
||||
// want here
|
||||
_ => return,
|
||||
}
|
||||
},
|
||||
None => replacement,
|
||||
}
|
||||
};
|
||||
|
||||
if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ret_span.from_expansion() || is_from_proc_macro(cx, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Returns may be used to turn an expression into a statement in rustc's AST.
|
||||
// This allows the addition of attributes, like `#[allow]` (See: clippy#9361)
|
||||
// `#[expect(clippy::needless_return)]` needs to be handled separately to
|
||||
// actually fulfill the expectation (clippy::#12998)
|
||||
match cx.tcx.hir_attrs(expr.hir_id) {
|
||||
[] => {},
|
||||
[attr] => {
|
||||
if matches!(Level::from_attr(attr), Some((Level::Expect, _)))
|
||||
&& let metas = attr.meta_item_list()
|
||||
&& let Some(lst) = metas
|
||||
&& let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice()
|
||||
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
|
||||
&& tool.ident.name == sym::clippy
|
||||
&& matches!(
|
||||
lint_name.ident.name,
|
||||
sym::needless_return | sym::style | sym::all | sym::warnings
|
||||
)
|
||||
{
|
||||
// This is an expectation of the `needless_return` lint
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
emit_return_lint(
|
||||
cx,
|
||||
peeled_drop_expr.span,
|
||||
ret_span,
|
||||
semi_spans,
|
||||
&replacement,
|
||||
expr.hir_id,
|
||||
);
|
||||
},
|
||||
ExprKind::If(_, then, else_clause_opt) => {
|
||||
check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone());
|
||||
if let Some(else_clause) = else_clause_opt {
|
||||
// The `RetReplacement` won't be used there as `else_clause` will be either a block or
|
||||
// a `if` expression.
|
||||
check_final_expr(cx, else_clause, semi_spans, RetReplacement::Empty, match_ty_opt);
|
||||
}
|
||||
},
|
||||
// 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) => {
|
||||
let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr);
|
||||
for arm in *arms {
|
||||
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty));
|
||||
}
|
||||
},
|
||||
// if it's a whole block, check it
|
||||
other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_return_lint(
|
||||
cx: &LateContext<'_>,
|
||||
lint_span: Span,
|
||||
ret_span: Span,
|
||||
semi_spans: Vec<Span>,
|
||||
replacement: &RetReplacement<'_>,
|
||||
at: HirId,
|
||||
) {
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
at,
|
||||
lint_span,
|
||||
"unneeded `return` statement",
|
||||
|diag| {
|
||||
let suggestions = std::iter::once((ret_span, replacement.to_string()))
|
||||
.chain(semi_spans.into_iter().map(|span| (span, String::new())))
|
||||
.collect();
|
||||
|
||||
diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
for_each_expr(cx, expr, |e| {
|
||||
if let Some(def_id) = fn_def_id(cx, e)
|
||||
&& cx
|
||||
.tcx
|
||||
.fn_sig(def_id)
|
||||
.instantiate_identity()
|
||||
.skip_binder()
|
||||
.output()
|
||||
.walk()
|
||||
.any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static()))
|
||||
{
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
// Go backwards while encountering whitespace and extend the given Span to that point.
|
||||
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
|
||||
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
|
||||
let ws = [b' ', b'\t', b'\n'];
|
||||
if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) {
|
||||
let len = prev_source.len() - non_ws_pos - 1;
|
||||
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
|
||||
}
|
||||
}
|
||||
|
||||
sp
|
||||
}
|
||||
86
clippy_lints/src/returns/let_and_return.rs
Normal file
86
clippy_lints/src/returns/let_and_return.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, path_to_local_id, span_contains_cfg};
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Expr, PatKind, StmtKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_span::edition::Edition;
|
||||
|
||||
use super::LET_AND_RETURN;
|
||||
|
||||
pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
// we need both a let-binding stmt and an expr
|
||||
if let Some(retexpr) = block.expr
|
||||
&& let Some(stmt) = block.stmts.last()
|
||||
&& let StmtKind::Let(local) = &stmt.kind
|
||||
&& local.ty.is_none()
|
||||
&& cx.tcx.hir_attrs(local.hir_id).is_empty()
|
||||
&& let Some(initexpr) = &local.init
|
||||
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
|
||||
&& path_to_local_id(retexpr, local_id)
|
||||
&& (cx.sess().edition() >= Edition::Edition2024 || !last_statement_borrows(cx, initexpr))
|
||||
&& !initexpr.span.in_external_macro(cx.sess().source_map())
|
||||
&& !retexpr.span.in_external_macro(cx.sess().source_map())
|
||||
&& !local.span.from_expansion()
|
||||
&& !span_contains_cfg(cx, stmt.span.between(retexpr.span))
|
||||
{
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
LET_AND_RETURN,
|
||||
retexpr.hir_id,
|
||||
retexpr.span,
|
||||
"returning the result of a `let` binding from a block",
|
||||
|err| {
|
||||
err.span_label(local.span, "unnecessary `let` binding");
|
||||
|
||||
if let Some(src) = initexpr.span.get_source_text(cx) {
|
||||
let sugg = if binary_expr_needs_parentheses(initexpr) {
|
||||
if has_enclosing_paren(&src) {
|
||||
src.to_owned()
|
||||
} else {
|
||||
format!("({src})")
|
||||
}
|
||||
} else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
|
||||
if has_enclosing_paren(&src) {
|
||||
format!("{src} as _")
|
||||
} else {
|
||||
format!("({src}) as _")
|
||||
}
|
||||
} else {
|
||||
src.to_owned()
|
||||
};
|
||||
err.multipart_suggestion(
|
||||
"return the expression directly",
|
||||
vec![(local.span, String::new()), (retexpr.span, sugg)],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
err.span_help(initexpr.span, "this expression can be directly returned");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
for_each_expr(cx, expr, |e| {
|
||||
if let Some(def_id) = fn_def_id(cx, e)
|
||||
&& cx
|
||||
.tcx
|
||||
.fn_sig(def_id)
|
||||
.instantiate_identity()
|
||||
.skip_binder()
|
||||
.output()
|
||||
.walk()
|
||||
.any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(re) if !re.is_static()))
|
||||
{
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
140
clippy_lints/src/returns/mod.rs
Normal file
140
clippy_lints/src/returns/mod.rs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Block, Body, FnDecl, Stmt};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
|
||||
mod let_and_return;
|
||||
mod needless_return;
|
||||
mod needless_return_with_question_mark;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `let`-bindings, which are subsequently
|
||||
/// returned.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It is just extraneous code. Remove it to make your code
|
||||
/// more rusty.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// In the case of some temporaries, e.g. locks, eliding the variable binding could lead
|
||||
/// to deadlocks. See [this issue](https://github.com/rust-lang/rust/issues/37612).
|
||||
/// This could become relevant if the code is later changed to use the code that would have been
|
||||
/// bound without first assigning it to a let-binding.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// fn foo() -> String {
|
||||
/// let x = String::new();
|
||||
/// x
|
||||
/// }
|
||||
/// ```
|
||||
/// instead, use
|
||||
/// ```no_run
|
||||
/// 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
|
||||
/// ```no_run
|
||||
/// fn foo(x: usize) -> usize {
|
||||
/// return x;
|
||||
/// }
|
||||
/// ```
|
||||
/// simplify to
|
||||
/// ```no_run
|
||||
/// fn foo(x: usize) -> usize {
|
||||
/// x
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub NEEDLESS_RETURN,
|
||||
// This lint requires some special handling in `check_final_expr` for `#[expect]`.
|
||||
// This handling needs to be updated if the group gets changed. This should also
|
||||
// be caught by tests.
|
||||
style,
|
||||
"using a return statement like `return expr;` where an expression would suffice"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for return statements on `Err` paired with the `?` operator.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The `return` is unnecessary.
|
||||
///
|
||||
/// Returns may be used to add attributes to the return expression. Return
|
||||
/// statements with attributes are therefore be accepted by this lint.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// fn foo(x: usize) -> Result<(), Box<dyn Error>> {
|
||||
/// if x == 0 {
|
||||
/// return Err(...)?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
/// simplify to
|
||||
/// ```rust,ignore
|
||||
/// fn foo(x: usize) -> Result<(), Box<dyn Error>> {
|
||||
/// if x == 0 {
|
||||
/// Err(...)?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
/// if paired with `try_err`, use instead:
|
||||
/// ```rust,ignore
|
||||
/// fn foo(x: usize) -> Result<(), Box<dyn Error>> {
|
||||
/// if x == 0 {
|
||||
/// return Err(...);
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.73.0"]
|
||||
pub NEEDLESS_RETURN_WITH_QUESTION_MARK,
|
||||
style,
|
||||
"using a return statement like `return Err(expr)?;` where removing it would suffice"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Return {
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
needless_return_with_question_mark::check_stmt(cx, stmt);
|
||||
}
|
||||
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
let_and_return::check_block(cx, block);
|
||||
}
|
||||
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: FnKind<'tcx>,
|
||||
_: &'tcx FnDecl<'tcx>,
|
||||
body: &'tcx Body<'tcx>,
|
||||
sp: Span,
|
||||
_: LocalDefId,
|
||||
) {
|
||||
needless_return::check_fn(cx, kind, body, sp);
|
||||
}
|
||||
}
|
||||
269
clippy_lints/src/returns/needless_return.rs
Normal file
269
clippy_lints/src/returns/needless_return.rs
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::{
|
||||
binary_expr_needs_parentheses, is_from_proc_macro, leaks_droppable_temporary_with_limited_lifetime,
|
||||
span_contains_cfg, span_find_starting_semi, sym,
|
||||
};
|
||||
use rustc_ast::MetaItemInner;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, Expr, ExprKind, HirId, LangItem, MatchSource, QPath, StmtKind};
|
||||
use rustc_lint::{LateContext, Level, LintContext};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::{BytePos, Pos, Span};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::NEEDLESS_RETURN;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum RetReplacement<'tcx> {
|
||||
Empty,
|
||||
Block,
|
||||
Unit,
|
||||
NeedsPar(Cow<'tcx, str>, Applicability),
|
||||
Expr(Cow<'tcx, str>, Applicability),
|
||||
}
|
||||
|
||||
impl RetReplacement<'_> {
|
||||
fn sugg_help(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Empty | Self::Expr(..) => "remove `return`",
|
||||
Self::Block => "replace `return` with an empty block",
|
||||
Self::Unit => "replace `return` with a unit value",
|
||||
Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses",
|
||||
}
|
||||
}
|
||||
|
||||
fn applicability(&self) -> Applicability {
|
||||
match self {
|
||||
Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap,
|
||||
_ => Applicability::MachineApplicable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RetReplacement<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Empty => f.write_str(""),
|
||||
Self::Block => f.write_str("{}"),
|
||||
Self::Unit => f.write_str("()"),
|
||||
Self::NeedsPar(inner, _) => write!(f, "({inner})"),
|
||||
Self::Expr(inner, _) => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_fn<'tcx>(cx: &LateContext<'tcx>, kind: FnKind<'tcx>, body: &'tcx Body<'tcx>, sp: Span) {
|
||||
if sp.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
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, vec![], replacement, None);
|
||||
},
|
||||
FnKind::ItemFn(..) | FnKind::Method(..) => {
|
||||
check_block_return(cx, &body.value.kind, sp, vec![]);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// if `expr` is a block, check if there are needless returns in it
|
||||
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) {
|
||||
if let ExprKind::Block(block, _) = expr_kind {
|
||||
if let Some(block_expr) = block.expr {
|
||||
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None);
|
||||
} else if let Some(stmt) = block.stmts.last() {
|
||||
if span_contains_cfg(
|
||||
cx,
|
||||
Span::between(
|
||||
stmt.span,
|
||||
cx.sess().source_map().end_point(block.span), // the closing brace of the block
|
||||
),
|
||||
) {
|
||||
return;
|
||||
}
|
||||
match stmt.kind {
|
||||
StmtKind::Expr(expr) => {
|
||||
check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None);
|
||||
},
|
||||
StmtKind::Semi(semi_expr) => {
|
||||
// Remove ending semicolons and any whitespace ' ' in between.
|
||||
// Without `return`, the suggestion might not compile if the semicolon is retained
|
||||
if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) {
|
||||
let semi_span_to_remove =
|
||||
span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi()));
|
||||
semi_spans.push(semi_span_to_remove);
|
||||
}
|
||||
check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_final_expr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
|
||||
* needless return */
|
||||
replacement: RetReplacement<'tcx>,
|
||||
match_ty_opt: Option<Ty<'_>>,
|
||||
) {
|
||||
let peeled_drop_expr = expr.peel_drop_temps();
|
||||
match &peeled_drop_expr.kind {
|
||||
// simple return is always "bad"
|
||||
ExprKind::Ret(inner) => {
|
||||
// check if expr return nothing
|
||||
let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
|
||||
extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)
|
||||
} else {
|
||||
peeled_drop_expr.span
|
||||
};
|
||||
|
||||
let replacement = if let Some(inner_expr) = inner {
|
||||
// if desugar of `do yeet`, don't lint
|
||||
if let ExprKind::Call(path_expr, [_]) = inner_expr.kind
|
||||
&& let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, ..)) = path_expr.kind
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability);
|
||||
if binary_expr_needs_parentheses(inner_expr) {
|
||||
RetReplacement::NeedsPar(snippet, applicability)
|
||||
} else {
|
||||
RetReplacement::Expr(snippet, applicability)
|
||||
}
|
||||
} else {
|
||||
match match_ty_opt {
|
||||
Some(match_ty) => {
|
||||
match match_ty.kind() {
|
||||
// If the code got till here with
|
||||
// tuple not getting detected before it,
|
||||
// then we are sure it's going to be Unit
|
||||
// type
|
||||
ty::Tuple(_) => RetReplacement::Unit,
|
||||
// We don't want to anything in this case
|
||||
// cause we can't predict what the user would
|
||||
// want here
|
||||
_ => return,
|
||||
}
|
||||
},
|
||||
None => replacement,
|
||||
}
|
||||
};
|
||||
|
||||
if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ret_span.from_expansion() || is_from_proc_macro(cx, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Returns may be used to turn an expression into a statement in rustc's AST.
|
||||
// This allows the addition of attributes, like `#[allow]` (See: clippy#9361)
|
||||
// `#[expect(clippy::needless_return)]` needs to be handled separately to
|
||||
// actually fulfill the expectation (clippy::#12998)
|
||||
match cx.tcx.hir_attrs(expr.hir_id) {
|
||||
[] => {},
|
||||
[attr] => {
|
||||
if matches!(Level::from_attr(attr), Some((Level::Expect, _)))
|
||||
&& let metas = attr.meta_item_list()
|
||||
&& let Some(lst) = metas
|
||||
&& let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice()
|
||||
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
|
||||
&& tool.ident.name == sym::clippy
|
||||
&& matches!(
|
||||
lint_name.ident.name,
|
||||
sym::needless_return | sym::style | sym::all | sym::warnings
|
||||
)
|
||||
{
|
||||
// This is an expectation of the `needless_return` lint
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
|
||||
emit_return_lint(
|
||||
cx,
|
||||
peeled_drop_expr.span,
|
||||
ret_span,
|
||||
semi_spans,
|
||||
&replacement,
|
||||
expr.hir_id,
|
||||
);
|
||||
},
|
||||
ExprKind::If(_, then, else_clause_opt) => {
|
||||
check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone());
|
||||
if let Some(else_clause) = else_clause_opt {
|
||||
// The `RetReplacement` won't be used there as `else_clause` will be either a block or
|
||||
// a `if` expression.
|
||||
check_final_expr(cx, else_clause, semi_spans, RetReplacement::Empty, match_ty_opt);
|
||||
}
|
||||
},
|
||||
// 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) => {
|
||||
let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr);
|
||||
for arm in *arms {
|
||||
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty));
|
||||
}
|
||||
},
|
||||
// if it's a whole block, check it
|
||||
other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_return_lint(
|
||||
cx: &LateContext<'_>,
|
||||
lint_span: Span,
|
||||
ret_span: Span,
|
||||
semi_spans: Vec<Span>,
|
||||
replacement: &RetReplacement<'_>,
|
||||
at: HirId,
|
||||
) {
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
at,
|
||||
lint_span,
|
||||
"unneeded `return` statement",
|
||||
|diag| {
|
||||
let suggestions = std::iter::once((ret_span, replacement.to_string()))
|
||||
.chain(semi_spans.into_iter().map(|span| (span, String::new())))
|
||||
.collect();
|
||||
|
||||
diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Go backwards while encountering whitespace and extend the given Span to that point.
|
||||
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
|
||||
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
|
||||
let ws = [b' ', b'\t', b'\n'];
|
||||
if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) {
|
||||
let len = prev_source.len() - non_ws_pos - 1;
|
||||
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
|
||||
}
|
||||
}
|
||||
|
||||
sp
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::{is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::ResultErr;
|
||||
use rustc_hir::{ExprKind, HirId, ItemKind, MatchSource, Node, OwnerNode, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
|
||||
use super::NEEDLESS_RETURN_WITH_QUESTION_MARK;
|
||||
|
||||
pub(super) fn check_stmt<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
if !stmt.span.in_external_macro(cx.sess().source_map())
|
||||
&& let StmtKind::Semi(expr) = stmt.kind
|
||||
&& let ExprKind::Ret(Some(ret)) = expr.kind
|
||||
// return Err(...)? desugars to a match
|
||||
// over a Err(...).branch()
|
||||
// which breaks down to a branch call, with the callee being
|
||||
// the constructor of the Err variant
|
||||
&& let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind
|
||||
&& let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind
|
||||
&& let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind
|
||||
&& is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr)
|
||||
|
||||
// Ensure this is not the final stmt, otherwise removing it would cause a compile error
|
||||
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir_get_parent_item(expr.hir_id))
|
||||
&& let ItemKind::Fn { body, .. } = item.kind
|
||||
&& let block = cx.tcx.hir_body(body).value
|
||||
&& let ExprKind::Block(block, _) = block.kind
|
||||
&& !is_inside_let_else(cx.tcx, expr)
|
||||
&& let [.., final_stmt] = block.stmts
|
||||
&& final_stmt.hir_id != stmt.hir_id
|
||||
&& !is_from_proc_macro(cx, expr)
|
||||
&& !stmt_needs_never_type(cx, stmt.hir_id)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_RETURN_WITH_QUESTION_MARK,
|
||||
expr.span.until(ret.span),
|
||||
"unneeded `return` statement with `?` operator",
|
||||
"remove it",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed.
|
||||
/// This is the case when the enclosing block expression is coerced to some other type,
|
||||
/// which only works because of the never-ness of `return` expressions
|
||||
fn stmt_needs_never_type(cx: &LateContext<'_>, stmt_hir_id: HirId) -> bool {
|
||||
cx.tcx
|
||||
.hir_parent_iter(stmt_hir_id)
|
||||
.find_map(|(_, node)| if let Node::Expr(expr) = node { Some(expr) } else { None })
|
||||
.is_some_and(|e| {
|
||||
cx.typeck_results()
|
||||
.expr_adjustments(e)
|
||||
.iter()
|
||||
.any(|adjust| adjust.target != cx.tcx.types.unit && matches!(adjust.kind, Adjust::NeverToAny))
|
||||
})
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, Expr, ExprKind, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
|
|
@ -82,6 +83,19 @@ impl SemicolonBlock {
|
|||
let insert_span = tail.span.source_callsite().shrink_to_hi();
|
||||
let remove_span = semi_span.with_lo(block.span.hi());
|
||||
|
||||
// If the block is surrounded by parens (`({ 0 });`), the author probably knows what
|
||||
// they're doing and why, so don't get in their way.
|
||||
//
|
||||
// This has the additional benefit of stopping the block being parsed as a function call:
|
||||
// ```
|
||||
// fn foo() {
|
||||
// ({ 0 }); // if we remove this `;`, this will parse as a `({ 0 })(5);` function call
|
||||
// (5);
|
||||
// }
|
||||
if remove_span.check_source_text(cx, |src| src.contains(')')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.semicolon_inside_block_ignore_singleline && get_line(cx, remove_span) == get_line(cx, insert_span) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{path_def_id, peel_middle_ty_refs};
|
||||
use clippy_utils::path_def_id;
|
||||
use clippy_utils::ty::peel_and_count_ty_refs;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
|
@ -59,7 +60,7 @@ impl LateLintPass<'_> for SizeOfRef {
|
|||
&& let Some(def_id) = path_def_id(cx, path)
|
||||
&& cx.tcx.is_diagnostic_item(sym::mem_size_of_val, def_id)
|
||||
&& let arg_ty = cx.typeck_results().expr_ty(arg)
|
||||
&& peel_middle_ty_refs(arg_ty).1 > 1
|
||||
&& peel_and_count_ty_refs(arg_ty).1 > 1
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
|
|
|||
119
clippy_lints/src/toplevel_ref_arg.rs
Normal file
119
clippy_lints/src/toplevel_ref_arg.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{is_lint_allowed, iter_input_pats};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{BindingMode, Body, ByRef, FnDecl, Mutability, PatKind, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
|
||||
use crate::ref_patterns::REF_PATTERNS;
|
||||
|
||||
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
|
||||
/// ```no_run
|
||||
/// fn foo(ref _x: u8) {}
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```no_run
|
||||
/// fn foo(_x: &u8) {}
|
||||
/// ```
|
||||
#[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_lint_pass!(ToplevelRefArg => [TOPLEVEL_REF_ARG]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ToplevelRefArg {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
k: FnKind<'tcx>,
|
||||
decl: &'tcx FnDecl<'_>,
|
||||
body: &'tcx Body<'_>,
|
||||
_: Span,
|
||||
_: LocalDefId,
|
||||
) {
|
||||
if !matches!(k, FnKind::Closure) {
|
||||
for arg in iter_input_pats(decl, body) {
|
||||
if let PatKind::Binding(BindingMode(ByRef::Yes(_), _), ..) = arg.pat.kind
|
||||
&& is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id)
|
||||
&& !arg.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
{
|
||||
span_lint_hir(
|
||||
cx,
|
||||
TOPLEVEL_REF_ARG,
|
||||
arg.hir_id,
|
||||
arg.pat.span,
|
||||
"`ref` directly on a function parameter does not prevent taking ownership of the passed argument. \
|
||||
Consider using a reference type instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
if let StmtKind::Let(local) = stmt.kind
|
||||
&& let PatKind::Binding(BindingMode(ByRef::Yes(mutabl), _), .., name, None) = local.pat.kind
|
||||
&& let Some(init) = local.init
|
||||
// Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue.
|
||||
&& is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id)
|
||||
&& !stmt.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
{
|
||||
let ctxt = local.span.ctxt();
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app);
|
||||
let (mutopt, initref) = match mutabl {
|
||||
Mutability::Mut => ("mut ", sugg_init.mut_addr()),
|
||||
Mutability::Not => ("", sugg_init.addr()),
|
||||
};
|
||||
let tyopt = if let Some(ty) = local.ty {
|
||||
let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0;
|
||||
format!(": &{mutopt}{ty_snip}")
|
||||
} 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, ".."),),
|
||||
app,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,16 +28,27 @@ pub(super) fn check<'tcx>(
|
|||
format!("transmute from a pointer type (`{from_ty}`) to a reference type (`{to_ty}`)"),
|
||||
|diag| {
|
||||
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||
let (deref, cast) = if *mutbl == Mutability::Mut {
|
||||
("&mut *", "*mut")
|
||||
} else {
|
||||
("&*", "*const")
|
||||
let (deref, cast) = match mutbl {
|
||||
Mutability::Mut => ("&mut *", "*mut"),
|
||||
Mutability::Not => ("&*", "*const"),
|
||||
};
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
|
||||
let sugg = if let Some(ty) = get_explicit_type(path) {
|
||||
let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app);
|
||||
if msrv.meets(cx, msrvs::POINTER_CAST) {
|
||||
if !to_ref_ty.is_sized(cx.tcx, cx.typing_env()) {
|
||||
// We can't suggest `.cast()`, because that requires `to_ref_ty` to be Sized.
|
||||
if from_ptr_ty.has_erased_regions() {
|
||||
// We can't suggest `as *mut/const () as *mut/const to_ref_ty`, because the former is a
|
||||
// thin pointer, whereas the latter is a wide pointer, due of its pointee, `to_ref_ty`,
|
||||
// being !Sized.
|
||||
//
|
||||
// The only remaining option is be to skip `*mut/const ()`, but that might not be safe
|
||||
// to do because of the erased regions in `from_ptr_ty`, so reduce the applicability.
|
||||
app = Applicability::MaybeIncorrect;
|
||||
}
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{cast} {ty_snip}"))).to_string()
|
||||
} else if msrv.meets(cx, msrvs::POINTER_CAST) {
|
||||
format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_paren())
|
||||
} else if from_ptr_ty.has_erased_regions() {
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {ty_snip}"))).to_string()
|
||||
|
|
@ -45,15 +56,22 @@ pub(super) fn check<'tcx>(
|
|||
sugg::make_unop(deref, arg.as_ty(format!("{cast} {ty_snip}"))).to_string()
|
||||
}
|
||||
} else if *from_ptr_ty == *to_ref_ty {
|
||||
if from_ptr_ty.has_erased_regions() {
|
||||
if msrv.meets(cx, msrvs::POINTER_CAST) {
|
||||
format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_paren())
|
||||
} else {
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}")))
|
||||
.to_string()
|
||||
}
|
||||
} else {
|
||||
if !from_ptr_ty.has_erased_regions() {
|
||||
sugg::make_unop(deref, arg).to_string()
|
||||
} else if !to_ref_ty.is_sized(cx.tcx, cx.typing_env()) {
|
||||
// 1. We can't suggest `.cast()`, because that requires `to_ref_ty` to be Sized.
|
||||
// 2. We can't suggest `as *mut/const () as *mut/const to_ref_ty`, because the former is a
|
||||
// thin pointer, whereas the latter is a wide pointer, due of its pointee, `to_ref_ty`,
|
||||
// being !Sized.
|
||||
//
|
||||
// The only remaining option is be to skip `*mut/const ()`, but that might not be safe to do
|
||||
// because of the erased regions in `from_ptr_ty`, so reduce the applicability.
|
||||
app = Applicability::MaybeIncorrect;
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{cast} {to_ref_ty}"))).to_string()
|
||||
} else if msrv.meets(cx, msrvs::POINTER_CAST) {
|
||||
format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_paren())
|
||||
} else {
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}"))).to_string()
|
||||
}
|
||||
} else {
|
||||
sugg::make_unop(deref, arg.as_ty(format!("{cast} {to_ref_ty}"))).to_string()
|
||||
|
|
|
|||
|
|
@ -45,7 +45,9 @@ pub(super) fn check<'tcx>(
|
|||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
triggered = true;
|
||||
} else if (cx.tcx.erase_and_anonymize_regions(from_ty) != cx.tcx.erase_and_anonymize_regions(to_ty)) && !const_context {
|
||||
} else if (cx.tcx.erase_and_anonymize_regions(from_ty) != cx.tcx.erase_and_anonymize_regions(to_ty))
|
||||
&& !const_context
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
TRANSMUTE_PTR_TO_PTR,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
|
||||
use clippy_utils::higher::{VecInitKind, get_vec_init_kind};
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty};
|
||||
use clippy_utils::{SpanlessEq, is_integer_literal, is_lint_allowed, path_to_local_id, peel_hir_expr_while, sym};
|
||||
|
|
@ -95,16 +95,13 @@ fn handle_uninit_vec_pair<'tcx>(
|
|||
|
||||
// Check T of Vec<T>
|
||||
if !is_uninit_value_valid_for_ty(cx, args.type_at(0)) {
|
||||
// FIXME: #7698, false positive of the internal lints
|
||||
#[expect(clippy::collapsible_span_lint_calls)]
|
||||
span_lint_and_then(
|
||||
span_lint_and_help(
|
||||
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`");
|
||||
},
|
||||
None,
|
||||
"initialize the buffer or wrap the content in `MaybeUninit`",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, peel_and_count_ty_refs};
|
||||
use clippy_utils::{fn_def_id, is_trait_method, path_to_local_id, peel_ref_operators, sym};
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_hir::intravisit::{Visitor, walk_expr};
|
||||
|
|
@ -61,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedPeekable {
|
|||
&& let Some(init) = local.init
|
||||
&& !init.span.from_expansion()
|
||||
&& let Some(ty) = cx.typeck_results().expr_ty_opt(init)
|
||||
&& let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty)
|
||||
&& let (ty, _, None | Some(Mutability::Mut)) = peel_and_count_ty_refs(ty)
|
||||
&& is_type_diagnostic_item(cx, ty, sym::IterPeekable)
|
||||
{
|
||||
let mut vis = PeekableVisitor::new(cx, binding);
|
||||
|
|
@ -211,7 +211,7 @@ impl<'tcx> Visitor<'tcx> for PeekableVisitor<'_, 'tcx> {
|
|||
|
||||
fn arg_is_mut_peekable(cx: &LateContext<'_>, arg: &Expr<'_>) -> bool {
|
||||
if let Some(ty) = cx.typeck_results().expr_ty_opt(arg)
|
||||
&& let (ty, _, Mutability::Mut) = peel_mid_ty_refs_is_mutable(ty)
|
||||
&& let (ty, _, None | Some(Mutability::Mut)) = peel_and_count_ty_refs(ty)
|
||||
&& is_type_diagnostic_item(cx, ty, sym::IterPeekable)
|
||||
{
|
||||
true
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
|
|||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
// Shouldn't lint when `expr` is in macro.
|
||||
if expr.span.in_external_macro(self.cx.tcx.sess.source_map()) {
|
||||
walk_expr(self, expr);
|
||||
return;
|
||||
}
|
||||
// Skip checking inside closures since they are visited through `Unwrap::check_fn()` already.
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@ use clippy_config::Conf;
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::ty::{same_type_and_consts, ty_from_hir_ty};
|
||||
use clippy_utils::ty::{same_type_modulo_regions, ty_from_hir_ty};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{CtorOf, DefKind, Res};
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty};
|
||||
use rustc_hir::{
|
||||
self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParam, GenericParamKind,
|
||||
HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind,
|
||||
self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParamKind, HirId, Impl,
|
||||
ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Ty as MiddleTy;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
use std::iter;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -101,17 +102,11 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
|
|||
let types_to_skip = generics
|
||||
.params
|
||||
.iter()
|
||||
.filter_map(|param| match param {
|
||||
GenericParam {
|
||||
kind:
|
||||
GenericParamKind::Const {
|
||||
ty: Ty { hir_id, .. }, ..
|
||||
},
|
||||
..
|
||||
} => Some(*hir_id),
|
||||
.filter_map(|param| match param.kind {
|
||||
GenericParamKind::Const { ty, .. } => Some(ty.hir_id),
|
||||
_ => None,
|
||||
})
|
||||
.chain(std::iter::once(self_ty.hir_id))
|
||||
.chain([self_ty.hir_id])
|
||||
.collect();
|
||||
StackItem::Check {
|
||||
impl_id: item.owner_id.def_id,
|
||||
|
|
@ -209,11 +204,11 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
|
|||
&& !types_to_skip.contains(&hir_ty.hir_id)
|
||||
&& let ty = ty_from_hir_ty(cx, hir_ty.as_unambig_ty())
|
||||
&& let impl_ty = cx.tcx.type_of(impl_id).instantiate_identity()
|
||||
&& same_type_and_consts(ty, impl_ty)
|
||||
&& same_type_modulo_regions(ty, impl_ty)
|
||||
// Ensure the type we encounter and the one from the impl have the same lifetime parameters. It may be that
|
||||
// the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`, in
|
||||
// the lifetime parameters of `ty` are elided (`impl<'a> Foo<'a> { fn new() -> Self { Foo{..} } }`), in
|
||||
// which case we must still trigger the lint.
|
||||
&& (has_no_lifetime(ty) || same_lifetimes(ty, impl_ty))
|
||||
&& same_lifetimes(ty, impl_ty)
|
||||
&& self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS)
|
||||
{
|
||||
span_lint(cx, hir_ty.span);
|
||||
|
|
@ -226,18 +221,16 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
|
|||
&& cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id).instantiate_identity()
|
||||
&& self.msrv.meets(cx, msrvs::TYPE_ALIAS_ENUM_VARIANTS)
|
||||
{
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
match expr.kind {
|
||||
ExprKind::Struct(QPath::Resolved(_, path), ..) => check_path(cx, path),
|
||||
ExprKind::Call(fun, _) => {
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind {
|
||||
check_path(cx, path);
|
||||
}
|
||||
},
|
||||
ExprKind::Path(QPath::Resolved(_, path)) => check_path(cx, path),
|
||||
_ => (),
|
||||
match expr.kind {
|
||||
ExprKind::Struct(QPath::Resolved(_, path), ..) => check_path(cx, path),
|
||||
ExprKind::Call(fun, _) => {
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind {
|
||||
check_path(cx, path);
|
||||
}
|
||||
},
|
||||
ExprKind::Path(QPath::Resolved(_, path)) => check_path(cx, path),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -307,36 +300,20 @@ fn lint_path_to_variant(cx: &LateContext<'_>, path: &Path<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if types `a` and `b` have the same lifetime parameters, otherwise returns
|
||||
/// `false`.
|
||||
/// Checks whether types `a` and `b` have the same lifetime parameters.
|
||||
///
|
||||
/// This function does not check that types `a` and `b` are the same types.
|
||||
fn same_lifetimes<'tcx>(a: MiddleTy<'tcx>, b: MiddleTy<'tcx>) -> bool {
|
||||
use rustc_middle::ty::{Adt, GenericArgKind};
|
||||
match (&a.kind(), &b.kind()) {
|
||||
(&Adt(_, args_a), &Adt(_, args_b)) => {
|
||||
args_a
|
||||
.iter()
|
||||
.zip(args_b.iter())
|
||||
.all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) {
|
||||
// TODO: Handle inferred lifetimes
|
||||
(GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b,
|
||||
(GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b),
|
||||
_ => true,
|
||||
})
|
||||
match (a.kind(), b.kind()) {
|
||||
(Adt(_, args_a), Adt(_, args_b)) => {
|
||||
iter::zip(*args_a, *args_b).all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) {
|
||||
// TODO: Handle inferred lifetimes
|
||||
(GenericArgKind::Lifetime(inner_a), GenericArgKind::Lifetime(inner_b)) => inner_a == inner_b,
|
||||
(GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => same_lifetimes(type_a, type_b),
|
||||
_ => true,
|
||||
})
|
||||
},
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `ty` has no lifetime parameter, otherwise returns `false`.
|
||||
fn has_no_lifetime(ty: MiddleTy<'_>) -> bool {
|
||||
use rustc_middle::ty::{Adt, GenericArgKind};
|
||||
match ty.kind() {
|
||||
&Adt(_, args) => !args
|
||||
.iter()
|
||||
// TODO: Handle inferred lifetimes
|
||||
.any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(..))),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_with_context};
|
||||
use clippy_utils::sugg::{DiagExt as _, Sugg};
|
||||
use clippy_utils::ty::{get_type_diagnostic_name, is_copy, is_type_diagnostic_item, same_type_and_consts};
|
||||
use clippy_utils::ty::{get_type_diagnostic_name, is_copy, is_type_diagnostic_item, same_type_modulo_regions};
|
||||
use clippy_utils::{
|
||||
get_parent_expr, is_inherent_method_call, is_trait_item, is_trait_method, is_ty_alias, path_to_local, sym,
|
||||
};
|
||||
|
|
@ -184,7 +184,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
&& (is_trait_item(cx, arg, sym::Into) || is_trait_item(cx, arg, sym::From))
|
||||
&& let ty::FnDef(_, args) = cx.typeck_results().expr_ty(arg).kind()
|
||||
&& let &[from_ty, to_ty] = args.into_type_list(cx.tcx).as_slice()
|
||||
&& same_type_and_consts(from_ty, to_ty)
|
||||
&& same_type_modulo_regions(from_ty, to_ty)
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
@ -207,7 +207,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
if is_trait_method(cx, e, sym::Into) && name.ident.name == sym::into {
|
||||
let a = cx.typeck_results().expr_ty(e);
|
||||
let b = cx.typeck_results().expr_ty(recv);
|
||||
if same_type_and_consts(a, b) {
|
||||
if same_type_modulo_regions(a, b) {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "<expr>", &mut app).0;
|
||||
span_lint_and_sugg(
|
||||
|
|
@ -324,7 +324,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
// If the types are identical then .into_iter() can be removed, unless the type
|
||||
// implements Copy, in which case .into_iter() returns a copy of the receiver and
|
||||
// cannot be safely omitted.
|
||||
if same_type_and_consts(a, b) && !is_copy(cx, b) {
|
||||
if same_type_modulo_regions(a, b) && !is_copy(cx, b) {
|
||||
// Below we check if the parent method call meets the following conditions:
|
||||
// 1. First parameter is `&mut self` (requires mutable reference)
|
||||
// 2. Second parameter implements the `FnMut` trait (e.g., Iterator::any)
|
||||
|
|
@ -371,7 +371,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
&& is_type_diagnostic_item(cx, a, sym::Result)
|
||||
&& let ty::Adt(_, args) = a.kind()
|
||||
&& let Some(a_type) = args.types().next()
|
||||
&& same_type_and_consts(a_type, b)
|
||||
&& same_type_modulo_regions(a_type, b)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
|
@ -396,7 +396,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
&& is_type_diagnostic_item(cx, a, sym::Result)
|
||||
&& let ty::Adt(_, args) = a.kind()
|
||||
&& let Some(a_type) = args.types().next()
|
||||
&& same_type_and_consts(a_type, b)
|
||||
&& same_type_modulo_regions(a_type, b)
|
||||
{
|
||||
let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from"));
|
||||
span_lint_and_help(
|
||||
|
|
@ -407,7 +407,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
None,
|
||||
hint,
|
||||
);
|
||||
} else if name == sym::from_fn && same_type_and_consts(a, b) {
|
||||
} else if name == sym::from_fn && same_type_modulo_regions(a, b) {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "<expr>", &mut app).maybe_paren();
|
||||
let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "clippy_utils"
|
||||
version = "0.1.91"
|
||||
version = "0.1.92"
|
||||
edition = "2024"
|
||||
description = "Helpful tools for writing lints, provided as they are used in Clippy"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
|
|||
|
||||
<!-- begin autogenerated nightly -->
|
||||
```
|
||||
nightly-2025-09-04
|
||||
nightly-2025-09-18
|
||||
```
|
||||
<!-- end autogenerated nightly -->
|
||||
|
||||
|
|
|
|||
|
|
@ -13,20 +13,24 @@
|
|||
//! if the span is not from a `macro_rules` based macro.
|
||||
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_ast as ast;
|
||||
use rustc_ast::AttrStyle;
|
||||
use rustc_ast::ast::{AttrKind, Attribute, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy};
|
||||
use rustc_ast::ast::{
|
||||
AttrKind, Attribute, GenericArgs, IntTy, LitIntType, LitKind, StrStyle, TraitObjectSyntax, UintTy,
|
||||
};
|
||||
use rustc_ast::token::CommentKind;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl,
|
||||
ImplItem, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety,
|
||||
TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
|
||||
ImplItem, ImplItemImplKind, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path,
|
||||
QPath, Safety, TraitImplHeader, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData,
|
||||
YieldSource,
|
||||
};
|
||||
use rustc_lint::{EarlyContext, LateContext, LintContext};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::symbol::{Ident, kw};
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_span::{Span, Symbol, sym};
|
||||
|
||||
/// The search pattern to look for. Used by `span_matches_pat`
|
||||
#[derive(Clone)]
|
||||
|
|
@ -289,7 +293,7 @@ fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
|
|||
&& !vis_span.is_empty()
|
||||
{
|
||||
start_pat = Pat::Str("pub");
|
||||
};
|
||||
}
|
||||
(start_pat, end_pat)
|
||||
}
|
||||
|
||||
|
|
@ -321,14 +325,17 @@ fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirI
|
|||
};
|
||||
match tcx.hir_node(hir_id) {
|
||||
Node::Item(Item { vis_span, .. })
|
||||
| Node::ImplItem(ImplItem { impl_kind: ImplItemImplKind::Inherent { vis_span, .. }, .. }) => {
|
||||
| Node::ImplItem(ImplItem {
|
||||
impl_kind: ImplItemImplKind::Inherent { vis_span, .. },
|
||||
..
|
||||
}) => {
|
||||
if !vis_span.is_empty() {
|
||||
start_pat = Pat::Str("pub")
|
||||
start_pat = Pat::Str("pub");
|
||||
}
|
||||
},
|
||||
Node::ImplItem(_) | Node::TraitItem(_) => {},
|
||||
_ => start_pat = Pat::Str(""),
|
||||
};
|
||||
}
|
||||
(start_pat, end_pat)
|
||||
}
|
||||
|
||||
|
|
@ -403,6 +410,7 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
|
|||
TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")),
|
||||
TyKind::Path(qpath) => qpath_search_pat(&qpath),
|
||||
TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")),
|
||||
TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ty_search_pat(binder_ty.inner_ty).1),
|
||||
TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => {
|
||||
(Pat::Str("dyn"), Pat::Str(""))
|
||||
},
|
||||
|
|
@ -411,6 +419,127 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) {
|
|||
}
|
||||
}
|
||||
|
||||
fn ast_ty_search_pat(ty: &ast::Ty) -> (Pat, Pat) {
|
||||
use ast::{Extern, FnRetTy, MutTy, Safety, TraitObjectSyntax, TyKind};
|
||||
|
||||
match &ty.kind {
|
||||
TyKind::Slice(..) | TyKind::Array(..) => (Pat::Str("["), Pat::Str("]")),
|
||||
TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ast_ty_search_pat(ty).1),
|
||||
TyKind::Ref(_, MutTy { ty, .. }) | TyKind::PinnedRef(_, MutTy { ty, .. }) => {
|
||||
(Pat::Str("&"), ast_ty_search_pat(ty).1)
|
||||
},
|
||||
TyKind::FnPtr(fn_ptr) => (
|
||||
if let Safety::Unsafe(_) = fn_ptr.safety {
|
||||
Pat::Str("unsafe")
|
||||
} else if let Extern::Explicit(strlit, _) = fn_ptr.ext
|
||||
&& strlit.symbol == sym::rust
|
||||
{
|
||||
Pat::MultiStr(&["fn", "extern"])
|
||||
} else {
|
||||
Pat::Str("extern")
|
||||
},
|
||||
match &fn_ptr.decl.output {
|
||||
FnRetTy::Default(_) => {
|
||||
if let [.., param] = &*fn_ptr.decl.inputs {
|
||||
ast_ty_search_pat(¶m.ty).1
|
||||
} else {
|
||||
Pat::Str("(")
|
||||
}
|
||||
},
|
||||
FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
|
||||
},
|
||||
),
|
||||
TyKind::Never => (Pat::Str("!"), Pat::Str("!")),
|
||||
// Parenthesis are trimmed from the text before the search patterns are matched.
|
||||
// See: `span_matches_pat`
|
||||
TyKind::Tup(tup) => match &**tup {
|
||||
[] => (Pat::Str(")"), Pat::Str("(")),
|
||||
[ty] => ast_ty_search_pat(ty),
|
||||
[head, .., tail] => (ast_ty_search_pat(head).0, ast_ty_search_pat(tail).1),
|
||||
},
|
||||
TyKind::ImplTrait(..) => (Pat::Str("impl"), Pat::Str("")),
|
||||
TyKind::Path(qself_path, path) => {
|
||||
let start = if qself_path.is_some() {
|
||||
Pat::Str("<")
|
||||
} else if let Some(first) = path.segments.first() {
|
||||
ident_search_pat(first.ident).0
|
||||
} else {
|
||||
// this shouldn't be possible, but sure
|
||||
Pat::Str("")
|
||||
};
|
||||
let end = if let Some(last) = path.segments.last() {
|
||||
match last.args.as_deref() {
|
||||
// last `>` in `std::foo::Bar<T>`
|
||||
Some(GenericArgs::AngleBracketed(_)) => Pat::Str(">"),
|
||||
Some(GenericArgs::Parenthesized(par_args)) => match &par_args.output {
|
||||
FnRetTy::Default(_) => {
|
||||
if let Some(last) = par_args.inputs.last() {
|
||||
// `B` in `(A, B)` -- `)` gets stripped
|
||||
ast_ty_search_pat(last).1
|
||||
} else {
|
||||
// `(` in `()` -- `)` gets stripped
|
||||
Pat::Str("(")
|
||||
}
|
||||
},
|
||||
// `C` in `(A, B) -> C`
|
||||
FnRetTy::Ty(ty) => ast_ty_search_pat(ty).1,
|
||||
},
|
||||
// last `..` in `(..)` -- `)` gets stripped
|
||||
Some(GenericArgs::ParenthesizedElided(_)) => Pat::Str(".."),
|
||||
// `bar` in `std::foo::bar`
|
||||
None => ident_search_pat(last.ident).1,
|
||||
}
|
||||
} else {
|
||||
// this shouldn't be possible, but sure
|
||||
#[allow(
|
||||
clippy::collapsible_else_if,
|
||||
reason = "we want to keep these cases together, since they are both impossible"
|
||||
)]
|
||||
if qself_path.is_some() {
|
||||
// last `>` in `<Vec as IntoIterator>`
|
||||
Pat::Str(">")
|
||||
} else {
|
||||
Pat::Str("")
|
||||
}
|
||||
};
|
||||
(start, end)
|
||||
},
|
||||
TyKind::Infer => (Pat::Str("_"), Pat::Str("_")),
|
||||
TyKind::Paren(ty) => ast_ty_search_pat(ty),
|
||||
TyKind::UnsafeBinder(binder_ty) => (Pat::Str("unsafe"), ast_ty_search_pat(&binder_ty.inner_ty).1),
|
||||
TyKind::TraitObject(_, trait_obj_syntax) => {
|
||||
if let TraitObjectSyntax::Dyn = trait_obj_syntax {
|
||||
(Pat::Str("dyn"), Pat::Str(""))
|
||||
} else {
|
||||
// NOTE: `TraitObject` is incomplete. It will always return true then.
|
||||
(Pat::Str(""), Pat::Str(""))
|
||||
}
|
||||
},
|
||||
TyKind::MacCall(mac_call) => {
|
||||
let start = if let Some(first) = mac_call.path.segments.first() {
|
||||
ident_search_pat(first.ident).0
|
||||
} else {
|
||||
Pat::Str("")
|
||||
};
|
||||
(start, Pat::Str(""))
|
||||
},
|
||||
|
||||
// implicit, so has no contents to match against
|
||||
TyKind::ImplicitSelf
|
||||
|
||||
// experimental
|
||||
|TyKind::Pat(..)
|
||||
|
||||
// unused
|
||||
| TyKind::CVarArgs
|
||||
| TyKind::Typeof(_)
|
||||
|
||||
// placeholder
|
||||
| TyKind::Dummy
|
||||
| TyKind::Err(_) => (Pat::Str(""), Pat::Str("")),
|
||||
}
|
||||
}
|
||||
|
||||
fn ident_search_pat(ident: Ident) -> (Pat, Pat) {
|
||||
(Pat::Sym(ident.name), Pat::Sym(ident.name))
|
||||
}
|
||||
|
|
@ -445,6 +574,7 @@ impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&sel
|
|||
impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self));
|
||||
|
||||
impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self));
|
||||
impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: ast::Ty) => ast_ty_search_pat(self));
|
||||
|
||||
impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
|
||||
type Context = LateContext<'cx>;
|
||||
|
|
|
|||
|
|
@ -329,13 +329,17 @@ pub fn is_wild(pat: &Pat<'_>) -> bool {
|
|||
matches!(pat.kind, PatKind::Wild)
|
||||
}
|
||||
|
||||
// Checks if arm has the form `None => None`
|
||||
pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
|
||||
matches!(
|
||||
arm.pat.kind,
|
||||
/// Checks if the `pat` is `None`.
|
||||
pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
|
||||
matches!(pat.kind,
|
||||
PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. })
|
||||
if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone)
|
||||
)
|
||||
if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionNone))
|
||||
}
|
||||
|
||||
/// Checks if `arm` has the form `None => None`.
|
||||
pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
|
||||
is_none_pattern(cx, arm.pat)
|
||||
&& matches!(peel_blocks(arm.body).kind, ExprKind::Path(qpath) if is_res_lang_ctor(cx, cx.qpath_res(&qpath, arm.body.hir_id), OptionNone))
|
||||
}
|
||||
|
||||
/// Checks if the given `QPath` belongs to a type alias.
|
||||
|
|
@ -2374,15 +2378,12 @@ pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize)
|
|||
}
|
||||
}
|
||||
|
||||
/// Peels off all references on the type. Returns the underlying type and the number of references
|
||||
/// removed.
|
||||
pub fn peel_middle_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) {
|
||||
let mut count = 0;
|
||||
while let rustc_ty::Ref(_, dest_ty, _) = ty.kind() {
|
||||
ty = *dest_ty;
|
||||
count += 1;
|
||||
/// Returns the base type for HIR references and pointers.
|
||||
pub fn peel_hir_ty_refs_and_ptrs<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
|
||||
match &ty.kind {
|
||||
TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty) => peel_hir_ty_refs_and_ptrs(mut_ty.ty),
|
||||
_ => ty,
|
||||
}
|
||||
(ty, count)
|
||||
}
|
||||
|
||||
/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ macro_rules! msrv_aliases {
|
|||
// names may refer to stabilized feature flags or library items
|
||||
msrv_aliases! {
|
||||
1,88,0 { LET_CHAINS }
|
||||
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF }
|
||||
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
|
||||
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }
|
||||
1,84,0 { CONST_OPTION_AS_SLICE, MANUAL_DANGLING_PTR }
|
||||
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
|
||||
|
|
@ -73,7 +73,7 @@ msrv_aliases! {
|
|||
1,29,0 { ITER_FLATTEN }
|
||||
1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
|
||||
1,27,0 { ITERATOR_TRY_FOLD }
|
||||
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
|
||||
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS }
|
||||
1,24,0 { IS_ASCII_DIGIT, PTR_NULL }
|
||||
1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
|
||||
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST, EXPECT_ERR }
|
||||
|
|
|
|||
|
|
@ -215,6 +215,11 @@ impl fmt::Display for SourceText {
|
|||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for SourceText {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_source_range(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> {
|
||||
let start = sm.lookup_byte_offset(sp.start);
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ generate! {
|
|||
cycle,
|
||||
cyclomatic_complexity,
|
||||
de,
|
||||
deprecated_in_future,
|
||||
diagnostics,
|
||||
disallowed_types,
|
||||
drain,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use rustc_hir as hir;
|
|||
use rustc_hir::attrs::AttributeKind;
|
||||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Expr, FnDecl, LangItem, TyKind, find_attr};
|
||||
use rustc_hir::{Expr, FnDecl, LangItem, find_attr};
|
||||
use rustc_hir_analysis::lower_ty;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -43,13 +43,8 @@ pub use type_certainty::expr_type_is_certain;
|
|||
/// Lower a [`hir::Ty`] to a [`rustc_middle::ty::Ty`].
|
||||
pub fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>) -> 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
|
||||
}
|
||||
})
|
||||
.filter(|results| results.hir_owner == hir_ty.hir_id.owner)
|
||||
.and_then(|results| results.node_type_opt(hir_ty.hir_id))
|
||||
.unwrap_or_else(|| lower_ty(cx.tcx, hir_ty))
|
||||
}
|
||||
|
||||
|
|
@ -475,63 +470,50 @@ pub fn needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
|||
needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Returns `true` if `ty` denotes an `unsafe fn`.
|
||||
pub fn is_unsafe_fn<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
ty.is_fn() && ty.fn_sig(cx.tcx).safety().is_unsafe()
|
||||
}
|
||||
|
||||
/// 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::Ref(_, ref mut_ty) => walk_ptrs_hir_ty(mut_ty.ty),
|
||||
_ => ty,
|
||||
/// Peels off all references on the type. Returns the underlying type, the number of references
|
||||
/// removed, and, if there were any such references, whether the pointer is ultimately mutable or
|
||||
/// not.
|
||||
pub fn peel_and_count_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize, Option<Mutability>) {
|
||||
let mut count = 0;
|
||||
let mut mutbl = None;
|
||||
while let ty::Ref(_, dest_ty, m) = ty.kind() {
|
||||
ty = *dest_ty;
|
||||
count += 1;
|
||||
mutbl.replace(mutbl.map_or(*m, |mutbl: Mutability| mutbl.min(*m)));
|
||||
}
|
||||
(ty, count, mutbl)
|
||||
}
|
||||
|
||||
/// 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<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
|
||||
/// Checks whether `a` and `b` are same types having same `Const` generic args, but ignores
|
||||
/// lifetimes.
|
||||
///
|
||||
/// For example, the function would return `true` for
|
||||
/// - `u32` and `u32`
|
||||
/// - `[u8; N]` and `[u8; M]`, if `N=M`
|
||||
/// - `Option<T>` and `Option<U>`, if `same_type_modulo_regions(T, U)` holds
|
||||
/// - `&'a str` and `&'b str`
|
||||
///
|
||||
/// and `false` for:
|
||||
/// - `Result<u32, String>` and `Result<usize, String>`
|
||||
pub fn same_type_modulo_regions<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
|
||||
match (&a.kind(), &b.kind()) {
|
||||
(&ty::Adt(did_a, args_a), &ty::Adt(did_b, args_b)) => {
|
||||
if did_a != did_b {
|
||||
return false;
|
||||
}
|
||||
|
||||
args_a
|
||||
.iter()
|
||||
.zip(args_b.iter())
|
||||
.all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) {
|
||||
(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,
|
||||
})
|
||||
iter::zip(*args_a, *args_b).all(|(arg_a, arg_b)| match (arg_a.kind(), arg_b.kind()) {
|
||||
(GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b,
|
||||
(GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => {
|
||||
same_type_modulo_regions(type_a, type_b)
|
||||
},
|
||||
_ => true,
|
||||
})
|
||||
},
|
||||
_ => a == b,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "declare_clippy_lint"
|
||||
version = "0.1.91"
|
||||
version = "0.1.92"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/rust-lang/rust-clippy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[toolchain]
|
||||
# begin autogenerated nightly
|
||||
channel = "nightly-2025-09-04"
|
||||
channel = "nightly-2025-09-18"
|
||||
# end autogenerated nightly
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
profile = "minimal"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ fn no_profile_in_cargo_toml() {
|
|||
// keep it fast and simple.
|
||||
for entry in WalkDir::new(".")
|
||||
.into_iter()
|
||||
// Do not recurse into `target` as lintcheck might put some sources (and their
|
||||
// `Cargo.toml`) there.
|
||||
.filter_entry(|e| e.file_name() != "target")
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| e.file_name().to_str() == Some("Cargo.toml"))
|
||||
{
|
||||
|
|
|
|||
114
tests/ui-toml/ref_option/ref_option.all.fixed
Normal file
114
tests/ui-toml/ref_option/ref_option.all.fixed
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//@aux-build:../../ui/auxiliary/proc_macros.rs
|
||||
//@revisions: private all
|
||||
//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private
|
||||
//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all
|
||||
|
||||
#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)]
|
||||
#![warn(clippy::ref_option)]
|
||||
|
||||
fn opt_u8(a: Option<&u8>) {}
|
||||
//~^ ref_option
|
||||
fn opt_gen<T>(a: Option<&T>) {}
|
||||
//~^ ref_option
|
||||
fn opt_string(a: std::option::Option<&String>) {}
|
||||
//~^ ref_option
|
||||
fn ret_u8<'a>(p: &'a str) -> Option<&'a u8> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
fn ret_u8_static() -> Option<&'static u8> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
fn mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
|
||||
//~^ ref_option
|
||||
fn ret_box<'a>() -> Option<&'a Box<u8>> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
|
||||
pub fn pub_opt_string(a: Option<&String>) {}
|
||||
//~[all]^ ref_option
|
||||
pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
|
||||
//~[all]^ ref_option
|
||||
|
||||
pub struct PubStruct;
|
||||
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: Option<&()>) {}
|
||||
//~[all]^ ref_option
|
||||
pub fn pub_opt_ret(&self) -> Option<&String> {
|
||||
//~[all]^ ref_option
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: Option<&()>) {}
|
||||
//~^ ref_option
|
||||
fn private_opt_ret(&self) -> Option<&String> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
// valid, don't change
|
||||
fn mut_u8(a: &mut Option<u8>) {}
|
||||
pub fn pub_mut_u8(a: &mut Option<String>) {}
|
||||
|
||||
// might be good to catch in the future
|
||||
fn mut_u8_ref(a: &mut &Option<u8>) {}
|
||||
pub fn pub_mut_u8_ref(a: &mut &Option<String>) {}
|
||||
fn lambdas() {
|
||||
// Not handled for now, not sure if we should
|
||||
let x = |a: &Option<String>| {};
|
||||
let x = |a: &Option<String>| -> &Option<String> { panic!() };
|
||||
}
|
||||
|
||||
pub mod external {
|
||||
proc_macros::external!(
|
||||
fn opt_u8(a: &Option<u8>) {}
|
||||
fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
panic!()
|
||||
}
|
||||
pub fn pub_opt_u8(a: &Option<u8>) {}
|
||||
|
||||
pub struct PubStruct;
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: &Option<()>) {}
|
||||
fn private_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
pub mod proc_macros {
|
||||
proc_macros::with_span!(
|
||||
span
|
||||
|
||||
fn opt_u8(a: &Option<u8>) {}
|
||||
fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
panic!()
|
||||
}
|
||||
pub fn pub_opt_u8(a: &Option<u8>) {}
|
||||
|
||||
pub struct PubStruct;
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: &Option<()>) {}
|
||||
fn private_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:8:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:9:1
|
||||
|
|
||||
LL | fn opt_u8(a: &Option<u8>) {}
|
||||
| ^^^^^^^^^^^^^-----------^^^^
|
||||
|
|
@ -10,7 +10,7 @@ LL | fn opt_u8(a: &Option<u8>) {}
|
|||
= help: to override `-D warnings` add `#[allow(clippy::ref_option)]`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:10:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:11:1
|
||||
|
|
||||
LL | fn opt_gen<T>(a: &Option<T>) {}
|
||||
| ^^^^^^^^^^^^^^^^^----------^^^^
|
||||
|
|
@ -18,7 +18,7 @@ LL | fn opt_gen<T>(a: &Option<T>) {}
|
|||
| help: change this to: `Option<&T>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:12:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:13:1
|
||||
|
|
||||
LL | fn opt_string(a: &std::option::Option<String>) {}
|
||||
| ^^^^^^^^^^^^^^^^^----------------------------^^^^
|
||||
|
|
@ -26,10 +26,10 @@ LL | fn opt_string(a: &std::option::Option<String>) {}
|
|||
| help: change this to: `std::option::Option<&String>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:14:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:15:1
|
||||
|
|
||||
LL | fn ret_string<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
| ^ -------------- help: change this to: `Option<&'a u8>`
|
||||
LL | fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
| ^ -------------- help: change this to: `Option<&'a u8>`
|
||||
| _|
|
||||
| |
|
||||
LL | |
|
||||
|
|
@ -38,10 +38,10 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:18:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:19:1
|
||||
|
|
||||
LL | fn ret_string_static() -> &'static Option<u8> {
|
||||
| ^ ------------------- help: change this to: `Option<&'static u8>`
|
||||
LL | fn ret_u8_static() -> &'static Option<u8> {
|
||||
| ^ ------------------- help: change this to: `Option<&'static u8>`
|
||||
| _|
|
||||
| |
|
||||
LL | |
|
||||
|
|
@ -50,7 +50,7 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:22:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:23:1
|
||||
|
|
||||
LL | fn mult_string(a: &Option<String>, b: &Option<Vec<u8>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -62,7 +62,7 @@ LL + fn mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
|
|||
|
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:24:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:25:1
|
||||
|
|
||||
LL | fn ret_box<'a>() -> &'a Option<Box<u8>> {
|
||||
| ^ ------------------- help: change this to: `Option<&'a Box<u8>>`
|
||||
|
|
@ -74,7 +74,7 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:29:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:30:1
|
||||
|
|
||||
LL | pub fn pub_opt_string(a: &Option<String>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^---------------^^^^
|
||||
|
|
@ -82,7 +82,7 @@ LL | pub fn pub_opt_string(a: &Option<String>) {}
|
|||
| help: change this to: `Option<&String>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:31:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:32:1
|
||||
|
|
||||
LL | pub fn pub_mult_string(a: &Option<String>, b: &Option<Vec<u8>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -94,39 +94,7 @@ LL + pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
|
|||
|
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:35:5
|
||||
|
|
||||
LL | fn pub_trait_opt(&self, a: &Option<Vec<u8>>);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^^
|
||||
| |
|
||||
| help: change this to: `Option<&Vec<u8>>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:37:5
|
||||
|
|
||||
LL | fn pub_trait_ret(&self) -> &Option<Vec<u8>>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------^
|
||||
| |
|
||||
| help: change this to: `Option<&Vec<u8>>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:42:5
|
||||
|
|
||||
LL | fn trait_opt(&self, a: &Option<String>);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^---------------^^
|
||||
| |
|
||||
| help: change this to: `Option<&String>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:44:5
|
||||
|
|
||||
LL | fn trait_ret(&self) -> &Option<String>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^---------------^
|
||||
| |
|
||||
| help: change this to: `Option<&String>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:51:5
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:38:5
|
||||
|
|
||||
LL | pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^
|
||||
|
|
@ -134,7 +102,7 @@ LL | pub fn pub_opt_params(&self, a: &Option<()>) {}
|
|||
| help: change this to: `Option<&()>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:53:5
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:40:5
|
||||
|
|
||||
LL | pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
| ^ --------------- help: change this to: `Option<&String>`
|
||||
|
|
@ -146,7 +114,7 @@ LL | | }
|
|||
| |_____^
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:58:5
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:45:5
|
||||
|
|
||||
LL | fn private_opt_params(&self, a: &Option<()>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^
|
||||
|
|
@ -154,7 +122,7 @@ LL | fn private_opt_params(&self, a: &Option<()>) {}
|
|||
| help: change this to: `Option<&()>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:60:5
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:47:5
|
||||
|
|
||||
LL | fn private_opt_ret(&self) -> &Option<String> {
|
||||
| ^ --------------- help: change this to: `Option<&String>`
|
||||
|
|
@ -165,5 +133,5 @@ LL | | panic!()
|
|||
LL | | }
|
||||
| |_____^
|
||||
|
||||
error: aborting due to 17 previous errors
|
||||
error: aborting due to 13 previous errors
|
||||
|
||||
114
tests/ui-toml/ref_option/ref_option.private.fixed
Normal file
114
tests/ui-toml/ref_option/ref_option.private.fixed
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//@aux-build:../../ui/auxiliary/proc_macros.rs
|
||||
//@revisions: private all
|
||||
//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private
|
||||
//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all
|
||||
|
||||
#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)]
|
||||
#![warn(clippy::ref_option)]
|
||||
|
||||
fn opt_u8(a: Option<&u8>) {}
|
||||
//~^ ref_option
|
||||
fn opt_gen<T>(a: Option<&T>) {}
|
||||
//~^ ref_option
|
||||
fn opt_string(a: std::option::Option<&String>) {}
|
||||
//~^ ref_option
|
||||
fn ret_u8<'a>(p: &'a str) -> Option<&'a u8> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
fn ret_u8_static() -> Option<&'static u8> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
fn mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
|
||||
//~^ ref_option
|
||||
fn ret_box<'a>() -> Option<&'a Box<u8>> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
|
||||
pub fn pub_opt_string(a: &Option<String>) {}
|
||||
//~[all]^ ref_option
|
||||
pub fn pub_mult_string(a: &Option<String>, b: &Option<Vec<u8>>) {}
|
||||
//~[all]^ ref_option
|
||||
|
||||
pub struct PubStruct;
|
||||
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
//~[all]^ ref_option
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
//~[all]^ ref_option
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: Option<&()>) {}
|
||||
//~^ ref_option
|
||||
fn private_opt_ret(&self) -> Option<&String> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
// valid, don't change
|
||||
fn mut_u8(a: &mut Option<u8>) {}
|
||||
pub fn pub_mut_u8(a: &mut Option<String>) {}
|
||||
|
||||
// might be good to catch in the future
|
||||
fn mut_u8_ref(a: &mut &Option<u8>) {}
|
||||
pub fn pub_mut_u8_ref(a: &mut &Option<String>) {}
|
||||
fn lambdas() {
|
||||
// Not handled for now, not sure if we should
|
||||
let x = |a: &Option<String>| {};
|
||||
let x = |a: &Option<String>| -> &Option<String> { panic!() };
|
||||
}
|
||||
|
||||
pub mod external {
|
||||
proc_macros::external!(
|
||||
fn opt_u8(a: &Option<u8>) {}
|
||||
fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
panic!()
|
||||
}
|
||||
pub fn pub_opt_u8(a: &Option<u8>) {}
|
||||
|
||||
pub struct PubStruct;
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: &Option<()>) {}
|
||||
fn private_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
pub mod proc_macros {
|
||||
proc_macros::with_span!(
|
||||
span
|
||||
|
||||
fn opt_u8(a: &Option<u8>) {}
|
||||
fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
panic!()
|
||||
}
|
||||
pub fn pub_opt_u8(a: &Option<u8>) {}
|
||||
|
||||
pub struct PubStruct;
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: &Option<()>) {}
|
||||
fn private_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:8:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:9:1
|
||||
|
|
||||
LL | fn opt_u8(a: &Option<u8>) {}
|
||||
| ^^^^^^^^^^^^^-----------^^^^
|
||||
|
|
@ -10,7 +10,7 @@ LL | fn opt_u8(a: &Option<u8>) {}
|
|||
= help: to override `-D warnings` add `#[allow(clippy::ref_option)]`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:10:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:11:1
|
||||
|
|
||||
LL | fn opt_gen<T>(a: &Option<T>) {}
|
||||
| ^^^^^^^^^^^^^^^^^----------^^^^
|
||||
|
|
@ -18,7 +18,7 @@ LL | fn opt_gen<T>(a: &Option<T>) {}
|
|||
| help: change this to: `Option<&T>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:12:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:13:1
|
||||
|
|
||||
LL | fn opt_string(a: &std::option::Option<String>) {}
|
||||
| ^^^^^^^^^^^^^^^^^----------------------------^^^^
|
||||
|
|
@ -26,10 +26,10 @@ LL | fn opt_string(a: &std::option::Option<String>) {}
|
|||
| help: change this to: `std::option::Option<&String>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:14:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:15:1
|
||||
|
|
||||
LL | fn ret_string<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
| ^ -------------- help: change this to: `Option<&'a u8>`
|
||||
LL | fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
| ^ -------------- help: change this to: `Option<&'a u8>`
|
||||
| _|
|
||||
| |
|
||||
LL | |
|
||||
|
|
@ -38,10 +38,10 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:18:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:19:1
|
||||
|
|
||||
LL | fn ret_string_static() -> &'static Option<u8> {
|
||||
| ^ ------------------- help: change this to: `Option<&'static u8>`
|
||||
LL | fn ret_u8_static() -> &'static Option<u8> {
|
||||
| ^ ------------------- help: change this to: `Option<&'static u8>`
|
||||
| _|
|
||||
| |
|
||||
LL | |
|
||||
|
|
@ -50,7 +50,7 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:22:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:23:1
|
||||
|
|
||||
LL | fn mult_string(a: &Option<String>, b: &Option<Vec<u8>>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -62,7 +62,7 @@ LL + fn mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
|
|||
|
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:24:1
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:25:1
|
||||
|
|
||||
LL | fn ret_box<'a>() -> &'a Option<Box<u8>> {
|
||||
| ^ ------------------- help: change this to: `Option<&'a Box<u8>>`
|
||||
|
|
@ -74,23 +74,7 @@ LL | | }
|
|||
| |_^
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:42:5
|
||||
|
|
||||
LL | fn trait_opt(&self, a: &Option<String>);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^---------------^^
|
||||
| |
|
||||
| help: change this to: `Option<&String>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:44:5
|
||||
|
|
||||
LL | fn trait_ret(&self) -> &Option<String>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^---------------^
|
||||
| |
|
||||
| help: change this to: `Option<&String>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:58:5
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:45:5
|
||||
|
|
||||
LL | fn private_opt_params(&self, a: &Option<()>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------^^^^
|
||||
|
|
@ -98,7 +82,7 @@ LL | fn private_opt_params(&self, a: &Option<()>) {}
|
|||
| help: change this to: `Option<&()>`
|
||||
|
||||
error: it is more idiomatic to use `Option<&T>` instead of `&Option<T>`
|
||||
--> tests/ui/ref_option/ref_option.rs:60:5
|
||||
--> tests/ui-toml/ref_option/ref_option.rs:47:5
|
||||
|
|
||||
LL | fn private_opt_ret(&self) -> &Option<String> {
|
||||
| ^ --------------- help: change this to: `Option<&String>`
|
||||
|
|
@ -109,5 +93,5 @@ LL | | panic!()
|
|||
LL | | }
|
||||
| |_____^
|
||||
|
||||
error: aborting due to 11 previous errors
|
||||
error: aborting due to 9 previous errors
|
||||
|
||||
114
tests/ui-toml/ref_option/ref_option.rs
Normal file
114
tests/ui-toml/ref_option/ref_option.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//@aux-build:../../ui/auxiliary/proc_macros.rs
|
||||
//@revisions: private all
|
||||
//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/private
|
||||
//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/ref_option/all
|
||||
|
||||
#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)]
|
||||
#![warn(clippy::ref_option)]
|
||||
|
||||
fn opt_u8(a: &Option<u8>) {}
|
||||
//~^ ref_option
|
||||
fn opt_gen<T>(a: &Option<T>) {}
|
||||
//~^ ref_option
|
||||
fn opt_string(a: &std::option::Option<String>) {}
|
||||
//~^ ref_option
|
||||
fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
fn ret_u8_static() -> &'static Option<u8> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
fn mult_string(a: &Option<String>, b: &Option<Vec<u8>>) {}
|
||||
//~^ ref_option
|
||||
fn ret_box<'a>() -> &'a Option<Box<u8>> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
|
||||
pub fn pub_opt_string(a: &Option<String>) {}
|
||||
//~[all]^ ref_option
|
||||
pub fn pub_mult_string(a: &Option<String>, b: &Option<Vec<u8>>) {}
|
||||
//~[all]^ ref_option
|
||||
|
||||
pub struct PubStruct;
|
||||
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
//~[all]^ ref_option
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
//~[all]^ ref_option
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: &Option<()>) {}
|
||||
//~^ ref_option
|
||||
fn private_opt_ret(&self) -> &Option<String> {
|
||||
//~^ ref_option
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
// valid, don't change
|
||||
fn mut_u8(a: &mut Option<u8>) {}
|
||||
pub fn pub_mut_u8(a: &mut Option<String>) {}
|
||||
|
||||
// might be good to catch in the future
|
||||
fn mut_u8_ref(a: &mut &Option<u8>) {}
|
||||
pub fn pub_mut_u8_ref(a: &mut &Option<String>) {}
|
||||
fn lambdas() {
|
||||
// Not handled for now, not sure if we should
|
||||
let x = |a: &Option<String>| {};
|
||||
let x = |a: &Option<String>| -> &Option<String> { panic!() };
|
||||
}
|
||||
|
||||
pub mod external {
|
||||
proc_macros::external!(
|
||||
fn opt_u8(a: &Option<u8>) {}
|
||||
fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
panic!()
|
||||
}
|
||||
pub fn pub_opt_u8(a: &Option<u8>) {}
|
||||
|
||||
pub struct PubStruct;
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: &Option<()>) {}
|
||||
fn private_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
pub mod proc_macros {
|
||||
proc_macros::with_span!(
|
||||
span
|
||||
|
||||
fn opt_u8(a: &Option<u8>) {}
|
||||
fn ret_u8<'a>(p: &'a str) -> &'a Option<u8> {
|
||||
panic!()
|
||||
}
|
||||
pub fn pub_opt_u8(a: &Option<u8>) {}
|
||||
|
||||
pub struct PubStruct;
|
||||
impl PubStruct {
|
||||
pub fn pub_opt_params(&self, a: &Option<()>) {}
|
||||
pub fn pub_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn private_opt_params(&self, a: &Option<()>) {}
|
||||
fn private_opt_ret(&self) -> &Option<String> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue