Skip to content

Commit 9a0162d

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 1b9ae9e commit 9a0162d

25 files changed

Lines changed: 738 additions & 19 deletions

Cargo.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3568,6 +3568,7 @@ dependencies = [
35683568
"itertools",
35693569
"polonius-engine",
35703570
"rustc_abi",
3571+
"rustc_ast",
35713572
"rustc_data_structures",
35723573
"rustc_errors",
35733574
"rustc_fluent_macro",
@@ -3579,6 +3580,7 @@ dependencies = [
35793580
"rustc_macros",
35803581
"rustc_middle",
35813582
"rustc_mir_dataflow",
3583+
"rustc_parse_format",
35823584
"rustc_session",
35833585
"rustc_span",
35843586
"rustc_trait_selection",

compiler/rustc_borrowck/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ either = "1.5.0"
99
itertools = "0.12"
1010
polonius-engine = "0.13.0"
1111
rustc_abi = { path = "../rustc_abi" }
12+
rustc_ast = { path = "../rustc_ast" }
1213
rustc_data_structures = { path = "../rustc_data_structures" }
1314
rustc_errors = { path = "../rustc_errors" }
1415
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
@@ -20,6 +21,7 @@ rustc_lexer = { path = "../rustc_lexer" }
2021
rustc_macros = { path = "../rustc_macros" }
2122
rustc_middle = { path = "../rustc_middle" }
2223
rustc_mir_dataflow = { path = "../rustc_mir_dataflow" }
24+
rustc_parse_format = { path = "../rustc_parse_format" }
2325
rustc_session = { path = "../rustc_session" }
2426
rustc_span = { path = "../rustc_span" }
2527
rustc_trait_selection = { path = "../rustc_trait_selection" }

compiler/rustc_borrowck/messages.ftl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ borrowck_higher_ranked_lifetime_error =
7777
borrowck_higher_ranked_subtype_error =
7878
higher-ranked subtype error
7979
80+
borrowck_ignored_diagnostic_option = `{$option_name}` is ignored due to previous definition of `{$option_name}`
81+
.other_label = `{$option_name}` is first declared here
82+
.label = `{$option_name}` is already declared here
8083
borrowck_implicit_static =
8184
this has an implicit `'static` lifetime requirement
8285
@@ -92,6 +95,13 @@ borrowck_lifetime_constraints_error =
9295
borrowck_limitations_implies_static =
9396
due to a current limitation of the type system, this implies a `'static` lifetime
9497
98+
borrowck_malformed_on_move_attr = unknown or malformed `on_move` attribute
99+
.help = only `message` and `label` are allowed as options
100+
.label = invalid option found here
101+
102+
borrowck_missing_options_for_on_move_attr = missing options for `on_move` attribute
103+
.help = at least one of the `message` and `label` options are expected
104+
95105
borrowck_move_closure_suggestion =
96106
consider adding 'move' keyword before the nested closure
97107
@@ -216,6 +226,8 @@ borrowck_ty_no_impl_copy =
216226
*[false] move
217227
} occurs because {$place} has type `{$ty}`, which does not implement the `Copy` trait
218228
229+
borrowck_unknown_format_parameter_for_on_move_attr = unknown parameter `{$argument_name}`
230+
.help = expect {"`{Self}`"} as format argument
219231
borrowck_use_due_to_use_closure =
220232
use occurs due to use in closure
221233

compiler/rustc_borrowck/src/borrowck_errors.rs

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

333-
struct_span_code_err!(
334-
self.dcx(),
335-
use_span,
336-
E0382,
337-
"{} of {}moved value{}",
338-
verb,
339-
optional_adverb_for_moved,
340-
moved_path,
341-
)
337+
struct_span_code_err!(
338+
self.dcx(),
339+
use_span,
340+
E0382,
341+
"{} of {}moved value{}",
342+
verb,
343+
optional_adverb_for_moved,
344+
moved_path,
345+
)
346+
}
342347
}
343348

344349
pub(crate) fn cannot_borrow_path_as_mutable_because(

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Lines changed: 232 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ use std::iter;
77
use std::ops::ControlFlow;
88

99
use either::Either;
10-
use hir::{ClosureKind, Path};
10+
use hir::{AttrArgs, Attribute, ClosureKind, Path};
11+
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
1112
use rustc_data_structures::fx::FxIndexSet;
1213
use rustc_errors::codes::*;
1314
use rustc_errors::{Applicability, Diag, MultiSpan, struct_span_code_err};
1415
use rustc_hir as hir;
1516
use rustc_hir::def::{DefKind, Res};
1617
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr};
1718
use rustc_hir::{CoroutineDesugaring, CoroutineKind, CoroutineSource, LangItem, PatField};
19+
use rustc_macros::LintDiagnostic;
1820
use rustc_middle::bug;
1921
use rustc_middle::hir::nested_filter::OnlyBodies;
2022
use rustc_middle::mir::{
@@ -29,9 +31,13 @@ use rustc_middle::ty::{
2931
suggest_constraining_type_params,
3032
};
3133
use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex};
34+
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
35+
use rustc_session::lint::builtin::{
36+
MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
37+
};
3238
use rustc_span::def_id::{DefId, LocalDefId};
3339
use rustc_span::hygiene::DesugaringKind;
34-
use rustc_span::{BytePos, Ident, Span, Symbol, kw, sym};
40+
use rustc_span::{BytePos, Ident, InnerSpan, Span, Symbol, kw, sym};
3541
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
3642
use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
3743
use rustc_trait_selection::error_reporting::traits::call_kind::CallKind;
@@ -69,6 +75,148 @@ enum StorageDeadOrDrop<'tcx> {
6975
Destructor(Ty<'tcx>),
7076
}
7177

