Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 10 additions & 14 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -222,27 +221,24 @@ 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,
is_incorrect_case: bool,
}

/// 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<MisspelledKw> {
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 {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,21 @@ pub(crate) struct UnknownFeature {
#[primary_span]
pub span: Span,
pub feature: Symbol,
#[subdiagnostic]
pub suggestion: Option<MisspelledFeature>,
}

#[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)]
Expand Down
30 changes: 24 additions & 6 deletions compiler/rustc_passes/src/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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)));
}
Expand All @@ -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;
}
Expand All @@ -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::<Vec<_>>();
let lib_features = crates
.into_iter()
.flat_map(|&cnum| {
tcx.lib_features(cnum).stability.keys().copied().into_sorted_stable_ord()
})
.collect::<Vec<_>>();

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() {
Expand Down
22 changes: 22 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 14 additions & 1 deletion tests/ui/feature-gates/unknown-feature.rs
Original file line number Diff line number Diff line change
@@ -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() {}
32 changes: 28 additions & 4 deletions tests/ui/feature-gates/unknown-feature.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading