diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index bd495f6ec1acb..0809f5467f813 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -16,7 +16,6 @@ use rustc_errors::{ pluralize, }; use rustc_session::errors::ExprParenthesesNeeded; -use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::source_map::Spanned; use rustc_span::symbol::used_keywords; use rustc_span::{BytePos, DUMMY_SP, Ident, Span, SpanSnippetError, Symbol, kw, sym}; @@ -229,20 +228,15 @@ struct MisspelledKw { } /// Checks if the given `lookup` identifier is similar to any keyword symbol in `candidates`. +/// +/// This is a specialized version of [`Symbol::find_similar`] that constructs an error when a +/// candidate is found. fn find_similar_kw(lookup: Ident, candidates: &[Symbol]) -> Option { - let lowercase = lookup.name.as_str().to_lowercase(); - let lowercase_sym = Symbol::intern(&lowercase); - if candidates.contains(&lowercase_sym) { - Some(MisspelledKw { similar_kw: lowercase, span: lookup.span, is_incorrect_case: true }) - } else if let Some(similar_sym) = find_best_match_for_name(candidates, lookup.name, None) { - Some(MisspelledKw { - similar_kw: similar_sym.to_string(), - span: lookup.span, - is_incorrect_case: false, - }) - } else { - None - } + lookup.name.find_similar(candidates).map(|(symbol, is_incorrect_case)| MisspelledKw { + similar_kw: symbol.to_string(), + is_incorrect_case, + span: lookup.span, + }) } struct MultiSugg { diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 91a20a12b6334..49edd508f1ede 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -420,6 +420,10 @@ passes_missing_panic_handler = passes_missing_stability_attr = {$descr} has missing stability attribute +passes_misspelled_feature = + unknown feature `{$misspelled_name}` + .suggestion = there is a feature with a similar name: `{$actual_name}` + passes_mixed_export_name_and_no_mangle = `{$no_mangle_attr}` attribute may not be used in combination with `{$export_name_attr}` .label = `{$no_mangle_attr}` is ignored .note = `{$export_name_attr}` takes precedence diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index e4826cccd32f8..b6b853ba3bc99 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1185,6 +1185,17 @@ pub(crate) struct UnknownFeature { pub feature: Symbol, } +#[derive(Diagnostic)] +#[diag(passes_misspelled_feature, code = E0635)] +pub(crate) struct MisspelledFeature { + #[primary_span] + pub span: Span, + pub misspelled_name: Symbol, + pub actual_name: Symbol, + #[suggestion(style = "verbose", code = "{actual_name}", applicability = "maybe-incorrect")] + pub suggestion: Span, +} + #[derive(Diagnostic)] #[diag(passes_unknown_feature_alias, code = E0635)] pub(crate) struct RenamedFeature { diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 39830db2b11db..d1179dc5939e1 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -6,7 +6,7 @@ use std::num::NonZero; use rustc_ast_lowering::stability::extern_abi_stability; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet}; -use rustc_feature::{EnabledLangFeature, EnabledLibFeature}; +use rustc_feature::{EnabledLangFeature, EnabledLibFeature, UNSTABLE_LANG_FEATURES}; use rustc_hir::attrs::{AttributeKind, DeprecatedSince}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalModDefId}; @@ -1093,8 +1093,36 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { } } - for (feature, span) in remaining_lib_features { - tcx.dcx().emit_err(errors::UnknownFeature { span, feature }); + if !remaining_lib_features.is_empty() { + let lang_features = + UNSTABLE_LANG_FEATURES.iter().map(|feature| feature.name).collect::>(); + let lib_features = tcx + .crates(()) + .into_iter() + .flat_map(|&cnum| { + tcx.lib_features(cnum).stability.keys().copied().into_sorted_stable_ord() + }) + .collect::>(); + + let valid_feature_names = [lang_features, lib_features].concat(); + + for (feature, span) in remaining_lib_features { + let suggestion = feature.find_similar(&valid_feature_names); + match suggestion { + Some((actual_name, _)) => { + let misspelled_name = feature; + tcx.dcx().emit_err(errors::MisspelledFeature { + span, + misspelled_name, + actual_name, + suggestion: span, + }); + } + None => { + tcx.dcx().emit_err(errors::UnknownFeature { span, feature }); + } + } + } } for (&implied_by, &feature) in remaining_implications.to_sorted_stable_ord() { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7e513160de0c3..171539b42a6e2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -14,6 +14,7 @@ use rustc_data_structures::stable_hasher::{ use rustc_data_structures::sync::Lock; use rustc_macros::{Decodable, Encodable, HashStable_Generic, symbols}; +use crate::edit_distance::find_best_match_for_name; use crate::{DUMMY_SP, Edition, Span, with_session_globals}; #[cfg(test)] @@ -2843,6 +2844,27 @@ impl Symbol { // Avoid creating an empty identifier, because that asserts in debug builds. if self == sym::empty { String::new() } else { Ident::with_dummy_span(self).to_string() } } + + /// Checks if `self` is similar to any symbol in `candidates`. + /// + /// The returned boolean represents whether the candidate is the same symbol with a different + /// casing. + /// + /// All the candidates are assumed to be lowercase. + pub fn find_similar( + self, + candidates: &[Symbol], + ) -> Option<(Symbol, /* is incorrect case */ bool)> { + let lowercase = self.as_str().to_lowercase(); + let lowercase_sym = Symbol::intern(&lowercase); + if candidates.contains(&lowercase_sym) { + Some((lowercase_sym, true)) + } else if let Some(similar_sym) = find_best_match_for_name(candidates, self, None) { + Some((similar_sym, false)) + } else { + None + } + } } impl fmt::Debug for Symbol { diff --git a/tests/ui/feature-gates/unknown-feature.rs b/tests/ui/feature-gates/unknown-feature.rs index 20fd932d4c2f6..1b55f95a5aeab 100644 --- a/tests/ui/feature-gates/unknown-feature.rs +++ b/tests/ui/feature-gates/unknown-feature.rs @@ -1,3 +1,15 @@ -#![feature(unknown_rust_feature)] //~ ERROR unknown feature +#![feature( + unknown_rust_feature, //~ ERROR unknown feature + + // Typo for lang feature + associated_types_default, + //~^ ERROR unknown feature + //~| HELP there is a feature with a similar name + + // Typo for lib feature + core_intrnisics, + //~^ ERROR unknown feature + //~| HELP there is a feature with a similar name +)] fn main() {} diff --git a/tests/ui/feature-gates/unknown-feature.stderr b/tests/ui/feature-gates/unknown-feature.stderr index 0a731ddcc0b62..50a9f6d560eb2 100644 --- a/tests/ui/feature-gates/unknown-feature.stderr +++ b/tests/ui/feature-gates/unknown-feature.stderr @@ -1,9 +1,33 @@ error[E0635]: unknown feature `unknown_rust_feature` - --> $DIR/unknown-feature.rs:1:12 + --> $DIR/unknown-feature.rs:2:5 | -LL | #![feature(unknown_rust_feature)] - | ^^^^^^^^^^^^^^^^^^^^ +LL | unknown_rust_feature, + | ^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error[E0635]: unknown feature `associated_types_default` + --> $DIR/unknown-feature.rs:5:5 + | +LL | associated_types_default, + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: there is a feature with a similar name: `associated_type_defaults` + | +LL - associated_types_default, +LL + associated_type_defaults, + | + +error[E0635]: unknown feature `core_intrnisics` + --> $DIR/unknown-feature.rs:10:5 + | +LL | core_intrnisics, + | ^^^^^^^^^^^^^^^ + | +help: there is a feature with a similar name: `core_intrinsics` + | +LL - core_intrnisics, +LL + core_intrinsics, + | + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0635`.