Rollup merge of #102623 - davidtwco:translation-eager, r=compiler-errors

translation: eager translation

Part of #100717. See [Zulip thread](https://rust-lang.zulipchat.com/#narrow/stream/336883-i18n/topic/.23100717.20lists!/near/295010720) for additional context.

- **Store diagnostic arguments in a `HashMap`**: Eager translation will enable subdiagnostics to be translated multiple times with different arguments - this requires the ability to replace the value of one argument with a new value, which is better suited to a `HashMap` than the previous storage, a `Vec`.
- **Add `AddToDiagnostic::add_to_diagnostic_with`**: `AddToDiagnostic::add_to_diagnostic_with` is similar to the previous `AddToDiagnostic::add_to_diagnostic` but takes a function that can be used by the caller to modify diagnostic messages originating from the subdiagnostic (such as performing translation eagerly). `add_to_diagnostic` now just calls `add_to_diagnostic_with` with an empty closure.
- **Add `DiagnosticMessage::Eager`**: Add variant of `DiagnosticMessage` for eagerly translated messages
(messages in the target language which don't need translated by the emitter during emission). Also adds `eager_subdiagnostic` function which is intended to be invoked by the diagnostic derive for subdiagnostic fields which are marked as needing eager translation.
- **Support `#[subdiagnostic(eager)]`**: Add support for `eager` argument to the `subdiagnostic` attribute which generates a call to `eager_subdiagnostic`.
- **Finish migrating `rustc_query_system`**: Using eager translation, migrate the remaining repeated cycle stack diagnostic.
- **Split formatting initialization and use in diagnostic derives**: Diagnostic derives have previously had to take special care when ordering the generated code so that fields were not used after a move.

  This is unlikely for most fields because a field is either annotated with a subdiagnostic attribute and is thus likely a `Span` and copiable, or is a argument, in which case it is only used once by `set_arg`
anyway.

  However, format strings for code in suggestions can result in fields being used after being moved if not ordered carefully. As a result, the derive currently puts `set_arg` calls last (just before emission), such as:

      let diag = { /* create diagnostic */ };

      diag.span_suggestion_with_style(
          span,
          fluent::crate::slug,
          format!("{}", __binding_0),
          Applicability::Unknown,
          SuggestionStyle::ShowAlways
      );
      /* + other subdiagnostic additions */

      diag.set_arg("foo", __binding_0);
      /* + other `set_arg` calls */

      diag.emit();

  For eager translation, this doesn't work, as the message being translated eagerly can assume that all arguments are available - so arguments _must_ be set first.

  Format strings for suggestion code are now separated into two parts - an initialization line that performs the formatting into a variable, and a usage in the subdiagnostic addition.

  By separating these parts, the initialization can happen before arguments are set, preserving the desired order so that code compiles, while still enabling arguments to be set before subdiagnostics are added.

      let diag = { /* create diagnostic */ };

      let __code_0 = format!("{}", __binding_0);
      /* + other formatting */

      diag.set_arg("foo", __binding_0);
      /* + other `set_arg` calls */

      diag.span_suggestion_with_style(
          span,
          fluent::crate::slug,
          __code_0,
          Applicability::Unknown,
          SuggestionStyle::ShowAlways
      );
      /* + other subdiagnostic additions */

      diag.emit();

- **Remove field ordering logic in diagnostic derive:** Following the approach taken in earlier commits to separate formatting initialization from use in the subdiagnostic derive, simplify the diagnostic derive by removing the field-ordering logic that previously solved this problem.

r? ```@compiler-errors```
This commit is contained in:
Dylan DPC 2022-10-12 22:13:23 +05:30 committed by GitHub
commit dc9f6f3243
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 540 additions and 236 deletions

View File

@ -1,4 +1,7 @@
use rustc_errors::{fluent, AddToDiagnostic, Applicability, Diagnostic, DiagnosticArgFromDisplay};
use rustc_errors::{
fluent, AddToDiagnostic, Applicability, Diagnostic, DiagnosticArgFromDisplay,
SubdiagnosticMessage,
};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::{symbol::Ident, Span, Symbol};
@ -19,7 +22,10 @@ pub struct UseAngleBrackets {
}
impl AddToDiagnostic for UseAngleBrackets {
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
diag.multipart_suggestion(
fluent::ast_lowering::use_angle_brackets,
vec![(self.open_param, String::from("<")), (self.close_param, String::from(">"))],
@ -69,7 +75,10 @@ pub enum AssocTyParenthesesSub {
}
impl AddToDiagnostic for AssocTyParenthesesSub {
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
match self {
Self::Empty { parentheses_span } => diag.multipart_suggestion(
fluent::ast_lowering::remove_parentheses,

View File

@ -1,6 +1,6 @@
//! Errors emitted by ast_passes.
use rustc_errors::{fluent, AddToDiagnostic, Applicability, Diagnostic};
use rustc_errors::{fluent, AddToDiagnostic, Applicability, Diagnostic, SubdiagnosticMessage};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::{Span, Symbol};
@ -17,7 +17,10 @@ pub struct ForbiddenLet {
}
impl AddToDiagnostic for ForbiddenLetReason {
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
match self {
Self::GenericForbidden => {}
Self::NotSupportedOr(span) => {
@ -228,7 +231,10 @@ pub struct ExternBlockSuggestion {
}
impl AddToDiagnostic for ExternBlockSuggestion {
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
let start_suggestion = if let Some(abi) = self.abi {
format!("extern \"{}\" {{", abi)
} else {

View File

@ -15,7 +15,10 @@
use rustc_data_structures::profiling::VerboseTimingGuard;
use rustc_data_structures::sync::Lrc;
use rustc_errors::emitter::Emitter;
use rustc_errors::{translation::Translate, DiagnosticId, FatalError, Handler, Level};
use rustc_errors::{
translation::{to_fluent_args, Translate},
DiagnosticId, FatalError, Handler, Level,
};
use rustc_fs_util::link_or_copy;
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
use rustc_incremental::{
@ -1740,7 +1743,7 @@ fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
impl Emitter for SharedEmitter {
fn emit_diagnostic(&mut self, diag: &rustc_errors::Diagnostic) {
let fluent_args = self.to_fluent_args(diag.args());
let fluent_args = to_fluent_args(diag.args());
drop(self.sender.send(SharedEmitterMessage::Diagnostic(Diagnostic {
msg: self.translate_messages(&diag.message, &fluent_args).to_string(),
code: diag.code.clone(),

View File

@ -12,6 +12,8 @@ query_system_cycle_usage = cycle used when {$usage}
query_system_cycle_stack_single = ...which immediately requires {$stack_bottom} again
query_system_cycle_stack_middle = ...which requires {$desc}...
query_system_cycle_stack_multiple = ...which again requires {$stack_bottom}, completing the cycle
query_system_cycle_recursive_ty_alias = type aliases cannot be recursive

View File

@ -277,6 +277,18 @@ pub enum SubdiagnosticMessage {
/// Non-translatable diagnostic message.
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
Str(String),
/// Translatable message which has already been translated eagerly.
///
/// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
/// be instantiated multiple times with different values. As translation normally happens
/// immediately prior to emission, after the diagnostic and subdiagnostic derive logic has run,
/// the setting of diagnostic arguments in the derived code will overwrite previous variable
/// values and only the final value will be set when translation occurs - resulting in
/// incorrect diagnostics. Eager translation results in translation for a subdiagnostic
/// happening immediately after the subdiagnostic derive's logic has been run. This variant
/// stores messages which have been translated eagerly.
// FIXME(#100717): can a `Cow<'static, str>` be used here?
Eager(String),
/// Identifier of a Fluent message. Instances of this variant are generated by the
/// `Subdiagnostic` derive.
FluentIdentifier(FluentId),
@ -304,8 +316,20 @@ fn from(s: S) -> Self {
#[rustc_diagnostic_item = "DiagnosticMessage"]
pub enum DiagnosticMessage {
/// Non-translatable diagnostic message.
// FIXME(davidtwco): can a `Cow<'static, str>` be used here?
// FIXME(#100717): can a `Cow<'static, str>` be used here?
Str(String),
/// Translatable message which has already been translated eagerly.
///
/// Some diagnostics have repeated subdiagnostics where the same interpolated variables would
/// be instantiated multiple times with different values. As translation normally happens
/// immediately prior to emission, after the diagnostic and subdiagnostic derive logic has run,
/// the setting of diagnostic arguments in the derived code will overwrite previous variable
/// values and only the final value will be set when translation occurs - resulting in
/// incorrect diagnostics. Eager translation results in translation for a subdiagnostic
/// happening immediately after the subdiagnostic derive's logic has been run. This variant
/// stores messages which have been translated eagerly.
// FIXME(#100717): can a `Cow<'static, str>` be used here?
Eager(String),
/// Identifier for a Fluent message (with optional attribute) corresponding to the diagnostic
/// message.
///
@ -324,6 +348,7 @@ impl DiagnosticMessage {
pub fn with_subdiagnostic_message(&self, sub: SubdiagnosticMessage) -> Self {
let attr = match sub {
SubdiagnosticMessage::Str(s) => return DiagnosticMessage::Str(s),
SubdiagnosticMessage::Eager(s) => return DiagnosticMessage::Eager(s),
SubdiagnosticMessage::FluentIdentifier(id) => {
return DiagnosticMessage::FluentIdentifier(id, None);
}
@ -332,6 +357,7 @@ pub fn with_subdiagnostic_message(&self, sub: SubdiagnosticMessage) -> Self {
match self {
DiagnosticMessage::Str(s) => DiagnosticMessage::Str(s.clone()),
DiagnosticMessage::Eager(s) => DiagnosticMessage::Eager(s.clone()),
DiagnosticMessage::FluentIdentifier(id, _) => {
DiagnosticMessage::FluentIdentifier(id.clone(), Some(attr))
}
@ -367,6 +393,7 @@ impl Into<SubdiagnosticMessage> for DiagnosticMessage {
fn into(self) -> SubdiagnosticMessage {
match self {
DiagnosticMessage::Str(s) => SubdiagnosticMessage::Str(s),
DiagnosticMessage::Eager(s) => SubdiagnosticMessage::Eager(s),
DiagnosticMessage::FluentIdentifier(id, None) => {
SubdiagnosticMessage::FluentIdentifier(id)
}

View File

@ -7,7 +7,7 @@
use crate::emitter::FileWithAnnotatedLines;
use crate::snippet::Line;
use crate::translation::Translate;
use crate::translation::{to_fluent_args, Translate};
use crate::{
CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle,
LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic,
@ -46,7 +46,7 @@ fn fallback_fluent_bundle(&self) -> &FluentBundle {
impl Emitter for AnnotateSnippetEmitterWriter {
/// The entry point for the diagnostics generation
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
let fluent_args = self.to_fluent_args(diag.args());
let fluent_args = to_fluent_args(diag.args());
let mut children = diag.children.clone();
let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);

View File

@ -27,7 +27,11 @@
/// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of
/// `DiagnosticArg` are converted to `FluentArgs` (consuming the collection) at the start of
/// diagnostic emission.
pub type DiagnosticArg<'source> = (Cow<'source, str>, DiagnosticArgValue<'source>);
pub type DiagnosticArg<'iter, 'source> =
(&'iter DiagnosticArgName<'source>, &'iter DiagnosticArgValue<'source>);
/// Name of a diagnostic argument.
pub type DiagnosticArgName<'source> = Cow<'source, str>;
/// Simplified version of `FluentValue` that can implement `Encodable` and `Decodable`. Converted
/// to a `FluentValue` by the emitter to be used in diagnostic translation.
@ -199,9 +203,20 @@ fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
/// `#[derive(Subdiagnostic)]` -- see [rustc_macros::Subdiagnostic].
#[cfg_attr(bootstrap, rustc_diagnostic_item = "AddSubdiagnostic")]
#[cfg_attr(not(bootstrap), rustc_diagnostic_item = "AddToDiagnostic")]
pub trait AddToDiagnostic {
pub trait AddToDiagnostic
where
Self: Sized,
{
/// Add a subdiagnostic to an existing diagnostic.
fn add_to_diagnostic(self, diag: &mut Diagnostic);
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
self.add_to_diagnostic_with(diag, |_, m| m);
}
/// Add a subdiagnostic to an existing diagnostic where `f` is invoked on every message used
/// (to optionally perform eager translation).
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, f: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage;
}
/// Trait implemented by lint types. This should not be implemented manually. Instead, use
@ -229,7 +244,7 @@ pub struct Diagnostic {
pub span: MultiSpan,
pub children: Vec<SubDiagnostic>,
pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
args: Vec<DiagnosticArg<'static>>,
args: FxHashMap<DiagnosticArgName<'static>, DiagnosticArgValue<'static>>,
/// This is not used for highlighting or rendering any error message. Rather, it can be used
/// as a sort key to sort a buffer of diagnostics. By default, it is the primary span of
@ -321,7 +336,7 @@ pub fn new_with_code<M: Into<DiagnosticMessage>>(
span: MultiSpan::new(),
children: vec![],
suggestions: Ok(vec![]),
args: vec![],
args: Default::default(),
sort_span: DUMMY_SP,
is_lint: false,
}
@ -917,13 +932,30 @@ pub fn tool_only_span_suggestion(
self
}
/// Add a subdiagnostic from a type that implements `Subdiagnostic` - see
/// [rustc_macros::Subdiagnostic].
/// Add a subdiagnostic from a type that implements `Subdiagnostic` (see
/// [rustc_macros::Subdiagnostic]).
pub fn subdiagnostic(&mut self, subdiagnostic: impl AddToDiagnostic) -> &mut Self {
subdiagnostic.add_to_diagnostic(self);
self
}
/// Add a subdiagnostic from a type that implements `Subdiagnostic` (see
/// [rustc_macros::Subdiagnostic]). Performs eager translation of any translatable messages
/// used in the subdiagnostic, so suitable for use with repeated messages (i.e. re-use of
/// interpolated variables).
pub fn eager_subdiagnostic(
&mut self,
handler: &crate::Handler,
subdiagnostic: impl AddToDiagnostic,
) -> &mut Self {
subdiagnostic.add_to_diagnostic_with(self, |diag, msg| {
let args = diag.args();
let msg = diag.subdiagnostic_message_to_diagnostic_message(msg);
handler.eagerly_translate(msg, args)
});
self
}
pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
self.span = sp.into();
if let Some(span) = self.span.primary_span() {
@ -956,8 +988,11 @@ pub fn set_primary_message(&mut self, msg: impl Into<DiagnosticMessage>) -> &mut
self
}
pub fn args(&self) -> &[DiagnosticArg<'static>] {
&self.args
// Exact iteration order of diagnostic arguments shouldn't make a difference to output because
// they're only used in interpolation.
#[allow(rustc::potential_query_instability)]
pub fn args<'a>(&'a self) -> impl Iterator<Item = DiagnosticArg<'a, 'static>> {
self.args.iter()
}
pub fn set_arg(
@ -965,7 +1000,7 @@ pub fn set_arg(
name: impl Into<Cow<'static, str>>,
arg: impl IntoDiagnosticArg,
) -> &mut Self {
self.args.push((name.into(), arg.into_diagnostic_arg()));
self.args.insert(name.into(), arg.into_diagnostic_arg());
self
}
@ -976,7 +1011,7 @@ pub fn styled_message(&self) -> &[(DiagnosticMessage, Style)] {
/// Helper function that takes a `SubdiagnosticMessage` and returns a `DiagnosticMessage` by
/// combining it with the primary message of the diagnostic (if translatable, otherwise it just
/// passes the user's string along).
fn subdiagnostic_message_to_diagnostic_message(
pub(crate) fn subdiagnostic_message_to_diagnostic_message(
&self,
attr: impl Into<SubdiagnosticMessage>,
) -> DiagnosticMessage {

View File

@ -14,7 +14,7 @@
use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString};
use crate::styled_buffer::StyledBuffer;
use crate::translation::Translate;
use crate::translation::{to_fluent_args, Translate};
use crate::{
CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, FluentBundle, Handler,
LazyFallbackBundle, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight, SuggestionStyle,
@ -535,7 +535,7 @@ fn source_map(&self) -> Option<&Lrc<SourceMap>> {
}
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
let fluent_args = self.to_fluent_args(diag.args());
let fluent_args = to_fluent_args(diag.args());
let mut children = diag.children.clone();
let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);

View File

@ -13,7 +13,7 @@
use crate::emitter::{Emitter, HumanReadableErrorType};
use crate::registry::Registry;
use crate::translation::Translate;
use crate::translation::{to_fluent_args, Translate};
use crate::DiagnosticId;
use crate::{
CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, SubDiagnostic,
@ -312,7 +312,7 @@ struct UnusedExterns<'a, 'b, 'c> {
impl Diagnostic {
fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
let args = je.to_fluent_args(diag.args());
let args = to_fluent_args(diag.args());
let sugg = diag.suggestions.iter().flatten().map(|sugg| {
let translated_message = je.translate_message(&sugg.msg, &args);
Diagnostic {

View File

@ -598,6 +598,17 @@ pub fn with_emitter_and_flags(
}
}
/// Translate `message` eagerly with `args`.
pub fn eagerly_translate<'a>(
&self,
message: DiagnosticMessage,
args: impl Iterator<Item = DiagnosticArg<'a, 'static>>,
) -> SubdiagnosticMessage {
let inner = self.inner.borrow();
let args = crate::translation::to_fluent_args(args);
SubdiagnosticMessage::Eager(inner.emitter.translate_message(&message, &args).to_string())
}
// This is here to not allow mutation of flags;
// as of this writing it's only used in tests in librustc_middle.
pub fn can_emit_warnings(&self) -> bool {

View File

@ -4,6 +4,27 @@
use rustc_error_messages::FluentArgs;
use std::borrow::Cow;
/// Convert diagnostic arguments (a rustc internal type that exists to implement
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
///
/// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
/// passed around as a reference thereafter.
pub fn to_fluent_args<'iter, 'arg: 'iter>(
iter: impl Iterator<Item = DiagnosticArg<'iter, 'arg>>,
) -> FluentArgs<'arg> {
let mut args = if let Some(size) = iter.size_hint().1 {
FluentArgs::with_capacity(size)
} else {
FluentArgs::new()
};
for (k, v) in iter {
args.set(k.clone(), v.clone());
}
args
}
pub trait Translate {
/// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
/// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
@ -15,15 +36,6 @@ pub trait Translate {
/// unavailable for the requested locale.
fn fallback_fluent_bundle(&self) -> &FluentBundle;
/// Convert diagnostic arguments (a rustc internal type that exists to implement
/// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
///
/// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
/// passed around as a reference thereafter.
fn to_fluent_args<'arg>(&self, args: &[DiagnosticArg<'arg>]) -> FluentArgs<'arg> {
FromIterator::from_iter(args.iter().cloned())
}
/// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
fn translate_messages(
&self,
@ -43,7 +55,9 @@ fn translate_message<'a>(
) -> Cow<'_, str> {
trace!(?message, ?args);
let (identifier, attr) = match message {
DiagnosticMessage::Str(msg) => return Cow::Borrowed(&msg),
DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => {
return Cow::Borrowed(&msg);
}
DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
};

View File

@ -1,6 +1,7 @@
use hir::GenericParamKind;
use rustc_errors::{
fluent, AddToDiagnostic, Applicability, DiagnosticMessage, DiagnosticStyledString, MultiSpan,
fluent, AddToDiagnostic, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString,
MultiSpan, SubdiagnosticMessage,
};
use rustc_hir as hir;
use rustc_hir::{FnRetTy, Ty};
@ -229,7 +230,10 @@ pub enum RegionOriginNote<'a> {
}
impl AddToDiagnostic for RegionOriginNote<'_> {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
let mut label_or_note = |span, msg: DiagnosticMessage| {
let sub_count = diag.children.iter().filter(|d| d.span.is_dummy()).count();
let expanded_sub_count = diag.children.iter().filter(|d| !d.span.is_dummy()).count();
@ -290,7 +294,10 @@ pub enum LifetimeMismatchLabels {
}
impl AddToDiagnostic for LifetimeMismatchLabels {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
match self {
LifetimeMismatchLabels::InRet { param_span, ret_span, span, label_var1 } => {
diag.span_label(param_span, fluent::infer::declared_different);
@ -340,7 +347,10 @@ pub struct AddLifetimeParamsSuggestion<'a> {
}
impl AddToDiagnostic for AddLifetimeParamsSuggestion<'_> {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
let mut mk_suggestion = || {
let (
hir::Ty { kind: hir::TyKind::Rptr(lifetime_sub, _), .. },
@ -439,7 +449,10 @@ pub struct IntroducesStaticBecauseUnmetLifetimeReq {
}
impl AddToDiagnostic for IntroducesStaticBecauseUnmetLifetimeReq {
fn add_to_diagnostic(mut self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(mut self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
self.unmet_requirements
.push_span_label(self.binding_span, fluent::infer::msl_introduces_static);
diag.span_note(self.unmet_requirements, fluent::infer::msl_unmet_req);
@ -451,7 +464,10 @@ pub struct ImplNote {
}
impl AddToDiagnostic for ImplNote {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
match self.impl_span {
Some(span) => diag.span_note(span, fluent::infer::msl_impl_note),
None => diag.note(fluent::infer::msl_impl_note),
@ -466,7 +482,10 @@ pub enum TraitSubdiag {
// FIXME(#100717) used in `Vec<TraitSubdiag>` so requires eager translation/list support
impl AddToDiagnostic for TraitSubdiag {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
match self {
TraitSubdiag::Note { span } => {
diag.span_note(span, "this has an implicit `'static` lifetime requirement");

View File

@ -1,5 +1,7 @@
use crate::infer::error_reporting::nice_region_error::find_anon_type;
use rustc_errors::{self, fluent, AddToDiagnostic, IntoDiagnosticArg};
use rustc_errors::{
self, fluent, AddToDiagnostic, Diagnostic, IntoDiagnosticArg, SubdiagnosticMessage,
};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::{symbol::kw, Span};
@ -159,7 +161,10 @@ pub fn new<'tcx>(
}
impl AddToDiagnostic for RegionExplanation<'_> {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
if let Some(span) = self.desc.span {
diag.span_note(span, fluent::infer::region_explanation);
} else {

View File

@ -1,4 +1,7 @@
use rustc_errors::{fluent, AddToDiagnostic, ErrorGuaranteed, Handler, IntoDiagnostic};
use rustc_errors::{
fluent, AddToDiagnostic, Diagnostic, ErrorGuaranteed, Handler, IntoDiagnostic,
SubdiagnosticMessage,
};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_session::lint::Level;
use rustc_span::{Span, Symbol};
@ -23,7 +26,10 @@ pub enum OverruledAttributeSub {
}
impl AddToDiagnostic for OverruledAttributeSub {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
match self {
OverruledAttributeSub::DefaultSource { id } => {
diag.note(fluent::lint::default_source);
@ -88,7 +94,10 @@ pub struct RequestedLevel {
}
impl AddToDiagnostic for RequestedLevel {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
diag.note(fluent::lint::requested_level);
diag.set_arg(
"level",

View File

@ -10,27 +10,31 @@
/// The central struct for constructing the `into_diagnostic` method from an annotated struct.
pub(crate) struct DiagnosticDerive<'a> {
structure: Structure<'a>,
handler: syn::Ident,
builder: DiagnosticDeriveBuilder,
}
impl<'a> DiagnosticDerive<'a> {
pub(crate) fn new(diag: syn::Ident, handler: syn::Ident, structure: Structure<'a>) -> Self {
Self {
builder: DiagnosticDeriveBuilder { diag, kind: DiagnosticDeriveKind::Diagnostic },
handler,
builder: DiagnosticDeriveBuilder {
diag,
kind: DiagnosticDeriveKind::Diagnostic { handler },
},
structure,
}
}
pub(crate) fn into_tokens(self) -> TokenStream {
let DiagnosticDerive { mut structure, handler, mut builder } = self;
let DiagnosticDerive { mut structure, mut builder } = self;
let implementation = builder.each_variant(&mut structure, |mut builder, variant| {
let preamble = builder.preamble(&variant);
let body = builder.body(&variant);
let diag = &builder.parent.diag;
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.parent.kind else {
unreachable!()
};
let init = match builder.slug.value_ref() {
None => {
span_err(builder.span, "diagnostic slug not specified")
@ -48,14 +52,17 @@ pub(crate) fn into_tokens(self) -> TokenStream {
}
};
let formatting_init = &builder.formatting_init;
quote! {
#init
#formatting_init
#preamble
#body
#diag
}
});
let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() };
structure.gen_impl(quote! {
gen impl<'__diagnostic_handler_sess, G>
rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G>
@ -96,17 +103,18 @@ pub(crate) fn into_tokens(self) -> TokenStream {
let body = builder.body(&variant);
let diag = &builder.parent.diag;
let formatting_init = &builder.formatting_init;
quote! {
#preamble
#formatting_init
#body
#diag
}
});
let msg = builder.each_variant(&mut structure, |mut builder, variant| {
// HACK(wafflelapkin): initialize slug (???)
let _preamble = builder.preamble(&variant);
// Collect the slug by generating the preamble.
let _ = builder.preamble(&variant);
match builder.slug.value_ref() {
None => {
@ -125,7 +133,10 @@ pub(crate) fn into_tokens(self) -> TokenStream {
let diag = &builder.diag;
structure.gen_impl(quote! {
gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self {
fn decorate_lint<'__b>(self, #diag: &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()>) -> &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()> {
fn decorate_lint<'__b>(
self,
#diag: &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()>
) -> &'__b mut rustc_errors::DiagnosticBuilder<'__a, ()> {
use rustc_errors::IntoDiagnosticArg;
#implementation
}

View File

@ -5,9 +5,9 @@
DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
bind_style_of_field, build_field_mapping, report_error_if_not_applied_to_span,
report_type_error, should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo,
FieldInnerTy, FieldMap, HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
build_field_mapping, report_error_if_not_applied_to_span, report_type_error,
should_generate_set_arg, type_is_unit, type_matches_path, FieldInfo, FieldInnerTy, FieldMap,
HasFieldMap, SetOnce, SpannedOption, SubdiagnosticKind,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
@ -17,9 +17,9 @@
use synstructure::{BindingInfo, Structure, VariantInfo};
/// What kind of diagnostic is being derived - a fatal/error/warning or a lint?
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq)]
pub(crate) enum DiagnosticDeriveKind {
Diagnostic,
Diagnostic { handler: syn::Ident },
LintDiagnostic,
}
@ -40,6 +40,9 @@ pub(crate) struct DiagnosticDeriveVariantBuilder<'parent> {
/// The parent builder for the entire type.
pub parent: &'parent DiagnosticDeriveBuilder,
/// Initialization of format strings for code suggestions.
pub formatting_init: TokenStream,
/// Span of the struct or the enum variant.
pub span: proc_macro::Span,
@ -88,19 +91,7 @@ pub fn each_variant<'s, F>(&mut self, structure: &mut Structure<'s>, f: F) -> To
}
}
for variant in structure.variants_mut() {
// First, change the binding style of each field based on the code that will be
// generated for the field - e.g. `set_arg` calls needs by-move bindings, whereas
// `set_primary_span` only needs by-ref.
variant.bind_with(|bi| bind_style_of_field(bi.ast()).0);
// Then, perform a stable sort on bindings which generates code for by-ref bindings
// before code generated for by-move bindings. Any code generated for the by-ref
// bindings which creates a reference to the by-move fields will happen before the
// by-move bindings move those fields and make them inaccessible.
variant.bindings_mut().sort_by_cached_key(|bi| bind_style_of_field(bi.ast()));
}
structure.bind_with(|_| synstructure::BindStyle::Move);
let variants = structure.each_variant(|variant| {
let span = match structure.ast().data {
syn::Data::Struct(..) => span,
@ -112,6 +103,7 @@ pub fn each_variant<'s, F>(&mut self, structure: &mut Structure<'s>, f: F) -> To
parent: &self,
span,
field_map: build_field_mapping(variant),
formatting_init: TokenStream::new(),
slug: None,
code: None,
};
@ -143,16 +135,14 @@ pub fn preamble<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
/// Generates calls to `span_label` and similar functions based on the attributes on fields or
/// calls to `set_arg` when no attributes are present.
///
/// Expects use of `Self::each_variant` which will have sorted bindings so that by-ref bindings
/// (which may create references to by-move bindings) have their code generated first -
/// necessary as code for suggestions uses formatting machinery and the value of other fields
/// (any given field can be referenced multiple times, so must be accessed through a borrow);
/// and when passing fields to `add_subdiagnostic` or `set_arg` for Fluent, fields must be
/// accessed by-move.
pub fn body<'s>(&mut self, variant: &VariantInfo<'s>) -> TokenStream {
let mut body = quote! {};
for binding in variant.bindings() {
// Generate `set_arg` calls first..
for binding in variant.bindings().iter().filter(|bi| should_generate_set_arg(bi.ast())) {
body.extend(self.generate_field_code(binding));
}
// ..and then subdiagnostic additions.
for binding in variant.bindings().iter().filter(|bi| !should_generate_set_arg(bi.ast())) {
body.extend(self.generate_field_attrs_code(binding));
}
body
@ -274,24 +264,27 @@ fn generate_structure_code_for_attr(
}
}
fn generate_field_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
let diag = &self.parent.diag;
let field = binding_info.ast();
let field_binding = &binding_info.binding;
let ident = field.ident.as_ref().unwrap();
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
quote! {
#diag.set_arg(
stringify!(#ident),
#field_binding
);
}
}
fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
let field = binding_info.ast();
let field_binding = &binding_info.binding;
if should_generate_set_arg(&field) {
let diag = &self.parent.diag;
let ident = field.ident.as_ref().unwrap();
// strip `r#` prefix, if present
let ident = format_ident!("{}", ident);
return quote! {
#diag.set_arg(
stringify!(#ident),
#field_binding
);
};
}
let needs_move = bind_style_of_field(&field).is_move();
let inner_ty = FieldInnerTy::from_type(&field.ty);
field
@ -304,10 +297,8 @@ fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> Token
let (binding, needs_destructure) = if needs_clone {
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
(quote! { #field_binding.clone() }, false)
} else if needs_move {
(quote! { #field_binding }, true)
} else {
(quote! { *#field_binding }, true)
(quote! { #field_binding }, true)
};
let generated_code = self
@ -340,18 +331,15 @@ fn generate_inner_field_code(
let diag = &self.parent.diag;
let meta = attr.parse_meta()?;
if let Meta::Path(_) = meta {
let ident = &attr.path.segments.last().unwrap().ident;
let name = ident.to_string();
let name = name.as_str();
match name {
"skip_arg" => {
// Don't need to do anything - by virtue of the attribute existing, the
// `set_arg` call will not be generated.
return Ok(quote! {});
}
"primary_span" => match self.parent.kind {
DiagnosticDeriveKind::Diagnostic => {
let ident = &attr.path.segments.last().unwrap().ident;
let name = ident.to_string();
match (&meta, name.as_str()) {
// Don't need to do anything - by virtue of the attribute existing, the
// `set_arg` call will not be generated.
(Meta::Path(_), "skip_arg") => return Ok(quote! {}),
(Meta::Path(_), "primary_span") => {
match self.parent.kind {
DiagnosticDeriveKind::Diagnostic { .. } => {
report_error_if_not_applied_to_span(attr, &info)?;
return Ok(quote! {
@ -363,10 +351,50 @@ fn generate_inner_field_code(
diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
})
}
},
"subdiagnostic" => return Ok(quote! { #diag.subdiagnostic(#binding); }),
_ => {}
}
}
(Meta::Path(_), "subdiagnostic") => {
return Ok(quote! { #diag.subdiagnostic(#binding); });
}
(Meta::NameValue(_), "subdiagnostic") => {
throw_invalid_attr!(attr, &meta, |diag| {
diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
})
}
(Meta::List(MetaList { ref nested, .. }), "subdiagnostic") => {
if nested.len() != 1 {
throw_invalid_attr!(attr, &meta, |diag| {
diag.help(
"`eager` is the only supported nested attribute for `subdiagnostic`",
)
})
}
let handler = match &self.parent.kind {
DiagnosticDeriveKind::Diagnostic { handler } => handler,
DiagnosticDeriveKind::LintDiagnostic => {
throw_invalid_attr!(attr, &meta, |diag| {
diag.help("eager subdiagnostics are not supported on lints")
})
}
};
let nested_attr = nested.first().expect("pop failed for single element list");
match nested_attr {
NestedMeta::Meta(meta @ Meta::Path(_))
if meta.path().segments.last().unwrap().ident.to_string().as_str()
== "eager" =>
{
return Ok(quote! { #diag.eager_subdiagnostic(#handler, #binding); });
}
_ => {
throw_invalid_nested_attr!(attr, nested_attr, |diag| {
diag.help("`eager` is the only supported nested attribute for `subdiagnostic`")
})
}
}
}
_ => (),
}
let (subdiag, slug) = self.parse_subdiag_attribute(attr)?;
@ -389,7 +417,8 @@ fn generate_inner_field_code(
SubdiagnosticKind::Suggestion {
suggestion_kind,
applicability: static_applicability,
code,
code_field,
code_init,
} => {
let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
@ -402,11 +431,12 @@ fn generate_inner_field_code(
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
let style = suggestion_kind.to_suggestion_style();
self.formatting_init.extend(code_init);
Ok(quote! {
#diag.span_suggestion_with_style(
#span_field,
rustc_errors::fluent::#slug,
#code,
#code_field,
#applicability,
#style
);
@ -451,7 +481,7 @@ fn span_and_applicability_of_ty(
// If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
let binding = &info.binding.binding;
Ok((quote!(*#binding), None))
Ok((quote!(#binding), None))
}
// If `ty` is `(Span, Applicability)` then return tokens accessing those.
Type::Tuple(tup) => {

View File

@ -9,7 +9,7 @@
pub(crate) use fluent::fluent_messages;
use proc_macro2::TokenStream;
use quote::format_ident;
use subdiagnostic::SubdiagnosticDerive;
use subdiagnostic::SubdiagnosticDeriveBuilder;
use synstructure::Structure;
/// Implements `#[derive(Diagnostic)]`, which allows for errors to be specified as a struct,
@ -155,5 +155,5 @@ pub fn lint_diagnostic_derive(s: Structure<'_>) -> TokenStream {
/// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident });
/// ```
pub fn session_subdiagnostic_derive(s: Structure<'_>) -> TokenStream {
SubdiagnosticDerive::new(s).into_tokens()
SubdiagnosticDeriveBuilder::new().into_tokens(s)
}

View File

@ -5,7 +5,7 @@
DiagnosticDeriveError,
};
use crate::diagnostics::utils::{
build_field_mapping, report_error_if_not_applied_to_applicability,
build_field_mapping, new_code_ident, report_error_if_not_applied_to_applicability,
report_error_if_not_applied_to_span, FieldInfo, FieldInnerTy, FieldMap, HasFieldMap, SetOnce,
SpannedOption, SubdiagnosticKind,
};
@ -15,19 +15,19 @@
use synstructure::{BindingInfo, Structure, VariantInfo};
/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
pub(crate) struct SubdiagnosticDerive<'a> {
structure: Structure<'a>,
pub(crate) struct SubdiagnosticDeriveBuilder {
diag: syn::Ident,
f: syn::Ident,
}
impl<'a> SubdiagnosticDerive<'a> {
pub(crate) fn new(structure: Structure<'a>) -> Self {
impl SubdiagnosticDeriveBuilder {
pub(crate) fn new() -> Self {
let diag = format_ident!("diag");
Self { structure, diag }
let f = format_ident!("f");
Self { diag, f }
}
pub(crate) fn into_tokens(self) -> TokenStream {
let SubdiagnosticDerive { mut structure, diag } = self;
pub(crate) fn into_tokens<'a>(self, mut structure: Structure<'a>) -> TokenStream {
let implementation = {
let ast = structure.ast();
let span = ast.span().unwrap();
@ -53,10 +53,11 @@ pub(crate) fn into_tokens(self) -> TokenStream {
structure.bind_with(|_| synstructure::BindStyle::Move);
let variants_ = structure.each_variant(|variant| {
let mut builder = SubdiagnosticDeriveBuilder {
diag: &diag,
let mut builder = SubdiagnosticDeriveVariantBuilder {
parent: &self,
variant,
span,
formatting_init: TokenStream::new(),
fields: build_field_mapping(variant),
span_field: None,
applicability: None,
@ -72,9 +73,17 @@ pub(crate) fn into_tokens(self) -> TokenStream {
}
};
let diag = &self.diag;
let f = &self.f;
let ret = structure.gen_impl(quote! {
gen impl rustc_errors::AddToDiagnostic for @Self {
fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
fn add_to_diagnostic_with<__F>(self, #diag: &mut rustc_errors::Diagnostic, #f: __F)
where
__F: Fn(
&mut rustc_errors::Diagnostic,
rustc_errors::SubdiagnosticMessage
) -> rustc_errors::SubdiagnosticMessage,
{
use rustc_errors::{Applicability, IntoDiagnosticArg};
#implementation
}
@ -88,15 +97,18 @@ fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
/// for the final generated method. This is a separate struct to `SubdiagnosticDerive`
/// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
/// double mut borrow later on.
struct SubdiagnosticDeriveBuilder<'a> {
struct SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
/// The identifier to use for the generated `DiagnosticBuilder` instance.
diag: &'a syn::Ident,
parent: &'parent SubdiagnosticDeriveBuilder,
/// Info for the current variant (or the type if not an enum).
variant: &'a VariantInfo<'a>,
/// Span for the entire type.
span: proc_macro::Span,
/// Initialization of format strings for code suggestions.
formatting_init: TokenStream,
/// Store a map of field name to its corresponding field. This is built on construction of the
/// derive builder.
fields: FieldMap,
@ -112,7 +124,7 @@ struct SubdiagnosticDeriveBuilder<'a> {
has_suggestion_parts: bool,
}
impl<'a> HasFieldMap for SubdiagnosticDeriveBuilder<'a> {
impl<'parent, 'a> HasFieldMap for SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
self.fields.get(field)
}
@ -156,7 +168,7 @@ fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self {
}
}
impl<'a> SubdiagnosticDeriveBuilder<'a> {
impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
let mut kind_slugs = vec![];
@ -187,7 +199,7 @@ fn generate_field_set_arg(&mut self, binding: &BindingInfo<'_>) -> TokenStream {
let ast = binding.ast();
assert_eq!(ast.attrs.len(), 0, "field with attribute used as diagnostic arg");
let diag = &self.diag;
let diag = &self.parent.diag;
let ident = ast.ident.as_ref().unwrap();
// strip `r#` prefix, if present
let ident = format_ident!("{}", ident);
@ -222,7 +234,7 @@ fn generate_field_attr_code(
};
let generated = self
.generate_field_code_inner(kind_stats, attr, info)
.generate_field_code_inner(kind_stats, attr, info, inner_ty.will_iterate())
.unwrap_or_else(|v| v.to_compile_error());
inner_ty.with(binding, generated)
@ -235,13 +247,18 @@ fn generate_field_code_inner(
kind_stats: KindsStatistics,
attr: &Attribute,
info: FieldInfo<'_>,
clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> {
let meta = attr.parse_meta()?;
match meta {
Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
Meta::List(list @ MetaList { .. }) => {
self.generate_field_code_inner_list(kind_stats, attr, info, list)
}
Meta::List(list @ MetaList { .. }) => self.generate_field_code_inner_list(
kind_stats,
attr,
info,
list,
clone_suggestion_code,
),
_ => throw_invalid_attr!(attr, &meta),
}
}
@ -345,6 +362,7 @@ fn generate_field_code_inner_list(
attr: &Attribute,
info: FieldInfo<'_>,
list: MetaList,
clone_suggestion_code: bool,
) -> Result<TokenStream, DiagnosticDeriveError> {
let span = attr.span().unwrap();
let ident = &list.path.segments.last().unwrap().ident;
@ -382,7 +400,8 @@ fn generate_field_code_inner_list(
match nested_name {
"code" => {
let formatted_str = self.build_format(&value.value(), value.span());
code.set_once(formatted_str, span);
let code_field = new_code_ident();
code.set_once((code_field, formatted_str), span);
}
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
diag.help("`code` is the only valid nested attribute")
@ -390,14 +409,20 @@ fn generate_field_code_inner_list(
}
}
let Some((code, _)) = code else {
let Some((code_field, formatted_str)) = code.value() else {
span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
.emit();
return Ok(quote! {});
};
let binding = info.binding;
Ok(quote! { suggestions.push((#binding, #code)); })
self.formatting_init.extend(quote! { let #code_field = #formatted_str; });
let code_field = if clone_suggestion_code {
quote! { #code_field.clone() }
} else {
quote! { #code_field }
};
Ok(quote! { suggestions.push((#binding, #code_field)); })
}
_ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
let mut span_attrs = vec![];
@ -442,13 +467,23 @@ pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
let span_field = self.span_field.value_ref();
let diag = &self.diag;
let diag = &self.parent.diag;
let f = &self.parent.f;
let mut calls = TokenStream::new();
for (kind, slug) in kind_slugs {
let message = format_ident!("__message");
calls.extend(quote! { let #message = #f(#diag, rustc_errors::fluent::#slug.into()); });
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
let message = quote! { rustc_errors::fluent::#slug };
let call = match kind {
SubdiagnosticKind::Suggestion { suggestion_kind, applicability, code } => {
SubdiagnosticKind::Suggestion {
suggestion_kind,
applicability,
code_init,
code_field,
} => {
self.formatting_init.extend(code_init);
let applicability = applicability
.value()
.map(|a| quote! { #a })
@ -457,8 +492,7 @@ pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
if let Some(span) = span_field {
let style = suggestion_kind.to_suggestion_style();
quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
quote! { #diag.#name(#span, #message, #code_field, #applicability, #style); }
} else {
span_err(self.span, "suggestion without `#[primary_span]` field").emit();
quote! { unreachable!(); }
@ -499,6 +533,7 @@ pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
}
}
};
calls.extend(call);
}
@ -510,11 +545,13 @@ pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
.map(|binding| self.generate_field_set_arg(binding))
.collect();
let formatting_init = &self.formatting_init;
Ok(quote! {
#init
#formatting_init
#attr_args
#calls
#plain_args
#calls
})
}
}

View File

@ -4,16 +4,29 @@
use proc_macro::Span;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use std::cmp::Ordering;
use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
use std::fmt;
use std::str::FromStr;
use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
use syn::{MetaList, MetaNameValue, NestedMeta, Path};
use synstructure::{BindStyle, BindingInfo, VariantInfo};
use synstructure::{BindingInfo, VariantInfo};
use super::error::invalid_nested_attr;
thread_local! {
pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
}
/// Returns an ident of the form `__code_N` where `N` is incremented once with every call.
pub(crate) fn new_code_ident() -> syn::Ident {
CODE_IDENT_COUNT.with(|count| {
let ident = format_ident!("__code_{}", *count.borrow());
*count.borrow_mut() += 1;
ident
})
}
/// Checks whether the type name of `ty` matches `name`.
///
/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
@ -142,6 +155,15 @@ pub(crate) fn from_type(ty: &'ty Type) -> Self {
unreachable!();
}
/// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e.
/// that cloning might be required for values moved in the loop body).
pub(crate) fn will_iterate(&self) -> bool {
match self {
FieldInnerTy::Vec(..) => true,
FieldInnerTy::Option(..) | FieldInnerTy::None => false,
}
}
/// Returns `Option` containing inner type if there is one.
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
match self {
@ -434,7 +456,12 @@ pub(super) enum SubdiagnosticKind {
Suggestion {
suggestion_kind: SuggestionKind,
applicability: SpannedOption<Applicability>,
code: TokenStream,
/// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation
/// of formatting and diagnostic emission so that `set_arg` calls can happen in-between..
code_field: syn::Ident,
/// Initialization logic for `code_field`'s variable, e.g.
/// `let __formatted_code = /* whatever */;`
code_init: TokenStream,
},
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
MultipartSuggestion {
@ -469,7 +496,8 @@ pub(super) fn from_attr(
SubdiagnosticKind::Suggestion {
suggestion_kind,
applicability: None,
code: TokenStream::new(),
code_field: new_code_ident(),
code_init: TokenStream::new(),
}
} else if let Some(suggestion_kind) =
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
@ -548,9 +576,10 @@ pub(super) fn from_attr(
};
match (nested_name, &mut kind) {
("code", SubdiagnosticKind::Suggestion { .. }) => {
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
let formatted_str = fields.build_format(&value.value(), value.span());
code.set_once(formatted_str, span);
let code_init = quote! { let #code_field = #formatted_str; };
code.set_once(code_init, span);
}
(
"applicability",
@ -582,13 +611,13 @@ pub(super) fn from_attr(
}
match kind {
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
*code_field = if let Some((code, _)) = code {
code
SubdiagnosticKind::Suggestion { ref code_field, ref mut code_init, .. } => {
*code_init = if let Some(init) = code.value() {
init
} else {
span_err(span, "suggestion without `code = \"...\"`").emit();
quote! { "" }
}
quote! { let #code_field: String = unreachable!(); }
};
}
SubdiagnosticKind::Label
| SubdiagnosticKind::Note
@ -620,65 +649,8 @@ fn span(&self) -> Option<proc_macro2::Span> {
}
}
/// Wrapper around `synstructure::BindStyle` which implements `Ord`.
#[derive(PartialEq, Eq)]
pub(super) struct OrderedBindStyle(pub(super) BindStyle);
impl OrderedBindStyle {
/// Is `BindStyle::Move` or `BindStyle::MoveMut`?
pub(super) fn is_move(&self) -> bool {
matches!(self.0, BindStyle::Move | BindStyle::MoveMut)
}
}
impl Ord for OrderedBindStyle {
fn cmp(&self, other: &Self) -> Ordering {
match (self.is_move(), other.is_move()) {
// If both `self` and `other` are the same, then ordering is equal.
(true, true) | (false, false) => Ordering::Equal,
// If `self` is not a move then it should be considered less than `other` (so that
// references are sorted first).
(false, _) => Ordering::Less,
// If `self` is a move then it must be greater than `other` (again, so that references
// are sorted first).
(true, _) => Ordering::Greater,
}
}
}
impl PartialOrd for OrderedBindStyle {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
/// call (like `span_label`).
pub(super) fn should_generate_set_arg(field: &Field) -> bool {
field.attrs.is_empty()
}
/// Returns `true` if `field` needs to have code generated in the by-move branch of the
/// generated derive rather than the by-ref branch.
pub(super) fn bind_style_of_field(field: &Field) -> OrderedBindStyle {
let generates_set_arg = should_generate_set_arg(field);
let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
// FIXME(davidtwco): better support for one field needing to be in the by-move and
// by-ref branches.
let is_subdiagnostic = field
.attrs
.iter()
.map(|attr| attr.path.segments.last().unwrap().ident.to_string())
.any(|attr| attr == "subdiagnostic");
// `set_arg` calls take their argument by-move..
let needs_move = generates_set_arg
// If this is a `MultiSpan` field then it needs to be moved to be used by any
// attribute..
|| is_multispan
// If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
// unlikely to be `Copy`..
|| is_subdiagnostic;
OrderedBindStyle(if needs_move { BindStyle::Move } else { BindStyle::Ref })
}

View File

@ -1,19 +1,15 @@
use rustc_errors::AddToDiagnostic;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_session::Limit;
use rustc_span::{Span, Symbol};
#[derive(Subdiagnostic)]
#[note(query_system::cycle_stack_middle)]
pub struct CycleStack {
#[primary_span]
pub span: Span,
pub desc: String,
}
impl AddToDiagnostic for CycleStack {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
diag.span_note(self.span, &format!("...which requires {}...", self.desc));
}
}
#[derive(Copy, Clone)]
pub enum HandleCycleError {
Error,
@ -53,7 +49,7 @@ pub struct Cycle {
#[primary_span]
pub span: Span,
pub stack_bottom: String,
#[subdiagnostic]
#[subdiagnostic(eager)]
pub cycle_stack: Vec<CycleStack>,
#[subdiagnostic]
pub stack_count: StackCount,

View File

@ -4,7 +4,7 @@
#![feature(min_specialization)]
#![feature(extern_types)]
#![allow(rustc::potential_query_instability)]
// #![deny(rustc::untranslatable_diagnostic)]
#![deny(rustc::untranslatable_diagnostic)]
#![deny(rustc::diagnostic_outside_of_impl)]
#[macro_use]

View File

@ -1,8 +1,9 @@
//! Validates syntax inside Rust code blocks (\`\`\`rust).
use rustc_data_structures::sync::{Lock, Lrc};
use rustc_errors::{
emitter::Emitter, translation::Translate, Applicability, Diagnostic, Handler,
LazyFallbackBundle,
emitter::Emitter,
translation::{to_fluent_args, Translate},
Applicability, Diagnostic, Handler, LazyFallbackBundle,
};
use rustc_parse::parse_stream_from_source_str;
use rustc_session::parse::ParseSess;
@ -193,7 +194,7 @@ impl Emitter for BufferEmitter {
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
let mut buffer = self.buffer.borrow_mut();
let fluent_args = self.to_fluent_args(diag.args());
let fluent_args = to_fluent_args(diag.args());
let translated_main_message = self.translate_message(&diag.message[0].0, &fluent_args);
buffer.messages.push(format!("error from rustc: {}", translated_main_message));

View File

@ -13,7 +13,7 @@
use rustc_errors::{
AddToDiagnostic, IntoDiagnostic, Diagnostic, DiagnosticBuilder,
ErrorGuaranteed, Handler, fluent
ErrorGuaranteed, Handler, fluent, SubdiagnosticMessage,
};
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::Span;
@ -52,7 +52,10 @@ fn into_diagnostic(self, handler: &'a Handler) -> DiagnosticBuilder<'a, ErrorGua
pub struct UntranslatableInAddToDiagnostic;
impl AddToDiagnostic for UntranslatableInAddToDiagnostic {
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
diag.note("untranslatable diagnostic");
//~^ ERROR diagnostics should be created using translatable messages
}
@ -61,7 +64,10 @@ fn add_to_diagnostic(self, diag: &mut Diagnostic) {
pub struct TranslatableInAddToDiagnostic;
impl AddToDiagnostic for TranslatableInAddToDiagnostic {
fn add_to_diagnostic(self, diag: &mut Diagnostic) {
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
where
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
{
diag.note(fluent::compiletest::note);
}
}

View File

@ -11,13 +11,13 @@ LL | #![deny(rustc::untranslatable_diagnostic)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: diagnostics should be created using translatable messages
--> $DIR/diagnostics.rs:56:14
--> $DIR/diagnostics.rs:59:14
|
LL | diag.note("untranslatable diagnostic");
| ^^^^
error: diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls
--> $DIR/diagnostics.rs:70:25
--> $DIR/diagnostics.rs:76:25
|
LL | let _diag = handler.struct_err(fluent::compiletest::example);
| ^^^^^^^^^^
@ -29,13 +29,13 @@ LL | #![deny(rustc::diagnostic_outside_of_impl)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls
--> $DIR/diagnostics.rs:73:25
--> $DIR/diagnostics.rs:79:25
|
LL | let _diag = handler.struct_err("untranslatable diagnostic");
| ^^^^^^^^^^
error: diagnostics should be created using translatable messages
--> $DIR/diagnostics.rs:73:25
--> $DIR/diagnostics.rs:79:25
|
LL | let _diag = handler.struct_err("untranslatable diagnostic");
| ^^^^^^^^^^

View File

@ -678,3 +678,74 @@ enum ExampleEnum {
struct RawIdentDiagnosticArg {
pub r#type: String,
}
#[derive(Diagnostic)]
#[diag(compiletest::example)]
struct SubdiagnosticBad {
#[subdiagnostic(bad)]
//~^ ERROR `#[subdiagnostic(bad)]` is not a valid attribute
note: Note,
}
#[derive(Diagnostic)]
#[diag(compiletest::example)]
struct SubdiagnosticBadStr {
#[subdiagnostic = "bad"]
//~^ ERROR `#[subdiagnostic = ...]` is not a valid attribute
note: Note,
}
#[derive(Diagnostic)]
#[diag(compiletest::example)]
struct SubdiagnosticBadTwice {
#[subdiagnostic(bad, bad)]
//~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute
note: Note,
}
#[derive(Diagnostic)]
#[diag(compiletest::example)]
struct SubdiagnosticBadLitStr {
#[subdiagnostic("bad")]
//~^ ERROR `#[subdiagnostic("...")]` is not a valid attribute
note: Note,
}
#[derive(LintDiagnostic)]
#[diag(compiletest::example)]
struct SubdiagnosticEagerLint {
#[subdiagnostic(eager)]
//~^ ERROR `#[subdiagnostic(...)]` is not a valid attribute
note: Note,
}
#[derive(Diagnostic)]
#[diag(compiletest::example)]
struct SubdiagnosticEagerCorrect {
#[subdiagnostic(eager)]
note: Note,
}
// Check that formatting of `correct` in suggestion doesn't move the binding for that field, making
// the `set_arg` call a compile error; and that isn't worked around by moving the `set_arg` call
// after the `span_suggestion` call - which breaks eager translation.
#[derive(Subdiagnostic)]
#[suggestion_short(
parser::use_instead,
applicability = "machine-applicable",
code = "{correct}"
)]
pub(crate) struct SubdiagnosticWithSuggestion {
#[primary_span]
span: Span,
invalid: String,
correct: String,
}
#[derive(Diagnostic)]
#[diag(compiletest::example)]
struct SubdiagnosticEagerSuggestion {
#[subdiagnostic(eager)]
sub: SubdiagnosticWithSuggestion,
}

View File

@ -533,6 +533,46 @@ LL | #[label]
|
= help: `#[label]` and `#[suggestion]` can only be applied to fields
error: `#[subdiagnostic(bad)]` is not a valid attribute
--> $DIR/diagnostic-derive.rs:685:21
|
LL | #[subdiagnostic(bad)]
| ^^^
|
= help: `eager` is the only supported nested attribute for `subdiagnostic`
error: `#[subdiagnostic = ...]` is not a valid attribute
--> $DIR/diagnostic-derive.rs:693:5
|
LL | #[subdiagnostic = "bad"]
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `eager` is the only supported nested attribute for `subdiagnostic`
error: `#[subdiagnostic(...)]` is not a valid attribute
--> $DIR/diagnostic-derive.rs:701:5
|
LL | #[subdiagnostic(bad, bad)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: `eager` is the only supported nested attribute for `subdiagnostic`
error: `#[subdiagnostic("...")]` is not a valid attribute
--> $DIR/diagnostic-derive.rs:709:21
|
LL | #[subdiagnostic("bad")]
| ^^^^^
|
= help: `eager` is the only supported nested attribute for `subdiagnostic`
error: `#[subdiagnostic(...)]` is not a valid attribute
--> $DIR/diagnostic-derive.rs:717:5
|
LL | #[subdiagnostic(eager)]
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: eager subdiagnostics are not supported on lints
error: cannot find attribute `nonsense` in this scope
--> $DIR/diagnostic-derive.rs:55:3
|
@ -607,7 +647,7 @@ LL | arg: impl IntoDiagnosticArg,
| ^^^^^^^^^^^^^^^^^ required by this bound in `DiagnosticBuilder::<'a, G>::set_arg`
= note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 75 previous errors
error: aborting due to 80 previous errors
Some errors have detailed explanations: E0277, E0425.
For more information about an error, try `rustc --explain E0277`.