78+
#[derive(Debug)]
79+
struct OnMoveDirective {
80+
message: Option<(Span, String)>,
81+
label: Option<(Span, String)>,
82+
}
83+
84+
#[derive(LintDiagnostic)]
85+
#[diag(borrowck_unknown_format_parameter_for_on_move_attr)]
86+
#[help]
87+
struct UnknownFormatParameterForOnMoveAttr {
88+
argument_name: String,
89+
}
90+
91+
#[derive(LintDiagnostic)]
92+
#[diag(borrowck_malformed_on_move_attr)]
93+
#[help]
94+
struct MalformedOnMoveAttr {
95+
#[label]
96+
span: Span,
97+
}
98+
99+
#[derive(LintDiagnostic)]
100+
#[diag(borrowck_missing_options_for_on_move_attr)]
101+
#[help]
102+
struct MissingOptionsForOnMoveAttr;
103+
104+
#[derive(LintDiagnostic)]
105+
#[diag(borrowck_ignored_diagnostic_option)]
106+
struct IgnoredDiagnosticOption {
107+
option_name: &'static str,
108+
#[label]
109+
span: Span,
110+
#[label(borrowck_other_label)]
111+
prev_span: Span,
112+
}
113+
impl IgnoredDiagnosticOption {
114+
fn maybe_emit_warning<'tcx>(
115+
tcx: TyCtxt<'tcx>,
116+
item_def_id: DefId,
117+
new: Option<Span>,
118+
old: Option<Span>,
119+
option_name: &'static str,
120+
) {
121+
if let (Some(new_item), Some(old_item)) = (new, old)
122+
&& let Some(item_def_id) = item_def_id.as_local()
123+
{
124+
tcx.emit_node_span_lint(
125+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
126+
tcx.local_def_id_to_hir_id(item_def_id),
127+
new_item,
128+
IgnoredDiagnosticOption { span: new_item, prev_span: old_item, option_name },
129+
);
130+
}
131+
}
132+
}
133+
134+
impl<'tcx> OnMoveDirective {
135+
fn new() -> Self {
136+
Self { message: None, label: None }
137+
}
138+
fn parse(tcx: TyCtxt<'tcx>, item_def_id: DefId, items: &[MetaItemInner]) -> Self {
139+
let get_value_and_span = |item: &_, key| {
140+
if let MetaItemInner::MetaItem(MetaItem {
141+
path,
142+
kind: MetaItemKind::NameValue(MetaItemLit { span, kind: LitKind::Str(s, _), .. }),
143+
..
144+
}) = item
145+
&& *path == key
146+
{
147+
Some((*s, *span))
148+
} else {
149+
None
150+
}
151+
};
152+
let parse_value = |value: Symbol, span: Span| {
153+
let mut parser = Parser::new(value.as_str(), None, None, false, ParseMode::Diagnostic);
154+
let mut value = String::new();
155+
let pieces: Vec<_> = parser.by_ref().collect();
156+
157+
for piece in pieces {
158+
match piece {
159+
Piece::Lit(lit) => value.push_str(lit),
160+
Piece::NextArgument(arg) => match arg.position {
161+
Position::ArgumentNamed(name) if name == kw::SelfUpper.as_str() => {
162+
let slf = match tcx.opt_item_name(item_def_id) {
163+
Some(name) => name.to_string(),
164+
None => "Self".to_string(),
165+
};
166+
value.push_str(&slf);
167+
}
168+
Position::ArgumentNamed(name) => {
169+
if let Some(item_def_id) = item_def_id.as_local() {
170+
let span = span.from_inner(InnerSpan {
171+
start: arg.position_span.start,
172+
end: arg.position_span.end,
173+
});
174+
tcx.emit_node_span_lint(
175+
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
176+
tcx.local_def_id_to_hir_id(item_def_id),
177+
span,
178+
UnknownFormatParameterForOnMoveAttr {
179+
argument_name: name.to_string(),
180+
},
181+
);
182+
}
183+
value.push_str(format!("{{{name}}}").as_str());
184+
}
185+
_ => {}
186+
},
187+
}
188+
}
189+
value
190+
};
191+
let mut message = None;
192+
let mut label = None;
193+
for item in items {
194+
if let Some((message_, span)) = get_value_and_span(item, sym::message)
195+
&& message.is_none()
196+
{
197+
let value = parse_value(message_, span);
198+
message = Some((item.span(), value));
199+
continue;
200+
} else if let Some((label_, span)) = get_value_and_span(item, sym::label)
201+
&& label.is_none()
202+
{
203+
let value = parse_value(label_, span);
204+
label = Some((item.span(), value));
205+
continue;
206+
}
207+
if let Some(def_id) = item_def_id.as_local() {
208+
tcx.emit_node_span_lint(
209+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
210+
tcx.local_def_id_to_hir_id(def_id),
211+
vec![item.span()],
212+
MalformedOnMoveAttr { span: item.span() },
213+
);
214+
}
215+
}
216+
Self { message, label }
217+
}
218+
}
219+
72220
impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
73221
pub(crate) fn report_use_of_moved_or_uninitialized(
74222
&mut self,
@@ -141,6 +289,75 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
141289
let partial_str = if is_partial_move { "partial " } else { "" };
142290
let partially_str = if is_partial_move { "partially " } else { "" };
143291

292+
let mut on_move_directive = if let ty::Adt(item_def, _) =
293+
self.body.local_decls[moved_place.local].ty.kind()
294+
{
295+
self.infcx
296+
.tcx
297+
.get_attrs_by_path(item_def.did(), &[sym::diagnostic, sym::on_move])
298+
.fold(None, |acc: Option<OnMoveDirective>, attr| {
299+
if let Some(items) = attr.meta_item_list() {
300+
let directive =
301+
OnMoveDirective::parse(self.infcx.tcx, item_def.did(), &items);
302+
if let Some(acc) = acc {
303+
IgnoredDiagnosticOption::maybe_emit_warning(
304+
self.infcx.tcx,
305+
item_def.did(),
306+
directive.message.as_ref().map(|attr| attr.0),
307+
acc.message.as_ref().map(|attr| attr.0),
308+
"message",
309+
);
310+
311+
IgnoredDiagnosticOption::maybe_emit_warning(
312+
self.infcx.tcx,
313+
item_def.did(),
314+
directive.label.as_ref().map(|attr| attr.0),
315+
acc.label.as_ref().map(|attr| attr.0),
316+
"label",
317+
);
318+
319+
Some(acc)
320+
} else {
321+
Some(directive)
322+
}
323+
} else {
324+
match attr {
325+
Attribute::Unparsed(p) if !matches!(p.args, AttrArgs::Empty) => {
326+
if let Some(item_def_id) = item_def.did().as_local() {
327+
self.infcx.tcx.emit_node_span_lint(
328+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
329+
self.infcx.tcx.local_def_id_to_hir_id(item_def_id),
330+
attr.span(),
331+
MalformedOnMoveAttr { span: attr.span() },
332+
);
333+
}
334+
}
335+
_ => {
336+
if let Some(item_def_id) = item_def.did().as_local() {
337+
self.infcx.tcx.emit_node_span_lint(
338+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
339+
self.infcx.tcx.local_def_id_to_hir_id(item_def_id),
340+
attr.span(),
341+
MissingOptionsForOnMoveAttr,
342+
)
343+
}
344+
}
345+
}
346+
Some(OnMoveDirective::new())
347+
}
348+
})
349+
} else {
350+
None
351+
};
352+
353+
let message = if let Some(on_move_directive) = &mut on_move_directive
354+
&& let Some(message) = on_move_directive.message.take()
355+
{
356+
Some(message.1)
357+
} else {
358+
None
359+
};
360+
144361
let mut err = self.cannot_act_on_moved_value(
145362
span,
146363
desired_action.as_noun(),
@@ -149,6 +366,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
149366
moved_place,
150367
DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
151368
),
369+
message,
152370
);
153371

