Rollup merge of #142724 - xizheyin:avoid_overwrite_args, r=oli-obk

Add runtime check to avoid overwrite arg in `Diag`

## Origin PR description
At first, I set up a `debug_assert` check for the arg method to make sure that `args` in `Diag` aren't easily overwritten, and I added the `remove_arg()` method, so that if you do need to overwrite an arg, then you can explicitly call `remove_arg()` to remove it first, then call `arg()` to overwrite it.

For the code before the rust-lang/rust#142015 change, it won't compile because it will report an error
```
arg `instance`already exists.
```

This PR also modifies all diagnostics that fail the check to pass the check. There are two cases of check failure:

1. ~~Between *the parent diagnostic and the subdiagnostic*, or *between the subdiagnostics* have the same field between them. In this case, I renamed the conflicting fields.~~
2. ~~For subdiagnostics stored in `Vec`, the rendering may iteratively write the same arg over and over again. In this case, I changed the auto-generation with `derive(SubDiagnostic)` to manually implementing `SubDiagnostic` and manually rendered it with `eagerly_translate()`, similar to https://github.com/rust-lang/rust/issues/142031#issuecomment-2984812090, and after rendering it I manually deleted useless arg with the newly added `remove_arg` method.~~

## Final Decision

After trying and discussing, we made a final decision.

For `#[derive(Subdiagnostic)]`, This PR made two changes:

1. After the subdiagnostic is rendered, remove all args of this subdiagnostic, which allows for usage like `Vec<Subdiag>`.
2. Store `diag.args` before setting arguments, so that you can restore the contents of the main diagnostic after deleting the arguments after subdiagnostic is rendered, to avoid deleting the main diagnostic's arg when they have the same name args.
This commit is contained in:
Jana Dönszelmann
2025-06-25 22:14:55 +02:00
committed by GitHub
22 changed files with 119 additions and 39 deletions
@@ -1284,8 +1284,14 @@ fn explain_captures(
&& !spans.is_empty()
{
let mut span: MultiSpan = spans.clone().into();
err.arg("ty", param_ty.to_string());
let msg = err.dcx.eagerly_translate_to_string(
fluent::borrowck_moved_a_fn_once_in_call_def,
err.args.iter(),
);
err.remove_arg("ty");
for sp in spans {
span.push_span_label(sp, fluent::borrowck_moved_a_fn_once_in_call_def);
span.push_span_label(sp, msg.clone());
}
span.push_span_label(
fn_call_span,
@@ -660,6 +660,7 @@ impl Subdiagnostic for FormatUnusedArg {
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
diag.arg("named", self.named);
let msg = diag.eagerly_translate(crate::fluent_generated::builtin_macros_format_unused_arg);
diag.remove_arg("named");
diag.span_label(self.span, msg);
}
}
+3
View File
@@ -291,6 +291,9 @@ fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
span.push_span_label(self.span, fluent::const_eval_frame_note_last);
}
let msg = diag.eagerly_translate(fluent::const_eval_frame_note);
diag.remove_arg("times");
diag.remove_arg("where_");
diag.remove_arg("instance");
diag.span_note(span, msg);
}
}
+31 -1
View File
@@ -289,6 +289,9 @@ pub struct DiagInner {
pub suggestions: Suggestions,
pub args: DiagArgMap,
// This is used to store args and restore them after a subdiagnostic is rendered.
pub reserved_args: DiagArgMap,
/// 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
/// `span` if there is one. Otherwise, it is `DUMMY_SP`.
@@ -319,6 +322,7 @@ pub fn new_with_messages(level: Level, messages: Vec<(DiagMessage, Style)>) -> S
children: vec![],
suggestions: Suggestions::Enabled(vec![]),
args: Default::default(),
reserved_args: Default::default(),
sort_span: DUMMY_SP,
is_lint: None,
long_ty_path: None,
@@ -390,7 +394,27 @@ pub(crate) fn sub(
}
pub(crate) fn arg(&mut self, name: impl Into<DiagArgName>, arg: impl IntoDiagArg) {
self.args.insert(name.into(), arg.into_diag_arg(&mut self.long_ty_path));
let name = name.into();
let value = arg.into_diag_arg(&mut self.long_ty_path);
// This assertion is to avoid subdiagnostics overwriting an existing diagnostic arg.
debug_assert!(
!self.args.contains_key(&name) || self.args.get(&name) == Some(&value),
"arg {} already exists",
name
);
self.args.insert(name, value);
}
pub fn remove_arg(&mut self, name: &str) {
self.args.swap_remove(name);
}
pub fn store_args(&mut self) {
self.reserved_args = self.args.clone();
}
pub fn restore_args(&mut self) {
self.args = std::mem::take(&mut self.reserved_args);
}
/// Fields used for Hash, and PartialEq trait.
@@ -1423,6 +1447,12 @@ pub fn delay_as_bug(mut self) -> G::EmitResult {
self.downgrade_to_delayed_bug();
self.emit()
}
pub fn remove_arg(&mut self, name: &str) {
if let Some(diag) = self.diag.as_mut() {
diag.remove_arg(name);
}
}
}
/// Destructor bomb: every `Diag` must be consumed (emitted, cancelled, etc.)
@@ -127,6 +127,7 @@ pub(crate) enum AssocItemNotFoundSugg<'a> {
SimilarInOtherTrait {
#[primary_span]
span: Span,
trait_name: &'a str,
assoc_kind: &'static str,
suggested_name: Symbol,
},
@@ -309,6 +309,7 @@ pub(super) fn report_unresolved_assoc_item<I>(
// change the associated item.
err.sugg = Some(errors::AssocItemNotFoundSugg::SimilarInOtherTrait {
span: assoc_ident.span,
trait_name: &trait_name,
assoc_kind: assoc_kind_str,
suggested_name,
});
+3 -2
View File
@@ -482,7 +482,8 @@ fn add_command_line(&mut self) {
let name = lint_name.as_str();
let suggestion = RenamedLintSuggestion::WithoutSpan { replace };
let requested_level = RequestedLevel { level, lint_name };
let lint = RenamedLintFromCommandLine { name, suggestion, requested_level };
let lint =
RenamedLintFromCommandLine { name, replace, suggestion, requested_level };
self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint);
}
CheckLintNameResult::Removed(ref reason) => {
@@ -824,7 +825,7 @@ fn add(
RenamedLintSuggestion::WithSpan { suggestion: sp, replace };
let name =
tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name);
let lint = RenamedLint { name: name.as_str(), suggestion };
let lint = RenamedLint { name: name.as_str(), replace, suggestion };
self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint);
}
+2 -2
View File
@@ -422,12 +422,12 @@ fn build_mismatch_suggestion(
lifetime_name: &str,
infos: &[&Info<'_>],
) -> lints::MismatchedLifetimeSyntaxesSuggestion {
let lifetime_name = lifetime_name.to_owned();
let lifetime_name_sugg = lifetime_name.to_owned();
let suggestions = infos.iter().map(|info| info.suggestion(&lifetime_name)).collect();
lints::MismatchedLifetimeSyntaxesSuggestion::Explicit {
lifetime_name,
lifetime_name_sugg,
suggestions,
tool_only: false,
}
+6 -5
View File
@@ -1089,6 +1089,7 @@ pub(crate) struct DeprecatedLintNameFromCommandLine<'a> {
#[diag(lint_renamed_lint)]
pub(crate) struct RenamedLint<'a> {
pub name: &'a str,
pub replace: &'a str,
#[subdiagnostic]
pub suggestion: RenamedLintSuggestion<'a>,
}
@@ -1109,6 +1110,7 @@ pub(crate) enum RenamedLintSuggestion<'a> {
#[diag(lint_renamed_lint)]
pub(crate) struct RenamedLintFromCommandLine<'a> {
pub name: &'a str,
pub replace: &'a str,
#[subdiagnostic]
pub suggestion: RenamedLintSuggestion<'a>,
#[subdiagnostic]
@@ -3244,7 +3246,7 @@ pub(crate) enum MismatchedLifetimeSyntaxesSuggestion {
},
Explicit {
lifetime_name: String,
lifetime_name_sugg: String,
suggestions: Vec<(Span, String)>,
tool_only: bool,
},
@@ -3298,13 +3300,12 @@ fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
);
}
Explicit { lifetime_name, suggestions, tool_only } => {
diag.arg("lifetime_name", lifetime_name);
Explicit { lifetime_name_sugg, suggestions, tool_only } => {
diag.arg("lifetime_name_sugg", lifetime_name_sugg);
let msg = diag.eagerly_translate(
fluent::lint_mismatched_lifetime_syntaxes_suggestion_explicit,
);
diag.remove_arg("lifetime_name_sugg");
diag.multipart_suggestion_with_style(
msg,
suggestions,
@@ -220,7 +220,7 @@ fn identify_kind(
}
/// Generates the code for a field with no attributes.
fn generate_field_arg(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
fn generate_field_arg(&mut self, binding_info: &BindingInfo<'_>) -> (TokenStream, TokenStream) {
let diag = &self.parent.diag;
let field = binding_info.ast();
@@ -230,12 +230,16 @@ fn generate_field_arg(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream
let ident = field.ident.as_ref().unwrap();
let ident = format_ident!("{}", ident); // strip `r#` prefix, if present
quote! {
let args = quote! {
#diag.arg(
stringify!(#ident),
#field_binding
);
}
};
let remove_args = quote! {
#diag.remove_arg(stringify!(#ident));
};
(args, remove_args)
}
/// Generates the necessary code for all attributes on a field.
@@ -600,8 +604,13 @@ pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveErro
calls.extend(call);
}
let plain_args: TokenStream = self
let store_args = quote! {
#diag.store_args();
};
let restore_args = quote! {
#diag.restore_args();
};
let (plain_args, remove_args): (TokenStream, TokenStream) = self
.variant
.bindings()
.iter()
@@ -610,12 +619,23 @@ pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveErro
.collect();
let formatting_init = &self.formatting_init;
// For #[derive(Subdiagnostic)]
//
// - Store args of the main diagnostic for later restore.
// - add args of subdiagnostic.
// - Generate the calls, such as note, label, etc.
// - Remove the arguments for allowing Vec<Subdiagnostic> to be used.
// - Restore the arguments for allowing main and subdiagnostic share the same fields.
Ok(quote! {
#init
#formatting_init
#attr_args
#store_args
#plain_args
#calls
#remove_args
#restore_args
})
}
}
@@ -314,7 +314,7 @@ fn add_library(
crate_name: tcx.crate_name(cnum),
non_static_deps: unavailable_as_static
.drain(..)
.map(|cnum| NonStaticCrateDep { crate_name: tcx.crate_name(cnum) })
.map(|cnum| NonStaticCrateDep { crate_name_: tcx.crate_name(cnum) })
.collect(),
rustc_driver_help: linking_to_rustc_driver.then_some(RustcDriverHelp),
});
+2 -1
View File
@@ -45,7 +45,8 @@ pub struct CrateDepMultiple {
#[derive(Subdiagnostic)]
#[note(metadata_crate_dep_not_static)]
pub struct NonStaticCrateDep {
pub crate_name: Symbol,
/// It's different from `crate_name` in main Diagnostic.
pub crate_name_: Symbol,
}
#[derive(Subdiagnostic)]
+13 -4
View File
@@ -994,14 +994,15 @@ pub(crate) struct PatternNotCovered<'s, 'tcx> {
pub(crate) uncovered: Uncovered,
#[subdiagnostic]
pub(crate) inform: Option<Inform>,
#[label(mir_build_confused)]
pub(crate) interpreted_as_const: Option<Span>,
#[subdiagnostic]
pub(crate) interpreted_as_const_sugg: Option<InterpretedAsConst>,
pub(crate) interpreted_as_const: Option<InterpretedAsConst>,
#[subdiagnostic]
pub(crate) interpreted_as_const_sugg: Option<InterpretedAsConstSugg>,
#[subdiagnostic]
pub(crate) adt_defined_here: Option<AdtDefinedHere<'tcx>>,
#[note(mir_build_privately_uninhabited)]
pub(crate) witness_1_is_privately_uninhabited: bool,
pub(crate) witness_1: String,
#[note(mir_build_pattern_ty)]
pub(crate) _p: (),
pub(crate) pattern_ty: Ty<'tcx>,
@@ -1016,6 +1017,14 @@ pub(crate) struct PatternNotCovered<'s, 'tcx> {
#[note(mir_build_more_information)]
pub(crate) struct Inform;
#[derive(Subdiagnostic)]
#[label(mir_build_confused)]
pub(crate) struct InterpretedAsConst {
#[primary_span]
pub(crate) span: Span,
pub(crate) variable: String,
}
pub(crate) struct AdtDefinedHere<'tcx> {
pub(crate) adt_def_span: Span,
pub(crate) ty: Ty<'tcx>,
@@ -1046,7 +1055,7 @@ fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
applicability = "maybe-incorrect",
style = "verbose"
)]
pub(crate) struct InterpretedAsConst {
pub(crate) struct InterpretedAsConstSugg {
#[primary_span]
pub(crate) span: Span,
pub(crate) variable: String,
@@ -690,8 +690,8 @@ fn check_binding_is_irrefutable(
let span = self.tcx.def_span(def_id);
let variable = self.tcx.item_name(def_id).to_string();
// When we encounter a constant as the binding name, point at the `const` definition.
interpreted_as_const = Some(span);
interpreted_as_const_sugg = Some(InterpretedAsConst { span: pat.span, variable });
interpreted_as_const = Some(InterpretedAsConst { span, variable: variable.clone() });
interpreted_as_const_sugg = Some(InterpretedAsConstSugg { span: pat.span, variable });
} else if let PatKind::Constant { .. } = unpeeled_pat.kind
&& let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(pat.span)
{
@@ -743,6 +743,8 @@ fn check_binding_is_irrefutable(
false
};
let witness_1 = cx.print_witness_pat(witnesses.get(0).unwrap());
self.error = Err(self.tcx.dcx().emit_err(PatternNotCovered {
span: pat.span,
origin,
@@ -751,6 +753,7 @@ fn check_binding_is_irrefutable(
interpreted_as_const,
interpreted_as_const_sugg,
witness_1_is_privately_uninhabited,
witness_1,
_p: (),
pattern_ty,
let_suggestion,
@@ -516,8 +516,12 @@ struct LocalLabel<'a> {
/// A custom `Subdiagnostic` implementation so that the notes are delivered in a specific order
impl Subdiagnostic for LocalLabel<'_> {
fn add_to_diag<G: rustc_errors::EmissionGuarantee>(self, diag: &mut rustc_errors::Diag<'_, G>) {
// Becuase parent uses this field , we need to remove it delay before adding it.
diag.remove_arg("name");
diag.arg("name", self.name);
diag.remove_arg("is_generated_name");
diag.arg("is_generated_name", self.is_generated_name);
diag.remove_arg("is_dropped_first_edition_2024");
diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
let msg = diag.eagerly_translate(crate::fluent_generated::mir_transform_tail_expr_local);
diag.span_label(self.span, msg);
+1 -1
View File
@@ -294,7 +294,7 @@ passes_duplicate_lang_item_crate_depends =
.second_definition_path = second definition in `{$crate_name}` loaded from {$path}
passes_enum_variant_same_name =
it is impossible to refer to the {$descr} `{$dead_name}` because it is shadowed by this enum variant with the same name
it is impossible to refer to the {$dead_descr} `{$dead_name}` because it is shadowed by this enum variant with the same name
passes_export_name =
attribute should be applied to a free function, impl method or static
+1 -1
View File
@@ -1056,7 +1056,7 @@ fn get_parent_if_enum_variant<'tcx>(
maybe_enum.variants().iter().find(|i| i.name == dead_item.name)
{
Some(crate::errors::EnumVariantSameName {
descr: tcx.def_descr(dead_item.def_id.to_def_id()),
dead_descr: tcx.def_descr(dead_item.def_id.to_def_id()),
dead_name: dead_item.name,
variant_span: tcx.def_span(variant.def_id),
})
+2 -1
View File
@@ -1516,7 +1516,7 @@ pub(crate) struct EnumVariantSameName<'tcx> {
#[primary_span]
pub variant_span: Span,
pub dead_name: Symbol,
pub descr: &'tcx str,
pub dead_descr: &'tcx str,
}
#[derive(Subdiagnostic)]
@@ -1714,6 +1714,7 @@ fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
#[derive(LintDiagnostic)]
#[diag(passes_unused_variable_try_ignore)]
pub(crate) struct UnusedVarTryIgnore {
pub name: String,
#[subdiagnostic]
pub sugg: UnusedVarTryIgnoreSugg,
}
+1
View File
@@ -1744,6 +1744,7 @@ fn report_unused(
.map(|(_, pat_span, _)| *pat_span)
.collect::<Vec<_>>(),
errors::UnusedVarTryIgnore {
name: name.clone(),
sugg: errors::UnusedVarTryIgnoreSugg {
shorthands,
non_shorthands,
+6 -8
View File
@@ -256,22 +256,19 @@ pub(crate) fn report_conflict(
};
let label = match new_binding.is_import_user_facing() {
true => errors::NameDefinedMultipleTimeLabel::Reimported { span, name },
false => errors::NameDefinedMultipleTimeLabel::Redefined { span, name },
true => errors::NameDefinedMultipleTimeLabel::Reimported { span },
false => errors::NameDefinedMultipleTimeLabel::Redefined { span },
};
let old_binding_label =
(!old_binding.span.is_dummy() && old_binding.span != span).then(|| {
let span = self.tcx.sess.source_map().guess_head_span(old_binding.span);
match old_binding.is_import_user_facing() {
true => errors::NameDefinedMultipleTimeOldBindingLabel::Import {
span,
name,
old_kind,
},
true => {
errors::NameDefinedMultipleTimeOldBindingLabel::Import { span, old_kind }
}
false => errors::NameDefinedMultipleTimeOldBindingLabel::Definition {
span,
name,
old_kind,
},
}
@@ -281,6 +278,7 @@ pub(crate) fn report_conflict(
.dcx()
.create_err(errors::NameDefinedMultipleTime {
span,
name,
descr: ns.descr(),
container,
label,
+1 -4
View File
@@ -978,6 +978,7 @@ pub(crate) struct VariableNotInAllPatterns {
pub(crate) struct NameDefinedMultipleTime {
#[primary_span]
pub(crate) span: Span,
pub(crate) name: Symbol,
pub(crate) descr: &'static str,
pub(crate) container: &'static str,
#[subdiagnostic]
@@ -992,13 +993,11 @@ pub(crate) enum NameDefinedMultipleTimeLabel {
Reimported {
#[primary_span]
span: Span,
name: Symbol,
},
#[label(resolve_name_defined_multiple_time_redefined)]
Redefined {
#[primary_span]
span: Span,
name: Symbol,
},
}
@@ -1008,14 +1007,12 @@ pub(crate) enum NameDefinedMultipleTimeOldBindingLabel {
Import {
#[primary_span]
span: Span,
name: Symbol,
old_kind: &'static str,
},
#[label(resolve_name_defined_multiple_time_old_binding_definition)]
Definition {
#[primary_span]
span: Span,
name: Symbol,
old_kind: &'static str,
},
}
@@ -163,12 +163,14 @@ pub fn new<'tcx>(
impl Subdiagnostic for RegionExplanation<'_> {
fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
diag.store_args();
diag.arg("pref_kind", self.prefix);
diag.arg("suff_kind", self.suffix);
diag.arg("desc_kind", self.desc.kind);
diag.arg("desc_arg", self.desc.arg);
let msg = diag.eagerly_translate(fluent::trait_selection_region_explanation);
diag.restore_args();
if let Some(span) = self.desc.span {
diag.span_note(span, msg);
} else {