Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItem

pub(crate) mod do_not_recommend;
pub(crate) mod on_const;
pub(crate) mod on_move;
pub(crate) mod on_unimplemented;

#[derive(Copy, Clone)]
Expand Down
116 changes: 116 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use rustc_feature::{AttributeTemplate, template};
use rustc_hir::attrs::{AttributeKind, OnMoveAttrArg, OnMoveAttribute};
use rustc_hir::lints::AttributeLintKind;
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
use rustc_session::lint::builtin::{
MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
};
use rustc_span::{InnerSpan, Span, Symbol, kw, sym};
use thin_vec::ThinVec;

use crate::attributes::{AttributeOrder, OnDuplicate, SingleAttributeParser};
use crate::context::{AcceptContext, Stage};
use crate::parser::ArgParser;
use crate::target_checking::{ALL_TARGETS, AllowedTargets};

pub(crate) struct OnMoveParser;

impl<S: Stage> SingleAttributeParser<S> for OnMoveParser {
const PATH: &[Symbol] = &[sym::diagnostic, sym::on_move];
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); // Check in check_attr.
const TEMPLATE: AttributeTemplate = template!(List: &["message", "label"]);

fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let get_format_ranges = |cx: &mut AcceptContext<'_, '_, S>,
symbol: &Symbol,
span: Span|
-> ThinVec<(usize, usize)> {
let mut parser = Parser::new(symbol.as_str(), None, None, false, ParseMode::Diagnostic);
let pieces: Vec<_> = parser.by_ref().collect();
let mut spans = ThinVec::new();

for piece in pieces {
match piece {
Piece::NextArgument(arg) => match arg.position {
Position::ArgumentNamed(name) if name == kw::SelfUpper.as_str() => {
spans.push((arg.position_span.start - 2, arg.position_span.end));
}
Position::ArgumentNamed(name) => {
let span = span.from_inner(InnerSpan {
start: arg.position_span.start,
end: arg.position_span.end,
});
cx.emit_lint(
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
AttributeLintKind::OnMoveMalformedFormatLiterals {
name: Symbol::intern(name),
},
span,
)
}
_ => {}
},
_ => continue,
}
}
spans
};
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span, args);
return None;
};

if list.is_empty() {
cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::OnMoveMalformedAttrExpectedLiteralOrDelimiter,
list.span,
);
return None;
}

let mut message = None;
let mut label = None;

for item in list.mixed() {
let Some(item) = item.meta_item() else {
cx.expected_specific_argument(item.span(), &[sym::message, sym::label]);
return None;
};

let Some(name_value) = item.args().name_value() else {
cx.expected_name_value(cx.attr_span, item.path().word_sym());
return None;
};

let Some(value) = name_value.value_as_str() else {
cx.expected_string_literal(name_value.value_span, None);
return None;
};

let value_span = name_value.value_span;
if item.path().word_is(sym::message) && message.is_none() {
let spans = get_format_ranges(cx, &value, value_span);
message = Some(OnMoveAttrArg::new(value, spans));
continue;
} else if item.path().word_is(sym::label) && label.is_none() {
let spans = get_format_ranges(cx, &value, value_span);
label = Some(OnMoveAttrArg::new(value, spans));
continue;
}

cx.emit_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
AttributeLintKind::OnMoveMalformedAttr,
item.span(),
)
}
Some(AttributeKind::OnMove(Box::new(OnMoveAttribute {
span: cx.attr_span,
message,
label,
})))
}
}
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::attributes::debugger::*;
use crate::attributes::deprecation::*;
use crate::attributes::diagnostic::do_not_recommend::*;
use crate::attributes::diagnostic::on_const::*;
use crate::attributes::diagnostic::on_move::*;
use crate::attributes::diagnostic::on_unimplemented::*;
use crate::attributes::doc::*;
use crate::attributes::dummy::*;
Expand Down Expand Up @@ -195,6 +196,7 @@ attribute_parsers!(
Single<MoveSizeLimitParser>,
Single<MustNotSuspendParser>,
Single<MustUseParser>,
Single<OnMoveParser>,
Single<OptimizeParser>,
Single<PatchableFunctionEntryParser>,
Single<PathAttributeParser>,
Expand Down
25 changes: 15 additions & 10 deletions compiler/rustc_borrowck/src/borrowck_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,18 +324,23 @@ impl<'infcx, 'tcx> crate::MirBorrowckCtxt<'_, 'infcx, 'tcx> {
verb: &str,
optional_adverb_for_moved: &str,
moved_path: Option<String>,
primary_message: Option<String>,
) -> Diag<'infcx> {
let moved_path = moved_path.map(|mp| format!(": `{mp}`")).unwrap_or_default();
if let Some(primary_message) = primary_message {
struct_span_code_err!(self.dcx(), use_span, E0382, "{}", primary_message)
} else {
let moved_path = moved_path.map(|mp| format!(": `{mp}`")).unwrap_or_default();

struct_span_code_err!(
self.dcx(),
use_span,
E0382,
"{} of {}moved value{}",
verb,
optional_adverb_for_moved,
moved_path,
)
struct_span_code_err!(
self.dcx(),
use_span,
E0382,
"{} of {}moved value{}",
verb,
optional_adverb_for_moved,
moved_path,
)
}
}

