Skip to content

Commit 8f547a0

Browse files
authored
Merge branch 'rust-lang:master' into promote_collection_is_never_read
2 parents c530d32 + 331d01e commit 8f547a0

9 files changed

+188
-40
lines changed

book/src/development/adding_lints.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ impl EarlyLintPass for FooFunctions {}
261261
[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60
262262
[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
263263
[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
264-
[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110
264+
[category_level_mapping]: ../index.html
265265

266266
## Lint registration
267267

clippy_lints/src/implicit_hasher.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ use rustc_hir as hir;
66
use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor};
77
use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
88
use rustc_hir_analysis::hir_ty_to_ty;
9-
use rustc_lint::{LateContext, LateLintPass, LintContext};
9+
use rustc_lint::{LateContext, LateLintPass};
1010
use rustc_middle::hir::nested_filter;
11-
use rustc_middle::lint::in_external_macro;
1211
use rustc_middle::ty::{Ty, TypeckResults};
1312
use rustc_session::{declare_lint_pass, declare_tool_lint};
1413
use rustc_span::source_map::Span;
@@ -162,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
162161
vis.visit_ty(ty);
163162

164163
for target in &vis.found {
165-
if in_external_macro(cx.sess(), generics.span) {
164+
if generics.span.from_expansion() {
166165
continue;
167166
}
168167
let generics_suggestion_span = generics.span.substitute_dummy({

clippy_lints/src/manual_non_exhaustive.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use clippy_utils::is_doc_hidden;
33
use clippy_utils::msrvs::{self, Msrv};
44
use clippy_utils::source::snippet_opt;
55
use rustc_ast::ast::{self, VisibilityKind};
6+
use rustc_ast::attr;
67
use rustc_data_structures::fx::FxHashSet;
78
use rustc_errors::Applicability;
89
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
@@ -158,7 +159,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
158159
let mut iter = def.variants.iter().filter_map(|v| {
159160
(matches!(v.data, hir::VariantData::Unit(_, _))
160161
&& v.ident.as_str().starts_with('_')
161-
&& is_doc_hidden(cx.tcx.hir().attrs(v.hir_id)))
162+
&& is_doc_hidden(cx.tcx.hir().attrs(v.hir_id))
163+
&& !attr::contains_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive))
162164
.then_some((v.def_id, v.span))
163165
});
164166
if let Some((id, span)) = iter.next()
@@ -198,16 +200,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
198200
enum_span,
199201
"this seems like a manual implementation of the non-exhaustive pattern",
200202
|diag| {
201-
if !cx.tcx.adt_def(enum_id).is_variant_list_non_exhaustive()
202-
&& let header_span = cx.sess().source_map().span_until_char(enum_span, '{')
203-
&& let Some(snippet) = snippet_opt(cx, header_span)
204-
{
205-
diag.span_suggestion(
206-
header_span,
207-
"add the attribute",
208-
format!("#[non_exhaustive] {snippet}"),
209-
Applicability::Unspecified,
210-
);
203+
let header_span = cx.sess().source_map().span_until_char(enum_span, '{');
204+
if let Some(snippet) = snippet_opt(cx, header_span) {
205+
diag.span_suggestion(
206+
header_span,
207+
"add the attribute",
208+
format!("#[non_exhaustive] {snippet}"),
209+
Applicability::Unspecified,
210+
);
211211
}
212212
diag.span_help(variant_span, "remove this variant");
213213
},

clippy_lints/src/write.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,9 +486,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
486486
&& let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind
487487
&& !arg.expr.span.from_expansion()
488488
&& let Some(value_string) = snippet_opt(cx, arg.expr.span)
489-
{
489+
{
490490
let (replacement, replace_raw) = match lit.kind {
491-
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
491+
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
492492
Some(extracted) => extracted,
493493
None => return,
494494
},
@@ -538,7 +538,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
538538
// `format!("{}", "a")`, `format!("{named}", named = "b")
539539
// ~~~~~ ~~~~~~~~~~~~~
540540
&& let Some(removal_span) = format_arg_removal_span(format_args, index) {
541-
let replacement = replacement.replace('{', "{{").replace('}', "}}");
541+
let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw);
542542
suggestion.push((*placeholder_span, replacement));
543543
suggestion.push((removal_span, String::new()));
544544
}
@@ -631,3 +631,47 @@ fn conservative_unescape(literal: &str) -> Result<String, UnescapeErr> {
631631

632632
if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) }
633633
}
634+
635+
/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces in
636+
/// `\u{xxxx}` are left unmodified
637+
#[expect(clippy::match_same_arms)]
638+
fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String {
639+
#[derive(Clone, Copy)]
640+
enum State {
641+
Normal,
642+
Backslash,
643+
UnicodeEscape,
644+
}
645+
646+
let mut escaped = String::with_capacity(literal.len());
647+
let mut state = State::Normal;
648+
649+
for ch in literal.chars() {
650+
state = match (ch, state) {
651+
// Escape braces outside of unicode escapes by doubling them up
652+
('{' | '}', State::Normal) => {
653+
escaped.push(ch);
654+
State::Normal
655+
},
656+
// If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise:
657+
//
658+
// \u{aaaa} \\ \x01
659+
// ^ ^ ^
660+
('\\', State::Normal) if preserve_unicode_escapes => State::Backslash,
661+
// \u{aaaa}
662+
// ^
663+
('u', State::Backslash) => State::UnicodeEscape,
664+
// \xAA \\
665+
// ^ ^
666+
(_, State::Backslash) => State::Normal,
667+
// \u{aaaa}
668+
// ^
669+
('}', State::UnicodeEscape) => State::Normal,
670+
_ => state,
671+
};
672+
673+
escaped.push(ch);
674+
}
675+
676+
escaped
677+
}

tests/ui/manual_non_exhaustive_enum.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ enum E {
1010
_C,
1111
}
1212

13-
// user forgot to remove the marker
13+
// if the user explicitly marks as nonexhaustive we shouldn't warn them
1414
#[non_exhaustive]
1515
enum Ep {
16-
//~^ ERROR: this seems like a manual implementation of the non-exhaustive pattern
1716
A,
1817
B,
1918
#[doc(hidden)]

tests/ui/manual_non_exhaustive_enum.stderr

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,5 @@ LL | _C,
2222
= note: `-D clippy::manual-non-exhaustive` implied by `-D warnings`
2323
= help: to override `-D warnings` add `#[allow(clippy::manual_non_exhaustive)]`
2424

25-
error: this seems like a manual implementation of the non-exhaustive pattern
26-
--> $DIR/manual_non_exhaustive_enum.rs:15:1
27-
|
28-
LL | / enum Ep {
29-
LL | |
30-
LL | | A,
31-
LL | | B,
32-
LL | | #[doc(hidden)]
33-
LL | | _C,
34-
LL | | }
35-
| |_^
36-
|
37-
help: remove this variant
38-
--> $DIR/manual_non_exhaustive_enum.rs:20:5
39-
|
40-
LL | _C,
41-
| ^^
42-
43-
error: aborting due to 2 previous errors
25+
error: aborting due to previous error
4426

tests/ui/print_literal.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,18 @@ fn main() {
5151
// The string literal from `file!()` has a callsite span that isn't marked as coming from an
5252
// expansion
5353
println!("file: {}", file!());
54+
55+
// Braces in unicode escapes should not be escaped
56+
println!("{{}} \x00 \u{ab123} \\\u{ab123} {{:?}}");
57+
println!("\\\u{1234}");
58+
// This does not lint because it would have to suggest unescaping the character
59+
println!(r"{}", "\u{ab123}");
60+
// These are not unicode escapes
61+
println!("\\u{{ab123}} \\u{{{{");
62+
println!(r"\u{{ab123}} \u{{{{");
63+
println!("\\{{ab123}} \\u{{{{");
64+
println!("\\u{{ab123}}");
65+
println!("\\\\u{{1234}}");
66+
67+
println!("mixed: {{hello}} {world}");
5468
}

tests/ui/print_literal.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,18 @@ fn main() {
5151
// The string literal from `file!()` has a callsite span that isn't marked as coming from an
5252
// expansion
5353
println!("file: {}", file!());
54+
55+
// Braces in unicode escapes should not be escaped
56+
println!("{}", "{} \x00 \u{ab123} \\\u{ab123} {:?}");
57+
println!("{}", "\\\u{1234}");
58+
// This does not lint because it would have to suggest unescaping the character
59+
println!(r"{}", "\u{ab123}");
60+
// These are not unicode escapes
61+
println!("{}", r"\u{ab123} \u{{");
62+
println!(r"{}", r"\u{ab123} \u{{");
63+
println!("{}", r"\{ab123} \u{{");
64+
println!("{}", "\\u{ab123}");
65+
println!("{}", "\\\\u{1234}");
66+
67+
println!("mixed: {} {world}", "{hello}");
5468
}

tests/ui/print_literal.stderr

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,101 @@ LL - println!("{bar} {foo}", foo = "hello", bar = "world");
9696
LL + println!("world hello");
9797
|
9898

99-
error: aborting due to 8 previous errors
99+
error: literal with an empty format string
100+
--> $DIR/print_literal.rs:56:20
101+
|
102+
LL | println!("{}", "{} \x00 \u{ab123} \\\u{ab123} {:?}");
103+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
104+
|
105+
help: try
106+
|
107+
LL - println!("{}", "{} \x00 \u{ab123} \\\u{ab123} {:?}");
108+
LL + println!("{{}} \x00 \u{ab123} \\\u{ab123} {{:?}}");
109+
|
110+
111+
error: literal with an empty format string
112+
--> $DIR/print_literal.rs:57:20
113+
|
114+
LL | println!("{}", "\\\u{1234}");
115+
| ^^^^^^^^^^^^
116+
|
117+
help: try
118+
|
119+
LL - println!("{}", "\\\u{1234}");
120+
LL + println!("\\\u{1234}");
121+
|
122+
123+
error: literal with an empty format string
124+
--> $DIR/print_literal.rs:61:20
125+
|
126+
LL | println!("{}", r"\u{ab123} \u{{");
127+
| ^^^^^^^^^^^^^^^^^
128+
|
129+
help: try
130+
|
131+
LL - println!("{}", r"\u{ab123} \u{{");
132+
LL + println!("\\u{{ab123}} \\u{{{{");
133+
|
134+
135+
error: literal with an empty format string
136+
--> $DIR/print_literal.rs:62:21
137+
|
138+
LL | println!(r"{}", r"\u{ab123} \u{{");
139+
| ^^^^^^^^^^^^^^^^^
140+
|
141+
help: try
142+
|
143+
LL - println!(r"{}", r"\u{ab123} \u{{");
144+
LL + println!(r"\u{{ab123}} \u{{{{");
145+
|
146+
147+
error: literal with an empty format string
148+
--> $DIR/print_literal.rs:63:20
149+
|
150+
LL | println!("{}", r"\{ab123} \u{{");
151+
| ^^^^^^^^^^^^^^^^
152+
|
153+
help: try
154+
|
155+
LL - println!("{}", r"\{ab123} \u{{");
156+
LL + println!("\\{{ab123}} \\u{{{{");
157+
|
158+
159+
error: literal with an empty format string
160+
--> $DIR/print_literal.rs:64:20
161+
|
162+
LL | println!("{}", "\\u{ab123}");
163+
| ^^^^^^^^^^^^
164+
|
165+
help: try
166+
|
167+
LL - println!("{}", "\\u{ab123}");
168+
LL + println!("\\u{{ab123}}");
169+
|
170+
171+
error: literal with an empty format string
172+
--> $DIR/print_literal.rs:65:20
173+
|
174+
LL | println!("{}", "\\\\u{1234}");
175+
| ^^^^^^^^^^^^^
176+
|
177+
help: try
178+
|
179+
LL - println!("{}", "\\\\u{1234}");
180+
LL + println!("\\\\u{{1234}}");
181+
|
182+
183+
error: literal with an empty format string
184+
--> $DIR/print_literal.rs:67:35
185+
|
186+
LL | println!("mixed: {} {world}", "{hello}");
187+
| ^^^^^^^^^
188+
|
189+
help: try
190+
|
191+
LL - println!("mixed: {} {world}", "{hello}");
192+
LL + println!("mixed: {{hello}} {world}");
193+
|
194+
195+
error: aborting due to 16 previous errors
100196

0 commit comments

Comments
 (0)