Skip to content

Commit 829651a

Browse files
committed
Introduce #[diagnostic::on_move]
This might be helpful for smart pointers to explains why they aren't Copy and what to do instead or just to let the user know that .clone() is very cheap and can be called without a performance penalty.
1 parent d29e478 commit 829651a

33 files changed

Lines changed: 690 additions & 18 deletions

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3548,6 +3548,7 @@ dependencies = [
35483548
"rustc_lexer",
35493549
"rustc_macros",
35503550
"rustc_parse",
3551+
"rustc_parse_format",
35513552
"rustc_session",
35523553
"rustc_span",
35533554
"rustc_target",

compiler/rustc_attr_parsing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rustc_hir = { path = "../rustc_hir" }
1515
rustc_lexer = { path = "../rustc_lexer" }
1616
rustc_macros = { path = "../rustc_macros" }
1717
rustc_parse = { path = "../rustc_parse" }
18+
rustc_parse_format = { path = "../rustc_parse_format" }
1819
rustc_session = { path = "../rustc_session" }
1920
rustc_span = { path = "../rustc_span" }
2021
rustc_target = { path = "../rustc_target" }

compiler/rustc_attr_parsing/src/attributes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub(crate) mod must_use;
5353
pub(crate) mod no_implicit_prelude;
5454
pub(crate) mod no_link;
5555
pub(crate) mod non_exhaustive;
56+
pub(crate) mod on_move;
5657
pub(crate) mod path;
5758
pub(crate) mod pin_v2;
5859
pub(crate) mod proc_macro_attrs;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use rustc_feature::{AttributeTemplate, template};
2+
use rustc_hir::attrs::{AttributeKind, OnMoveAttrArg, OnMoveAttribute};
3+
use rustc_hir::lints::AttributeLintKind;
4+
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
5+
use rustc_session::lint::builtin::{
6+
MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
7+
};
8+
use rustc_span::{InnerSpan, Span, Symbol, kw, sym};
9+
use thin_vec::ThinVec;
10+
11+
use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
12+
use crate::context::{AcceptContext, Stage};
13+
use crate::parser::ArgParser;
14+
use crate::target_checking::{ALL_TARGETS, AllowedTargets};
15+
16+
pub(crate) struct OnMoveParser;
17+
18+
impl<S: Stage> SingleAttributeParser<S> for OnMoveParser {
19+
const PATH: &[Symbol] = &[sym::diagnostic, sym::on_move];
20+
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
21+
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
22+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); // Check in check_attr.
23+
const TEMPLATE: AttributeTemplate = template!(List: &["message", "label"]);
24+
25+
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
26+
let get_format_ranges = |cx: &mut AcceptContext<'_, '_, S>,
27+
symbol: &Symbol,
28+
span: Span|
29+
-> ThinVec<(usize, usize)> {
30+
let mut parser = Parser::new(symbol.as_str(), None, None, false, ParseMode::Diagnostic);
31+
let pieces: Vec<_> = parser.by_ref().collect();
32+
let mut spans = ThinVec::new();
33+
34+
for piece in pieces {
35+
match piece {
36+
Piece::NextArgument(arg) => match arg.position {
37+
Position::ArgumentNamed(name) if name == kw::SelfUpper.as_str() => {
38+
spans.push((arg.position_span.start - 2, arg.position_span.end));
39+
}
40+
Position::ArgumentNamed(name) => {
41+
let span = span.from_inner(InnerSpan {
42+
start: arg.position_span.start,
43+
end: arg.position_span.end,
44+
});
45+
cx.emit_lint(
46+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
47+
AttributeLintKind::OnMoveMalformedFormatLiterals {
48+
name: Symbol::intern(name),
49+
},
50+
span,
51+
)
52+
}
53+
_ => {}
54+
},
55+
_ => continue,
56+
}
57+
}
58+
spans
59+
};
60+
let Some(list) = args.list() else {
61+
cx.expected_list(cx.attr_span, args);
62+
return None;
63+
};
64+
let mut message = None;
65+
let mut label = None;
66+
67+
for item in list.mixed() {
68+
let Some(item) = item.meta_item() else {
69+
cx.expected_specific_argument(item.span(), &[sym::message, sym::label]);
70+
return None;
71+
};
72+
73+
let Some(name_value) = item.args().name_value() else {
74+
cx.expected_name_value(cx.attr_span, item.path().word_sym());
75+
return None;
76+
};
77+
78+
let Some(value) = name_value.value_as_str() else {
79+
cx.expected_string_literal(name_value.value_span, None);
80+
return None;
81+
};
82+
83+
let value_span = name_value.value_span;
84+
if item.path().word_is(sym::message) && message.is_none() {
85+
let spans = get_format_ranges(cx, &value, value_span);
86+
message = Some(OnMoveAttrArg::new(value, spans));
87+
continue;
88+
} else if item.path().word_is(sym::label) && label.is_none() {
89+
let spans = get_format_ranges(cx, &value, value_span);
90+
label = Some(OnMoveAttrArg::new(value, spans));
91+
continue;
92+
}
93+
94+
cx.emit_lint(
95+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
96+
AttributeLintKind::OnMoveMalformedAttr,
97+
item.span(),
98+
)
99+
}
100+
Some(AttributeKind::OnMove(Box::new(OnMoveAttribute {
101+
span: cx.attr_span,
102+
message,
103+
label,
104+
})))
105+
}
106+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ use crate::attributes::must_use::MustUseParser;
5959
use crate::attributes::no_implicit_prelude::NoImplicitPreludeParser;
6060
use crate::attributes::no_link::NoLinkParser;
6161
use crate::attributes::non_exhaustive::NonExhaustiveParser;
62+
use crate::attributes::on_move::OnMoveParser;
6263
use crate::attributes::path::PathParser as PathAttributeParser;
6364
use crate::attributes::pin_v2::PinV2Parser;
6465
use crate::attributes::proc_macro_attrs::{
@@ -225,6 +226,7 @@ attribute_parsers!(
225226
Single<MustUseParser>,
226227
Single<ObjcClassParser>,
227228
Single<ObjcSelectorParser>,
229+
Single<OnMoveParser>,
228230
Single<OptimizeParser>,
229231
Single<PatchableFunctionEntryParser>,
230232
Single<PathAttributeParser>,

compiler/rustc_borrowck/src/borrowck_errors.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -324,18 +324,23 @@ impl<'infcx, 'tcx> crate::MirBorrowckCtxt<'_, 'infcx, 'tcx> {
324324
verb: &str,
325325
optional_adverb_for_moved: &str,
326326
moved_path: Option<String>,
327+
primary_message: Option<String>,
327328
) -> Diag<'infcx> {
328-
let moved_path = moved_path.map(|mp| format!(": `{mp}`")).unwrap_or_default();
329+
if let Some(primary_message) = primary_message {
330+
struct_span_code_err!(self.dcx(), use_span, E0382, "{}", primary_message)
331+
} else {
332+
let moved_path = moved_path.map(|mp| format!(": `{mp}`")).unwrap_or_default();
329333

330-
struct_span_code_err!(
331-
self.dcx(),
332-
use_span,
333-
E0382,
334-
"{} of {}moved value{}",
335-
verb,
336-
optional_adverb_for_moved,
337-
moved_path,
338-
)
334+
struct_span_code_err!(
335+
self.dcx(),
336+
use_span,
337+
E0382,
338+
"{} of {}moved value{}",
339+
verb,
340+
optional_adverb_for_moved,
341+
moved_path,
342+
)
343+
}
339344
}
340345

