Merge commit 'cdbbf3afda' into clippy-subtree-update

This commit is contained in:
Philipp Krones 2025-07-10 20:25:36 +02:00
parent 0a64bfd685
commit 4e614bf683
126 changed files with 2759 additions and 475 deletions

View file

@ -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\"}"

View file

@ -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
View 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 }}

View file

@ -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" }

View file

@ -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

View file

@ -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

View file

@ -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`

View file

@ -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.

View file

@ -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(())
}

View file

@ -2,7 +2,6 @@
rustc_private,
exit_status_error,
if_let_guard,
let_chains,
os_str_slice,
os_string_truncate,
slice_split_once

View file

@ -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;

View file

@ -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)
{

View file

@ -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);
}
});

View file

@ -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);

View file

@ -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 {

View file

@ -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;

View file

@ -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,

View file

@ -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))

View file

@ -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()
{

View file

@ -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);
},
_ => {},

View file

@ -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

View file

@ -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`");
}

View file

@ -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,

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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()),

View file

@ -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);
}
}
}

View file

@ -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))

View file

@ -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!(

View file

@ -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);

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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",

View file

@ -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);

View file

@ -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`].

View file

@ -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())

View file

@ -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}")

View 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"

View file

@ -0,0 +1 @@
fn main() {}

View file

@ -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 -->

View file

@ -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()

View file

@ -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,
}
}

View file

@ -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,
}
}

View file

@ -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
}

View file

@ -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 }

View file

@ -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)?;
},

View file

@ -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 {

View file

@ -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})")
}
}

View file

@ -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),
}

View file

@ -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"

View file

@ -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();
}

View file

@ -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

View file

@ -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() {}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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) {}

View file

@ -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) {}

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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

View 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() {}

View 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);
}

View 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

View 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);
}

View 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

View 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
View file

@ -0,0 +1,8 @@
//@ check-pass
//@compile-flags: --test
#![warn(clippy::exit)]
fn main() {
std::process::exit(0)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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() {}

View file

@ -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() {}

View file

@ -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

View file

@ -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
}

View file

@ -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