Skip to content

Commit 09b255d

Browse files
authored
Rollup merge of #130116 - veera-sivarajan:freeze-suggestions, r=chenyukang
Implement a Method to Seal `DiagInner`'s Suggestions This PR adds a method on `DiagInner` called `.seal_suggestions()` to prevent new suggestions from being added while preserving existing suggestions. This is useful because currently there is no way to prevent new suggestions from being added to a diagnostic. `.disable_suggestions()` is the closest but it gets rid of all suggestions before and after the call. Therefore, `.seal_suggestions()` can be used when, for example, misspelled keyword is detected and reported. In such cases, we may want to prevent other suggestions from being added to the diagnostic, as they would likely be meaningless once the misspelled keyword is identified. For context: #129899 (comment) To store an additional state, the type of the `suggestions` field in `DiagInner` was changed into a three variant enum. While this change affects files across different crates, care was taken to preserve the existing code's semantics. This is validated by the fact that all UI tests pass without any modifications. r? chenyukang
2 parents f7b4c72 + 7410057 commit 09b255d

File tree

13 files changed

+102
-53
lines changed

13 files changed

+102
-53
lines changed

compiler/rustc_codegen_ssa/src/back/write.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_errors::emitter::Emitter;
1616
use rustc_errors::translation::Translate;
1717
use rustc_errors::{
1818
Diag, DiagArgMap, DiagCtxt, DiagMessage, ErrCode, FatalError, FluentBundle, Level, MultiSpan,
19-
Style,
19+
Style, Suggestions,
2020
};
2121
use rustc_fs_util::link_or_copy;
2222
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
@@ -1903,7 +1903,7 @@ impl Emitter for SharedEmitter {
19031903
// Check that we aren't missing anything interesting when converting to
19041904
// the cut-down local `DiagInner`.
19051905
assert_eq!(diag.span, MultiSpan::new());
1906-
assert_eq!(diag.suggestions, Ok(vec![]));
1906+
assert_eq!(diag.suggestions, Suggestions::Enabled(vec![]));
19071907
assert_eq!(diag.sort_span, rustc_span::DUMMY_SP);
19081908
assert_eq!(diag.is_lint, None);
19091909
// No sensible check for `diag.emitted_at`.

compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl Emitter for AnnotateSnippetEmitter {
4848
fn emit_diagnostic(&mut self, mut diag: DiagInner) {
4949
let fluent_args = to_fluent_args(diag.args.iter());
5050

51-
let mut suggestions = diag.suggestions.unwrap_or(vec![]);
51+
let mut suggestions = diag.suggestions.unwrap_tag();
5252
self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
5353

5454
self.fix_multispans_in_extern_macros_and_render_macro_backtrace(

compiler/rustc_errors/src/diagnostic.rs

+24-12
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@ use crate::snippet::Style;
1919
use crate::{
2020
CodeSuggestion, DiagCtxtHandle, DiagMessage, ErrCode, ErrorGuaranteed, ExplicitBug, Level,
2121
MultiSpan, StashKey, SubdiagMessage, Substitution, SubstitutionPart, SuggestionStyle,
22+
Suggestions,
2223
};
2324

24-
/// Error type for `DiagInner`'s `suggestions` field, indicating that
25-
/// `.disable_suggestions()` was called on the `DiagInner`.
26-
#[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
27-
pub struct SuggestionsDisabled;
28-
2925
/// Simplified version of `FluentArg` that can implement `Encodable` and `Decodable`. Collection of
3026
/// `DiagArg` are converted to `FluentArgs` (consuming the collection) at the start of diagnostic
3127
/// emission.
@@ -296,7 +292,7 @@ pub struct DiagInner {
296292
pub code: Option<ErrCode>,
297293
pub span: MultiSpan,
298294
pub children: Vec<Subdiag>,
299-
pub suggestions: Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
295+
pub suggestions: Suggestions,
300296
pub args: DiagArgMap,
301297

302298
/// This is not used for highlighting or rendering any error message. Rather, it can be used
@@ -325,7 +321,7 @@ impl DiagInner {
325321
code: None,
326322
span: MultiSpan::new(),
327323
children: vec![],
328-
suggestions: Ok(vec![]),
324+
suggestions: Suggestions::Enabled(vec![]),
329325
args: Default::default(),
330326
sort_span: DUMMY_SP,
331327
is_lint: None,
@@ -409,7 +405,7 @@ impl DiagInner {
409405
&Option<ErrCode>,
410406
&MultiSpan,
411407
&[Subdiag],
412-
&Result<Vec<CodeSuggestion>, SuggestionsDisabled>,
408+
&Suggestions,
413409
Vec<(&DiagArgName, &DiagArgValue)>,
414410
&Option<IsLint>,
415411
) {
@@ -823,16 +819,32 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
823819
self
824820
}
825821

826-
/// Disallow attaching suggestions this diagnostic.
822+
/// Disallow attaching suggestions to this diagnostic.
827823
/// Any suggestions attached e.g. with the `span_suggestion_*` methods
828824
/// (before and after the call to `disable_suggestions`) will be ignored.
829825
#[rustc_lint_diagnostics]
830826
pub fn disable_suggestions(&mut self) -> &mut Self {
831-
self.suggestions = Err(SuggestionsDisabled);
827+
self.suggestions = Suggestions::Disabled;
832828
self
833829
}
834830

835-
/// Helper for pushing to `self.suggestions`, if available (not disable).
831+
/// Prevent new suggestions from being added to this diagnostic.
832+
///
833+
/// Suggestions added before the call to `.seal_suggestions()` will be preserved
834+
/// and new suggestions will be ignored.
835+
#[rustc_lint_diagnostics]
836+
pub fn seal_suggestions(&mut self) -> &mut Self {
837+
if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
838+
let suggestions_slice = std::mem::take(suggestions).into_boxed_slice();
839+
self.suggestions = Suggestions::Sealed(suggestions_slice);
840+
}
841+
self
842+
}
843+
844+
/// Helper for pushing to `self.suggestions`.
845+
///
846+
/// A new suggestion is added if suggestions are enabled for this diagnostic.
847+
/// Otherwise, they are ignored.
836848
#[rustc_lint_diagnostics]
837849
fn push_suggestion(&mut self, suggestion: CodeSuggestion) {
838850
for subst in &suggestion.substitutions {
@@ -846,7 +858,7 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
846858
}
847859
}
848860

849-
if let Ok(suggestions) = &mut self.suggestions {
861+
if let Suggestions::Enabled(suggestions) = &mut self.suggestions {
850862
suggestions.push(suggestion);
851863
}
852864
}

compiler/rustc_errors/src/emitter.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ impl Emitter for HumanEmitter {
498498
fn emit_diagnostic(&mut self, mut diag: DiagInner) {
499499
let fluent_args = to_fluent_args(diag.args.iter());
500500

501-
let mut suggestions = diag.suggestions.unwrap_or(vec![]);
501+
let mut suggestions = diag.suggestions.unwrap_tag();
502502
self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
503503

504504
self.fix_multispans_in_extern_macros_and_render_macro_backtrace(

compiler/rustc_errors/src/json.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ use crate::emitter::{
3333
use crate::registry::Registry;
3434
use crate::translation::{to_fluent_args, Translate};
3535
use crate::{
36-
CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, TerminalUrl,
36+
CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, Suggestions,
37+
TerminalUrl,
3738
};
3839

3940
#[cfg(test)]
@@ -292,7 +293,7 @@ impl Diagnostic {
292293
/// Converts from `rustc_errors::DiagInner` to `Diagnostic`.
293294
fn from_errors_diagnostic(diag: crate::DiagInner, je: &JsonEmitter) -> Diagnostic {
294295
let args = to_fluent_args(diag.args.iter());
295-
let sugg = diag.suggestions.iter().flatten().map(|sugg| {
296+
let sugg_to_diag = |sugg: &CodeSuggestion| {
296297
let translated_message =
297298
je.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
298299
Diagnostic {
@@ -303,7 +304,12 @@ impl Diagnostic {
303304
children: vec![],
304305
rendered: None,
305306
}
306-
});
307+
};
308+
let sugg = match &diag.suggestions {
309+
Suggestions::Enabled(suggestions) => suggestions.iter().map(sugg_to_diag),
310+
Suggestions::Sealed(suggestions) => suggestions.iter().map(sugg_to_diag),
311+
Suggestions::Disabled => [].iter().map(sugg_to_diag),
312+
};
307313

308314
// generate regular command line output and store it in the json
309315

compiler/rustc_errors/src/lib.rs

+35
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,41 @@ impl SuggestionStyle {
126126
}
127127
}
128128

129+
/// Represents the help messages seen on a diagnostic.
130+
#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
131+
pub enum Suggestions {
132+
/// Indicates that new suggestions can be added or removed from this diagnostic.
133+
///
134+
/// `DiagInner`'s new_* methods initialize the `suggestions` field with
135+
/// this variant. Also, this is the default variant for `Suggestions`.
136+
Enabled(Vec<CodeSuggestion>),
137+
/// Indicates that suggestions cannot be added or removed from this diagnostic.
138+
///
139+
/// Gets toggled when `.seal_suggestions()` is called on the `DiagInner`.
140+
Sealed(Box<[CodeSuggestion]>),
141+
/// Indicates that no suggestion is available for this diagnostic.
142+
///
143+
/// Gets toggled when `.disable_suggestions()` is called on the `DiagInner`.
144+
Disabled,
145+
}
146+
147+
impl Suggestions {
148+
/// Returns the underlying list of suggestions.
149+
pub fn unwrap_tag(self) -> Vec<CodeSuggestion> {
150+
match self {
151+
Suggestions::Enabled(suggestions) => suggestions,
152+
Suggestions::Sealed(suggestions) => suggestions.into_vec(),
153+
Suggestions::Disabled => Vec::new(),
154+
}
155+
}
156+
}
157+
158+
impl Default for Suggestions {
159+
fn default() -> Self {
160+
Self::Enabled(vec![])
161+
}
162+
}
163+
129164
#[derive(Clone, Debug, PartialEq, Hash, Encodable, Decodable)]
130165
pub struct CodeSuggestion {
131166
/// Each substitute can have multiple variants due to multiple

compiler/rustc_hir_analysis/src/collect/type_of.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::ops::ControlFlow;
22

3-
use rustc_errors::{Applicability, StashKey};
3+
use rustc_errors::{Applicability, StashKey, Suggestions};
44
use rustc_hir as hir;
55
use rustc_hir::def_id::{DefId, LocalDefId};
66
use rustc_hir::HirId;
@@ -670,7 +670,7 @@ fn infer_placeholder_type<'tcx>(
670670

671671
// The parser provided a sub-optimal `HasPlaceholders` suggestion for the type.
672672
// We are typeck and have the real type, so remove that and suggest the actual type.
673-
if let Ok(suggestions) = &mut err.suggestions {
673+
if let Suggestions::Enabled(suggestions) = &mut err.suggestions {
674674
suggestions.clear();
675675
}
676676

compiler/rustc_parse/src/parser/diagnostics.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_ast_pretty::pprust;
1616
use rustc_data_structures::fx::FxHashSet;
1717
use rustc_errors::{
1818
pluralize, Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, FatalError, PErr, PResult,
19-
Subdiagnostic,
19+
Subdiagnostic, Suggestions,
2020
};
2121
use rustc_session::errors::ExprParenthesesNeeded;
2222
use rustc_span::edit_distance::find_best_match_for_name;
@@ -775,7 +775,7 @@ impl<'a> Parser<'a> {
775775
}
776776

777777
// Check for misspelled keywords if there are no suggestions added to the diagnostic.
778-
if err.suggestions.as_ref().is_ok_and(|code_suggestions| code_suggestions.is_empty()) {
778+
if matches!(&err.suggestions, Suggestions::Enabled(list) if list.is_empty()) {
779779
self.check_for_misspelled_kw(&mut err, &expected);
780780
}
781781
Err(err)
@@ -803,6 +803,9 @@ impl<'a> Parser<'a> {
803803
&& let Some(misspelled_kw) = find_similar_kw(curr_ident, &expected_keywords)
804804
{
805805
err.subdiagnostic(misspelled_kw);
806+
// We don't want other suggestions to be added as they are most likely meaningless
807+
// when there is a misspelled keyword.
808+
err.seal_suggestions();
806809
} else if let Some((prev_ident, _)) = self.prev_token.ident()
807810
&& !prev_ident.is_used_keyword()
808811
{
@@ -818,6 +821,9 @@ impl<'a> Parser<'a> {
818821
// positives like suggesting keyword `for` for `extern crate foo {}`.
819822
if let Some(misspelled_kw) = find_similar_kw(prev_ident, &all_keywords) {
820823
err.subdiagnostic(misspelled_kw);
824+
// We don't want other suggestions to be added as they are most likely meaningless
825+
// when there is a misspelled keyword.
826+
err.seal_suggestions();
821827
}
822828
}
823829
}

compiler/rustc_resolve/src/late.rs

+17-11
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_ast::visit::{visit_opt, walk_list, AssocCtxt, BoundKind, FnCtxt, FnKin
1717
use rustc_ast::*;
1818
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
1919
use rustc_errors::codes::*;
20-
use rustc_errors::{Applicability, DiagArgValue, IntoDiagArg, StashKey};
20+
use rustc_errors::{Applicability, DiagArgValue, IntoDiagArg, StashKey, Suggestions};
2121
use rustc_hir::def::Namespace::{self, *};
2222
use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS};
2323
use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE};
@@ -4085,17 +4085,23 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
40854085
err.sort_span = parent_err.sort_span;
40864086
err.is_lint = parent_err.is_lint.clone();
40874087

4088-
// merge the parent's suggestions with the typo suggestions
4089-
fn append_result<T, E>(res1: &mut Result<Vec<T>, E>, res2: Result<Vec<T>, E>) {
4090-
match res1 {
4091-
Ok(vec1) => match res2 {
4092-
Ok(mut vec2) => vec1.append(&mut vec2),
4093-
Err(e) => *res1 = Err(e),
4094-
},
4095-
Err(_) => (),
4096-
};
4088+
// merge the parent_err's suggestions with the typo (err's) suggestions
4089+
match &mut err.suggestions {
4090+
Suggestions::Enabled(typo_suggestions) => match &mut parent_err.suggestions {
4091+
Suggestions::Enabled(parent_suggestions) => {
4092+
// If both suggestions are enabled, append parent_err's suggestions to err's suggestions.
4093+
typo_suggestions.append(parent_suggestions)
4094+
}
4095+
Suggestions::Sealed(_) | Suggestions::Disabled => {
4096+
// If the parent's suggestions are either sealed or disabled, it signifies that
4097+
// new suggestions cannot be added or removed from the diagnostic. Therefore,
4098+
// we assign both types of suggestions to err's suggestions and discard the
4099+
// existing suggestions in err.
4100+
err.suggestions = std::mem::take(&mut parent_err.suggestions);
4101+
}
4102+
},
4103+
Suggestions::Sealed(_) | Suggestions::Disabled => (),
40974104
}
4098-
append_result(&mut err.suggestions, parent_err.suggestions.clone());
40994105

41004106
parent_err.cancel();
41014107

compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rustc_data_structures::unord::UnordSet;
66
use rustc_errors::codes::*;
77
use rustc_errors::{
88
pluralize, struct_span_code_err, Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey,
9-
StringPart,
9+
StringPart, Suggestions,
1010
};
1111
use rustc_hir::def::Namespace;
1212
use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE};
@@ -2137,8 +2137,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
21372137
if let Some(span) = err.span.primary_span()
21382138
&& let Some(mut diag) =
21392139
self.dcx().steal_non_err(span, StashKey::AssociatedTypeSuggestion)
2140-
&& let Ok(ref mut s1) = err.suggestions
2141-
&& let Ok(ref mut s2) = diag.suggestions
2140+
&& let Suggestions::Enabled(ref mut s1) = err.suggestions
2141+
&& let Suggestions::Enabled(ref mut s2) = diag.suggestions
21422142
{
21432143
s1.append(s2);
21442144
diag.cancel()

tests/ui/parser/issues/issue-70549-resolve-after-recovered-self-ctor.stderr

-8
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ help: there is a keyword `mut` with a similar name
1414
|
1515
LL | fn foo(&mut Self) {}
1616
| ~~~
17-
help: declare the type after the parameter binding
18-
|
19-
LL | fn foo(<identifier>: <type>) {}
20-
| ~~~~~~~~~~~~~~~~~~~~
2117

2218
error: unexpected lifetime `'static` in pattern
2319
--> $DIR/issue-70549-resolve-after-recovered-self-ctor.rs:8:13
@@ -47,10 +43,6 @@ help: there is a keyword `mut` with a similar name
4743
|
4844
LL | fn bar(&'static mut Self) {}
4945
| ~~~
50-
help: declare the type after the parameter binding
51-
|
52-
LL | fn bar(<identifier>: <type>) {}
53-
| ~~~~~~~~~~~~~~~~~~~~
5446

5547
error: expected one of `:`, `@`, or `|`, found keyword `Self`
5648
--> $DIR/issue-70549-resolve-after-recovered-self-ctor.rs:14:17

tests/ui/parser/misspelled-keywords/impl-trait.stderr

-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ help: there is a keyword `impl` with a similar name
88
|
99
LL | fn code<T: impl Debug>() -> u8 {}
1010
| ~~~~
11-
help: you might have meant to end the type parameters here
12-
|
13-
LL | fn code<T: impll> Debug>() -> u8 {}
14-
| +
1511

1612
error: aborting due to 1 previous error
1713

tests/ui/parser/misspelled-keywords/ref.stderr

-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ help: there is a keyword `ref` with a similar name
88
|
99
LL | Some(ref list) => println!("{list:?}"),
1010
| ~~~
11-
help: missing `,`
12-
|
13-
LL | Some(refe, list) => println!("{list:?}"),
14-
| +
1511

1612
error[E0023]: this pattern has 2 fields, but the corresponding tuple variant has 1 field
1713
--> $DIR/ref.rs:4:14

0 commit comments

Comments
 (0)