341346
pub(crate) fn cannot_borrow_path_as_mutable_because(

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ use rustc_data_structures::fx::FxIndexSet;
99
use rustc_errors::codes::*;
1010
use rustc_errors::{Applicability, Diag, MultiSpan, struct_span_code_err};
1111
use rustc_hir as hir;
12+
use rustc_hir::attrs::AttributeKind;
1213
use rustc_hir::def::{DefKind, Res};
1314
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr};
14-
use rustc_hir::{CoroutineDesugaring, CoroutineKind, CoroutineSource, LangItem, PatField};
15+
use rustc_hir::{
16+
CoroutineDesugaring, CoroutineKind, CoroutineSource, LangItem, PatField, find_attr,
17+
};
1518
use rustc_middle::bug;
1619
use rustc_middle::hir::nested_filter::OnlyBodies;
1720
use rustc_middle::mir::{
@@ -138,6 +141,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
138141
let partial_str = if is_partial_move { "partial " } else { "" };
139142
let partially_str = if is_partial_move { "partially " } else { "" };
140143

144+
let (on_move_message, on_move_label) = if let ty::Adt(item_def, _) =
145+
self.body.local_decls[moved_place.local].ty.kind()
146+
&& let Some(attr) = find_attr!(self.infcx.tcx.get_all_attrs(item_def.did()), AttributeKind::OnMove(attr) => attr)
147+
{
148+
let item_name = self.infcx.tcx.item_name(item_def.did());
149+
(
150+
attr.message.as_ref().map(|e| e.format_args_with(item_name.as_str())),
151+
attr.label.as_ref().map(|e| e.format_args_with(item_name.as_str())),
152+
)
153+
} else {
154+
(None, None)
155+
};
156+
141157
let mut err = self.cannot_act_on_moved_value(
142158
span,
143159
desired_action.as_noun(),
@@ -146,6 +162,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
146162
moved_place,
147163
DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
148164
),
165+
on_move_message,
149166
);
150167

