Skip to content

Commit ccfb4fa

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 11ad63a commit ccfb4fa

31 files changed

Lines changed: 751 additions & 19 deletions

compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem
2222

2323
pub(crate) mod do_not_recommend;
2424
pub(crate) mod on_const;
25+
pub(crate) mod on_move;
2526
pub(crate) mod on_unimplemented;
2627

2728
#[derive(Copy, Clone)]
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
65+
if list.is_empty() {
66+
cx.emit_lint(
67+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
68+
AttributeLintKind::OnMoveMalformedAttrExpectedLiteralOrDelimiter,
69+
list.span,
70+
);
71+
return None;
72+
}
73+
74+
let mut message = None;
75+
let mut label = None;
76+
77+
for item in list.mixed() {
78+
let Some(item) = item.meta_item() else {
79+
cx.expected_specific_argument(item.span(), &[sym::message, sym::label]);
80+
return None;
81+
};
82+
83+
let Some(name_value) = item.args().name_value() else {
84+
cx.expected_name_value(cx.attr_span, item.path().word_sym());
85+
return None;
86+
};
87+
88+
let Some(value) = name_value.value_as_str() else {
89+
cx.expected_string_literal(name_value.value_span, None);
90+
return None;
91+
};
92+
93+
let value_span = name_value.value_span;
94+
if item.path().word_is(sym::message) && message.is_none() {
95+
let spans = get_format_ranges(cx, &value, value_span);
96+
message = Some(OnMoveAttrArg::new(value, spans));
97+
continue;
98+
} else if item.path().word_is(sym::label) && label.is_none() {
99+
let spans = get_format_ranges(cx, &value, value_span);
100+
label = Some(OnMoveAttrArg::new(value, spans));
101+
continue;
102+
}
103+
104+
cx.emit_lint(
105+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
106+
AttributeLintKind::OnMoveMalformedAttr,
107+
item.span(),
108+
)
109+
}
110+
Some(AttributeKind::OnMove(Box::new(OnMoveAttribute {
111+
span: cx.attr_span,
112+
message,
113+
label,
114+
})))
115+
}
116+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::attributes::debugger::*;
2828
use crate::attributes::deprecation::*;
2929
use crate::attributes::diagnostic::do_not_recommend::*;
3030
use crate::attributes::diagnostic::on_const::*;
31+
use crate::attributes::diagnostic::on_move::*;
3132
use crate::attributes::diagnostic::on_unimplemented::*;
3233
use crate::attributes::doc::*;
3334
use crate::attributes::dummy::*;
@@ -198,6 +199,7 @@ attribute_parsers!(
198199
Single<MustUseParser>,
199200
Single<ObjcClassParser>,
200201
Single<ObjcSelectorParser>,
202+
Single<OnMoveParser>,
201203
Single<OptimizeParser>,
202204
Single<PatchableFunctionEntryParser>,
203205
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: 29 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,20 @@ 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) =
147+
find_attr!(self.infcx.tcx, item_def.did(), AttributeKind::OnMove(attr) => attr)
148+
{
149+
let item_name = self.infcx.tcx.item_name(item_def.did());
150+
(
151+
attr.message.as_ref().map(|e| e.format_args_with(item_name.as_str())),
152+
attr.label.as_ref().map(|e| e.format_args_with(item_name.as_str())),
153+
)
154+
} else {
155+
(None, None)
156+
};
157+
141158
let mut err = self.cannot_act_on_moved_value(
142159
span,
143160
desired_action.as_noun(),
@@ -146,6 +163,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
146163
moved_place,
147164
DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
148165
),
166+
on_move_message,
149167
);
150168

151169
let reinit_spans = maybe_reinitialized_locations
@@ -275,12 +293,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
275293
if needs_note {
276294
if let Some(local) = place.as_local() {
277295
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-
});
296+
if let Some(on_move_label) = on_move_label {
297+
err.span_label(span, on_move_label);
298+
} else {
299+
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
300+
is_partial_move,
301+
ty,
302+
place: &note_msg,
303+
span,
304+
});
305+
}
284306
} else {
285307
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Note {
286308
is_partial_move,

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 32 additions & 1 deletion
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::*;
@@ -631,6 +632,33 @@ impl<E: rustc_span::SpanEncoder> rustc_serialize::Encodable<E> for DocAttribute
631632
}
632633
}
633634

