Skip to content

Commit 91e9881

Browse files
committed
Look for typos when reporting an unknown nightly feature
1 parent dfee0da commit 91e9881

File tree

8 files changed

+103
-27
lines changed

8 files changed

+103
-27
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use rustc_errors::{
1616
pluralize,
1717
};
1818
use rustc_session::errors::ExprParenthesesNeeded;
19-
use rustc_span::edit_distance::find_best_match_for_name;
2019
use rustc_span::source_map::Spanned;
2120
use rustc_span::symbol::used_keywords;
2221
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, SpanSnippetError, Symbol, kw, sym};
@@ -222,27 +221,24 @@ impl std::fmt::Display for UnaryFixity {
222221
style = "verbose"
223222
)]
224223
struct MisspelledKw {
224+
// We use a String here because `Symbol::into_diag_arg` calls `Symbol::to_ident_string`, which
225+
// prefix the keyword with a `r#` because it aims to print the symbol as an identifier.
225226
similar_kw: String,
226227
#[primary_span]
227228
span: Span,
228229
is_incorrect_case: bool,
229230
}
230231

231232
/// Checks if the given `lookup` identifier is similar to any keyword symbol in `candidates`.
233+
///
234+
/// This is a specialized version of [`Symbol::find_similar`] that constructs an error when a
235+
/// candidate is found.
232236
fn find_similar_kw(lookup: Ident, candidates: &[Symbol]) -> Option<MisspelledKw> {
233-
let lowercase = lookup.name.as_str().to_lowercase();
234-
let lowercase_sym = Symbol::intern(&lowercase);
235-
if candidates.contains(&lowercase_sym) {
236-
Some(MisspelledKw { similar_kw: lowercase, span: lookup.span, is_incorrect_case: true })
237-
} else if let Some(similar_sym) = find_best_match_for_name(candidates, lookup.name, None) {
238-
Some(MisspelledKw {
239-
similar_kw: similar_sym.to_string(),
240-
span: lookup.span,
241-
is_incorrect_case: false,
242-
})
243-
} else {
244-
None
245-
}
237+
lookup.name.find_similar(candidates).map(|(similar_kw, is_incorrect_case)| MisspelledKw {
238+
similar_kw: similar_kw.to_string(),
239+
is_incorrect_case,
240+
span: lookup.span,
241+
})
246242
}
247243

248244
struct MultiSugg {

compiler/rustc_passes/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,10 @@ passes_missing_panic_handler =
421421
passes_missing_stability_attr =
422422
{$descr} has missing stability attribute
423423
424+
passes_misspelled_feature =
425+
unknown feature `{$misspelled_name}`
426+
.suggestion = there is a feature with a similar name: `{$actual_name}`
427+
424428
passes_mixed_export_name_and_no_mangle = `{$no_mangle_attr}` attribute may not be used in combination with `{$export_name_attr}`
425429
.label = `{$no_mangle_attr}` is ignored
426430
.note = `{$export_name_attr}` takes precedence

compiler/rustc_passes/src/errors.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,17 @@ pub(crate) struct UnknownFeature {
11851185
pub feature: Symbol,
11861186
}
11871187

1188+
#[derive(Diagnostic)]
1189+
#[diag(passes_misspelled_feature, code = E0635)]
1190+
pub(crate) struct MisspelledFeature {
1191+
#[primary_span]
1192+
pub span: Span,
1193+
pub misspelled_name: Symbol,
1194+
pub actual_name: Symbol,
1195+
#[suggestion(style = "verbose", code = "{actual_name}", applicability = "maybe-incorrect")]
1196+
pub suggestion: Span,
1197+
}
1198+
11881199
#[derive(Diagnostic)]
11891200
#[diag(passes_unknown_feature_alias, code = E0635)]
11901201
pub(crate) struct RenamedFeature {

compiler/rustc_passes/src/stability.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::num::NonZero;
66
use rustc_ast_lowering::stability::extern_abi_stability;
77
use rustc_data_structures::fx::FxIndexMap;
88
use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet};
9-
use rustc_feature::{EnabledLangFeature, EnabledLibFeature};
9+
use rustc_feature::{EnabledLangFeature, EnabledLibFeature, UNSTABLE_LANG_FEATURES};
1010
use rustc_hir::attrs::{AttributeKind, DeprecatedSince};
1111
use rustc_hir::def::{DefKind, Res};
1212
use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalModDefId};
@@ -1062,11 +1062,13 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
10621062
// no unknown features, because the collection also does feature attribute validation.
10631063
let local_defined_features = tcx.lib_features(LOCAL_CRATE);
10641064
if !remaining_lib_features.is_empty() || !remaining_implications.is_empty() {
1065+
let crates = tcx.crates(());
1066+
10651067
// Loading the implications of all crates is unavoidable to be able to emit the partial
10661068
// stabilization diagnostic, but it can be avoided when there are no
10671069
// `remaining_lib_features`.
10681070
let mut all_implications = remaining_implications.clone();
1069-
for &cnum in tcx.crates(()) {
1071+
for &cnum in crates {
10701072
all_implications
10711073
.extend_unord(tcx.stability_implications(cnum).items().map(|(k, v)| (*k, *v)));
10721074
}
@@ -1079,7 +1081,7 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
10791081
&all_implications,
10801082
);
10811083

1082-
for &cnum in tcx.crates(()) {
1084+
for &cnum in crates {
10831085
if remaining_lib_features.is_empty() && remaining_implications.is_empty() {
10841086
break;
10851087
}
@@ -1091,10 +1093,37 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
10911093
&all_implications,
10921094
);
10931095
}
1094-
}
10951096