151168
let reinit_spans = maybe_reinitialized_locations
@@ -275,12 +292,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
275292
if needs_note {
276293
if let Some(local) = place.as_local() {
277294
let span = self.body.local_decls[local].source_info.span;
278-
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
279-
is_partial_move,
280-
ty,
281-
place: &note_msg,
282-
span,
283-
});
295+
if let Some(on_move_label) = on_move_label {
296+
err.span_label(span, on_move_label);
297+
} else {
298+
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
299+
is_partial_move,
300+
ty,
301+
place: &note_msg,
302+
span,
303+
});
304+
}
284305
} else {
285306
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Note {
286307
is_partial_move,

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::ops::Range;
23
use std::path::PathBuf;
34

45
pub use ReprAttr::*;
@@ -568,6 +569,33 @@ impl<E: rustc_span::SpanEncoder> rustc_serialize::Encodable<E> for DocAttribute
568569
}
569570
}
570571

572+
#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic, PrintAttribute)]
573+
pub struct OnMoveAttrArg {
574+
pub symbol: Symbol,
575+
pub format_ranges: ThinVec<(usize, usize)>,
576+
}
577+
578+
impl OnMoveAttrArg {
579+
pub fn new(symbol: Symbol, format_ranges: ThinVec<(usize, usize)>) -> Self {
580+
Self { symbol, format_ranges }
581+
}
582+
583+
pub fn format_args_with(&self, item_name: &str) -> String {
584+
let mut arg = self.symbol.to_string();
585+
for fmt_idx in &self.format_ranges {
586+
arg.replace_range(Range { start: fmt_idx.0, end: fmt_idx.1 }, item_name);
587+
}
588+
arg
589+
}
590+
}
591+
592+
#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic, PrintAttribute)]
593+
pub struct OnMoveAttribute {
594+
pub span: Span,
595+
pub message: Option<OnMoveAttrArg>,
596+
pub label: Option<OnMoveAttrArg>,
597+
}
598+
571599
/// How to perform collapse macros debug info
572600
/// if-ext - if macro from different crate (related to callsite code)
573601
/// | cmd \ attr | no | (unspecified) | external | yes |
@@ -984,6 +1012,9 @@ pub enum AttributeKind {
9841012
/// Represents `#[rustc_objc_selector]`
9851013
ObjcSelector { methname: Symbol, span: Span },
9861014

1015+
/// Represents `#[diagnostic::on_move]`
1016+
OnMove(Box<OnMoveAttribute>),
1017+
9871018
/// Represents `#[optimize(size|speed)]`
9881019
Optimize(OptimizeAttr, Span),
9891020

compiler/rustc_hir/src/attrs/encode_cross_crate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ impl AttributeKind {
8989
NonExhaustive(..) => Yes, // Needed for rustdoc
9090
ObjcClass { .. } => No,
9191
ObjcSelector { .. } => No,
92+
OnMove { .. } => Yes,
9293
Optimize(..) => No,
9394
PanicRuntime => No,
9495
ParenSugar(..) => No,

compiler/rustc_lint/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,12 @@ lint_macro_expr_fragment_specifier_2024_migration =
558558
559559
lint_malformed_attribute = malformed lint attribute input
560560
561+
lint_malformed_on_move_attr = unknown or malformed `on_move` attribute
562+
.help = only `message` and `label` are allowed as options
563+
.label = invalid option found here
564+
565+
lint_malformed_on_move_format_literals = unknown parameter `{$name}`
566+
.help = expect {"`{Self}`"} as format argument
561567
lint_map_unit_fn = `Iterator::map` call that discard the iterator's values
562568
.note = `Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated
563569
.function_label = this function returns `()`, which is likely not what you wanted

0 commit comments

Comments
 (0)