Merge commit 'cdbbf3afda' into clippy-subtree-update
This commit is contained in:
parent
0a64bfd685
commit
4e614bf683
126 changed files with 2759 additions and 475 deletions
14
.github/workflows/feature_freeze.yml
vendored
14
.github/workflows/feature_freeze.yml
vendored
|
|
@ -1,7 +1,11 @@
|
|||
name: Feature freeze check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'clippy_lints/src/declared_lints.rs'
|
||||
|
||||
|
|
@ -9,6 +13,12 @@ jobs:
|
|||
auto-comment:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
# Do not in any case add code that runs anything coming from the the content
|
||||
# of the pull request, as malicious code would be able to access the private
|
||||
# GitHub token.
|
||||
steps:
|
||||
- name: Check PR Changes
|
||||
id: pr-changes
|
||||
|
|
@ -19,7 +29,7 @@ jobs:
|
|||
run: |
|
||||
# Use GitHub API to create a comment on the PR
|
||||
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||
COMMENT="**Seems that you are trying to add a new lint!**\nWe 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 August 1st and focusing on bugfixes.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team"
|
||||
COMMENT="**Seems that you are trying to add a new lint!**\nWe 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.\nThanks a lot for your contribution, and sorry for the inconvenience.\nWith ❤ from the Clippy team\n\n@rustbot note Feature-freeze\n@rustbot blocked\n@rustbot label +A-lint\n"
|
||||
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
|
||||
COMMENT_URL="https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments"
|
||||
curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}"
|
||||
|
|
|
|||
28
.github/workflows/lintcheck.yml
vendored
28
.github/workflows/lintcheck.yml
vendored
|
|
@ -128,21 +128,27 @@ jobs:
|
|||
- name: Download JSON
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Store PR number
|
||||
run: echo ${{ github.event.pull_request.number }} > pr.txt
|
||||
|
||||
- name: Diff results
|
||||
# GH's summery has a maximum size of 1024k:
|
||||
# https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary
|
||||
# That's why we first log to file and then to the summary and logs
|
||||
# GH's summery has a maximum size of 1MiB:
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#step-isolation-and-limits
|
||||
# We upload the full diff as an artifact in case it's truncated
|
||||
run: |
|
||||
./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --truncate >> truncated_diff.md
|
||||
head -c 1024000 truncated_diff.md >> $GITHUB_STEP_SUMMARY
|
||||
cat truncated_diff.md
|
||||
./target/debug/lintcheck diff {base,head}/ci_crates_logs.json >> full_diff.md
|
||||
./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --truncate | head -c 1M > $GITHUB_STEP_SUMMARY
|
||||
./target/debug/lintcheck diff {base,head}/ci_crates_logs.json --write-summary summary.json > full_diff.md
|
||||
|
||||
- name: Upload full diff
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: diff
|
||||
if-no-files-found: ignore
|
||||
name: full_diff
|
||||
path: full_diff.md
|
||||
|
||||
- name: Upload summary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: summary
|
||||
path: |
|
||||
full_diff.md
|
||||
truncated_diff.md
|
||||
summary.json
|
||||
pr.txt
|
||||
|
|
|
|||
106
.github/workflows/lintcheck_summary.yml
vendored
Normal file
106
.github/workflows/lintcheck_summary.yml
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
name: Lintcheck summary
|
||||
|
||||
# The workflow_run event runs in the context of the Clippy repo giving it write
|
||||
# access, needed here to create a PR comment when the PR originates from a fork
|
||||
#
|
||||
# The summary artifact is a JSON file that we verify in this action to prevent
|
||||
# the creation of arbitrary comments
|
||||
#
|
||||
# This action must not checkout/run code from the originating workflow_run
|
||||
# or directly interpolate ${{}} untrusted fields into code
|
||||
#
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run
|
||||
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [Lintcheck]
|
||||
types: [completed]
|
||||
|
||||
# Restrict the default permission scope https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#defining-access-for-the-github_token-scopes
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
download:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: summary
|
||||
path: untrusted
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Format comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require("fs");
|
||||
const assert = require("assert/strict");
|
||||
|
||||
function validateName(s) {
|
||||
assert.match(s, /^[a-z0-9_:]+$/);
|
||||
return s;
|
||||
}
|
||||
|
||||
function validateInt(i) {
|
||||
assert.ok(Number.isInteger(i));
|
||||
return i;
|
||||
}
|
||||
|
||||
function tryReadSummary() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync("untrusted/summary.json"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const prNumber = parseInt(fs.readFileSync("untrusted/pr.txt"), 10);
|
||||
core.exportVariable("PR", prNumber.toString());
|
||||
|
||||
const untrustedSummary = tryReadSummary();
|
||||
if (!Array.isArray(untrustedSummary)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let summary = `Lintcheck changes for ${context.payload.workflow_run.head_sha}
|
||||
|
||||
| Lint | Added | Removed | Changed |
|
||||
| ---- | ----: | ------: | ------: |
|
||||
`;
|
||||
|
||||
for (const untrustedRow of untrustedSummary) {
|
||||
const name = validateName(untrustedRow.name);
|
||||
|
||||
const added = validateInt(untrustedRow.added);
|
||||
const removed = validateInt(untrustedRow.removed);
|
||||
const changed = validateInt(untrustedRow.changed);
|
||||
|
||||
const id = name.replace("clippy::", "user-content-").replaceAll("_", "-");
|
||||
const url = `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${context.payload.workflow_run.id}#${id}`;
|
||||
|
||||
summary += `| [\`${name}\`](${url}) | ${added} | ${removed} | ${changed} |\n`;
|
||||
}
|
||||
|
||||
summary += "\nThis comment will be updated if you push new changes";
|
||||
|
||||
fs.writeFileSync("summary.md", summary);
|
||||
|
||||
- name: Create/update comment
|
||||
run: |
|
||||
if [[ -f summary.md ]]; then
|
||||
gh pr comment "$PR" --body-file summary.md --edit-last --create-if-none
|
||||
else
|
||||
# There were no changes detected by Lintcheck
|
||||
# - If a comment exists from a previous run that did detect changes, edit it (--edit-last)
|
||||
# - If no comment exists do not create one, the `gh` command exits with an error which
|
||||
# `|| true` ignores
|
||||
gh pr comment "$PR" --body "No changes for ${{ github.event.workflow_run.head_sha }}" --edit-last || true
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
12
Cargo.toml
12
Cargo.toml
|
|
@ -28,13 +28,13 @@ declare_clippy_lint = { path = "declare_clippy_lint" }
|
|||
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
|
||||
clippy_lints_internal = { path = "clippy_lints_internal", optional = true }
|
||||
tempfile = { version = "3.20", optional = true }
|
||||
termize = "0.1"
|
||||
termize = "0.2"
|
||||
color-print = "0.3.4"
|
||||
anstream = "0.6.18"
|
||||
|
||||
[dev-dependencies]
|
||||
cargo_metadata = "0.18.1"
|
||||
ui_test = "0.29.2"
|
||||
ui_test = "0.30.2"
|
||||
regex = "1.5.5"
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
serde_json = "1.0.122"
|
||||
|
|
@ -45,14 +45,6 @@ itertools = "0.12"
|
|||
pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] }
|
||||
askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] }
|
||||
|
||||
# UI test dependencies
|
||||
if_chain = "1.0"
|
||||
quote = "1.0.25"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
futures = "0.3"
|
||||
parking_lot = "0.12"
|
||||
tokio = { version = "1", features = ["io-util"] }
|
||||
|
||||
[build-dependencies]
|
||||
rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" }
|
||||
|
||||
|
|
|
|||
|
|
@ -109,4 +109,4 @@ worth backporting this.
|
|||
When a PR is backported to Rust `beta`, label the PR with `beta-accepted`. This
|
||||
will then get picked up when [writing the changelog].
|
||||
|
||||
[writing the changelog]: changelog_update.md#31-include-beta-accepted-prs
|
||||
[writing the changelog]: changelog_update.md#4-include-beta-accepted-prs
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ Usually you want to write the changelog of the **upcoming stable release**. Make
|
|||
sure though, that `beta` was already branched in the Rust repository.
|
||||
|
||||
To find the commit hash, issue the following command when in a `rust-lang/rust`
|
||||
checkout:
|
||||
checkout (most of the time on the `upstream/beta` branch):
|
||||
```
|
||||
git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g"
|
||||
```
|
||||
|
|
@ -48,16 +48,13 @@ git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into
|
|||
Once you've got the correct commit range, run
|
||||
|
||||
```
|
||||
util/fetch_prs_between.sh commit1 commit2 > changes.txt
|
||||
util/fetch_prs_between.sh start_commit end_commit > changes.txt
|
||||
```
|
||||
|
||||
where `commit2` is the commit hash from the previous command and `commit1`
|
||||
is the commit hash from the current CHANGELOG file.
|
||||
where `end_commit` is the commit hash from the previous command and `start_commit`
|
||||
is [the commit hash][beta_section] from the current CHANGELOG file.
|
||||
Open `changes.txt` file in your editor of choice.
|
||||
|
||||
When updating the changelog it's also a good idea to make sure that `commit1` is
|
||||
already correct in the current changelog.
|
||||
|
||||
### 3. Authoring the final changelog
|
||||
|
||||
The above script should have dumped all the relevant PRs to the file you
|
||||
|
|
@ -70,17 +67,7 @@ With the PRs filtered, you can start to take each PR and move the `changelog: `
|
|||
content to `CHANGELOG.md`. Adapt the wording as you see fit but try to keep it
|
||||
somewhat coherent.
|
||||
|
||||
The order should roughly be:
|
||||
|
||||
1. New lints
|
||||
2. Moves or deprecations of lints
|
||||
3. Changes that expand what code existing lints cover
|
||||
4. False positive fixes
|
||||
5. ICE fixes
|
||||
6. Documentation improvements
|
||||
7. Others
|
||||
|
||||
As section headers, we use:
|
||||
The sections order should roughly be:
|
||||
|
||||
```
|
||||
### New Lints
|
||||
|
|
@ -97,10 +84,10 @@ As section headers, we use:
|
|||
### Others
|
||||
```
|
||||
|
||||
Please also be sure to update the Beta/Unreleased sections at the top with the
|
||||
relevant commit ranges.
|
||||
Please also be sure to update [the `Unreleased/Beta/In Rust Nightly` section][beta_section] at the top with the
|
||||
relevant commits ranges and to add the `Rust <version>` section with release date and PR ranges.
|
||||
|
||||
#### 3.1 Include `beta-accepted` PRs
|
||||
### 4. Include `beta-accepted` PRs
|
||||
|
||||
Look for the [`beta-accepted`] label and make sure to also include the PRs with
|
||||
that label in the changelog. If you can, remove the `beta-accepted` labels
|
||||
|
|
@ -109,7 +96,7 @@ that label in the changelog. If you can, remove the `beta-accepted` labels
|
|||
> _Note:_ Some of those PRs might even get backported to the previous `beta`.
|
||||
> Those have to be included in the changelog of the _previous_ release.
|
||||
|
||||
### 4. Update `clippy::version` attributes
|
||||
### 5. Update `clippy::version` attributes
|
||||
|
||||
Next, make sure to check that the `#[clippy::version]` attributes for the added
|
||||
lints contain the correct version.
|
||||
|
|
@ -129,3 +116,4 @@ written for. If not, update the version to the changelog version.
|
|||
[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy
|
||||
[rust_stable_tools]: https://github.com/rust-lang/rust/releases
|
||||
[`beta-accepted`]: https://github.com/rust-lang/rust-clippy/issues?q=label%3Abeta-accepted+
|
||||
[beta_section]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md#unreleased--beta--in-rust-nightly
|
||||
|
|
|
|||
|
|
@ -892,6 +892,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
|
|||
* [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns)
|
||||
* [`unused_trait_names`](https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names)
|
||||
* [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self)
|
||||
* [`zero_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr)
|
||||
|
||||
|
||||
## `pass-by-value-size-limit`
|
||||
|
|
|
|||
|
|
@ -794,6 +794,7 @@ define_Conf! {
|
|||
unnested_or_patterns,
|
||||
unused_trait_names,
|
||||
use_self,
|
||||
zero_ptr,
|
||||
)]
|
||||
msrv: Msrv = Msrv::default(),
|
||||
/// The minimum size (in bytes) to consider a type for passing by reference instead of by value.
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
|
|||
if check {
|
||||
return Err(Error::CheckFailed);
|
||||
}
|
||||
fs::write(path, new_text.as_bytes())?;
|
||||
fs::write(path, new_text)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
rustc_private,
|
||||
exit_status_error,
|
||||
if_let_guard,
|
||||
let_chains,
|
||||
os_str_slice,
|
||||
os_string_truncate,
|
||||
slice_split_once
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
|
|||
.tcx
|
||||
.hir_attrs(item.hir_id())
|
||||
.iter()
|
||||
.any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::Repr{ .. })))
|
||||
.any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::Repr { .. })))
|
||||
{
|
||||
// Do not lint items with a `#[repr]` attribute as their layout may be imposed by an external API.
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ pub(super) fn check<'tcx>(
|
|||
cast_to: &'tcx Ty<'_>,
|
||||
msrv: Msrv,
|
||||
) -> bool {
|
||||
if matches!(cast_to.kind, TyKind::Ptr(_))
|
||||
if let TyKind::Ptr(target) = cast_to.kind
|
||||
&& !matches!(target.ty.kind, TyKind::TraitObject(..))
|
||||
&& let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind
|
||||
&& !is_lint_allowed(cx, BORROW_AS_PTR, expr.hir_id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
|
||||
use clippy_utils::{expr_or_init, sym};
|
||||
use clippy_utils::{expr_or_init, is_in_const_context, sym};
|
||||
use rustc_abi::IntegerType;
|
||||
use rustc_errors::{Applicability, Diag};
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
|
|
@ -168,7 +168,9 @@ pub(super) fn check(
|
|||
|
||||
span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, msg, |diag| {
|
||||
diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...");
|
||||
if !cast_from.is_floating_point() {
|
||||
// TODO: Remove the condition for const contexts when `try_from` and other commonly used methods
|
||||
// become const fn.
|
||||
if !is_in_const_context(cx) && !cast_from.is_floating_point() {
|
||||
offer_suggestion(cx, expr, cast_expr, cast_to_span, diag);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -878,7 +878,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
|
||||
fn_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to);
|
||||
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to);
|
||||
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
|
||||
zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir, self.msrv);
|
||||
|
||||
if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) {
|
||||
manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::{is_in_const_context, is_integer_literal, std_or_core};
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -7,10 +8,10 @@ use rustc_lint::LateContext;
|
|||
|
||||
use super::ZERO_PTR;
|
||||
|
||||
pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) {
|
||||
pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>, msrv: Msrv) {
|
||||
if let TyKind::Ptr(ref mut_ty) = to.kind
|
||||
&& is_integer_literal(from, 0)
|
||||
&& !is_in_const_context(cx)
|
||||
&& (!is_in_const_context(cx) || msrv.meets(cx, msrvs::PTR_NULL))
|
||||
&& let Some(std_or_core) = std_or_core(cx)
|
||||
{
|
||||
let (msg, sugg_fn) = match mut_ty.mutbl {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::sugg::{self, Sugg};
|
||||
use clippy_utils::sym;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::adjustment::{Adjust, PointerCoercion};
|
||||
use rustc_middle::ty::{self, ExistentialPredicate, Ty, TyCtxt};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
||||
|
|
@ -49,23 +50,18 @@ declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
// If this expression has an effective type of `&dyn Any` ...
|
||||
{
|
||||
let coerced_ty = cx.typeck_results().expr_ty_adjusted(e);
|
||||
|
||||
let ty::Ref(_, coerced_ref_ty, _) = *coerced_ty.kind() else {
|
||||
return;
|
||||
};
|
||||
if !is_dyn_any(cx.tcx, coerced_ref_ty) {
|
||||
return;
|
||||
}
|
||||
// If this expression was coerced to `&dyn Any` ...
|
||||
if !cx.typeck_results().expr_adjustments(e).last().is_some_and(|adj| {
|
||||
matches!(adj.kind, Adjust::Pointer(PointerCoercion::Unsize)) && is_ref_dyn_any(cx.tcx, adj.target)
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
let expr_ty = cx.typeck_results().expr_ty(e);
|
||||
let ty::Ref(_, expr_ref_ty, _) = *expr_ty.kind() else {
|
||||
return;
|
||||
};
|
||||
// ... but only due to coercion ...
|
||||
// ... but it's not actually `&dyn Any` ...
|
||||
if is_dyn_any(cx.tcx, expr_ref_ty) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -78,23 +74,37 @@ impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny {
|
|||
}
|
||||
|
||||
// ... that's probably not intended.
|
||||
let (span, deref_count) = match e.kind {
|
||||
let (target_expr, deref_count) = match e.kind {
|
||||
// If `e` was already an `&` expression, skip `*&` in the suggestion
|
||||
ExprKind::AddrOf(_, _, referent) => (referent.span, depth),
|
||||
_ => (e.span, depth + 1),
|
||||
ExprKind::AddrOf(_, _, referent) => (referent, depth),
|
||||
_ => (e, depth + 1),
|
||||
};
|
||||
let ty::Ref(_, _, mutability) = *cx.typeck_results().expr_ty_adjusted(e).kind() else {
|
||||
return;
|
||||
};
|
||||
let sugg = sugg::make_unop(
|
||||
&format!("{}{}", mutability.ref_prefix_str(), str::repeat("*", deref_count)),
|
||||
Sugg::hir(cx, target_expr, ".."),
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
COERCE_CONTAINER_TO_ANY,
|
||||
e.span,
|
||||
format!("coercing `{expr_ty}` to `&dyn Any`"),
|
||||
format!("coercing `{expr_ty}` to `{}dyn Any`", mutability.ref_prefix_str()),
|
||||
"consider dereferencing",
|
||||
format!("&{}{}", str::repeat("*", deref_count), snippet(cx, span, "x")),
|
||||
sugg.to_string(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ref_dyn_any(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool {
|
||||
let ty::Ref(_, ref_ty, _) = *ty.kind() else {
|
||||
return false;
|
||||
};
|
||||
is_dyn_any(tcx, ref_ty)
|
||||
}
|
||||
|
||||
fn is_dyn_any(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool {
|
||||
let ty::Dynamic(traits, ..) = ty.kind() else {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -14,18 +14,25 @@ use rustc_span::def_id::LocalDefId;
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for methods with high cognitive complexity.
|
||||
/// We used to think it measured how hard a method is to understand.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Methods of high cognitive complexity tend to be hard to
|
||||
/// both read and maintain. Also LLVM will tend to optimize small methods better.
|
||||
/// Ideally, we would like to be able to measure how hard a function is
|
||||
/// to understand given its context (what we call its Cognitive Complexity).
|
||||
/// But that's not what this lint does. See "Known problems"
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Sometimes it's hard to find a way to reduce the
|
||||
/// complexity.
|
||||
/// The true Cognitive Complexity of a method is not something we can
|
||||
/// calculate using modern technology. This lint has been left in the
|
||||
/// `nursery` so as to not mislead users into using this lint as a
|
||||
/// measurement tool.
|
||||
///
|
||||
/// ### Example
|
||||
/// You'll see it when you get the warning.
|
||||
/// For more detailed information, see [rust-clippy#3793](https://github.com/rust-lang/rust-clippy/issues/3793)
|
||||
///
|
||||
/// ### Lints to consider instead of this
|
||||
///
|
||||
/// * [`excessive_nesting`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting)
|
||||
/// * [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines)
|
||||
#[clippy::version = "1.35.0"]
|
||||
pub COGNITIVE_COMPLEXITY,
|
||||
nursery,
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ impl EarlyLintPass for DisallowedScriptIdents {
|
|||
// Fast path for ascii-only idents.
|
||||
if !symbol_str.is_ascii()
|
||||
&& let Some(script) = symbol_str.chars().find_map(|c| {
|
||||
if c.is_ascii() {
|
||||
return None;
|
||||
}
|
||||
|
||||
c.script_extension()
|
||||
.iter()
|
||||
.find(|script| !self.whitelist.contains(script))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
|
|||
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
||||
use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item};
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use clippy_utils::{fulfill_or_allowed, is_doc_hidden, method_chain_args, return_ty};
|
||||
use clippy_utils::{fulfill_or_allowed, is_doc_hidden, is_inside_always_const_context, method_chain_args, return_ty};
|
||||
use rustc_hir::{BodyId, FnSig, OwnerId, Safety};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
|
|
@ -99,13 +99,16 @@ fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> {
|
|||
let mut panic_span = None;
|
||||
let typeck = cx.tcx.typeck_body(body_id);
|
||||
for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| {
|
||||
if is_inside_always_const_context(cx.tcx, expr.hir_id) {
|
||||
return ControlFlow::<!>::Continue(());
|
||||
}
|
||||
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
|
||||
&& (is_panic(cx, macro_call.def_id)
|
||||
|| matches!(
|
||||
cx.tcx.get_diagnostic_name(macro_call.def_id),
|
||||
Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro)
|
||||
))
|
||||
&& !cx.tcx.hir_is_inside_const_context(expr.hir_id)
|
||||
&& !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id])
|
||||
&& panic_span.is_none()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1249,7 +1249,9 @@ fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> {
|
|||
b'[' => {
|
||||
start = Some(i + offset);
|
||||
},
|
||||
b']' if let Some(start) = start => {
|
||||
b']' if let Some(start) = start
|
||||
&& doc.as_bytes().get(i + offset + 1) == Some(&b':') =>
|
||||
{
|
||||
return Some(start..i + offset + 1);
|
||||
},
|
||||
_ => {},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::indent_of;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_attr_data_structures::find_attr;
|
||||
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_entrypoint_fn;
|
||||
use rustc_hir::{Expr, ExprKind, Item, ItemKind, OwnerNode};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
|
|
@ -7,7 +6,8 @@ use rustc_span::sym;
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Detects calls to the `exit()` function which terminates the program.
|
||||
/// Detects calls to the `exit()` function that are not in the `main` function. Calls to `exit()`
|
||||
/// immediately terminate the program.
|
||||
///
|
||||
/// ### Why restrict this?
|
||||
/// `exit()` immediately terminates the program with no information other than an exit code.
|
||||
|
|
@ -15,11 +15,24 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// Codebases may use this lint to require that all exits are performed either by panicking
|
||||
/// (which produces a message, a code location, and optionally a backtrace)
|
||||
/// or by returning from `main()` (which is a single place to look).
|
||||
/// or by calling `exit()` from `main()` (which is a single place to look).
|
||||
///
|
||||
/// ### Example
|
||||
/// ### Good example
|
||||
/// ```no_run
|
||||
/// std::process::exit(0)
|
||||
/// fn main() {
|
||||
/// std::process::exit(0);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Bad example
|
||||
/// ```no_run
|
||||
/// fn main() {
|
||||
/// other_function();
|
||||
/// }
|
||||
///
|
||||
/// fn other_function() {
|
||||
/// std::process::exit(0);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
|
@ -36,7 +49,7 @@ declare_clippy_lint! {
|
|||
#[clippy::version = "1.41.0"]
|
||||
pub EXIT,
|
||||
restriction,
|
||||
"detects `std::process::exit` calls"
|
||||
"detects `std::process::exit` calls outside of `main`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Exit => [EXIT]);
|
||||
|
|
@ -48,10 +61,14 @@ impl<'tcx> LateLintPass<'tcx> for Exit {
|
|||
&& let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::process_exit, def_id)
|
||||
&& let parent = cx.tcx.hir_get_parent_item(e.hir_id)
|
||||
&& let OwnerNode::Item(Item{kind: ItemKind::Fn{ .. }, ..}) = cx.tcx.hir_owner_node(parent)
|
||||
// If the next item up is a function we check if it is an entry point
|
||||
&& let OwnerNode::Item(Item{kind: ItemKind::Fn{ ident, .. }, ..}) = cx.tcx.hir_owner_node(parent)
|
||||
// If the next item up is a function we check if it isn't named "main"
|
||||
// and only then emit a linter warning
|
||||
&& !is_entrypoint_fn(cx, parent.to_def_id())
|
||||
|
||||
// if you instead check for the parent of the `exit()` call being the entrypoint function, as this worked before,
|
||||
// in compilation contexts like --all-targets (which include --tests), you get false positives
|
||||
// because in a test context, main is not the entrypoint function
|
||||
&& ident.name != sym::main
|
||||
{
|
||||
span_lint(cx, EXIT, e.span, "usage of `process::exit`");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use clippy_utils::consts::Constant::{F32, F64, Int};
|
|||
use clippy_utils::consts::{ConstEvalCtxt, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::{
|
||||
eq_expr_value, get_parent_expr, higher, is_in_const_context, is_inherent_method_call, is_no_std_crate,
|
||||
numeric_literal, peel_blocks, sugg, sym,
|
||||
eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context,
|
||||
is_inherent_method_call, is_no_std_crate, numeric_literal, peel_blocks, sugg, sym,
|
||||
};
|
||||
use rustc_ast::ast;
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -455,7 +455,6 @@ fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'
|
|||
None
|
||||
}
|
||||
|
||||
// TODO: Fix rust-lang/rust-clippy#4735
|
||||
fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let ExprKind::Binary(
|
||||
Spanned {
|
||||
|
|
@ -491,6 +490,14 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
return;
|
||||
};
|
||||
|
||||
// Check if any variable in the expression has an ambiguous type (could be f32 or f64)
|
||||
// see: https://github.com/rust-lang/rust-clippy/issues/14897
|
||||
if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _)))
|
||||
&& has_ambiguous_literal_in_expr(cx, recv)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SUBOPTIMAL_FLOPS,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
use super::EMPTY_LOOP;
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{is_in_panic_handler, is_no_std_crate};
|
||||
use clippy_utils::{is_in_panic_handler, is_no_std_crate, sym};
|
||||
|
||||
use rustc_hir::{Block, Expr};
|
||||
use rustc_hir::{Block, Expr, ItemKind, Node};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, loop_block: &Block<'_>) {
|
||||
let parent_hir_id = cx.tcx.parent_hir_id(expr.hir_id);
|
||||
if let Node::Item(parent_node) = cx.tcx.hir_node(parent_hir_id)
|
||||
&& matches!(parent_node.kind, ItemKind::Fn { .. })
|
||||
&& let attrs = cx.tcx.hir_attrs(parent_hir_id)
|
||||
&& attrs.iter().any(|attr| attr.has_name(sym::rustc_intrinsic))
|
||||
{
|
||||
// Intrinsic functions are expanded into an empty loop when lowering the AST
|
||||
// to simplify the job of later passes which might expect any function to have a body.
|
||||
return;
|
||||
}
|
||||
|
||||
if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) {
|
||||
let msg = "empty `loop {}` wastes CPU cycles";
|
||||
let help = if is_no_std_crate(cx) {
|
||||
|
|
|
|||
|
|
@ -245,17 +245,36 @@ fn replace_in_pattern(
|
|||
}
|
||||
|
||||
match pat.kind {
|
||||
PatKind::Binding(_ann, _id, binding_name, opt_subpt) => {
|
||||
let Some((pat_to_put, binding_mode)) = ident_map.get(&binding_name.name) else {
|
||||
break 'a;
|
||||
};
|
||||
let sn_pfx = binding_mode.prefix_str();
|
||||
let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app);
|
||||
if let Some(subpt) = opt_subpt {
|
||||
let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false);
|
||||
return format!("{sn_pfx}{sn_ptp} @ {subpt}");
|
||||
PatKind::Binding(ann, _id, binding_name, opt_subpt) => {
|
||||
match (ident_map.get(&binding_name.name), opt_subpt) {
|
||||
(Some((pat_to_put, binding_mode)), opt_subpt) => {
|
||||
let sn_pfx = binding_mode.prefix_str();
|
||||
let (sn_ptp, _) = snippet_with_context(cx, pat_to_put.span, span.ctxt(), "", app);
|
||||
if let Some(subpt) = opt_subpt {
|
||||
let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false);
|
||||
return format!("{sn_pfx}{sn_ptp} @ {subpt}");
|
||||
}
|
||||
return format!("{sn_pfx}{sn_ptp}");
|
||||
},
|
||||
(None, Some(subpt)) => {
|
||||
let subpt = replace_in_pattern(cx, span, ident_map, subpt, app, false);
|
||||
// scanning for a value that matches is not sensitive to order
|
||||
#[expect(rustc::potential_query_instability)]
|
||||
if ident_map.values().any(|(other_pat, _)| {
|
||||
if let PatKind::Binding(_, _, other_name, _) = other_pat.kind {
|
||||
other_name == binding_name
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
// this name is shadowed, and, therefore, not usable
|
||||
return subpt;
|
||||
}
|
||||
let binding_pfx = ann.prefix_str();
|
||||
return format!("{binding_pfx}{binding_name} @ {subpt}");
|
||||
},
|
||||
(None, None) => break 'a,
|
||||
}
|
||||
return format!("{sn_pfx}{sn_ptp}");
|
||||
},
|
||||
PatKind::Or(pats) => {
|
||||
let patterns = pats
|
||||
|
|
|
|||
|
|
@ -4,16 +4,15 @@ use clippy_utils::is_doc_hidden;
|
|||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::snippet_indent;
|
||||
use itertools::Itertools;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||||
use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::Span;
|
||||
use rustc_attr_data_structures::find_attr;
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
|
|||
|
|
@ -332,9 +332,7 @@ impl<'a> NormalizedPat<'a> {
|
|||
// TODO: Handle negative integers. They're currently treated as a wild match.
|
||||
PatExprKind::Lit { lit, negated: false } => match lit.node {
|
||||
LitKind::Str(sym, _) => Self::LitStr(sym),
|
||||
LitKind::ByteStr(byte_sym, _) | LitKind::CStr(byte_sym, _) => {
|
||||
Self::LitBytes(byte_sym)
|
||||
}
|
||||
LitKind::ByteStr(byte_sym, _) | LitKind::CStr(byte_sym, _) => Self::LitBytes(byte_sym),
|
||||
LitKind::Byte(val) => Self::LitInt(val.into()),
|
||||
LitKind::Char(val) => Self::LitInt(val.into()),
|
||||
LitKind::Int(val, _) => Self::LitInt(val.get()),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{get_parent_expr, sym};
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath};
|
||||
use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::{BytePos, Span, sym};
|
||||
use rustc_span::{Span, Symbol};
|
||||
|
||||
use super::MANUAL_IS_VARIANT_AND;
|
||||
|
||||
|
|
@ -62,54 +64,154 @@ pub(super) fn check(
|
|||
);
|
||||
}
|
||||
|
||||
fn emit_lint(cx: &LateContext<'_>, op: BinOpKind, parent: &Expr<'_>, method_span: Span, is_option: bool) {
|
||||
if let Some(before_map_snippet) = snippet_opt(cx, parent.span.with_hi(method_span.lo()))
|
||||
&& let Some(after_map_snippet) = snippet_opt(cx, method_span.with_lo(method_span.lo() + BytePos(3)))
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
parent.span,
|
||||
format!(
|
||||
"called `.map() {}= {}()`",
|
||||
if op == BinOpKind::Eq { '=' } else { '!' },
|
||||
if is_option { "Some" } else { "Ok" },
|
||||
),
|
||||
"use",
|
||||
if is_option && op == BinOpKind::Ne {
|
||||
format!("{before_map_snippet}is_none_or{after_map_snippet}",)
|
||||
} else {
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Flavor {
|
||||
Option,
|
||||
Result,
|
||||
}
|
||||
|
||||
impl Flavor {
|
||||
const fn symbol(self) -> Symbol {
|
||||
match self {
|
||||
Self::Option => sym::Option,
|
||||
Self::Result => sym::Result,
|
||||
}
|
||||
}
|
||||
|
||||
const fn positive(self) -> Symbol {
|
||||
match self {
|
||||
Self::Option => sym::Some,
|
||||
Self::Result => sym::Ok,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Op {
|
||||
Eq,
|
||||
Ne,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Op {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Eq => write!(f, "=="),
|
||||
Self::Ne => write!(f, "!="),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BinOpKind> for Op {
|
||||
type Error = ();
|
||||
fn try_from(op: BinOpKind) -> Result<Self, Self::Error> {
|
||||
match op {
|
||||
BinOpKind::Eq => Ok(Self::Eq),
|
||||
BinOpKind::Ne => Ok(Self::Ne),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the argument of the `.map()` function, as a closure or as a path
|
||||
/// in case η-reduction is used.
|
||||
enum MapFunc<'hir> {
|
||||
Closure(&'hir Closure<'hir>),
|
||||
Path(&'hir Expr<'hir>),
|
||||
}
|
||||
|
||||
impl<'hir> TryFrom<&'hir Expr<'hir>> for MapFunc<'hir> {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(expr: &'hir Expr<'hir>) -> Result<Self, Self::Error> {
|
||||
match expr.kind {
|
||||
ExprKind::Closure(closure) => Ok(Self::Closure(closure)),
|
||||
ExprKind::Path(_) => Ok(Self::Path(expr)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'hir> MapFunc<'hir> {
|
||||
/// Build a suggestion suitable for use in a `.map()`-like function. η-expansion will be applied
|
||||
/// as needed.
|
||||
fn sugg(self, cx: &LateContext<'hir>, invert: bool, app: &mut Applicability) -> String {
|
||||
match self {
|
||||
Self::Closure(closure) => {
|
||||
let body = Sugg::hir_with_applicability(cx, cx.tcx.hir_body(closure.body).value, "..", app);
|
||||
format!(
|
||||
"{}{before_map_snippet}{}{after_map_snippet}",
|
||||
if op == BinOpKind::Eq { "" } else { "!" },
|
||||
if is_option { "is_some_and" } else { "is_ok_and" },
|
||||
"{} {}",
|
||||
snippet_with_applicability(cx, closure.fn_decl_span, "|..|", app),
|
||||
if invert { !body } else { body }
|
||||
)
|
||||
},
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
Self::Path(expr) => {
|
||||
let path = snippet_with_applicability(cx, expr.span, "_", app);
|
||||
if invert {
|
||||
format!("|x| !{path}(x)")
|
||||
} else {
|
||||
path.to_string()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_lint<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
span: Span,
|
||||
op: Op,
|
||||
flavor: Flavor,
|
||||
in_some_or_ok: bool,
|
||||
map_func: MapFunc<'tcx>,
|
||||
recv: &Expr<'_>,
|
||||
) {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let recv = snippet_with_applicability(cx, recv.span, "_", &mut app);
|
||||
|
||||
let (invert_expr, method, invert_body) = match (flavor, op) {
|
||||
(Flavor::Option, Op::Eq) => (false, "is_some_and", !in_some_or_ok),
|
||||
(Flavor::Option, Op::Ne) => (false, "is_none_or", in_some_or_ok),
|
||||
(Flavor::Result, Op::Eq) => (false, "is_ok_and", !in_some_or_ok),
|
||||
(Flavor::Result, Op::Ne) => (true, "is_ok_and", !in_some_or_ok),
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MANUAL_IS_VARIANT_AND,
|
||||
span,
|
||||
format!("called `.map() {op} {pos}()`", pos = flavor.positive(),),
|
||||
"use",
|
||||
format!(
|
||||
"{inversion}{recv}.{method}({body})",
|
||||
inversion = if invert_expr { "!" } else { "" },
|
||||
body = map_func.sugg(cx, invert_body, &mut app),
|
||||
),
|
||||
app,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let Some(parent_expr) = get_parent_expr(cx, expr)
|
||||
&& let ExprKind::Binary(op, left, right) = parent_expr.kind
|
||||
&& matches!(op.node, BinOpKind::Eq | BinOpKind::Ne)
|
||||
&& op.span.eq_ctxt(expr.span)
|
||||
&& let Ok(op) = Op::try_from(op.node)
|
||||
{
|
||||
// Check `left` and `right` expression in any order, and for `Option` and `Result`
|
||||
for (expr1, expr2) in [(left, right), (right, left)] {
|
||||
for item in [sym::Option, sym::Result] {
|
||||
if let ExprKind::Call(call, ..) = expr1.kind
|
||||
for flavor in [Flavor::Option, Flavor::Result] {
|
||||
if let ExprKind::Call(call, [arg]) = expr1.kind
|
||||
&& let ExprKind::Lit(lit) = arg.kind
|
||||
&& let LitKind::Bool(bool_cst) = lit.node
|
||||
&& let ExprKind::Path(QPath::Resolved(_, path)) = call.kind
|
||||
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res
|
||||
&& let ty = cx.typeck_results().expr_ty(expr1)
|
||||
&& let ty::Adt(adt, args) = ty.kind()
|
||||
&& cx.tcx.is_diagnostic_item(item, adt.did())
|
||||
&& cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did())
|
||||
&& args.type_at(0).is_bool()
|
||||
&& let ExprKind::MethodCall(_, recv, _, span) = expr2.kind
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), item)
|
||||
&& let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), flavor.symbol())
|
||||
&& let Ok(map_func) = MapFunc::try_from(map_expr)
|
||||
{
|
||||
return emit_lint(cx, op.node, parent_expr, span, item == sym::Option);
|
||||
return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ pub(super) fn check<'tcx>(
|
|||
fun_span: Option<Span>,
|
||||
) -> bool {
|
||||
// (path, fn_has_argument, methods, suffix)
|
||||
const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 5] = [
|
||||
const KNOW_TYPES: [(Symbol, bool, &[Symbol], &str); 7] = [
|
||||
(sym::BTreeEntry, false, &[sym::or_insert], "with"),
|
||||
(sym::HashMapEntry, false, &[sym::or_insert], "with"),
|
||||
(
|
||||
|
|
@ -146,7 +146,9 @@ pub(super) fn check<'tcx>(
|
|||
"else",
|
||||
),
|
||||
(sym::Option, false, &[sym::get_or_insert], "with"),
|
||||
(sym::Option, true, &[sym::and], "then"),
|
||||
(sym::Result, true, &[sym::map_or, sym::or, sym::unwrap_or], "else"),
|
||||
(sym::Result, true, &[sym::and], "then"),
|
||||
];
|
||||
|
||||
if KNOW_TYPES.iter().any(|k| k.2.contains(&name))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{self as hir, Node};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArg, Ty};
|
||||
use rustc_span::sym;
|
||||
|
|
@ -9,7 +9,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
|
|||
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability};
|
||||
use clippy_utils::ty::get_type_diagnostic_name;
|
||||
use clippy_utils::visitors::for_each_unconsumed_temporary;
|
||||
use clippy_utils::{get_parent_expr, peel_blocks};
|
||||
use clippy_utils::{peel_blocks, potential_return_of_enclosing_body};
|
||||
|
||||
use super::RETURN_AND_THEN;
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
|
|||
recv: &'tcx hir::Expr<'tcx>,
|
||||
arg: &'tcx hir::Expr<'_>,
|
||||
) {
|
||||
if cx.tcx.hir_get_fn_id_for_return_block(expr.hir_id).is_none() {
|
||||
if !potential_return_of_enclosing_body(cx, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -55,15 +55,28 @@ pub(super) fn check<'tcx>(
|
|||
None => &body_snip,
|
||||
};
|
||||
|
||||
// If suggestion is going to get inserted as part of a `return` expression, it must be blockified.
|
||||
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) {
|
||||
let base_indent = indent_of(cx, parent_expr.span);
|
||||
// If suggestion is going to get inserted as part of a `return` expression or as a match expression
|
||||
// arm, it must be blockified.
|
||||
let (parent_span_for_indent, opening_paren, closing_paren) = match cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
Node::Expr(parent_expr) if matches!(parent_expr.kind, hir::ExprKind::Break(..)) => {
|
||||
(Some(parent_expr.span), "(", ")")
|
||||
},
|
||||
Node::Expr(parent_expr) => (Some(parent_expr.span), "", ""),
|
||||
Node::Arm(match_arm) => (Some(match_arm.span), "", ""),
|
||||
_ => (None, "", ""),
|
||||
};
|
||||
let sugg = if let Some(span) = parent_span_for_indent {
|
||||
let base_indent = indent_of(cx, span);
|
||||
let inner_indent = base_indent.map(|i| i + 4);
|
||||
format!(
|
||||
"{}\n{}\n{}",
|
||||
reindent_multiline(&format!("{{\nlet {arg_snip} = {recv_snip}?;"), true, inner_indent),
|
||||
reindent_multiline(
|
||||
&format!("{opening_paren}{{\nlet {arg_snip} = {recv_snip}?;"),
|
||||
true,
|
||||
inner_indent
|
||||
),
|
||||
reindent_multiline(inner, false, inner_indent),
|
||||
reindent_multiline("}", false, base_indent),
|
||||
reindent_multiline(&format!("}}{closing_paren}"), false, base_indent),
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use rustc_ast::BorrowKind;
|
|||
use rustc_errors::{Applicability, Diag};
|
||||
use rustc_hir::{Expr, ExprKind, Node, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_span::sym;
|
||||
|
||||
use super::SWAP_WITH_TEMPORARY;
|
||||
|
|
@ -11,12 +12,12 @@ use super::SWAP_WITH_TEMPORARY;
|
|||
const MSG_TEMPORARY: &str = "this expression returns a temporary value";
|
||||
const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value";
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'_>, args: &'tcx [Expr<'_>]) {
|
||||
if let ExprKind::Path(QPath::Resolved(_, func_path)) = func.kind
|
||||
&& let Some(func_def_id) = func_path.res.opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::mem_swap, func_def_id)
|
||||
{
|
||||
match (ArgKind::new(&args[0]), ArgKind::new(&args[1])) {
|
||||
match (ArgKind::new(cx, &args[0]), ArgKind::new(cx, &args[1])) {
|
||||
(ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => {
|
||||
emit_lint_useless(cx, expr, &args[0], &args[1], left_temp, right_temp);
|
||||
},
|
||||
|
|
@ -28,10 +29,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
|
|||
}
|
||||
|
||||
enum ArgKind<'tcx> {
|
||||
// Mutable reference to a place, coming from a macro
|
||||
RefMutToPlaceAsMacro(&'tcx Expr<'tcx>),
|
||||
// Place behind a mutable reference
|
||||
RefMutToPlace(&'tcx Expr<'tcx>),
|
||||
// Mutable reference to a place, coming from a macro, and number of dereferences to use
|
||||
RefMutToPlaceAsMacro(&'tcx Expr<'tcx>, usize),
|
||||
// Place behind a mutable reference, and number of dereferences to use
|
||||
RefMutToPlace(&'tcx Expr<'tcx>, usize),
|
||||
// Temporary value behind a mutable reference
|
||||
RefMutToTemp(&'tcx Expr<'tcx>),
|
||||
// Any other case
|
||||
|
|
@ -39,13 +40,29 @@ enum ArgKind<'tcx> {
|
|||
}
|
||||
|
||||
impl<'tcx> ArgKind<'tcx> {
|
||||
fn new(arg: &'tcx Expr<'tcx>) -> Self {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind {
|
||||
if target.is_syntactic_place_expr() {
|
||||
/// Build a new `ArgKind` from `arg`. There must be no false positive when returning a
|
||||
/// `ArgKind::RefMutToTemp` variant, as this may cause a spurious lint to be emitted.
|
||||
fn new(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Self {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind
|
||||
&& let adjustments = cx.typeck_results().expr_adjustments(arg)
|
||||
&& adjustments
|
||||
.first()
|
||||
.is_some_and(|adj| matches!(adj.kind, Adjust::Deref(None)))
|
||||
&& adjustments
|
||||
.last()
|
||||
.is_some_and(|adj| matches!(adj.kind, Adjust::Borrow(_)))
|
||||
{
|
||||
let extra_derefs = adjustments[1..adjustments.len() - 1]
|
||||
.iter()
|
||||
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
|
||||
.count();
|
||||
// If a deref is used, `arg` might be a place expression. For example, a mutex guard
|
||||
// would dereference into the mutex content which is probably not temporary.
|
||||
if target.is_syntactic_place_expr() || extra_derefs > 0 {
|
||||
if arg.span.from_expansion() {
|
||||
ArgKind::RefMutToPlaceAsMacro(arg)
|
||||
ArgKind::RefMutToPlaceAsMacro(arg, extra_derefs)
|
||||
} else {
|
||||
ArgKind::RefMutToPlace(target)
|
||||
ArgKind::RefMutToPlace(target, extra_derefs)
|
||||
}
|
||||
} else {
|
||||
ArgKind::RefMutToTemp(target)
|
||||
|
|
@ -106,10 +123,15 @@ fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>,
|
|||
let mut applicability = Applicability::MachineApplicable;
|
||||
let ctxt = expr.span.ctxt();
|
||||
let assign_target = match target {
|
||||
ArgKind::Expr(target) | ArgKind::RefMutToPlaceAsMacro(target) => {
|
||||
Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref()
|
||||
},
|
||||
ArgKind::RefMutToPlace(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability),
|
||||
ArgKind::Expr(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref(),
|
||||
ArgKind::RefMutToPlaceAsMacro(arg, derefs) => (0..*derefs).fold(
|
||||
Sugg::hir_with_context(cx, arg, ctxt, "_", &mut applicability).deref(),
|
||||
|sugg, _| sugg.deref(),
|
||||
),
|
||||
ArgKind::RefMutToPlace(target, derefs) => (0..*derefs).fold(
|
||||
Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability),
|
||||
|sugg, _| sugg.deref(),
|
||||
),
|
||||
ArgKind::RefMutToTemp(_) => unreachable!(),
|
||||
};
|
||||
let assign_source = Sugg::hir_with_context(cx, temp, ctxt, "_", &mut applicability);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ pub(super) fn check<'a>(
|
|||
|
||||
let ext_def_span = def.span.until(map.span);
|
||||
|
||||
let (sugg, method, applicability) = if let ExprKind::Closure(map_closure) = map.kind
|
||||
let (sugg, method, applicability) = if cx.typeck_results().expr_adjustments(recv).is_empty()
|
||||
&& let ExprKind::Closure(map_closure) = map.kind
|
||||
&& let closure_body = cx.tcx.hir_body(map_closure.body)
|
||||
&& let closure_body_value = closure_body.value.peel_blocks()
|
||||
&& let ExprKind::Binary(op, l, r) = closure_body_value.kind
|
||||
|
|
|
|||
|
|
@ -199,11 +199,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
|
|||
let mut applicability = Applicability::MachineApplicable;
|
||||
let cond = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
|
||||
let lhs = snippet_with_applicability(cx, lhs_a.span, "..", &mut applicability);
|
||||
let sugg = if a == b {
|
||||
let mut sugg = if a == b {
|
||||
format!("{cond}; {lhs} = {a:?};")
|
||||
} else {
|
||||
format!("{lhs} = {};", if a { cond } else { !cond })
|
||||
};
|
||||
|
||||
if is_else_clause(cx.tcx, e) {
|
||||
sugg = format!("{{ {sugg} }}");
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_BOOL_ASSIGN,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use clippy_utils::consts::{self, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::source::{snippet, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use rustc_ast::util::parser::ExprPrecedence;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -33,6 +33,19 @@ declare_clippy_lint! {
|
|||
|
||||
declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]);
|
||||
|
||||
fn is_in_parens_with_postfix(cx: &LateContext<'_>, mul_expr: &Expr<'_>) -> bool {
|
||||
if let Some(parent) = get_parent_expr(cx, mul_expr) {
|
||||
let mult_snippet = snippet(cx, mul_expr.span, "");
|
||||
if has_enclosing_paren(&mult_snippet)
|
||||
&& let ExprKind::MethodCall(_, _, _, _) = parent.kind
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NegMultiply {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Binary(ref op, left, right) = e.kind
|
||||
|
|
@ -40,15 +53,15 @@ impl<'tcx> LateLintPass<'tcx> for NegMultiply {
|
|||
{
|
||||
match (&left.kind, &right.kind) {
|
||||
(&ExprKind::Unary(..), &ExprKind::Unary(..)) => {},
|
||||
(&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e.span, lit, right),
|
||||
(_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e.span, lit, left),
|
||||
(&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e, lit, right),
|
||||
(_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e, lit, left),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
|
||||
fn check_mul(cx: &LateContext<'_>, mul_expr: &Expr<'_>, lit: &Expr<'_>, exp: &Expr<'_>) {
|
||||
const F16_ONE: u16 = 1.0_f16.to_bits();
|
||||
const F128_ONE: u128 = 1.0_f128.to_bits();
|
||||
if let ExprKind::Lit(l) = lit.kind
|
||||
|
|
@ -63,8 +76,19 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
|
|||
&& cx.typeck_results().expr_ty(exp).is_numeric()
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability);
|
||||
let suggestion = if !from_macro && cx.precedence(exp) < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) {
|
||||
let (snip, from_macro) = snippet_with_context(cx, exp.span, mul_expr.span.ctxt(), "..", &mut applicability);
|
||||
|
||||
let needs_parens_for_postfix = is_in_parens_with_postfix(cx, mul_expr);
|
||||
|
||||
let suggestion = if needs_parens_for_postfix {
|
||||
// Special case: when the multiplication is in parentheses followed by a method call
|
||||
// we need to preserve the grouping but negate the inner expression.
|
||||
// Consider this expression: `((a.delta - 0.5).abs() * -1.0).total_cmp(&1.0)`
|
||||
// We need to end up with: `(-(a.delta - 0.5).abs()).total_cmp(&1.0)`
|
||||
// Otherwise, without the parentheses we would try to negate an Ordering:
|
||||
// `-(a.delta - 0.5).abs().total_cmp(&1.0)`
|
||||
format!("(-{snip})")
|
||||
} else if !from_macro && cx.precedence(exp) < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) {
|
||||
format!("-({snip})")
|
||||
} else {
|
||||
format!("-{snip}")
|
||||
|
|
@ -72,7 +96,7 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
|
|||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEG_MULTIPLY,
|
||||
span,
|
||||
mul_expr.span,
|
||||
"this multiplication by -1 can be written more succinctly",
|
||||
"consider using",
|
||||
suggestion,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::ty::has_drop;
|
||||
use clippy_utils::ty::{expr_type_is_certain, has_drop};
|
||||
use clippy_utils::{
|
||||
in_automatically_derived, is_inside_always_const_context, is_lint_allowed, path_to_local, peel_blocks,
|
||||
};
|
||||
|
|
@ -340,11 +340,13 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
|
|||
},
|
||||
ExprKind::Array(v) | ExprKind::Tup(v) => Some(v.iter().collect()),
|
||||
ExprKind::Repeat(inner, _)
|
||||
| ExprKind::Cast(inner, _)
|
||||
| ExprKind::Type(inner, _)
|
||||
| ExprKind::Unary(_, inner)
|
||||
| ExprKind::Field(inner, _)
|
||||
| ExprKind::AddrOf(_, _, inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])),
|
||||
ExprKind::Cast(inner, _) if expr_type_is_certain(cx, inner) => {
|
||||
reduce_expression(cx, inner).or_else(|| Some(vec![inner]))
|
||||
},
|
||||
ExprKind::Struct(_, fields, ref base) => {
|
||||
if has_drop(cx, cx.typeck_results().expr_ty(expr)) {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::get_enclosing_block;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
|
|
@ -61,12 +61,13 @@ pub(crate) fn check<'tcx>(
|
|||
e.span,
|
||||
"needlessly taken reference of both operands",
|
||||
|diag| {
|
||||
let lsnip = snippet(cx, l.span, "...").to_string();
|
||||
let rsnip = snippet(cx, r.span, "...").to_string();
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability);
|
||||
let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability);
|
||||
diag.multipart_suggestion(
|
||||
"use the values directly",
|
||||
vec![(left.span, lsnip), (right.span, rsnip)],
|
||||
Applicability::MachineApplicable,
|
||||
vec![(left.span, lsnip.to_string()), (right.span, rsnip.to_string())],
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -80,13 +81,9 @@ pub(crate) fn check<'tcx>(
|
|||
e.span,
|
||||
"needlessly taken reference of left operand",
|
||||
|diag| {
|
||||
let lsnip = snippet(cx, l.span, "...").to_string();
|
||||
diag.span_suggestion(
|
||||
left.span,
|
||||
"use the left value directly",
|
||||
lsnip,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability);
|
||||
diag.span_suggestion(left.span, "use the left value directly", lsnip, applicability);
|
||||
},
|
||||
);
|
||||
} else if !lcpy
|
||||
|
|
@ -99,7 +96,8 @@ pub(crate) fn check<'tcx>(
|
|||
e.span,
|
||||
"needlessly taken reference of right operand",
|
||||
|diag| {
|
||||
let rsnip = snippet(cx, r.span, "...").to_string();
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability);
|
||||
diag.span_suggestion(
|
||||
right.span,
|
||||
"use the right value directly",
|
||||
|
|
@ -131,7 +129,8 @@ pub(crate) fn check<'tcx>(
|
|||
e.span,
|
||||
"needlessly taken reference of left operand",
|
||||
|diag| {
|
||||
let lsnip = snippet(cx, l.span, "...").to_string();
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (lsnip, _) = snippet_with_context(cx, l.span, e.span.ctxt(), "...", &mut applicability);
|
||||
diag.span_suggestion(
|
||||
left.span,
|
||||
"use the left value directly",
|
||||
|
|
@ -158,7 +157,8 @@ pub(crate) fn check<'tcx>(
|
|||
&& implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()])
|
||||
{
|
||||
span_lint_and_then(cx, OP_REF, e.span, "taken reference of right operand", |diag| {
|
||||
let rsnip = snippet(cx, r.span, "...").to_string();
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let (rsnip, _) = snippet_with_context(cx, r.span, e.span.ctxt(), "...", &mut applicability);
|
||||
diag.span_suggestion(
|
||||
right.span,
|
||||
"use the right value directly",
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ use hir::Param;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
|
||||
use rustc_hir::{
|
||||
ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, ExprKind, Node, intravisit as hir_visit,
|
||||
};
|
||||
use rustc_hir::{ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, ExprKind, intravisit as hir_visit};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty;
|
||||
|
|
@ -198,15 +196,15 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
|
|||
hint = hint.asyncify();
|
||||
}
|
||||
|
||||
let is_in_fn_call_arg = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) {
|
||||
matches!(expr.kind, ExprKind::Call(_, _))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// avoid clippy::double_parens
|
||||
if !is_in_fn_call_arg {
|
||||
hint = hint.maybe_paren();
|
||||
// If the closure body is a block with a single expression, suggest just the inner expression,
|
||||
// not the block. Example: `(|| { Some(true) })()` should suggest
|
||||
// `Some(true)`
|
||||
if let ExprKind::Block(block, _) = body.kind
|
||||
&& block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
{
|
||||
hint = Sugg::hir_with_context(cx, expr, full_expr.span.ctxt(), "..", &mut applicability)
|
||||
.maybe_paren();
|
||||
}
|
||||
|
||||
diag.span_suggestion(full_expr.span, "try doing something like", hint, applicability);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use rustc_attr_data_structures::{StabilityLevel, StableSince};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Block, Body, HirId, Path, PathSegment};
|
||||
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
|
||||
|
|
@ -88,49 +88,52 @@ declare_clippy_lint! {
|
|||
}
|
||||
|
||||
pub struct StdReexports {
|
||||
lint_point: (Span, Option<LintPoint>),
|
||||
lint_points: Option<(Span, Vec<LintPoint>)>,
|
||||
msrv: Msrv,
|
||||
}
|
||||
|
||||
impl StdReexports {
|
||||
pub fn new(conf: &'static Conf) -> Self {
|
||||
Self {
|
||||
lint_point: Default::default(),
|
||||
lint_points: Option::default(),
|
||||
msrv: conf.msrv,
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_if_finish(&mut self, cx: &LateContext<'_>, (span, item): (Span, Option<LintPoint>)) {
|
||||
if span.source_equal(self.lint_point.0) {
|
||||
return;
|
||||
fn lint_if_finish(&mut self, cx: &LateContext<'_>, krate: Span, lint_point: LintPoint) {
|
||||
match &mut self.lint_points {
|
||||
Some((prev_krate, prev_lints)) if prev_krate.overlaps(krate) => {
|
||||
prev_lints.push(lint_point);
|
||||
},
|
||||
_ => emit_lints(cx, self.lint_points.replace((krate, vec![lint_point]))),
|
||||
}
|
||||
|
||||
if !self.lint_point.0.is_dummy() {
|
||||
emit_lints(cx, &self.lint_point);
|
||||
}
|
||||
|
||||
self.lint_point = (span, item);
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]);
|
||||
|
||||
type LintPoint = (&'static Lint, &'static str, &'static str);
|
||||
#[derive(Debug)]
|
||||
enum LintPoint {
|
||||
Available(Span, &'static Lint, &'static str, &'static str),
|
||||
Conflict,
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for StdReexports {
|
||||
fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
|
||||
if let Res::Def(_, def_id) = path.res
|
||||
if let Res::Def(def_kind, def_id) = path.res
|
||||
&& let Some(first_segment) = get_first_segment(path)
|
||||
&& is_stable(cx, def_id, self.msrv)
|
||||
&& !path.span.in_external_macro(cx.sess().source_map())
|
||||
&& !is_from_proc_macro(cx, &first_segment.ident)
|
||||
&& !matches!(def_kind, DefKind::Macro(_))
|
||||
&& let Some(last_segment) = path.segments.last()
|
||||
{
|
||||
let (lint, used_mod, replace_with) = match first_segment.ident.name {
|
||||
sym::std => match cx.tcx.crate_name(def_id.krate) {
|
||||
sym::core => (STD_INSTEAD_OF_CORE, "std", "core"),
|
||||
sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"),
|
||||
_ => {
|
||||
self.lint_if_finish(cx, (first_segment.ident.span, None));
|
||||
self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict);
|
||||
return;
|
||||
},
|
||||
},
|
||||
|
|
@ -138,44 +141,84 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
|
|||
if cx.tcx.crate_name(def_id.krate) == sym::core {
|
||||
(ALLOC_INSTEAD_OF_CORE, "alloc", "core")
|
||||
} else {
|
||||
self.lint_if_finish(cx, (first_segment.ident.span, None));
|
||||
self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict);
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => return,
|
||||
_ => {
|
||||
self.lint_if_finish(cx, first_segment.ident.span, LintPoint::Conflict);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
self.lint_if_finish(cx, (first_segment.ident.span, Some((lint, used_mod, replace_with))));
|
||||
self.lint_if_finish(
|
||||
cx,
|
||||
first_segment.ident.span,
|
||||
LintPoint::Available(last_segment.ident.span, lint, used_mod, replace_with),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &Block<'tcx>) {
|
||||
self.lint_if_finish(cx, Default::default());
|
||||
emit_lints(cx, self.lint_points.take());
|
||||
}
|
||||
|
||||
fn check_body_post(&mut self, cx: &LateContext<'tcx>, _: &Body<'tcx>) {
|
||||
self.lint_if_finish(cx, Default::default());
|
||||
emit_lints(cx, self.lint_points.take());
|
||||
}
|
||||
|
||||
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
|
||||
self.lint_if_finish(cx, Default::default());
|
||||
emit_lints(cx, self.lint_points.take());
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_lints(cx: &LateContext<'_>, (span, item): &(Span, Option<LintPoint>)) {
|
||||
let Some((lint, used_mod, replace_with)) = item else {
|
||||
fn emit_lints(cx: &LateContext<'_>, lint_points: Option<(Span, Vec<LintPoint>)>) {
|
||||
let Some((krate_span, lint_points)) = lint_points else {
|
||||
return;
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
lint,
|
||||
*span,
|
||||
format!("used import from `{used_mod}` instead of `{replace_with}`"),
|
||||
format!("consider importing the item from `{replace_with}`"),
|
||||
(*replace_with).to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
let mut lint: Option<(&'static Lint, &'static str, &'static str)> = None;
|
||||
let mut has_conflict = false;
|
||||
for lint_point in &lint_points {
|
||||
match lint_point {
|
||||
LintPoint::Available(_, l, used_mod, replace_with)
|
||||
if lint.is_none_or(|(prev_l, ..)| l.name == prev_l.name) =>
|
||||
{
|
||||
lint = Some((l, used_mod, replace_with));
|
||||
},
|
||||
_ => {
|
||||
has_conflict = true;
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if !has_conflict && let Some((lint, used_mod, replace_with)) = lint {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
lint,
|
||||
krate_span,
|
||||
format!("used import from `{used_mod}` instead of `{replace_with}`"),
|
||||
format!("consider importing the item from `{replace_with}`"),
|
||||
(*replace_with).to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for lint_point in lint_points {
|
||||
let LintPoint::Available(span, lint, used_mod, replace_with) = lint_point else {
|
||||
continue;
|
||||
};
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
lint,
|
||||
span,
|
||||
format!("used import from `{used_mod}` instead of `{replace_with}`"),
|
||||
None,
|
||||
format!("consider importing the item from `{replace_with}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first named segment of a [`Path`].
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
|
|||
if let Some(tail) = block.expr
|
||||
&& !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id)
|
||||
&& !tail.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, tail.span, tail.hir_id)
|
||||
&& let HasSafetyComment::Yes(pos) =
|
||||
stmt_has_safety_comment(cx, tail.span, tail.hir_id, self.accept_comment_above_attributes)
|
||||
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos)
|
||||
{
|
||||
span_lint_and_then(
|
||||
|
|
@ -167,7 +168,8 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
|
|||
};
|
||||
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id)
|
||||
&& !stmt.span.in_external_macro(cx.tcx.sess.source_map())
|
||||
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, stmt.span, stmt.hir_id)
|
||||
&& let HasSafetyComment::Yes(pos) =
|
||||
stmt_has_safety_comment(cx, stmt.span, stmt.hir_id, self.accept_comment_above_attributes)
|
||||
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos)
|
||||
{
|
||||
span_lint_and_then(
|
||||
|
|
@ -540,7 +542,12 @@ fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSaf
|
|||
|
||||
/// Checks if the lines immediately preceding the item contain a safety comment.
|
||||
#[allow(clippy::collapsible_match)]
|
||||
fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> HasSafetyComment {
|
||||
fn stmt_has_safety_comment(
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
hir_id: HirId,
|
||||
accept_comment_above_attributes: bool,
|
||||
) -> HasSafetyComment {
|
||||
match span_from_macro_expansion_has_safety_comment(cx, span) {
|
||||
HasSafetyComment::Maybe => (),
|
||||
has_safety_comment => return has_safety_comment,
|
||||
|
|
@ -555,6 +562,13 @@ fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> H
|
|||
_ => return HasSafetyComment::Maybe,
|
||||
};
|
||||
|
||||
// if span_with_attrs_has_safety_comment(cx, span, hir_id, accept_comment_above_attrib
|
||||
// }
|
||||
let mut span = span;
|
||||
if accept_comment_above_attributes {
|
||||
span = include_attrs_in_span(cx, hir_id, span);
|
||||
}
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
if let Some(comment_start) = comment_start
|
||||
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ pub struct Symbols {
|
|||
impl_lint_pass!(Symbols => [INTERNING_LITERALS, SYMBOL_AS_STR]);
|
||||
|
||||
impl Symbols {
|
||||
fn lit_suggestion(&self, lit: &Lit) -> Option<(Span, String)> {
|
||||
fn lit_suggestion(&self, lit: Lit) -> Option<(Span, String)> {
|
||||
if let LitKind::Str(name, _) = lit.node {
|
||||
let sugg = if let Some((prefix, name)) = self.symbol_map.get(&name.as_u32()) {
|
||||
format!("{prefix}::{name}")
|
||||
|
|
|
|||
17
clippy_test_deps/Cargo.toml
Normal file
17
clippy_test_deps/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "clippy_test_deps"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# Add dependencies here to make them available in ui tests.
|
||||
|
||||
[dependencies]
|
||||
regex = "1.5.5"
|
||||
serde = { version = "1.0.145", features = ["derive"] }
|
||||
if_chain = "1.0"
|
||||
quote = "1.0.25"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
futures = "0.3"
|
||||
parking_lot = "0.12"
|
||||
tokio = { version = "1", features = ["io-util"] }
|
||||
itertools = "0.12"
|
||||
1
clippy_test_deps/src/main.rs
Normal file
1
clippy_test_deps/src/main.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
fn main() {}
|
||||
|
|
@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
|
|||
|
||||
<!-- begin autogenerated nightly -->
|
||||
```
|
||||
nightly-2025-06-26
|
||||
nightly-2025-07-10
|
||||
```
|
||||
<!-- end autogenerated nightly -->
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use crate::source::SpanRangeExt;
|
||||
use crate::{sym, tokenize_with_text};
|
||||
use rustc_ast::attr;
|
||||
use rustc_ast::attr::AttributeExt;
|
||||
use rustc_attr_data_structures::{AttributeKind, find_attr};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -7,10 +10,6 @@ use rustc_middle::ty::{AdtDef, TyCtxt};
|
|||
use rustc_session::Session;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use std::str::FromStr;
|
||||
use rustc_attr_data_structures::find_attr;
|
||||
use crate::source::SpanRangeExt;
|
||||
use crate::{sym, tokenize_with_text};
|
||||
use rustc_attr_data_structures::AttributeKind;
|
||||
|
||||
/// Deprecation status of attributes known by Clippy.
|
||||
pub enum DeprecationStatus {
|
||||
|
|
@ -168,7 +167,8 @@ pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
|
|||
adt.is_variant_list_non_exhaustive()
|
||||
|| find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..))
|
||||
|| adt.variants().iter().any(|variant_def| {
|
||||
variant_def.is_field_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..))
|
||||
variant_def.is_field_list_non_exhaustive()
|
||||
|| find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..))
|
||||
})
|
||||
|| adt
|
||||
.all_fields()
|
||||
|
|
|
|||
|
|
@ -304,9 +304,7 @@ pub fn lit_to_mir_constant<'tcx>(lit: &LitKind, ty: Option<Ty<'tcx>>) -> Constan
|
|||
match *lit {
|
||||
LitKind::Str(ref is, _) => Constant::Str(is.to_string()),
|
||||
LitKind::Byte(b) => Constant::Int(u128::from(b)),
|
||||
LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => {
|
||||
Constant::Binary(s.as_byte_str().to_vec())
|
||||
}
|
||||
LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => Constant::Binary(s.as_byte_str().to_vec()),
|
||||
LitKind::Char(c) => Constant::Char(c),
|
||||
LitKind::Int(n, _) => Constant::Int(n.get()),
|
||||
LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty {
|
||||
|
|
@ -568,9 +566,7 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
|
|||
} else {
|
||||
match &lit.node {
|
||||
LitKind::Str(is, _) => Some(is.is_empty()),
|
||||
LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => {
|
||||
Some(s.as_byte_str().is_empty())
|
||||
}
|
||||
LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => Some(s.as_byte_str().is_empty()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::consts::ConstEvalCtxt;
|
|||
use crate::macros::macro_backtrace;
|
||||
use crate::source::{SpanRange, SpanRangeExt, walk_span_to_context};
|
||||
use crate::tokenize_with_text;
|
||||
use rustc_ast::ast;
|
||||
use rustc_ast::ast::InlineAsmTemplatePiece;
|
||||
use rustc_data_structures::fx::FxHasher;
|
||||
use rustc_hir::MatchSource::TryDesugar;
|
||||
|
|
@ -9,8 +10,8 @@ use rustc_hir::def::{DefKind, Res};
|
|||
use rustc_hir::{
|
||||
AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr, ExprField,
|
||||
ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeKind,
|
||||
Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, StructTailExpr,
|
||||
TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind,
|
||||
Node, Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
|
||||
StructTailExpr, TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind,
|
||||
};
|
||||
use rustc_lexer::{TokenKind, tokenize};
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -1004,8 +1005,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
self.hash_expr(e);
|
||||
}
|
||||
},
|
||||
ExprKind::Match(e, arms, s) => {
|
||||
self.hash_expr(e);
|
||||
ExprKind::Match(scrutinee, arms, _) => {
|
||||
self.hash_expr(scrutinee);
|
||||
|
||||
for arm in *arms {
|
||||
self.hash_pat(arm.pat);
|
||||
|
|
@ -1014,8 +1015,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
}
|
||||
self.hash_expr(arm.body);
|
||||
}
|
||||
|
||||
s.hash(&mut self.s);
|
||||
},
|
||||
ExprKind::MethodCall(path, receiver, args, _fn_span) => {
|
||||
self.hash_name(path.ident.name);
|
||||
|
|
@ -1058,8 +1057,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
ExprKind::Use(expr, _) => {
|
||||
self.hash_expr(expr);
|
||||
},
|
||||
ExprKind::Unary(lop, le) => {
|
||||
std::mem::discriminant(lop).hash(&mut self.s);
|
||||
ExprKind::Unary(l_op, le) => {
|
||||
std::mem::discriminant(l_op).hash(&mut self.s);
|
||||
self.hash_expr(le);
|
||||
},
|
||||
ExprKind::UnsafeBinderCast(kind, expr, ty) => {
|
||||
|
|
@ -1394,3 +1393,70 @@ fn eq_span_tokens(
|
|||
}
|
||||
f(cx, left.into_range(), right.into_range(), pred)
|
||||
}
|
||||
|
||||
/// Returns true if the expression contains ambiguous literals (unsuffixed float or int literals)
|
||||
/// that could be interpreted as either f32/f64 or i32/i64 depending on context.
|
||||
pub fn has_ambiguous_literal_in_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
match expr.kind {
|
||||
ExprKind::Path(ref qpath) => {
|
||||
if let Res::Local(hir_id) = cx.qpath_res(qpath, expr.hir_id)
|
||||
&& let Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id)
|
||||
&& local.ty.is_none()
|
||||
&& let Some(init) = local.init
|
||||
{
|
||||
return has_ambiguous_literal_in_expr(cx, init);
|
||||
}
|
||||
false
|
||||
},
|
||||
ExprKind::Lit(lit) => matches!(
|
||||
lit.node,
|
||||
ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) | ast::LitKind::Int(_, ast::LitIntType::Unsuffixed)
|
||||
),
|
||||
|
||||
ExprKind::Array(exprs) | ExprKind::Tup(exprs) => exprs.iter().any(|e| has_ambiguous_literal_in_expr(cx, e)),
|
||||
|
||||
ExprKind::Assign(lhs, rhs, _) | ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Binary(_, lhs, rhs) => {
|
||||
has_ambiguous_literal_in_expr(cx, lhs) || has_ambiguous_literal_in_expr(cx, rhs)
|
||||
},
|
||||
|
||||
ExprKind::Unary(_, e)
|
||||
| ExprKind::Cast(e, _)
|
||||
| ExprKind::Type(e, _)
|
||||
| ExprKind::DropTemps(e)
|
||||
| ExprKind::AddrOf(_, _, e)
|
||||
| ExprKind::Field(e, _)
|
||||
| ExprKind::Index(e, _, _)
|
||||
| ExprKind::Yield(e, _) => has_ambiguous_literal_in_expr(cx, e),
|
||||
|
||||
ExprKind::MethodCall(_, receiver, args, _) | ExprKind::Call(receiver, args) => {
|
||||
has_ambiguous_literal_in_expr(cx, receiver) || args.iter().any(|e| has_ambiguous_literal_in_expr(cx, e))
|
||||
},
|
||||
|
||||
ExprKind::Closure(Closure { body, .. }) => {
|
||||
let body = cx.tcx.hir_body(*body);
|
||||
let closure_expr = crate::peel_blocks(body.value);
|
||||
has_ambiguous_literal_in_expr(cx, closure_expr)
|
||||
},
|
||||
|
||||
ExprKind::Block(blk, _) => blk.expr.as_ref().is_some_and(|e| has_ambiguous_literal_in_expr(cx, e)),
|
||||
|
||||
ExprKind::If(cond, then_expr, else_expr) => {
|
||||
has_ambiguous_literal_in_expr(cx, cond)
|
||||
|| has_ambiguous_literal_in_expr(cx, then_expr)
|
||||
|| else_expr.as_ref().is_some_and(|e| has_ambiguous_literal_in_expr(cx, e))
|
||||
},
|
||||
|
||||
ExprKind::Match(scrutinee, arms, _) => {
|
||||
has_ambiguous_literal_in_expr(cx, scrutinee)
|
||||
|| arms.iter().any(|arm| has_ambiguous_literal_in_expr(cx, arm.body))
|
||||
},
|
||||
|
||||
ExprKind::Loop(body, ..) => body.expr.is_some_and(|e| has_ambiguous_literal_in_expr(cx, e)),
|
||||
|
||||
ExprKind::Ret(opt_expr) | ExprKind::Break(_, opt_expr) => {
|
||||
opt_expr.as_ref().is_some_and(|e| has_ambiguous_literal_in_expr(cx, e))
|
||||
},
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ pub mod visitors;
|
|||
pub use self::attrs::*;
|
||||
pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
|
||||
pub use self::hir_utils::{
|
||||
HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over,
|
||||
HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, has_ambiguous_literal_in_expr, hash_expr,
|
||||
hash_stmt, is_bool, over,
|
||||
};
|
||||
|
||||
use core::mem;
|
||||
|
|
@ -3497,3 +3498,64 @@ pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) ->
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `expr` may be directly used as the return value of its enclosing body.
|
||||
/// The following cases are covered:
|
||||
/// - `expr` as the last expression of the body, or of a block that can be used as the return value
|
||||
/// - `return expr`
|
||||
/// - then or else part of a `if` in return position
|
||||
/// - arm body of a `match` in a return position
|
||||
/// - `break expr` or `break 'label expr` if the loop or block being exited is used as a return
|
||||
/// value
|
||||
///
|
||||
/// Contrary to [`TyCtxt::hir_get_fn_id_for_return_block()`], if `expr` is part of a
|
||||
/// larger expression, for example a field expression of a `struct`, it will not be
|
||||
/// considered as matching the condition and will return `false`.
|
||||
///
|
||||
/// Also, even if `expr` is assigned to a variable which is later returned, this function
|
||||
/// will still return `false` because `expr` is not used *directly* as the return value
|
||||
/// as it goes through the intermediate variable.
|
||||
pub fn potential_return_of_enclosing_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
let enclosing_body_owner = cx
|
||||
.tcx
|
||||
.local_def_id_to_hir_id(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
|
||||
let mut prev_id = expr.hir_id;
|
||||
let mut skip_until_id = None;
|
||||
for (hir_id, node) in cx.tcx.hir_parent_iter(expr.hir_id) {
|
||||
if hir_id == enclosing_body_owner {
|
||||
return true;
|
||||
}
|
||||
if let Some(id) = skip_until_id {
|
||||
prev_id = hir_id;
|
||||
if id == hir_id {
|
||||
skip_until_id = None;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
match node {
|
||||
Node::Block(Block { expr, .. }) if expr.is_some_and(|expr| expr.hir_id == prev_id) => {},
|
||||
Node::Arm(arm) if arm.body.hir_id == prev_id => {},
|
||||
Node::Expr(expr) => match expr.kind {
|
||||
ExprKind::Ret(_) => return true,
|
||||
ExprKind::If(_, then, opt_else)
|
||||
if then.hir_id == prev_id || opt_else.is_some_and(|els| els.hir_id == prev_id) => {},
|
||||
ExprKind::Match(_, arms, _) if arms.iter().any(|arm| arm.hir_id == prev_id) => {},
|
||||
ExprKind::Block(block, _) if block.hir_id == prev_id => {},
|
||||
ExprKind::Break(
|
||||
Destination {
|
||||
target_id: Ok(target_id),
|
||||
..
|
||||
},
|
||||
_,
|
||||
) => skip_until_id = Some(target_id),
|
||||
_ => break,
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
prev_id = hir_id;
|
||||
}
|
||||
|
||||
// `expr` is used as part of "something" and is not returned directly from its
|
||||
// enclosing body.
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ msrv_aliases! {
|
|||
1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
|
||||
1,27,0 { ITERATOR_TRY_FOLD }
|
||||
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN }
|
||||
1,24,0 { IS_ASCII_DIGIT }
|
||||
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 }
|
||||
1,16,0 { STR_REPEAT }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::msrvs::Msrv;
|
||||
use crate::qualify_min_const_fn::is_stable_const_fn;
|
||||
use crate::ty::needs_ordered_drop;
|
||||
use crate::{get_enclosing_block, path_to_local_id};
|
||||
use crate::qualify_min_const_fn::is_stable_const_fn;
|
||||
use core::ops::ControlFlow;
|
||||
use rustc_ast::visit::{VisitorResult, try_visit};
|
||||
use rustc_hir::def::{CtorKind, DefKind, Res};
|
||||
|
|
@ -355,7 +355,7 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) ->
|
|||
ExprKind::Binary(_, lhs, rhs)
|
||||
if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
|
||||
&& self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
|
||||
ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
|
||||
ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_raw_ptr() => (),
|
||||
ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
|
||||
ExprKind::Index(base, _, _)
|
||||
if matches!(
|
||||
|
|
@ -390,7 +390,8 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) ->
|
|||
| ExprKind::Repeat(..)
|
||||
| ExprKind::Struct(..)
|
||||
| ExprKind::Tup(_)
|
||||
| ExprKind::Type(..) => (),
|
||||
| ExprKind::Type(..)
|
||||
| ExprKind::UnsafeBinderCast(..) => (),
|
||||
|
||||
_ => {
|
||||
return ControlFlow::Break(());
|
||||
|
|
@ -678,10 +679,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
|
|||
helper(typeck, true, else_expr, f)?;
|
||||
}
|
||||
},
|
||||
ExprKind::Type(e, _) => {
|
||||
helper(typeck, consume, e, f)?;
|
||||
},
|
||||
ExprKind::UnsafeBinderCast(_, e, _) => {
|
||||
ExprKind::Type(e, _) | ExprKind::UnsafeBinderCast(_, e, _) => {
|
||||
helper(typeck, consume, e, f)?;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ pub(crate) enum Commands {
|
|||
/// This will limit the number of warnings that will be printed for each lint
|
||||
#[clap(long)]
|
||||
truncate: bool,
|
||||
/// Write the diff summary to a JSON file if there are any changes
|
||||
#[clap(long, value_name = "PATH")]
|
||||
write_summary: Option<PathBuf>,
|
||||
},
|
||||
/// Create a lintcheck crates TOML file containing the top N popular crates
|
||||
Popular {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
//! loading warnings from JSON files, and generating human-readable diffs
|
||||
//! between different linting runs.
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fmt, fs};
|
||||
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -17,7 +17,6 @@ const DEFAULT_LIMIT_PER_LINT: usize = 300;
|
|||
/// Target for total warnings to display across all lints when truncating output.
|
||||
const TRUNCATION_TOTAL_TARGET: usize = 1000;
|
||||
|
||||
/// Representation of a single Clippy warning for JSON serialization.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct LintJson {
|
||||
/// The lint name e.g. `clippy::bytes_nth`
|
||||
|
|
@ -29,7 +28,6 @@ struct LintJson {
|
|||
}
|
||||
|
||||
impl LintJson {
|
||||
/// Returns a tuple of name and `file_line` for sorting and comparison.
|
||||
fn key(&self) -> impl Ord + '_ {
|
||||
(self.name.as_str(), self.file_line.as_str())
|
||||
}
|
||||
|
|
@ -40,6 +38,57 @@ impl LintJson {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct SummaryRow {
|
||||
name: String,
|
||||
added: usize,
|
||||
removed: usize,
|
||||
changed: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Summary(Vec<SummaryRow>);
|
||||
|
||||
impl fmt::Display for Summary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(
|
||||
"\
|
||||
| Lint | Added | Removed | Changed |
|
||||
| ---- | ----: | ------: | ------: |
|
||||
",
|
||||
)?;
|
||||
|
||||
for SummaryRow {
|
||||
name,
|
||||
added,
|
||||
changed,
|
||||
removed,
|
||||
} in &self.0
|
||||
{
|
||||
let html_id = to_html_id(name);
|
||||
writeln!(f, "| [`{name}`](#{html_id}) | {added} | {changed} | {removed} |")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Summary {
|
||||
fn new(lints: &[LintWarnings]) -> Self {
|
||||
Summary(
|
||||
lints
|
||||
.iter()
|
||||
.map(|lint| SummaryRow {
|
||||
name: lint.name.clone(),
|
||||
added: lint.added.len(),
|
||||
removed: lint.removed.len(),
|
||||
changed: lint.changed.len(),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the log file output for [`crate::config::OutputFormat::Json`]
|
||||
pub(crate) fn output(clippy_warnings: Vec<ClippyWarning>) -> String {
|
||||
let mut lints: Vec<LintJson> = clippy_warnings
|
||||
|
|
@ -74,7 +123,7 @@ fn load_warnings(path: &Path) -> Vec<LintJson> {
|
|||
///
|
||||
/// Compares warnings from `old_path` and `new_path`, then displays a summary table
|
||||
/// and detailed information about added, removed, and changed warnings.
|
||||
pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) {
|
||||
pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool, write_summary: Option<PathBuf>) {
|
||||
let old_warnings = load_warnings(old_path);
|
||||
let new_warnings = load_warnings(new_path);
|
||||
|
||||
|
|
@ -108,13 +157,16 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) {
|
|||
}
|
||||
}
|
||||
|
||||
print_summary_table(&lint_warnings);
|
||||
println!();
|
||||
|
||||
if lint_warnings.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let summary = Summary::new(&lint_warnings);
|
||||
if let Some(path) = write_summary {
|
||||
let json = serde_json::to_string(&summary).unwrap();
|
||||
fs::write(path, json).unwrap();
|
||||
}
|
||||
|
||||
let truncate_after = if truncate {
|
||||
// Max 15 ensures that we at least have five messages per lint
|
||||
DEFAULT_LIMIT_PER_LINT
|
||||
|
|
@ -126,6 +178,7 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path, truncate: bool) {
|
|||
usize::MAX
|
||||
};
|
||||
|
||||
println!("{summary}");
|
||||
for lint in lint_warnings {
|
||||
print_lint_warnings(&lint, truncate_after);
|
||||
}
|
||||
|
|
@ -140,13 +193,11 @@ struct LintWarnings {
|
|||
changed: Vec<(LintJson, LintJson)>,
|
||||
}
|
||||
|
||||
/// Prints a formatted report for a single lint type with its warnings.
|
||||
fn print_lint_warnings(lint: &LintWarnings, truncate_after: usize) {
|
||||
let name = &lint.name;
|
||||
let html_id = to_html_id(name);
|
||||
|
||||
// The additional anchor is added for non GH viewers that don't prefix ID's
|
||||
println!(r#"## `{name}` <a id="user-content-{html_id}"></a>"#);
|
||||
println!(r#"<h2 id="{html_id}"><code>{name}</code></h2>"#);
|
||||
println!();
|
||||
|
||||
print!(
|
||||
|
|
@ -162,22 +213,6 @@ fn print_lint_warnings(lint: &LintWarnings, truncate_after: usize) {
|
|||
print_changed_diff(&lint.changed, truncate_after / 3);
|
||||
}
|
||||
|
||||
/// Prints a summary table of all lints with counts of added, removed, and changed warnings.
|
||||
fn print_summary_table(lints: &[LintWarnings]) {
|
||||
println!("| Lint | Added | Removed | Changed |");
|
||||
println!("| ------------------------------------------ | ------: | ------: | ------: |");
|
||||
|
||||
for lint in lints {
|
||||
println!(
|
||||
"| {:<62} | {:>7} | {:>7} | {:>7} |",
|
||||
format!("[`{}`](#user-content-{})", lint.name, to_html_id(&lint.name)),
|
||||
lint.added.len(),
|
||||
lint.removed.len(),
|
||||
lint.changed.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a section of warnings with a header and formatted code blocks.
|
||||
fn print_warnings(title: &str, warnings: &[LintJson], truncate_after: usize) {
|
||||
if warnings.is_empty() {
|
||||
|
|
@ -248,17 +283,16 @@ fn truncate<T>(list: &[T], truncate_after: usize) -> &[T] {
|
|||
}
|
||||
}
|
||||
|
||||
/// Prints a level 3 heading with an appropriate HTML ID for linking.
|
||||
fn print_h3(lint: &str, title: &str) {
|
||||
let html_id = to_html_id(lint);
|
||||
// We have to use HTML here to be able to manually add an id.
|
||||
println!(r#"### {title} <a id="user-content-{html_id}-{title}"></a>"#);
|
||||
// We have to use HTML here to be able to manually add an id, GitHub doesn't add them automatically
|
||||
println!(r#"<h3 id="{html_id}-{title}">{title}</h3>"#);
|
||||
}
|
||||
|
||||
/// GitHub's markdown parsers doesn't like IDs with `::` and `_`. This simplifies
|
||||
/// the lint name for the HTML ID.
|
||||
/// Creates a custom ID allowed by GitHub, they must start with `user-content-` and cannot contain
|
||||
/// `::`/`_`
|
||||
fn to_html_id(lint_name: &str) -> String {
|
||||
lint_name.replace("clippy::", "").replace('_', "-")
|
||||
lint_name.replace("clippy::", "user-content-").replace('_', "-")
|
||||
}
|
||||
|
||||
/// This generates the `x added` string for the start of the job summery.
|
||||
|
|
@ -270,9 +304,6 @@ fn count_string(lint: &str, label: &str, count: usize) -> String {
|
|||
format!("0 {label}")
|
||||
} else {
|
||||
let html_id = to_html_id(lint);
|
||||
// GitHub's job summaries don't add HTML ids to headings. That's why we
|
||||
// manually have to add them. GitHub prefixes these manual ids with
|
||||
// `user-content-` and that's how we end up with these awesome links :D
|
||||
format!("[{count} {label}](#user-content-{html_id}-{label})")
|
||||
format!("[{count} {label}](#{html_id}-{label})")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,7 +303,12 @@ fn main() {
|
|||
let config = LintcheckConfig::new();
|
||||
|
||||
match config.subcommand {
|
||||
Some(Commands::Diff { old, new, truncate }) => json::diff(&old, &new, truncate),
|
||||
Some(Commands::Diff {
|
||||
old,
|
||||
new,
|
||||
truncate,
|
||||
write_summary,
|
||||
}) => json::diff(&old, &new, truncate, write_summary),
|
||||
Some(Commands::Popular { output, number }) => popular_crates::fetch(output, number).unwrap(),
|
||||
None => lintcheck(config),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[toolchain]
|
||||
# begin autogenerated nightly
|
||||
channel = "nightly-2025-06-26"
|
||||
channel = "nightly-2025-07-10"
|
||||
# end autogenerated nightly
|
||||
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
|
||||
profile = "minimal"
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ use test_utils::IS_RUSTC_TEST_SUITE;
|
|||
use ui_test::custom_flags::Flag;
|
||||
use ui_test::custom_flags::edition::Edition;
|
||||
use ui_test::custom_flags::rustfix::RustfixMode;
|
||||
use ui_test::dependencies::DependencyBuilder;
|
||||
use ui_test::spanned::Spanned;
|
||||
use ui_test::{Args, CommandBuilder, Config, Match, error_on_output_conflict, status_emitter};
|
||||
use ui_test::status_emitter::StatusEmitter;
|
||||
use ui_test::{Args, CommandBuilder, Config, Match, error_on_output_conflict};
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::env::{self, set_var, var_os};
|
||||
|
|
@ -27,46 +29,26 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::mpsc::{Sender, channel};
|
||||
use std::{fs, iter, thread};
|
||||
|
||||
// Test dependencies may need an `extern crate` here to ensure that they show up
|
||||
// in the depinfo file (otherwise cargo thinks they are unused)
|
||||
extern crate futures;
|
||||
extern crate if_chain;
|
||||
extern crate itertools;
|
||||
extern crate parking_lot;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
extern crate tokio;
|
||||
|
||||
mod test_utils;
|
||||
|
||||
/// All crates used in UI tests are listed here
|
||||
static TEST_DEPENDENCIES: &[&str] = &[
|
||||
"clippy_config",
|
||||
"clippy_lints",
|
||||
"clippy_utils",
|
||||
"futures",
|
||||
"if_chain",
|
||||
"itertools",
|
||||
"parking_lot",
|
||||
"quote",
|
||||
"regex",
|
||||
"serde_derive",
|
||||
"serde",
|
||||
"syn",
|
||||
"tokio",
|
||||
];
|
||||
/// All crates used in internal UI tests are listed here.
|
||||
/// We directly re-use these crates from their normal clippy builds, so we don't have them
|
||||
/// in `clippy_test_devs`. That saves a lot of time but also means they don't work in a stage 1
|
||||
/// test in rustc bootstrap.
|
||||
static INTERNAL_TEST_DEPENDENCIES: &[&str] = &["clippy_config", "clippy_lints", "clippy_utils"];
|
||||
|
||||
/// Produces a string with an `--extern` flag for all UI test crate
|
||||
/// dependencies.
|
||||
/// Produces a string with an `--extern` flag for all `INTERNAL_TEST_DEPENDENCIES`.
|
||||
///
|
||||
/// The dependency files are located by parsing the depinfo file for this test
|
||||
/// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
|
||||
/// dependencies must be added to Cargo.toml at the project root. Test
|
||||
/// dependencies that are not *directly* used by this test module require an
|
||||
/// `extern crate` declaration.
|
||||
fn extern_flags() -> Vec<String> {
|
||||
fn internal_extern_flags() -> Vec<String> {
|
||||
let current_exe_path = env::current_exe().unwrap();
|
||||
let deps_path = current_exe_path.parent().unwrap();
|
||||
let current_exe_depinfo = {
|
||||
let mut path = env::current_exe().unwrap();
|
||||
let mut path = current_exe_path.clone();
|
||||
path.set_extension("d");
|
||||
fs::read_to_string(path).unwrap()
|
||||
};
|
||||
|
|
@ -88,7 +70,7 @@ fn extern_flags() -> Vec<String> {
|
|||
Some((name, path_str))
|
||||
};
|
||||
if let Some((name, path)) = parse_name_path()
|
||||
&& TEST_DEPENDENCIES.contains(&name)
|
||||
&& INTERNAL_TEST_DEPENDENCIES.contains(&name)
|
||||
{
|
||||
// A dependency may be listed twice if it is available in sysroot,
|
||||
// and the sysroot dependencies are listed first. As of the writing,
|
||||
|
|
@ -96,7 +78,7 @@ fn extern_flags() -> Vec<String> {
|
|||
crates.insert(name, path);
|
||||
}
|
||||
}
|
||||
let not_found: Vec<&str> = TEST_DEPENDENCIES
|
||||
let not_found: Vec<&str> = INTERNAL_TEST_DEPENDENCIES
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|n| !crates.contains_key(n))
|
||||
|
|
@ -111,6 +93,7 @@ fn extern_flags() -> Vec<String> {
|
|||
crates
|
||||
.into_iter()
|
||||
.map(|(name, path)| format!("--extern={name}={path}"))
|
||||
.chain([format!("-Ldependency={}", deps_path.display())])
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +102,6 @@ const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal");
|
|||
|
||||
struct TestContext {
|
||||
args: Args,
|
||||
extern_flags: Vec<String>,
|
||||
diagnostic_collector: Option<DiagnosticCollector>,
|
||||
collector_thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
|
@ -134,7 +116,6 @@ impl TestContext {
|
|||
.unzip();
|
||||
Self {
|
||||
args,
|
||||
extern_flags: extern_flags(),
|
||||
diagnostic_collector,
|
||||
collector_thread,
|
||||
}
|
||||
|
|
@ -158,6 +139,15 @@ impl TestContext {
|
|||
};
|
||||
let defaults = config.comment_defaults.base();
|
||||
defaults.set_custom("edition", Edition("2024".into()));
|
||||
defaults.set_custom(
|
||||
"dependencies",
|
||||
DependencyBuilder {
|
||||
program: CommandBuilder::cargo(),
|
||||
crate_manifest_path: Path::new("clippy_test_deps").join("Cargo.toml"),
|
||||
build_std: None,
|
||||
bless_lockfile: self.args.bless,
|
||||
},
|
||||
);
|
||||
defaults.exit_status = None.into();
|
||||
if mandatory_annotations {
|
||||
defaults.require_annotations = Some(Spanned::dummy(true)).into();
|
||||
|
|
@ -182,12 +172,10 @@ impl TestContext {
|
|||
"-Zui-testing",
|
||||
"-Zdeduplicate-diagnostics=no",
|
||||
"-Dwarnings",
|
||||
&format!("-Ldependency={}", deps_path.display()),
|
||||
]
|
||||
.map(OsString::from),
|
||||
);
|
||||
|
||||
config.program.args.extend(self.extern_flags.iter().map(OsString::from));
|
||||
// Prevent rustc from creating `rustc-ice-*` files the console output is enough.
|
||||
config.program.envs.push(("RUSTC_ICE".into(), Some("0".into())));
|
||||
|
||||
|
|
@ -217,7 +205,7 @@ fn run_ui(cx: &TestContext) {
|
|||
vec![config],
|
||||
ui_test::default_file_filter,
|
||||
ui_test::default_per_file_config,
|
||||
status_emitter::Text::from(cx.args.format),
|
||||
Box::<dyn StatusEmitter>::from(cx.args.format),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -227,13 +215,17 @@ fn run_internal_tests(cx: &TestContext) {
|
|||
return;
|
||||
}
|
||||
let mut config = cx.base_config("ui-internal", true);
|
||||
config
|
||||
.program
|
||||
.args
|
||||
.extend(internal_extern_flags().iter().map(OsString::from));
|
||||
config.bless_command = Some("cargo uitest --features internal -- -- --bless".into());
|
||||
|
||||
ui_test::run_tests_generic(
|
||||
vec![config],
|
||||
ui_test::default_file_filter,
|
||||
ui_test::default_per_file_config,
|
||||
status_emitter::Text::from(cx.args.format),
|
||||
Box::<dyn StatusEmitter>::from(cx.args.format),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -257,7 +249,7 @@ fn run_ui_toml(cx: &TestContext) {
|
|||
.envs
|
||||
.push(("CLIPPY_CONF_DIR".into(), Some(path.parent().unwrap().into())));
|
||||
},
|
||||
status_emitter::Text::from(cx.args.format),
|
||||
Box::<dyn StatusEmitter>::from(cx.args.format),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -304,7 +296,7 @@ fn run_ui_cargo(cx: &TestContext) {
|
|||
.then(|| ui_test::default_any_file_filter(path, config) && !ignored_32bit(path))
|
||||
},
|
||||
|_config, _file_contents| {},
|
||||
status_emitter::Text::from(cx.args.format),
|
||||
Box::<dyn StatusEmitter>::from(cx.args.format),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,5 +450,13 @@ help: consider removing the safety comment
|
|||
LL | // SAFETY: unnecessary_safety_comment triggers here
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 52 previous errors
|
||||
error: unsafe block missing a safety comment
|
||||
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:733:12
|
||||
|
|
||||
LL | return unsafe { h() };
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: consider adding a safety comment on the preceding line
|
||||
|
||||
error: aborting due to 53 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -723,4 +723,15 @@ fn issue_13039() {
|
|||
_ = unsafe { foo() }
|
||||
}
|
||||
|
||||
fn rfl_issue15034() -> i32 {
|
||||
unsafe fn h() -> i32 {
|
||||
1i32
|
||||
}
|
||||
// This shouldn't lint with accept-comment-above-attributes! Thus fixing a false positive!
|
||||
// SAFETY: My safety comment!
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
return unsafe { h() };
|
||||
//~[disabled]^ ERROR: unsafe block missing a safety comment
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -47,3 +47,9 @@ fn implicit_cast() {
|
|||
// Do not lint references to temporaries
|
||||
core::ptr::eq(&0i32, &1i32);
|
||||
}
|
||||
|
||||
fn issue_15141() {
|
||||
let a = String::new();
|
||||
// Don't lint cast to dyn trait pointers
|
||||
let b = &a as *const dyn std::any::Any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,3 +47,9 @@ fn implicit_cast() {
|
|||
// Do not lint references to temporaries
|
||||
core::ptr::eq(&0i32, &1i32);
|
||||
}
|
||||
|
||||
fn issue_15141() {
|
||||
let a = String::new();
|
||||
// Don't lint cast to dyn trait pointers
|
||||
let b = &a as *const dyn std::any::Any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,14 @@ error: casting `usize` to `f64` causes a loss of precision on targets with 64-bi
|
|||
LL | 9_999_999_999_999_999usize as f64;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: casting `usize` to `u16` may truncate the value
|
||||
--> tests/ui/cast_size.rs:71:20
|
||||
|
|
||||
LL | const N: u16 = M as u16;
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
|
||||
error: literal out of range for `usize`
|
||||
--> tests/ui/cast_size.rs:63:5
|
||||
|
|
||||
|
|
@ -186,5 +194,5 @@ LL | 9_999_999_999_999_999usize as f64;
|
|||
= note: the literal `9_999_999_999_999_999usize` does not fit into the type `usize` whose range is `0..=4294967295`
|
||||
= note: `#[deny(overflowing_literals)]` on by default
|
||||
|
||||
error: aborting due to 19 previous errors
|
||||
error: aborting due to 20 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -177,5 +177,13 @@ error: casting `usize` to `f64` causes a loss of precision on targets with 64-bi
|
|||
LL | 9_999_999_999_999_999usize as f64;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 18 previous errors
|
||||
error: casting `usize` to `u16` may truncate the value
|
||||
--> tests/ui/cast_size.rs:71:20
|
||||
|
|
||||
LL | const N: u16 = M as u16;
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...
|
||||
|
||||
error: aborting due to 19 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -65,3 +65,9 @@ fn main() {
|
|||
//~[32bit]^^ ERROR: literal out of range for `usize`
|
||||
// 999_999_999_999_999_999_999_999_999_999u128 as f128;
|
||||
}
|
||||
|
||||
fn issue15163() {
|
||||
const M: usize = 100;
|
||||
const N: u16 = M as u16;
|
||||
//~^ cast_possible_truncation
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use std::any::Any;
|
||||
|
||||
fn main() {
|
||||
let x: Box<dyn Any> = Box::new(());
|
||||
let mut x: Box<dyn Any> = Box::new(());
|
||||
let ref_x = &x;
|
||||
|
||||
f(&*x);
|
||||
|
|
@ -15,12 +15,23 @@ fn main() {
|
|||
let _: &dyn Any = &*x;
|
||||
//~^ coerce_container_to_any
|
||||
|
||||
let _: &dyn Any = &*x;
|
||||
//~^ coerce_container_to_any
|
||||
|
||||
let _: &mut dyn Any = &mut *x;
|
||||
//~^ coerce_container_to_any
|
||||
|
||||
f(&42);
|
||||
f(&Box::new(()));
|
||||
f(&Box::new(Box::new(())));
|
||||
let ref_x = &x;
|
||||
f(&**ref_x);
|
||||
f(&*x);
|
||||
let _: &dyn Any = &*x;
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/15045
|
||||
#[allow(clippy::needless_borrow)]
|
||||
(&x).downcast_ref::<()>().unwrap();
|
||||
}
|
||||
|
||||
fn f(_: &dyn Any) {}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use std::any::Any;
|
||||
|
||||
fn main() {
|
||||
let x: Box<dyn Any> = Box::new(());
|
||||
let mut x: Box<dyn Any> = Box::new(());
|
||||
let ref_x = &x;
|
||||
|
||||
f(&x);
|
||||
|
|
@ -15,12 +15,23 @@ fn main() {
|
|||
let _: &dyn Any = &x;
|
||||
//~^ coerce_container_to_any
|
||||
|
||||
let _: &dyn Any = &mut x;
|
||||
//~^ coerce_container_to_any
|
||||
|
||||
let _: &mut dyn Any = &mut x;
|
||||
//~^ coerce_container_to_any
|
||||
|
||||
f(&42);
|
||||
f(&Box::new(()));
|
||||
f(&Box::new(Box::new(())));
|
||||
let ref_x = &x;
|
||||
f(&**ref_x);
|
||||
f(&*x);
|
||||
let _: &dyn Any = &*x;
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/15045
|
||||
#[allow(clippy::needless_borrow)]
|
||||
(&x).downcast_ref::<()>().unwrap();
|
||||
}
|
||||
|
||||
fn f(_: &dyn Any) {}
|
||||
|
|
|
|||
|
|
@ -19,5 +19,17 @@ error: coercing `&std::boxed::Box<dyn std::any::Any>` to `&dyn Any`
|
|||
LL | let _: &dyn Any = &x;
|
||||
| ^^ help: consider dereferencing: `&*x`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: coercing `&mut std::boxed::Box<dyn std::any::Any>` to `&dyn Any`
|
||||
--> tests/ui/coerce_container_to_any.rs:18:23
|
||||
|
|
||||
LL | let _: &dyn Any = &mut x;
|
||||
| ^^^^^^ help: consider dereferencing: `&*x`
|
||||
|
||||
error: coercing `&mut std::boxed::Box<dyn std::any::Any>` to `&mut dyn Any`
|
||||
--> tests/ui/coerce_container_to_any.rs:21:27
|
||||
|
|
||||
LL | let _: &mut dyn Any = &mut x;
|
||||
| ^^^^^^ help: consider dereferencing: `&mut *x`
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -15,3 +15,17 @@ fn main() {
|
|||
let カウンタ = 10;
|
||||
//~^ disallowed_script_idents
|
||||
}
|
||||
|
||||
fn issue15116() {
|
||||
const ÄÖÜ: u8 = 0;
|
||||
const _ÄÖÜ: u8 = 0;
|
||||
const Ä_ÖÜ: u8 = 0;
|
||||
const ÄÖ_Ü: u8 = 0;
|
||||
const ÄÖÜ_: u8 = 0;
|
||||
let äöüß = 1;
|
||||
let _äöüß = 1;
|
||||
let ä_öüß = 1;
|
||||
let äö_üß = 1;
|
||||
let äöü_ß = 1;
|
||||
let äöüß_ = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,6 @@ pub struct NotEmptyTight;
|
|||
|
||||
/// ## Heading
|
||||
///
|
||||
/// - [x][] - Done
|
||||
//~^ ERROR: link reference defined in list item
|
||||
/// - [ ][] - Not Done
|
||||
//~^ ERROR: link reference defined in list item
|
||||
/// - [x] - Done
|
||||
/// - [ ] - Not Done
|
||||
pub struct GithubCheckboxes;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,5 @@ pub struct NotEmptyTight;
|
|||
/// ## Heading
|
||||
///
|
||||
/// - [x] - Done
|
||||
//~^ ERROR: link reference defined in list item
|
||||
/// - [ ] - Not Done
|
||||
//~^ ERROR: link reference defined in list item
|
||||
pub struct GithubCheckboxes;
|
||||
|
|
|
|||
|
|
@ -144,29 +144,5 @@ help: for an intra-doc link, add `[]` between the label and the colon
|
|||
LL | /// - [link][]: def "title"
|
||||
| ++
|
||||
|
||||
error: link reference defined in list item
|
||||
--> tests/ui/doc/doc_nested_refdef_list_item.rs:75:7
|
||||
|
|
||||
LL | /// - [x] - Done
|
||||
| ^^^
|
||||
|
|
||||
= help: link definitions are not shown in rendered documentation
|
||||
help: for an intra-doc link, add `[]` between the label and the colon
|
||||
|
|
||||
LL | /// - [x][] - Done
|
||||
| ++
|
||||
|
||||
error: link reference defined in list item
|
||||
--> tests/ui/doc/doc_nested_refdef_list_item.rs:77:7
|
||||
|
|
||||
LL | /// - [ ] - Not Done
|
||||
| ^^^
|
||||
|
|
||||
= help: link definitions are not shown in rendered documentation
|
||||
help: for an intra-doc link, add `[]` between the label and the colon
|
||||
|
|
||||
LL | /// - [ ][] - Not Done
|
||||
| ++
|
||||
|
||||
error: aborting due to 14 previous errors
|
||||
error: aborting due to 12 previous errors
|
||||
|
||||
|
|
|
|||
13
tests/ui/empty_loop_intrinsic.rs
Normal file
13
tests/ui/empty_loop_intrinsic.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//@check-pass
|
||||
|
||||
#![warn(clippy::empty_loop)]
|
||||
#![feature(intrinsics)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
// From issue #15200
|
||||
#[rustc_intrinsic]
|
||||
#[rustc_nounwind]
|
||||
/// # Safety
|
||||
pub const unsafe fn simd_insert<T, U>(x: T, idx: u32, val: U) -> T;
|
||||
|
||||
fn main() {}
|
||||
17
tests/ui/exit1_compile_flag_test.rs
Normal file
17
tests/ui/exit1_compile_flag_test.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//@compile-flags: --test
|
||||
#![warn(clippy::exit)]
|
||||
|
||||
fn not_main() {
|
||||
if true {
|
||||
std::process::exit(4);
|
||||
//~^ exit
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if true {
|
||||
std::process::exit(2);
|
||||
};
|
||||
not_main();
|
||||
std::process::exit(1);
|
||||
}
|
||||
11
tests/ui/exit1_compile_flag_test.stderr
Normal file
11
tests/ui/exit1_compile_flag_test.stderr
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
error: usage of `process::exit`
|
||||
--> tests/ui/exit1_compile_flag_test.rs:6:9
|
||||
|
|
||||
LL | std::process::exit(4);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::exit` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::exit)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
15
tests/ui/exit2_compile_flag_test.rs
Normal file
15
tests/ui/exit2_compile_flag_test.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//@compile-flags: --test
|
||||
#![warn(clippy::exit)]
|
||||
|
||||
fn also_not_main() {
|
||||
std::process::exit(3);
|
||||
//~^ exit
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if true {
|
||||
std::process::exit(2);
|
||||
};
|
||||
also_not_main();
|
||||
std::process::exit(1);
|
||||
}
|
||||
11
tests/ui/exit2_compile_flag_test.stderr
Normal file
11
tests/ui/exit2_compile_flag_test.stderr
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
error: usage of `process::exit`
|
||||
--> tests/ui/exit2_compile_flag_test.rs:5:5
|
||||
|
|
||||
LL | std::process::exit(3);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::exit` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::exit)]`
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
11
tests/ui/exit3_compile_flag_test.rs
Normal file
11
tests/ui/exit3_compile_flag_test.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
//@ check-pass
|
||||
//@compile-flags: --test
|
||||
|
||||
#![warn(clippy::exit)]
|
||||
|
||||
fn main() {
|
||||
if true {
|
||||
std::process::exit(2);
|
||||
};
|
||||
std::process::exit(1);
|
||||
}
|
||||
8
tests/ui/exit4.rs
Normal file
8
tests/ui/exit4.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//@ check-pass
|
||||
//@compile-flags: --test
|
||||
|
||||
#![warn(clippy::exit)]
|
||||
|
||||
fn main() {
|
||||
std::process::exit(0)
|
||||
}
|
||||
|
|
@ -69,3 +69,47 @@ fn _issue11831() {
|
|||
|
||||
let _ = a + b * c;
|
||||
}
|
||||
|
||||
fn _issue14897() {
|
||||
let x = 1.0;
|
||||
let _ = x * 2.0 + 0.5; // should not suggest mul_add
|
||||
let _ = 0.5 + x * 2.0; // should not suggest mul_add
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
let _ = 1.2 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = -1.0;
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = { 4.0 };
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = if 1 > 2 { 1.0 } else { 2.0 };
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = 2.4 + 1.2;
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let f = || 4.0;
|
||||
let x = f();
|
||||
let _ = 0.5 + f() * 1.2; // should not suggest mul_add
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = 0.1;
|
||||
let y = x;
|
||||
let z = y;
|
||||
let _ = 0.5 + z * 1.2; // should not suggest mul_add
|
||||
|
||||
let _ = 2.0f64.mul_add(x, 0.5);
|
||||
//~^ suboptimal_flops
|
||||
let _ = 2.0f64.mul_add(x, 0.5);
|
||||
//~^ suboptimal_flops
|
||||
|
||||
let _ = 2.0f64.mul_add(4.0, x);
|
||||
//~^ suboptimal_flops
|
||||
|
||||
let y: f64 = 1.0;
|
||||
let _ = y.mul_add(2.0, 0.5);
|
||||
//~^ suboptimal_flops
|
||||
let _ = 1.0f64.mul_add(2.0, 0.5);
|
||||
//~^ suboptimal_flops
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,3 +69,47 @@ fn _issue11831() {
|
|||
|
||||
let _ = a + b * c;
|
||||
}
|
||||
|
||||
fn _issue14897() {
|
||||
let x = 1.0;
|
||||
let _ = x * 2.0 + 0.5; // should not suggest mul_add
|
||||
let _ = 0.5 + x * 2.0; // should not suggest mul_add
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
let _ = 1.2 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = -1.0;
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = { 4.0 };
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = if 1 > 2 { 1.0 } else { 2.0 };
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = 2.4 + 1.2;
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let f = || 4.0;
|
||||
let x = f();
|
||||
let _ = 0.5 + f() * 1.2; // should not suggest mul_add
|
||||
let _ = 0.5 + x * 1.2; // should not suggest mul_add
|
||||
|
||||
let x = 0.1;
|
||||
let y = x;
|
||||
let z = y;
|
||||
let _ = 0.5 + z * 1.2; // should not suggest mul_add
|
||||
|
||||
let _ = 0.5 + 2.0 * x;
|
||||
//~^ suboptimal_flops
|
||||
let _ = 2.0 * x + 0.5;
|
||||
//~^ suboptimal_flops
|
||||
|
||||
let _ = x + 2.0 * 4.0;
|
||||
//~^ suboptimal_flops
|
||||
|
||||
let y: f64 = 1.0;
|
||||
let _ = y * 2.0 + 0.5;
|
||||
//~^ suboptimal_flops
|
||||
let _ = 1.0 * 2.0 + 0.5;
|
||||
//~^ suboptimal_flops
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,5 +79,35 @@ error: multiply and add expressions can be calculated more efficiently and accur
|
|||
LL | let _ = a - (b * u as f64);
|
||||
| ^^^^^^^^^^^^^^^^^^ help: consider using: `b.mul_add(-(u as f64), a)`
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
error: multiply and add expressions can be calculated more efficiently and accurately
|
||||
--> tests/ui/floating_point_mul_add.rs:102:13
|
||||
|
|
||||
LL | let _ = 0.5 + 2.0 * x;
|
||||
| ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(x, 0.5)`
|
||||
|
||||
error: multiply and add expressions can be calculated more efficiently and accurately
|
||||
--> tests/ui/floating_point_mul_add.rs:104:13
|
||||
|
|
||||
LL | let _ = 2.0 * x + 0.5;
|
||||
| ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(x, 0.5)`
|
||||
|
||||
error: multiply and add expressions can be calculated more efficiently and accurately
|
||||
--> tests/ui/floating_point_mul_add.rs:107:13
|
||||
|
|
||||
LL | let _ = x + 2.0 * 4.0;
|
||||
| ^^^^^^^^^^^^^ help: consider using: `2.0f64.mul_add(4.0, x)`
|
||||
|
||||
error: multiply and add expressions can be calculated more efficiently and accurately
|
||||
--> tests/ui/floating_point_mul_add.rs:111:13
|
||||
|
|
||||
LL | let _ = y * 2.0 + 0.5;
|
||||
| ^^^^^^^^^^^^^ help: consider using: `y.mul_add(2.0, 0.5)`
|
||||
|
||||
error: multiply and add expressions can be calculated more efficiently and accurately
|
||||
--> tests/ui/floating_point_mul_add.rs:113:13
|
||||
|
|
||||
LL | let _ = 1.0 * 2.0 + 0.5;
|
||||
| ^^^^^^^^^^^^^^^ help: consider using: `1.0f64.mul_add(2.0, 0.5)`
|
||||
|
||||
error: aborting due to 18 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ fn option_methods() {
|
|||
|
||||
let _ = Some(2).is_some_and(|x| x % 2 == 0);
|
||||
//~^ manual_is_variant_and
|
||||
let _ = Some(2).is_none_or(|x| x % 2 == 0);
|
||||
let _ = Some(2).is_none_or(|x| x % 2 != 0);
|
||||
//~^ manual_is_variant_and
|
||||
let _ = Some(2).is_some_and(|x| x % 2 == 0);
|
||||
//~^ manual_is_variant_and
|
||||
|
|
@ -116,3 +116,113 @@ fn main() {
|
|||
option_methods();
|
||||
result_methods();
|
||||
}
|
||||
|
||||
fn issue15202() {
|
||||
let xs = [None, Some(b'_'), Some(b'1')];
|
||||
for x in xs {
|
||||
let a1 = x.is_none_or(|b| !b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_none_or(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.is_none_or(|b| b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_none_or(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.is_some_and(|b| b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_some_and(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.is_some_and(|b| !b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_some_and(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
let xs = [Err("foo"), Ok(b'_'), Ok(b'1')];
|
||||
for x in xs {
|
||||
let a1 = !x.is_ok_and(|b| b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !x.is_ok_and(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = !x.is_ok_and(|b| !b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !x.is_ok_and(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.is_ok_and(|b| b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_ok_and(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.is_ok_and(|b| !b.is_ascii_digit());
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_ok_and(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
}
|
||||
|
||||
mod with_func {
|
||||
fn iad(b: u8) -> bool {
|
||||
b.is_ascii_digit()
|
||||
}
|
||||
|
||||
fn check_option(b: Option<u8>) {
|
||||
let a1 = b.is_some_and(iad);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_some_and(iad);
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.is_some_and(|x| !iad(x));
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_some_and(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.is_none_or(|x| !iad(x));
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_none_or(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.is_none_or(iad);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_none_or(iad);
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
fn check_result(b: Result<u8, ()>) {
|
||||
let a1 = b.is_ok_and(iad);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_ok_and(iad);
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.is_ok_and(|x| !iad(x));
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_ok_and(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = !b.is_ok_and(iad);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !b.is_ok_and(iad);
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = !b.is_ok_and(|x| !iad(x));
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !b.is_ok_and(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,3 +125,113 @@ fn main() {
|
|||
option_methods();
|
||||
result_methods();
|
||||
}
|
||||
|
||||
fn issue15202() {
|
||||
let xs = [None, Some(b'_'), Some(b'1')];
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) != Some(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_none_or(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) != Some(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_none_or(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) == Some(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_some_and(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) == Some(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_some_and(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
let xs = [Err("foo"), Ok(b'_'), Ok(b'1')];
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) != Ok(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !x.is_ok_and(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) != Ok(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !x.is_ok_and(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) == Ok(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_ok_and(|b| b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
for x in xs {
|
||||
let a1 = x.map(|b| b.is_ascii_digit()) == Ok(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = x.is_ok_and(|b| !b.is_ascii_digit());
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
}
|
||||
|
||||
mod with_func {
|
||||
fn iad(b: u8) -> bool {
|
||||
b.is_ascii_digit()
|
||||
}
|
||||
|
||||
fn check_option(b: Option<u8>) {
|
||||
let a1 = b.map(iad) == Some(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_some_and(iad);
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.map(iad) == Some(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_some_and(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.map(iad) != Some(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_none_or(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.map(iad) != Some(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_none_or(iad);
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
|
||||
fn check_result(b: Result<u8, ()>) {
|
||||
let a1 = b.map(iad) == Ok(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_ok_and(iad);
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.map(iad) == Ok(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = b.is_ok_and(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.map(iad) != Ok(true);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !b.is_ok_and(iad);
|
||||
assert_eq!(a1, a2);
|
||||
|
||||
let a1 = b.map(iad) != Ok(false);
|
||||
//~^ manual_is_variant_and
|
||||
let a2 = !b.is_ok_and(|x| !iad(x));
|
||||
assert_eq!(a1, a2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ error: called `.map() != Some()`
|
|||
--> tests/ui/manual_is_variant_and.rs:70:13
|
||||
|
|
||||
LL | let _ = Some(2).map(|x| x % 2 == 0) != Some(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Some(2).is_none_or(|x| x % 2 == 0)`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `Some(2).is_none_or(|x| x % 2 != 0)`
|
||||
|
||||
error: called `.map() == Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:72:13
|
||||
|
|
@ -126,5 +126,101 @@ error: called `map(<f>).unwrap_or_default()` on a `Result` value
|
|||
LL | let _ = res2.map(char::is_alphanumeric).unwrap_or_default(); // should lint
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `is_ok_and(char::is_alphanumeric)`
|
||||
|
||||
error: aborting due to 15 previous errors
|
||||
error: called `.map() != Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:132:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) != Some(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_none_or(|b| !b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() != Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:139:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) != Some(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_none_or(|b| b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() == Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:146:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) == Some(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_some_and(|b| b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() == Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:153:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) == Some(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_some_and(|b| !b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() != Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:161:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) != Ok(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!x.is_ok_and(|b| b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() != Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:168:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) != Ok(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!x.is_ok_and(|b| !b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() == Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:175:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) == Ok(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_ok_and(|b| b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() == Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:182:18
|
||||
|
|
||||
LL | let a1 = x.map(|b| b.is_ascii_digit()) == Ok(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.is_ok_and(|b| !b.is_ascii_digit())`
|
||||
|
||||
error: called `.map() == Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:195:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) == Some(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_some_and(iad)`
|
||||
|
||||
error: called `.map() == Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:200:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) == Some(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_some_and(|x| !iad(x))`
|
||||
|
||||
error: called `.map() != Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:205:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) != Some(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_none_or(|x| !iad(x))`
|
||||
|
||||
error: called `.map() != Some()`
|
||||
--> tests/ui/manual_is_variant_and.rs:210:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) != Some(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_none_or(iad)`
|
||||
|
||||
error: called `.map() == Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:217:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) == Ok(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_ok_and(iad)`
|
||||
|
||||
error: called `.map() == Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:222:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) == Ok(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.is_ok_and(|x| !iad(x))`
|
||||
|
||||
error: called `.map() != Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:227:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) != Ok(true);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: use: `!b.is_ok_and(iad)`
|
||||
|
||||
error: called `.map() != Ok()`
|
||||
--> tests/ui/manual_is_variant_and.rs:232:18
|
||||
|
|
||||
LL | let a1 = b.map(iad) != Ok(false);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ help: use: `!b.is_ok_and(|x| !iad(x))`
|
||||
|
||||
error: aborting due to 31 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -137,3 +137,48 @@ fn not_fire() {
|
|||
fn issue11579() {
|
||||
let Some(msg) = Some("hi") else { unreachable!("can't happen") };
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Issue9939<T> {
|
||||
avalanche: T,
|
||||
}
|
||||
|
||||
fn issue9939() {
|
||||
let issue = Some(Issue9939 { avalanche: 1 });
|
||||
let Some(Issue9939 { avalanche: tornado }) = issue else { unreachable!("can't happen") };
|
||||
let issue = Some(Issue9939 { avalanche: true });
|
||||
let Some(Issue9939 { avalanche: acid_rain }) = issue else { unreachable!("can't happen") };
|
||||
assert_eq!(tornado, 1);
|
||||
assert!(acid_rain);
|
||||
|
||||
// without shadowing
|
||||
let _x @ Some(Issue9939 { avalanche: _y }) = issue else { unreachable!("can't happen") };
|
||||
|
||||
// with shadowing
|
||||
let Some(Issue9939 { avalanche: _x }) = issue else { unreachable!("can't happen") };
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Issue9939b<T, U> {
|
||||
earthquake: T,
|
||||
hurricane: U,
|
||||
}
|
||||
|
||||
fn issue9939b() {
|
||||
let issue = Some(Issue9939b {
|
||||
earthquake: true,
|
||||
hurricane: 1,
|
||||
});
|
||||
let issue @ Some(Issue9939b { earthquake: flood, hurricane: drought }) = issue else { unreachable!("can't happen") };
|
||||
assert_eq!(drought, 1);
|
||||
assert!(flood);
|
||||
assert!(issue.is_some());
|
||||
|
||||
// without shadowing
|
||||
let _x @ Some(Issue9939b { earthquake: erosion, hurricane: _y }) = issue else { unreachable!("can't happen") };
|
||||
assert!(erosion);
|
||||
|
||||
// with shadowing
|
||||
let Some(Issue9939b { earthquake: erosion, hurricane: _x }) = issue else { unreachable!("can't happen") };
|
||||
assert!(erosion);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,3 +177,76 @@ fn issue11579() {
|
|||
_ => unreachable!("can't happen"),
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Issue9939<T> {
|
||||
avalanche: T,
|
||||
}
|
||||
|
||||
fn issue9939() {
|
||||
let issue = Some(Issue9939 { avalanche: 1 });
|
||||
let tornado = match issue {
|
||||
//~^ manual_let_else
|
||||
Some(Issue9939 { avalanche }) => avalanche,
|
||||
_ => unreachable!("can't happen"),
|
||||
};
|
||||
let issue = Some(Issue9939 { avalanche: true });
|
||||
let acid_rain = match issue {
|
||||
//~^ manual_let_else
|
||||
Some(Issue9939 { avalanche: tornado }) => tornado,
|
||||
_ => unreachable!("can't happen"),
|
||||
};
|
||||
assert_eq!(tornado, 1);
|
||||
assert!(acid_rain);
|
||||
|
||||
// without shadowing
|
||||
let _y = match issue {
|
||||
//~^ manual_let_else
|
||||
_x @ Some(Issue9939 { avalanche }) => avalanche,
|
||||
None => unreachable!("can't happen"),
|
||||
};
|
||||
|
||||
// with shadowing
|
||||
let _x = match issue {
|
||||
//~^ manual_let_else
|
||||
_x @ Some(Issue9939 { avalanche }) => avalanche,
|
||||
None => unreachable!("can't happen"),
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Issue9939b<T, U> {
|
||||
earthquake: T,
|
||||
hurricane: U,
|
||||
}
|
||||
|
||||
fn issue9939b() {
|
||||
let issue = Some(Issue9939b {
|
||||
earthquake: true,
|
||||
hurricane: 1,
|
||||
});
|
||||
let (issue, drought, flood) = match issue {
|
||||
//~^ manual_let_else
|
||||
flood @ Some(Issue9939b { earthquake, hurricane }) => (flood, hurricane, earthquake),
|
||||
None => unreachable!("can't happen"),
|
||||
};
|
||||
assert_eq!(drought, 1);
|
||||
assert!(flood);
|
||||
assert!(issue.is_some());
|
||||
|
||||
// without shadowing
|
||||
let (_y, erosion) = match issue {
|
||||
//~^ manual_let_else
|
||||
_x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake),
|
||||
None => unreachable!("can't happen"),
|
||||
};
|
||||
assert!(erosion);
|
||||
|
||||
// with shadowing
|
||||
let (_x, erosion) = match issue {
|
||||
//~^ manual_let_else
|
||||
_x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake),
|
||||
None => unreachable!("can't happen"),
|
||||
};
|
||||
assert!(erosion);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,5 +101,75 @@ LL | | _ => unreachable!("can't happen"),
|
|||
LL | | };
|
||||
| |______^ help: consider writing: `let Some(msg) = Some("hi") else { unreachable!("can't happen") };`
|
||||
|
||||
error: aborting due to 10 previous errors
|
||||
error: this could be rewritten as `let...else`
|
||||
--> tests/ui/manual_let_else_match.rs:188:5
|
||||
|
|
||||
LL | / let tornado = match issue {
|
||||
LL | |
|
||||
LL | | Some(Issue9939 { avalanche }) => avalanche,
|
||||
LL | | _ => unreachable!("can't happen"),
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let Some(Issue9939 { avalanche: tornado }) = issue else { unreachable!("can't happen") };`
|
||||
|
||||
error: this could be rewritten as `let...else`
|
||||
--> tests/ui/manual_let_else_match.rs:194:5
|
||||
|
|
||||
LL | / let acid_rain = match issue {
|
||||
LL | |
|
||||
LL | | Some(Issue9939 { avalanche: tornado }) => tornado,
|
||||
LL | | _ => unreachable!("can't happen"),
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let Some(Issue9939 { avalanche: acid_rain }) = issue else { unreachable!("can't happen") };`
|
||||
|
||||
error: this could be rewritten as `let...else`
|
||||
--> tests/ui/manual_let_else_match.rs:203:5
|
||||
|
|
||||
LL | / let _y = match issue {
|
||||
LL | |
|
||||
LL | | _x @ Some(Issue9939 { avalanche }) => avalanche,
|
||||
LL | | None => unreachable!("can't happen"),
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let _x @ Some(Issue9939 { avalanche: _y }) = issue else { unreachable!("can't happen") };`
|
||||
|
||||
error: this could be rewritten as `let...else`
|
||||
--> tests/ui/manual_let_else_match.rs:210:5
|
||||
|
|
||||
LL | / let _x = match issue {
|
||||
LL | |
|
||||
LL | | _x @ Some(Issue9939 { avalanche }) => avalanche,
|
||||
LL | | None => unreachable!("can't happen"),
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let Some(Issue9939 { avalanche: _x }) = issue else { unreachable!("can't happen") };`
|
||||
|
||||
error: this could be rewritten as `let...else`
|
||||
--> tests/ui/manual_let_else_match.rs:228:5
|
||||
|
|
||||
LL | / let (issue, drought, flood) = match issue {
|
||||
LL | |
|
||||
LL | | flood @ Some(Issue9939b { earthquake, hurricane }) => (flood, hurricane, earthquake),
|
||||
LL | | None => unreachable!("can't happen"),
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let issue @ Some(Issue9939b { earthquake: flood, hurricane: drought }) = issue else { unreachable!("can't happen") };`
|
||||
|
||||
error: this could be rewritten as `let...else`
|
||||
--> tests/ui/manual_let_else_match.rs:238:5
|
||||
|
|
||||
LL | / let (_y, erosion) = match issue {
|
||||
LL | |
|
||||
LL | | _x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake),
|
||||
LL | | None => unreachable!("can't happen"),
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let _x @ Some(Issue9939b { earthquake: erosion, hurricane: _y }) = issue else { unreachable!("can't happen") };`
|
||||
|
||||
error: this could be rewritten as `let...else`
|
||||
--> tests/ui/manual_let_else_match.rs:246:5
|
||||
|
|
||||
LL | / let (_x, erosion) = match issue {
|
||||
LL | |
|
||||
LL | | _x @ Some(Issue9939b { earthquake, hurricane }) => (hurricane, earthquake),
|
||||
LL | | None => unreachable!("can't happen"),
|
||||
LL | | };
|
||||
| |______^ help: consider writing: `let Some(Issue9939b { earthquake: erosion, hurricane: _x }) = issue else { unreachable!("can't happen") };`
|
||||
|
||||
error: aborting due to 17 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -250,3 +250,31 @@ pub fn issue_12760<const N: usize>() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This needs documenting
|
||||
pub fn unwrap_expect_etc_in_const() {
|
||||
let a = const { std::num::NonZeroUsize::new(1).unwrap() };
|
||||
// This should still pass the lint even if it is guaranteed to panic at compile-time
|
||||
let b = const { std::num::NonZeroUsize::new(0).unwrap() };
|
||||
}
|
||||
|
||||
/// This needs documenting
|
||||
pub const fn unwrap_expect_etc_in_const_fn_fails() {
|
||||
//~^ missing_panics_doc
|
||||
let a = std::num::NonZeroUsize::new(1).unwrap();
|
||||
}
|
||||
|
||||
/// This needs documenting
|
||||
pub const fn assert_in_const_fn_fails() {
|
||||
//~^ missing_panics_doc
|
||||
let x = 0;
|
||||
if x == 0 {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
/// This needs documenting
|
||||
pub const fn in_const_fn<const N: usize>(n: usize) {
|
||||
//~^ missing_panics_doc
|
||||
assert!(N > n);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,5 +180,41 @@ note: first possible panic found here
|
|||
LL | *v.last().expect("passed an empty thing")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 15 previous errors
|
||||
error: docs for function which may panic missing `# Panics` section
|
||||
--> tests/ui/missing_panics_doc.rs:262:1
|
||||
|
|
||||
LL | pub const fn unwrap_expect_etc_in_const_fn_fails() {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: first possible panic found here
|
||||
--> tests/ui/missing_panics_doc.rs:264:13
|
||||
|
|
||||
LL | let a = std::num::NonZeroUsize::new(1).unwrap();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: docs for function which may panic missing `# Panics` section
|
||||
--> tests/ui/missing_panics_doc.rs:268:1
|
||||
|
|
||||
LL | pub const fn assert_in_const_fn_fails() {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: first possible panic found here
|
||||
--> tests/ui/missing_panics_doc.rs:272:9
|
||||
|
|
||||
LL | panic!();
|
||||
| ^^^^^^^^
|
||||
|
||||
error: docs for function which may panic missing `# Panics` section
|
||||
--> tests/ui/missing_panics_doc.rs:277:1
|
||||
|
|
||||
LL | pub const fn in_const_fn<const N: usize>(n: usize) {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
note: first possible panic found here
|
||||
--> tests/ui/missing_panics_doc.rs:279:5
|
||||
|
|
||||
LL | assert!(N > n);
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 18 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -33,3 +33,12 @@ fn main() {
|
|||
b = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn issue15063(x: bool, y: bool) {
|
||||
let mut z = false;
|
||||
|
||||
if x && y {
|
||||
todo!()
|
||||
} else { z = x || y; }
|
||||
//~^^^^^ needless_bool_assign
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,3 +45,16 @@ fn main() {
|
|||
b = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn issue15063(x: bool, y: bool) {
|
||||
let mut z = false;
|
||||
|
||||
if x && y {
|
||||
todo!()
|
||||
} else if x || y {
|
||||
z = true;
|
||||
} else {
|
||||
z = false;
|
||||
}
|
||||
//~^^^^^ needless_bool_assign
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,5 +51,16 @@ LL | | }
|
|||
= note: `-D clippy::if-same-then-else` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::if_same_then_else)]`
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
error: this if-then-else expression assigns a bool literal
|
||||
--> tests/ui/needless_bool_assign.rs:54:12
|
||||
|
|
||||
LL | } else if x || y {
|
||||
| ____________^
|
||||
LL | | z = true;
|
||||
LL | | } else {
|
||||
LL | | z = false;
|
||||
LL | | }
|
||||
| |_____^ help: you can reduce it to: `{ z = x || y; }`
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -82,3 +82,15 @@ fn float() {
|
|||
|
||||
-1.0 * -1.0; // should be ok
|
||||
}
|
||||
|
||||
struct Y {
|
||||
delta: f64,
|
||||
}
|
||||
|
||||
fn nested() {
|
||||
let a = Y { delta: 1.0 };
|
||||
let b = Y { delta: 1.0 };
|
||||
let _ = (-(a.delta - 0.5).abs()).total_cmp(&1.0);
|
||||
//~^ neg_multiply
|
||||
let _ = (-(a.delta - 0.5).abs()).total_cmp(&1.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,3 +82,15 @@ fn float() {
|
|||
|
||||
-1.0 * -1.0; // should be ok
|
||||
}
|
||||
|
||||
struct Y {
|
||||
delta: f64,
|
||||
}
|
||||
|
||||
fn nested() {
|
||||
let a = Y { delta: 1.0 };
|
||||
let b = Y { delta: 1.0 };
|
||||
let _ = ((a.delta - 0.5).abs() * -1.0).total_cmp(&1.0);
|
||||
//~^ neg_multiply
|
||||
let _ = (-(a.delta - 0.5).abs()).total_cmp(&1.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,5 +97,11 @@ error: this multiplication by -1 can be written more succinctly
|
|||
LL | (3.0_f32 as f64) * -1.0;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `-(3.0_f32 as f64)`
|
||||
|
||||
error: aborting due to 16 previous errors
|
||||
error: this multiplication by -1 can be written more succinctly
|
||||
--> tests/ui/neg_multiply.rs:93:13
|
||||
|
|
||||
LL | let _ = ((a.delta - 0.5).abs() * -1.0).total_cmp(&1.0);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `(-(a.delta - 0.5).abs())`
|
||||
|
||||
error: aborting due to 17 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -110,3 +110,37 @@ mod issue_2597 {
|
|||
&array[idx] < val
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_if)]
|
||||
fn issue15063() {
|
||||
use std::ops::BitAnd;
|
||||
|
||||
macro_rules! mac {
|
||||
($e:expr) => {
|
||||
$e.clone()
|
||||
};
|
||||
}
|
||||
|
||||
let x = 1;
|
||||
if x == mac!(1) {}
|
||||
//~^ op_ref
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Y(i32);
|
||||
impl BitAnd for Y {
|
||||
type Output = Y;
|
||||
fn bitand(self, rhs: Y) -> Y {
|
||||
Y(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
impl<'a> BitAnd<&'a Y> for Y {
|
||||
type Output = Y;
|
||||
fn bitand(self, rhs: &'a Y) -> Y {
|
||||
Y(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
let x = Y(1);
|
||||
let y = Y(2);
|
||||
let z = x & mac!(y);
|
||||
//~^ op_ref
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,3 +110,37 @@ mod issue_2597 {
|
|||
&array[idx] < val
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_if)]
|
||||
fn issue15063() {
|
||||
use std::ops::BitAnd;
|
||||
|
||||
macro_rules! mac {
|
||||
($e:expr) => {
|
||||
$e.clone()
|
||||
};
|
||||
}
|
||||
|
||||
let x = 1;
|
||||
if &x == &mac!(1) {}
|
||||
//~^ op_ref
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Y(i32);
|
||||
impl BitAnd for Y {
|
||||
type Output = Y;
|
||||
fn bitand(self, rhs: Y) -> Y {
|
||||
Y(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
impl<'a> BitAnd<&'a Y> for Y {
|
||||
type Output = Y;
|
||||
fn bitand(self, rhs: &'a Y) -> Y {
|
||||
Y(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
let x = Y(1);
|
||||
let y = Y(2);
|
||||
let z = x & &mac!(y);
|
||||
//~^ op_ref
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,5 +36,25 @@ LL | let _ = two + &three;
|
|||
| |
|
||||
| help: use the right value directly: `three`
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
error: needlessly taken reference of both operands
|
||||
--> tests/ui/op_ref.rs:125:8
|
||||
|
|
||||
LL | if &x == &mac!(1) {}
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: use the values directly
|
||||
|
|
||||
LL - if &x == &mac!(1) {}
|
||||
LL + if x == mac!(1) {}
|
||||
|
|
||||
|
||||
error: taken reference of right operand
|
||||
--> tests/ui/op_ref.rs:144:13
|
||||
|
|
||||
LL | let z = x & &mac!(y);
|
||||
| ^^^^--------
|
||||
| |
|
||||
| help: use the right value directly: `mac!(y)`
|
||||
|
||||
error: aborting due to 6 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -439,4 +439,24 @@ fn test_option_get_or_insert() {
|
|||
//~^ or_fun_call
|
||||
}
|
||||
|
||||
fn test_option_and() {
|
||||
// assume that this is slow call
|
||||
fn g() -> Option<u8> {
|
||||
Some(99)
|
||||
}
|
||||
let mut x = Some(42_u8);
|
||||
let _ = x.and_then(|_| g());
|
||||
//~^ or_fun_call
|
||||
}
|
||||
|
||||
fn test_result_and() {
|
||||
// assume that this is slow call
|
||||
fn g() -> Result<u8, ()> {
|
||||
Ok(99)
|
||||
}
|
||||
let mut x: Result<u8, ()> = Ok(42);
|
||||
let _ = x.and_then(|_| g());
|
||||
//~^ or_fun_call
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -439,4 +439,24 @@ fn test_option_get_or_insert() {
|
|||
//~^ or_fun_call
|
||||
}
|
||||
|
||||
fn test_option_and() {
|
||||
// assume that this is slow call
|
||||
fn g() -> Option<u8> {
|
||||
Some(99)
|
||||
}
|
||||
let mut x = Some(42_u8);
|
||||
let _ = x.and(g());
|
||||
//~^ or_fun_call
|
||||
}
|
||||
|
||||
fn test_result_and() {
|
||||
// assume that this is slow call
|
||||
fn g() -> Result<u8, ()> {
|
||||
Ok(99)
|
||||
}
|
||||
let mut x: Result<u8, ()> = Ok(42);
|
||||
let _ = x.and(g());
|
||||
//~^ or_fun_call
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
|||
|
|
@ -264,5 +264,17 @@ error: function call inside of `get_or_insert`
|
|||
LL | let _ = x.get_or_insert(g());
|
||||
| ^^^^^^^^^^^^^^^^^^ help: try: `get_or_insert_with(g)`
|
||||
|
||||
error: aborting due to 41 previous errors
|
||||
error: function call inside of `and`
|
||||
--> tests/ui/or_fun_call.rs:448:15
|
||||
|
|
||||
LL | let _ = x.and(g());
|
||||
| ^^^^^^^^ help: try: `and_then(|_| g())`
|
||||
|
||||
error: function call inside of `and`
|
||||
--> tests/ui/or_fun_call.rs:458:15
|
||||
|
|
||||
LL | let _ = x.and(g());
|
||||
| ^^^^^^^^ help: try: `and_then(|_| g())`
|
||||
|
||||
error: aborting due to 43 previous errors
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ fn issue9956() {
|
|||
//~^ redundant_closure_call
|
||||
|
||||
// immediately calling only one closure, so we can't remove the other ones
|
||||
let a = (|| || 123);
|
||||
let a = || || 123;
|
||||
//~^ redundant_closure_call
|
||||
dbg!(a()());
|
||||
|
||||
|
|
@ -144,3 +144,15 @@ fn issue_12358() {
|
|||
// different.
|
||||
make_closure!(x)();
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn issue_9583() {
|
||||
Some(true) == Some(true);
|
||||
//~^ redundant_closure_call
|
||||
Some(true) == Some(true);
|
||||
//~^ redundant_closure_call
|
||||
Some(if 1 > 2 {1} else {2}) == Some(2);
|
||||
//~^ redundant_closure_call
|
||||
Some( 1 > 2 ) == Some(true);
|
||||
//~^ redundant_closure_call
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,3 +144,15 @@ fn issue_12358() {
|
|||
// different.
|
||||
make_closure!(x)();
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn issue_9583() {
|
||||
(|| { Some(true) })() == Some(true);
|
||||
//~^ redundant_closure_call
|
||||
(|| Some(true))() == Some(true);
|
||||
//~^ redundant_closure_call
|
||||
(|| { Some(if 1 > 2 {1} else {2}) })() == Some(2);
|
||||
//~^ redundant_closure_call
|
||||
(|| { Some( 1 > 2 ) })() == Some(true);
|
||||
//~^ redundant_closure_call
|
||||
}
|
||||
|
|
|
|||
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