diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index bd495f6ec1acb..d4667a09af6d9 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}; @@ -222,6 +221,8 @@ impl std::fmt::Display for UnaryFixity { style = "verbose" )] struct MisspelledKw { + // We use a String here because `Symbol::into_diag_arg` calls `Symbol::to_ident_string`, which + // prefix the keyword with a `r#` because it aims to print the symbol as an identifier. similar_kw: String, #[primary_span] span: Span, @@ -229,20 +230,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(|(similar_kw, is_incorrect_case)| MisspelledKw { + similar_kw: similar_kw.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 09ac3ae1c3a92..70e91c081776a 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -421,6 +421,8 @@ passes_missing_panic_handler = passes_missing_stability_attr = {$descr} has missing stability attribute +passes_misspelled_feature = 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..6aa0f5212af70 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1183,6 +1183,21 @@ pub(crate) struct UnknownFeature { #[primary_span] pub span: Span, pub feature: Symbol, + #[subdiagnostic] + pub suggestion: Option, +} + +#[derive(Subdiagnostic)] +#[suggestion( + passes_misspelled_feature, + style = "verbose", + code = "{actual_name}", + applicability = "maybe-incorrect" +)] +pub(crate) struct MisspelledFeature { + #[primary_span] + pub span: Span, + pub actual_name: Symbol, } #[derive(Diagnostic)] diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 39830db2b11db..9d94c4cc62256 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}; @@ -1062,11 +1062,13 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { // no unknown features, because the collection also does feature attribute validation. let local_defined_features = tcx.lib_features(LOCAL_CRATE); if !remaining_lib_features.is_empty() || !remaining_implications.is_empty() { + let crates = tcx.crates(()); + // Loading the implications of all crates is unavoidable to be able to emit the partial // stabilization diagnostic, but it can be avoided when there are no // `remaining_lib_features`. let mut all_implications = remaining_implications.clone(); - for &cnum in tcx.crates(()) { + for &cnum in crates { all_implications .extend_unord(tcx.stability_implications(cnum).items().map(|(k, v)| (*k, *v))); } @@ -1079,7 +1081,7 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { &all_implications, ); - for &cnum in tcx.crates(()) { + for &cnum in crates { if remaining_lib_features.is_empty() && remaining_implications.is_empty() { break; } @@ -1091,10 +1093,26 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { &all_implications, ); } - } - 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 = 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) + .map(|(actual_name, _)| errors::MisspelledFeature { span, actual_name }); + tcx.dcx().emit_err(errors::UnknownFeature { span, feature, suggestion }); + } + } } 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..a9e8e046eb168 100644 --- a/tests/ui/feature-gates/unknown-feature.rs +++ b/tests/ui/feature-gates/unknown-feature.rs @@ -1,3 +1,16 @@ -#![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..1e5b953e99cab 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:6: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:11: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`.