pub(crate) fn cannot_borrow_path_as_mutable_because(
Expand Down
35 changes: 28 additions & 7 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use rustc_errors::{Applicability, Diag, MultiSpan, struct_span_code_err};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{Visitor, walk_block, walk_expr};
use rustc_hir::{CoroutineDesugaring, CoroutineKind, CoroutineSource, LangItem, PatField};
use rustc_hir::{
CoroutineDesugaring, CoroutineKind, CoroutineSource, LangItem, PatField, find_attr,
};
use rustc_middle::bug;
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::mir::{
Expand Down Expand Up @@ -138,6 +140,20 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
let partial_str = if is_partial_move { "partial " } else { "" };
let partially_str = if is_partial_move { "partially " } else { "" };

let (on_move_message, on_move_label) = if let ty::Adt(item_def, _) =
self.body.local_decls[moved_place.local].ty.kind()
&& let Some(attr) =
find_attr!(self.infcx.tcx, item_def.did(), OnMove(attr) => attr)
{
let item_name = self.infcx.tcx.item_name(item_def.did());
(
attr.message.as_ref().map(|e| e.format_args_with(item_name.as_str())),
attr.label.as_ref().map(|e| e.format_args_with(item_name.as_str())),
)
} else {
(None, None)
};

let mut err = self.cannot_act_on_moved_value(
span,
desired_action.as_noun(),
Expand All @@ -146,6 +162,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
moved_place,
DescribePlaceOpt { including_downcast: true, including_tuple_field: true },
),
on_move_message,
);

let reinit_spans = maybe_reinitialized_locations
Expand Down Expand Up @@ -275,12 +292,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
if needs_note {
if let Some(local) = place.as_local() {
let span = self.body.local_decls[local].source_info.span;
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
is_partial_move,
ty,
place: &note_msg,
span,
});
if let Some(on_move_label) = on_move_label {
err.span_label(span, on_move_label);
} else {
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {
is_partial_move,
ty,
place: &note_msg,
span,
});
}
} else {
err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Note {
is_partial_move,
Expand Down
32 changes: 31 additions & 1 deletion compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::ops::Range;
use std::path::PathBuf;

pub use ReprAttr::*;
Expand Down Expand Up @@ -631,6 +632,33 @@ impl<E: rustc_span::SpanEncoder> rustc_serialize::Encodable<E> for DocAttribute
}
}

#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic, PrintAttribute)]
pub struct OnMoveAttrArg {
pub symbol: Symbol,
pub format_ranges: ThinVec<(usize, usize)>,
}
Comment on lines +635 to +639
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I plan on moving a fair bit of the format and string stuff that on_unimplemented uses into rustc_hir; you should be able to reuse some of that in this PR :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will do


impl OnMoveAttrArg {
pub fn new(symbol: Symbol, format_ranges: ThinVec<(usize, usize)>) -> Self {
Self { symbol, format_ranges }
}

pub fn format_args_with(&self, item_name: &str) -> String {
let mut arg = self.symbol.to_string();
for fmt_idx in &self.format_ranges {
arg.replace_range(Range { start: fmt_idx.0, end: fmt_idx.1 }, item_name);
}
arg
}
}

#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic, PrintAttribute)]
pub struct OnMoveAttribute {
pub span: Span,
pub message: Option<OnMoveAttrArg>,
pub label: Option<OnMoveAttrArg>,
}

/// How to perform collapse macros debug info
/// if-ext - if macro from different crate (related to callsite code)
/// | cmd \ attr | no | (unspecified) | external | yes |
Expand Down Expand Up @@ -1080,13 +1108,15 @@ pub enum AttributeKind {
directive: Option<Box<Directive>>,
},