635+
#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic, PrintAttribute)]
636+
pub struct OnMoveAttrArg {
637+
pub symbol: Symbol,
638+
pub format_ranges: ThinVec<(usize, usize)>,
639+
}
640+
641+
impl OnMoveAttrArg {
642+
pub fn new(symbol: Symbol, format_ranges: ThinVec<(usize, usize)>) -> Self {
643+
Self { symbol, format_ranges }
644+
}
645+
646+
pub fn format_args_with(&self, item_name: &str) -> String {
647+
let mut arg = self.symbol.to_string();
648+
for fmt_idx in &self.format_ranges {
649+
arg.replace_range(Range { start: fmt_idx.0, end: fmt_idx.1 }, item_name);
650+
}
651+
arg
652+
}
653+
}
654+
655+
#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic, PrintAttribute)]
656+
pub struct OnMoveAttribute {
657+
pub span: Span,
658+
pub message: Option<OnMoveAttrArg>,
659+
pub label: Option<OnMoveAttrArg>,
660+
}
661+
634662
/// How to perform collapse macros debug info
635663
/// if-ext - if macro from different crate (related to callsite code)
636664
/// | cmd \ attr | no | (unspecified) | external | yes |
@@ -1090,13 +1118,16 @@ pub enum AttributeKind {
10901118
directive: Option<Box<Directive>>,
10911119
},
10921120

1121+
/// Represents `#[diagnostic::on_move]`
1122+
OnMove(Box<OnMoveAttribute>),
1123+
1124+
10931125
/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
10941126
OnUnimplemented {
10951127
span: Span,
10961128
/// None if the directive was malformed in some way.
10971129
directive: Option<Box<Directive>>,
10981130
},
1099-
11001131
/// Represents `#[optimize(size|speed)]`
11011132
Optimize(OptimizeAttr, Span),
11021133

compiler/rustc_hir/src/attrs/encode_cross_crate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ impl AttributeKind {
7979
NoStd(..) => No,
8080
NonExhaustive(..) => Yes, // Needed for rustdoc
8181
OnConst { .. } => Yes,
82+
OnMove { .. } => Yes,
8283
OnUnimplemented { .. } => Yes,
8384
Optimize(..) => No,
8485
PanicRuntime => No,

compiler/rustc_lint/src/early/diagnostics.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,5 +473,13 @@ pub fn decorate_attribute_lint(
473473
&AttributeLintKind::MissingOptionsForOnConst => {
474474
lints::MissingOptionsForOnConstAttr.decorate_lint(diag)
475475
}
476+
&AttributeLintKind::OnMoveMalformedAttr => lints::OnMoveMalformedAttr.decorate_lint(diag),
477+
478+
&AttributeLintKind::OnMoveMalformedFormatLiterals { name } => {
479+
lints::OnMoveMalformedFormatLiterals { name }.decorate_lint(diag)
480+
}
481+
&AttributeLintKind::OnMoveMalformedAttrExpectedLiteralOrDelimiter => {
482+
lints::OnMoveMalformedAttrExpectedLiteralOrDelimiter.decorate_lint(diag)
483+
}
476484
}
477485
}

compiler/rustc_lint/src/lints.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3926,3 +3926,20 @@ pub(crate) struct MalformedOnConstAttrLint {
39263926
#[label("invalid option found here")]
39273927
pub span: Span,
39283928
}
3929+
3930+
#[derive(LintDiagnostic)]
3931+
#[diag("unknown or malformed `on_move` attribute")]
3932+
#[help("only `message` and `label` are allowed as options")]
3933+
pub(crate) struct OnMoveMalformedAttr;
3934+
3935+
#[derive(LintDiagnostic)]
3936+
#[diag("unknown parameter `{$name}`")]
3937+
#[help("expect `Self` as format argument")]
3938+
pub(crate) struct OnMoveMalformedFormatLiterals {
3939+
pub name: Symbol,
3940+
}
3941+
3942+
#[derive(LintDiagnostic)]
3943+
#[diag("expected a literal or missing delimiter")]
3944+
#[help("only literals are allowed as values of `message` and `label`, separated by a comma")]
3945+
pub(crate) struct OnMoveMalformedAttrExpectedLiteralOrDelimiter;

compiler/rustc_lint_defs/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,11 @@ pub enum AttributeLintKind {
855855
},
856856
MissingOptionsForOnUnimplemented,
857857
MissingOptionsForOnConst,
858+
OnMoveMalformedAttr,
859+
OnMoveMalformedFormatLiterals {
860+
name: Symbol,
861+
},
862+
OnMoveMalformedAttrExpectedLiteralOrDelimiter,
858863
}
859864

860865
#[derive(Debug, Clone, HashStable_Generic)]

0 commit comments

Comments
 (0)