154372
let reinit_spans = maybe_reinitialized_locations
@@ -278,12 +496,18 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
278496
if needs_note {
279497
if let Some(local) = place.as_local() {
280498
let span = self.body.local_decls[local].source_info.span;
281-
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
282-
is_partial_move,
283-
ty,
284-
place: &note_msg,
285-
span,
286-
});
499+
if let Some(on_move_directive) = on_move_directive
500+
&& let Some(label) = on_move_directive.label
501+
{
502+
err.span_label(span, label.1);
503+
} else {
504+
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
505+
is_partial_move,
506+
ty,
507+
place: &note_msg,
508+
span,
509+
});
510+
}
287511
} else {
288512
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Note {
289513
is_partial_move,

compiler/rustc_passes/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ passes_diagnostic_diagnostic_on_const_only_for_trait_impls =
100100
`#[diagnostic::on_const]` can only be applied to trait impls
101101
.label = not a trait impl
102102
103+
passes_diagnostic_diagnostic_on_move_only_for_adt =
104+
`#[diagnostic::on_move]` can only be applied to enums, structs or unions
105+
103106
passes_diagnostic_diagnostic_on_unimplemented_only_for_traits =
104107
`#[diagnostic::on_unimplemented]` can only be applied to trait definitions
105108

0 commit comments

Comments
 (0)