/// Represents `#[diagnostic::on_move]`
OnMove(Box<OnMoveAttribute>),

/// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`.
OnUnimplemented {
span: Span,
/// None if the directive was malformed in some way.
directive: Option<Box<Directive>>,
},

/// Represents `#[optimize(size|speed)]`
Optimize(OptimizeAttr, Span),

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl AttributeKind {
NoStd(..) => No,
NonExhaustive(..) => Yes, // Needed for rustdoc
OnConst { .. } => Yes,
OnMove { .. } => Yes,
OnUnimplemented { .. } => Yes,
Optimize(..) => No,
PanicRuntime => No,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_lint/src/early/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,5 +473,13 @@ pub fn decorate_attribute_lint(
&AttributeLintKind::MissingOptionsForOnConst => {
lints::MissingOptionsForOnConstAttr.decorate_lint(diag)
}
&AttributeLintKind::OnMoveMalformedAttr => lints::OnMoveMalformedAttr.decorate_lint(diag),

&AttributeLintKind::OnMoveMalformedFormatLiterals { name } => {
lints::OnMoveMalformedFormatLiterals { name }.decorate_lint(diag)
}
&AttributeLintKind::OnMoveMalformedAttrExpectedLiteralOrDelimiter => {
lints::OnMoveMalformedAttrExpectedLiteralOrDelimiter.decorate_lint(diag)
}
}
}
17 changes: 17 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3932,3 +3932,20 @@ pub(crate) struct MalformedOnConstAttrLint {
#[label("invalid option found here")]
pub span: Span,
}

#[derive(LintDiagnostic)]
#[diag("unknown or malformed `on_move` attribute")]
#[help("only `message` and `label` are allowed as options")]
pub(crate) struct OnMoveMalformedAttr;

#[derive(LintDiagnostic)]
#[diag("unknown parameter `{$name}`")]
#[help("expect `Self` as format argument")]
pub(crate) struct OnMoveMalformedFormatLiterals {
pub name: Symbol,
}

#[derive(LintDiagnostic)]
#[diag("expected a literal or missing delimiter")]
#[help("only literals are allowed as values of `message` and `label`, separated by a comma")]
pub(crate) struct OnMoveMalformedAttrExpectedLiteralOrDelimiter;
5 changes: 5 additions & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,11 @@ pub enum AttributeLintKind {
},
MissingOptionsForOnUnimplemented,
MissingOptionsForOnConst,
OnMoveMalformedAttr,
OnMoveMalformedFormatLiterals {
name: Symbol,
},
OnMoveMalformedAttrExpectedLiteralOrDelimiter,
}

#[derive(Debug, Clone, HashStable_Generic)]
Expand Down
19 changes: 19 additions & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ struct DiagnosticOnConstOnlyForNonConstTraitImpls {
item_span: Span,
}

#[derive(Diagnostic)]
#[diag("`#[diagnostic::on_move]` can only be applied to enums, structs or unions")]
struct DiagnosticOnMoveOnlyForAdt;

fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) -> Target {
match impl_item.kind {
hir::ImplItemKind::Const(..) => Target::AssocConst,
Expand Down Expand Up @@ -234,6 +238,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)},
Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())},
Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)}
Attribute::Parsed(AttributeKind::OnMove(attr)) => {
self.check_diagnostic_on_move(attr.span, hir_id, target)
},
Attribute::Parsed(
// tidy-alphabetical-start
AttributeKind::RustcAllowIncoherentImpl(..)
Expand Down Expand Up @@ -693,6 +700,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
// The traits' or the impls'?
}

/// Checks if `#[diagnostic::on_move]` is applied to an ADT definition
fn check_diagnostic_on_move(&self, attr_span: Span, hir_id: HirId, target: Target) {
if !matches!(target, Target::Enum | Target::Struct | Target::Union) {
self.tcx.emit_node_span_lint(
MISPLACED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
attr_span,
DiagnosticOnMoveOnlyForAdt,
);
}
}

/// Checks if an `#[inline]` is applied to a function or a closure.
fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) {
match target {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
}

const DIAG_ATTRS: &[Symbol] =
&[sym::on_unimplemented, sym::do_not_recommend, sym::on_const];
&[sym::on_unimplemented, sym::do_not_recommend, sym::on_const, sym::on_move];

if res == Res::NonMacroAttr(NonMacroAttrKind::Tool)
&& let [namespace, attribute, ..] = &*path.segments
Expand Down
Loading
Loading