1096-
for (feature, span) in remaining_lib_features {
1097-
tcx.dcx().emit_err(errors::UnknownFeature { span, feature });
1097+
if !remaining_lib_features.is_empty() {
1098+
let lang_features =
1099+
UNSTABLE_LANG_FEATURES.iter().map(|feature| feature.name).collect::<Vec<_>>();
1100+
let lib_features = crates
1101+
.into_iter()
1102+
.flat_map(|&cnum| {
1103+
tcx.lib_features(cnum).stability.keys().copied().into_sorted_stable_ord()
1104+
})
1105+
.collect::<Vec<_>>();
1106+
1107+
let valid_feature_names = [lang_features, lib_features].concat();
1108+
1109+
for (feature, span) in remaining_lib_features {
1110+
let suggestion = feature.find_similar(&valid_feature_names);
1111+
match suggestion {
1112+
Some((actual_name, _)) => {
1113+
let misspelled_name = feature;
1114+
tcx.dcx().emit_err(errors::MisspelledFeature {
1115+
span,
1116+
misspelled_name,
1117+
actual_name,
1118+
suggestion: span,
1119+
});
1120+
}
1121+
None => {
1122+
tcx.dcx().emit_err(errors::UnknownFeature { span, feature });
1123+
}
1124+
}
1125+
}
1126+
}
10981127
}
10991128

11001129
for (&implied_by, &feature) in remaining_implications.to_sorted_stable_ord() {

compiler/rustc_span/src/symbol.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_data_structures::stable_hasher::{
1414
use rustc_data_structures::sync::Lock;
1515
use rustc_macros::{Decodable, Encodable, HashStable_Generic, symbols};
1616

17+
use crate::edit_distance::find_best_match_for_name;
1718
use crate::{DUMMY_SP, Edition, Span, with_session_globals};
1819

1920
#[cfg(test)]
@@ -2843,6 +2844,27 @@ impl Symbol {
28432844
// Avoid creating an empty identifier, because that asserts in debug builds.
28442845
if self == sym::empty { String::new() } else { Ident::with_dummy_span(self).to_string() }
28452846
}
2847+
2848+
/// Checks if `self` is similar to any symbol in `candidates`.
2849+
///
2850+
/// The returned boolean represents whether the candidate is the same symbol with a different
2851+
/// casing.
2852+
///
2853+
/// All the candidates are assumed to be lowercase.
2854+
pub fn find_similar(
2855+
self,
2856+
candidates: &[Symbol],
2857+
) -> Option<(Symbol, /* is incorrect case */ bool)> {
2858+
let lowercase = self.as_str().to_lowercase();
2859+
let lowercase_sym = Symbol::intern(&lowercase);
2860+
if candidates.contains(&lowercase_sym) {
2861+
Some((lowercase_sym, true))
2862+
} else if let Some(similar_sym) = find_best_match_for_name(candidates, self, None) {
2863+
Some((similar_sym, false))
2864+
} else {
2865+
None
2866+
}
2867+
}
28462868
}
28472869

28482870
impl fmt::Debug for Symbol {

src/bootstrap/src/core/config/config.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2395,11 +2395,11 @@ pub(crate) fn update_submodule<'a>(
23952395
.arg(relative_path)
23962396
.run_capture_stdout(dwn_ctx.exec_ctx)
23972397
.stdout();
2398-
2399-
let actual_hash = recorded
2400-
.split_whitespace()
2401-
.nth(2)
2402-
.unwrap_or_else(|| panic!("unexpected output `{recorded}`"));
2398+
2399+
let actual_hash = recorded
2400+
.split_whitespace()
2401+
.nth(2)
2402+
.unwrap_or_else(|| panic!("unexpected out put `{recorded}`"));
24032403

24042404
if actual_hash == checked_out_hash {
24052405
// already checked out
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
#![feature(
22
unknown_rust_feature,
33
//~^ ERROR unknown feature
4-
4+
55
// Typo for lang feature
66
associated_types_default,
77
//~^ ERROR unknown feature
8+
//~| HELP there is a feature with a similar name
89
910
// Typo for lib feature
1011
core_intrnisics,
1112
//~^ ERROR unknown feature
13+
//~| HELP there is a feature with a similar name
1214
)]
1315

1416
fn main() {}

tests/ui/feature-gates/unknown-feature.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,24 @@ error[E0635]: unknown feature `associated_types_default`
99
|
1010
LL | associated_types_default,
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^
12+
|
13+
help: there is a feature with a similar name: `associated_type_defaults`
14+
|
15+
LL - associated_types_default,
16+
LL + associated_type_defaults,
17+
|
1218

1319
error[E0635]: unknown feature `core_intrnisics`
14-
--> $DIR/unknown-feature.rs:10:5
20+
--> $DIR/unknown-feature.rs:11:5
1521
|
1622
LL | core_intrnisics,
1723
| ^^^^^^^^^^^^^^^
24+
|
25+
help: there is a feature with a similar name: `core_intrinsics`
26+
|
27+
LL - core_intrnisics,
28+
LL + core_intrinsics,
29+
|
1830

1931
error: aborting due to 3 previous errors
2032

0 commit comments

Comments
 (0)