Skip to content

Commit ae31f7a

Browse files
committed
Merge commit '1e5237f4a56ae958af7e5824343eacf737b67083' into clippy-subtree-update
2 parents d8e44b7 + 1e5237f commit ae31f7a

File tree

216 files changed

+4611
-1144
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

216 files changed

+4611
-1144
lines changed

src/tools/clippy/.github/workflows/clippy_mq.yml

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ jobs:
2727
host: x86_64-pc-windows-msvc
2828
- os: macos-13
2929
host: x86_64-apple-darwin
30+
- os: macos-latest
31+
host: aarch64-apple-darwin
3032

3133
runs-on: ${{ matrix.os }}
3234

src/tools/clippy/.github/workflows/remark.yml

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ jobs:
3737
- name: Linkcheck book
3838
run: |
3939
rustup toolchain install nightly --component rust-docs
40+
rustup override set nightly
4041
curl https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/linkchecker/linkcheck.sh -o linkcheck.sh
4142
sh linkcheck.sh clippy --path ./book
4243

src/tools/clippy/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5570,6 +5570,7 @@ Released 2018-09-13
55705570
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
55715571
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
55725572
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
5573+
[`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks
55735574
[`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg
55745575
[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
55755576
[`doc_link_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code
@@ -6372,6 +6373,7 @@ Released 2018-09-13
63726373
[`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold
63736374
[`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items
63746375
[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings
6376+
[`module-items-ordered-within-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-items-ordered-within-groupings
63756377
[`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv
63766378
[`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit
63776379
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior

src/tools/clippy/COPYRIGHT

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// REUSE-IgnoreStart
22

3-
Copyright 2014-2024 The Rust Project Developers
3+
Copyright 2014-2025 The Rust Project Developers
44

55
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
66
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license

src/tools/clippy/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ anstream = "0.6.18"
3333

3434
[dev-dependencies]
3535
cargo_metadata = "0.18.1"
36-
ui_test = "0.26.4"
36+
ui_test = "0.29.2"
3737
regex = "1.5.5"
3838
serde = { version = "1.0.145", features = ["derive"] }
3939
serde_json = "1.0.122"

src/tools/clippy/LICENSE-APACHE

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright 2014-2024 The Rust Project Developers
189+
Copyright 2014-2025 The Rust Project Developers
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

src/tools/clippy/LICENSE-MIT

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2014-2024 The Rust Project Developers
3+
Copyright (c) 2014-2025 The Rust Project Developers
44

55
Permission is hereby granted, free of charge, to any
66
person obtaining a copy of this software and associated

src/tools/clippy/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ If you want to contribute to Clippy, you can find more information in [CONTRIBUT
277277

278278
<!-- REUSE-IgnoreStart -->
279279

280-
Copyright 2014-2024 The Rust Project Developers
280+
Copyright 2014-2025 The Rust Project Developers
281281

282282
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
283283
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license

src/tools/clippy/book/src/development/macro_expansions.md

+76
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,85 @@ if foo_span.in_external_macro(cx.sess().source_map()) {
150150
}
151151
```
152152

153+
### The `is_from_proc_macro` function
154+
A common point of confusion is the existence of [`is_from_proc_macro`]
155+
and how it differs from the other [`in_external_macro`]/[`from_expansion`] functions.
156+
157+
While [`in_external_macro`] and [`from_expansion`] both work perfectly fine for detecting expanded code
158+
from *declarative* macros (i.e. `macro_rules!` and macros 2.0),
159+
detecting *proc macro*-generated code is a bit more tricky, as proc macros can (and often do)
160+
freely manipulate the span of returned tokens.
161+
162+
In practice, this often happens through the use of [`quote::quote_spanned!`] with a span from the input tokens.
163+
164+
In those cases, there is no *reliable* way for the compiler (and tools like Clippy)
165+
to distinguish code that comes from such a proc macro from code that the user wrote directly,
166+
and [`in_external_macro`] will return `false`.
167+
168+
This is usually not an issue for the compiler and actually helps proc macro authors create better error messages,
169+
as it allows associating parts of the expansion with parts of the macro input and lets the compiler
170+
point the user to the relevant code in case of a compile error.
171+
172+
However, for Clippy this is inconvenient, because most of the time *we don't* want
173+
to lint proc macro-generated code and this makes it impossible to tell what is and isn't proc macro code.
174+
175+
> NOTE: this is specifically only an issue when a proc macro explicitly sets the span to that of an **input span**.
176+
>
177+
> For example, other common ways of creating `TokenStream`s, such as `"fn foo() {...}".parse::<TokenStream>()`,
178+
> sets each token's span to `Span::call_site()`, which already marks the span as coming from a proc macro
179+
> and the usual span methods have no problem detecting that as a macro span.
180+
181+
As such, Clippy has its own `is_from_proc_macro` function which tries to *approximate*
182+
whether a span comes from a proc macro, by checking whether the source text at the given span
183+
lines up with the given AST node.
184+
185+
This function is typically used in combination with the other mentioned macro span functions,
186+
but is usually called much later into the condition chain as it's a bit heavier than most other conditions,
187+
so that the other cheaper conditions can fail faster. For example, the `borrow_deref_ref` lint:
188+
```rs
189+
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
190+
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
191+
if let ... = ...
192+
&& ...
193+
&& !e.span.from_expansion()
194+
&& ...
195+
&& ...
196+
&& !is_from_proc_macro(cx, e)
197+
&& ...
198+
{
199+
...
200+
}
201+
}
202+
}
203+
```
204+
205+
### Testing lints with macro expansions
206+
To test that all of these cases are handled correctly in your lint,
207+
we have a helper auxiliary crate that exposes various macros, used by tests like so:
208+
```rust
209+
//@aux-build:proc_macros.rs
210+
211+
extern crate proc_macros;
212+
213+
fn main() {
214+
proc_macros::external!{ code_that_should_trigger_your_lint }
215+
proc_macros::with_span!{ span code_that_should_trigger_your_lint }
216+
}
217+
```
218+
This exercises two cases:
219+
- `proc_macros::external!` is a simple proc macro that echos the input tokens back but with a macro span:
220+
this represents the usual, common case where an external macro expands to code that your lint would trigger,
221+
and is correctly handled by `in_external_macro` and `Span::from_expansion`.
222+
223+
- `proc_macros::with_span!` echos back the input tokens starting from the second token
224+
with the span of the first token: this is where the other functions will fail and `is_from_proc_macro` is needed
225+
226+
153227
[`ctxt`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.ctxt
154228
[expansion]: https://rustc-dev-guide.rust-lang.org/macro-expansion.html#expansion-and-ast-integration
155229
[`from_expansion`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion
156230
[`in_external_macro`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html#method.in_external_macro
157231
[Span]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/struct.Span.html
158232
[SyntaxContext]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_span/hygiene/struct.SyntaxContext.html
233+
[`is_from_proc_macro`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.is_from_proc_macro.html
234+
[`quote::quote_spanned!`]: https://docs.rs/quote/latest/quote/macro.quote_spanned.html

src/tools/clippy/book/src/development/the_team.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ is responsible for maintaining Clippy.
102102

103103
5. **Update the changelog**
104104

105-
This needs to be done for every release, every six weeks. This is usually
106-
done by @xFrednet.
105+
This needs to be done for every release, every six weeks.
107106

108107
### Membership
109108

src/tools/clippy/book/src/lint_configuration.md

+14
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,19 @@ The named groupings of different source item kinds within modules.
744744
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
745745

746746

747+
## `module-items-ordered-within-groupings`
748+
Whether the items within module groups should be ordered alphabetically or not.
749+
750+
This option can be configured to "all", "none", or a list of specific grouping names that should be checked
751+
(e.g. only "enums").
752+
753+
**Default Value:** `"none"`
754+
755+
---
756+
**Affected lints:**
757+
* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering)
758+
759+
747760
## `msrv`
748761
The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
749762

@@ -806,6 +819,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
806819
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)
807820
* [`option_map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or)
808821
* [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr)
822+
* [`question_mark`](https://rust-lang.github.io/rust-clippy/master/index.html#question_mark)
809823
* [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names)
810824
* [`redundant_static_lifetimes`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes)
811825
* [`repeat_vec_with_capacity`](https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity)

src/tools/clippy/clippy_config/src/conf.rs

+67-12
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ use crate::types::{
33
DisallowedPath, DisallowedPathWithoutReplacement, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour,
44
Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings,
55
SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
6+
SourceItemOrderingWithinModuleItemGroupings,
67
};
78
use clippy_utils::msrvs::Msrv;
9+
use itertools::Itertools;
810
use rustc_errors::Applicability;
911
use rustc_session::Session;
1012
use rustc_span::edit_distance::edit_distance;
1113
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
1214
use serde::de::{IgnoredAny, IntoDeserializer, MapAccess, Visitor};
1315
use serde::{Deserialize, Deserializer, Serialize};
16+
use std::collections::HashMap;
1417
use std::fmt::{Debug, Display, Formatter};
1518
use std::ops::Range;
1619
use std::path::PathBuf;
@@ -79,6 +82,7 @@ const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = {
7982
#[derive(Default)]
8083
struct TryConf {
8184
conf: Conf,
85+
value_spans: HashMap<String, Range<usize>>,
8286
errors: Vec<ConfError>,
8387
warnings: Vec<ConfError>,
8488
}
@@ -87,6 +91,7 @@ impl TryConf {
8791
fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
8892
Self {
8993
conf: Conf::default(),
94+
value_spans: HashMap::default(),
9095
errors: vec![ConfError::from_toml(file, error)],
9196
warnings: vec![],
9297
}
@@ -210,6 +215,7 @@ macro_rules! define_Conf {
210215
}
211216

212217
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
218+
let mut value_spans = HashMap::new();
213219
let mut errors = Vec::new();
214220
let mut warnings = Vec::new();
215221
$(let mut $name = None;)*
@@ -232,6 +238,7 @@ macro_rules! define_Conf {
232238
}
233239
None => {
234240
$name = Some(value);
241+
value_spans.insert(name.get_ref().as_str().to_string(), value_span);
235242
// $new_conf is the same as one of the defined `$name`s, so
236243
// this variable is defined in line 2 of this function.
237244
$(match $new_conf {
@@ -250,7 +257,7 @@ macro_rules! define_Conf {
250257
}
251258
}
252259
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
253-
Ok(TryConf { conf, errors, warnings })
260+
Ok(TryConf { conf, value_spans, errors, warnings })
254261
}
255262
}
256263

@@ -596,6 +603,13 @@ define_Conf! {
596603
/// The named groupings of different source item kinds within modules.
597604
#[lints(arbitrary_source_item_ordering)]
598605
module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(),
606+
/// Whether the items within module groups should be ordered alphabetically or not.
607+
///
608+
/// This option can be configured to "all", "none", or a list of specific grouping names that should be checked
609+
/// (e.g. only "enums").
610+
#[lints(arbitrary_source_item_ordering)]
611+
module_items_ordered_within_groupings: SourceItemOrderingWithinModuleItemGroupings =
612+
SourceItemOrderingWithinModuleItemGroupings::None,
599613
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
600614
#[default_text = "current version"]
601615
#[lints(
@@ -654,6 +668,7 @@ define_Conf! {
654668
option_as_ref_deref,
655669
option_map_unwrap_or,
656670
ptr_as_ptr,
671+
question_mark,
657672
redundant_field_names,
658673
redundant_static_lifetimes,
659674
repeat_vec_with_capacity,
@@ -815,6 +830,36 @@ fn deserialize(file: &SourceFile) -> TryConf {
815830
&mut conf.conf.allow_renamed_params_for,
816831
DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
817832
);
833+
834+
// Confirms that the user has not accidentally configured ordering requirements for groups that
835+
// aren't configured.
836+
if let SourceItemOrderingWithinModuleItemGroupings::Custom(groupings) =
837+
&conf.conf.module_items_ordered_within_groupings
838+
{
839+
for grouping in groupings {
840+
if !conf.conf.module_item_order_groupings.is_grouping(grouping) {
841+
// Since this isn't fixable by rustfix, don't emit a `Suggestion`. This just adds some useful
842+
// info for the user instead.
843+
844+
let names = conf.conf.module_item_order_groupings.grouping_names();
845+
let suggestion = suggest_candidate(grouping, names.iter().map(String::as_str))
846+
.map(|s| format!(" perhaps you meant `{s}`?"))
847+
.unwrap_or_default();
848+
let names = names.iter().map(|s| format!("`{s}`")).join(", ");
849+
let message = format!(
850+
"unknown ordering group: `{grouping}` was not specified in `module-items-ordered-within-groupings`,{suggestion} expected one of: {names}"
851+
);
852+
853+
let span = conf
854+
.value_spans
855+
.get("module_item_order_groupings")
856+
.cloned()
857+
.unwrap_or_default();
858+
conf.errors.push(ConfError::spanned(file, message, None, span));
859+
}
860+
}
861+
}
862+
818863
// TODO: THIS SHOULD BE TESTED, this comment will be gone soon
819864
if conf.conf.allowed_idents_below_min_chars.iter().any(|e| e == "..") {
820865
conf.conf
@@ -860,6 +905,7 @@ impl Conf {
860905

861906
let TryConf {
862907
mut conf,
908+
value_spans: _,
863909
errors,
864910
warnings,
865911
} = match path {
@@ -950,17 +996,10 @@ impl serde::de::Error for FieldError {
950996
}
951997
}
952998

953-
let suggestion = expected
954-
.iter()
955-
.filter_map(|expected| {
956-
let dist = edit_distance(field, expected, 4)?;
957-
Some((dist, expected))
958-
})
959-
.min_by_key(|&(dist, _)| dist)
960-
.map(|(_, suggestion)| Suggestion {
961-
message: "perhaps you meant",
962-
suggestion,
963-
});
999+
let suggestion = suggest_candidate(field, expected).map(|suggestion| Suggestion {
1000+
message: "perhaps you meant",
1001+
suggestion,
1002+
});
9641003

9651004
Self { error: msg, suggestion }
9661005
}
@@ -998,6 +1037,22 @@ fn calculate_dimensions(fields: &[&str]) -> (usize, Vec<usize>) {
9981037
(rows, column_widths)
9991038
}
10001039

1040+
/// Given a user-provided value that couldn't be matched to a known option, finds the most likely
1041+
/// candidate among candidates that the user might have meant.
1042+
fn suggest_candidate<'a, I>(value: &str, candidates: I) -> Option<&'a str>
1043+
where
1044+
I: IntoIterator<Item = &'a str>,
1045+
{
1046+
candidates
1047+
.into_iter()
1048+
.filter_map(|expected| {
1049+
let dist = edit_distance(value, expected, 4)?;
1050+
Some((dist, expected))
1051+
})
1052+
.min_by_key(|&(dist, _)| dist)
1053+
.map(|(_, suggestion)| suggestion)
1054+
}
1055+
10011056
#[cfg(test)]
10021057
mod tests {
10031058
use serde::de::IgnoredAny;

0 commit comments

Comments
 (0)