Make public API, docs algorithm-agnostic
This commit is contained in:
parent
c2f2a3cff2
commit
378c4ab9ab
16 changed files with 81 additions and 73 deletions
|
@ -13,7 +13,7 @@ use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
|
||||||
use rustc_hir::PredicateOrigin;
|
use rustc_hir::PredicateOrigin;
|
||||||
use rustc_index::vec::{Idx, IndexVec};
|
use rustc_index::vec::{Idx, IndexVec};
|
||||||
use rustc_middle::ty::{DefIdTree, ResolverAstLowering, TyCtxt};
|
use rustc_middle::ty::{DefIdTree, ResolverAstLowering, TyCtxt};
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::source_map::DesugaringKind;
|
use rustc_span::source_map::DesugaringKind;
|
||||||
use rustc_span::symbol::{kw, sym, Ident};
|
use rustc_span::symbol::{kw, sym, Ident};
|
||||||
use rustc_span::{Span, Symbol};
|
use rustc_span::{Span, Symbol};
|
||||||
|
|
|
@ -6,7 +6,7 @@ use rustc_hir as hir;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
use rustc_session::parse::feature_err;
|
use rustc_session::parse::feature_err;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::symbol::{sym, Ident};
|
use rustc_span::symbol::{sym, Ident};
|
||||||
use rustc_span::{Span, Symbol, DUMMY_SP};
|
use rustc_span::{Span, Symbol, DUMMY_SP};
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ use rustc_middle::ty::DynKind;
|
||||||
use rustc_middle::ty::GenericParamDefKind;
|
use rustc_middle::ty::GenericParamDefKind;
|
||||||
use rustc_middle::ty::{self, Const, DefIdTree, IsSuggestable, Ty, TyCtxt, TypeVisitable};
|
use rustc_middle::ty::{self, Const, DefIdTree, IsSuggestable, Ty, TyCtxt, TypeVisitable};
|
||||||
use rustc_session::lint::builtin::{AMBIGUOUS_ASSOCIATED_ITEMS, BARE_TRAIT_OBJECTS};
|
use rustc_session::lint::builtin::{AMBIGUOUS_ASSOCIATED_ITEMS, BARE_TRAIT_OBJECTS};
|
||||||
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
|
||||||
use rustc_span::symbol::{kw, Ident, Symbol};
|
use rustc_span::symbol::{kw, Ident, Symbol};
|
||||||
use rustc_span::{sym, Span, DUMMY_SP};
|
use rustc_span::{sym, Span, DUMMY_SP};
|
||||||
use rustc_target::spec::abi;
|
use rustc_target::spec::abi;
|
||||||
|
|
|
@ -45,8 +45,8 @@ use rustc_middle::ty::subst::SubstsRef;
|
||||||
use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitable};
|
use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitable};
|
||||||
use rustc_session::errors::ExprParenthesesNeeded;
|
use rustc_session::errors::ExprParenthesesNeeded;
|
||||||
use rustc_session::parse::feature_err;
|
use rustc_session::parse::feature_err;
|
||||||
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::hygiene::DesugaringKind;
|
use rustc_span::hygiene::DesugaringKind;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
|
||||||
use rustc_span::source_map::{Span, Spanned};
|
use rustc_span::source_map::{Span, Spanned};
|
||||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||||
use rustc_target::spec::abi::Abi::RustIntrinsic;
|
use rustc_target::spec::abi::Abi::RustIntrinsic;
|
||||||
|
|
|
@ -25,8 +25,8 @@ use rustc_middle::ty::{InternalSubsts, SubstsRef};
|
||||||
use rustc_session::lint;
|
use rustc_session::lint;
|
||||||
use rustc_span::def_id::DefId;
|
use rustc_span::def_id::DefId;
|
||||||
use rustc_span::def_id::LocalDefId;
|
use rustc_span::def_id::LocalDefId;
|
||||||
use rustc_span::lev_distance::{
|
use rustc_span::edit_distance::{
|
||||||
find_best_match_for_name_with_substrings, lev_distance_with_substrings,
|
edit_distance_with_substrings, find_best_match_for_name_with_substrings,
|
||||||
};
|
};
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
use rustc_span::{symbol::Ident, Span, Symbol, DUMMY_SP};
|
use rustc_span::{symbol::Ident, Span, Symbol, DUMMY_SP};
|
||||||
|
@ -70,7 +70,7 @@ struct ProbeContext<'a, 'tcx> {
|
||||||
impl_dups: FxHashSet<DefId>,
|
impl_dups: FxHashSet<DefId>,
|
||||||
|
|
||||||
/// When probing for names, include names that are close to the
|
/// When probing for names, include names that are close to the
|
||||||
/// requested name (by Levenshtein distance)
|
/// requested name (by edit distance)
|
||||||
allow_similar_names: bool,
|
allow_similar_names: bool,
|
||||||
|
|
||||||
/// Some(candidate) if there is a private candidate
|
/// Some(candidate) if there is a private candidate
|
||||||
|
@ -1794,7 +1794,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
|
||||||
|
|
||||||
/// Similarly to `probe_for_return_type`, this method attempts to find the best matching
|
/// Similarly to `probe_for_return_type`, this method attempts to find the best matching
|
||||||
/// candidate method where the method name may have been misspelled. Similarly to other
|
/// candidate method where the method name may have been misspelled. Similarly to other
|
||||||
/// Levenshtein based suggestions, we provide at most one such suggestion.
|
/// edit distance based suggestions, we provide at most one such suggestion.
|
||||||
fn probe_for_similar_candidate(&mut self) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> {
|
fn probe_for_similar_candidate(&mut self) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> {
|
||||||
debug!("probing for method names similar to {:?}", self.method_name);
|
debug!("probing for method names similar to {:?}", self.method_name);
|
||||||
|
|
||||||
|
@ -2052,8 +2052,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
|
||||||
if self.matches_by_doc_alias(x.def_id) {
|
if self.matches_by_doc_alias(x.def_id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
match lev_distance_with_substrings(name.as_str(), x.name.as_str(), max_dist)
|
match edit_distance_with_substrings(
|
||||||
{
|
name.as_str(),
|
||||||
|
x.name.as_str(),
|
||||||
|
max_dist,
|
||||||
|
) {
|
||||||
Some(d) => d > 0,
|
Some(d) => d > 0,
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ use rustc_middle::ty::{self, DefIdTree, GenericArgKind, Ty, TyCtxt, TypeVisitabl
|
||||||
use rustc_middle::ty::{IsSuggestable, ToPolyTraitRef};
|
use rustc_middle::ty::{IsSuggestable, ToPolyTraitRef};
|
||||||
use rustc_span::symbol::{kw, sym, Ident};
|
use rustc_span::symbol::{kw, sym, Ident};
|
||||||
use rustc_span::Symbol;
|
use rustc_span::Symbol;
|
||||||
use rustc_span::{lev_distance, source_map, ExpnKind, FileName, MacroKind, Span};
|
use rustc_span::{edit_distance, source_map, ExpnKind, FileName, MacroKind, Span};
|
||||||
use rustc_trait_selection::traits::error_reporting::on_unimplemented::OnUnimplementedNote;
|
use rustc_trait_selection::traits::error_reporting::on_unimplemented::OnUnimplementedNote;
|
||||||
use rustc_trait_selection::traits::error_reporting::on_unimplemented::TypeErrCtxtExt as _;
|
use rustc_trait_selection::traits::error_reporting::on_unimplemented::TypeErrCtxtExt as _;
|
||||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||||
|
@ -1014,7 +1014,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
// that had unsatisfied trait bounds
|
// that had unsatisfied trait bounds
|
||||||
if unsatisfied_predicates.is_empty() && rcvr_ty.is_enum() {
|
if unsatisfied_predicates.is_empty() && rcvr_ty.is_enum() {
|
||||||
let adt_def = rcvr_ty.ty_adt_def().expect("enum is not an ADT");
|
let adt_def = rcvr_ty.ty_adt_def().expect("enum is not an ADT");
|
||||||
if let Some(suggestion) = lev_distance::find_best_match_for_name(
|
if let Some(suggestion) = edit_distance::find_best_match_for_name(
|
||||||
&adt_def.variants().iter().map(|s| s.name).collect::<Vec<_>>(),
|
&adt_def.variants().iter().map(|s| s.name).collect::<Vec<_>>(),
|
||||||
item_name.name,
|
item_name.name,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -14,8 +14,8 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi
|
||||||
use rustc_middle::middle::stability::EvalResult;
|
use rustc_middle::middle::stability::EvalResult;
|
||||||
use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeVisitable};
|
use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeVisitable};
|
||||||
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
|
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
|
||||||
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::hygiene::DesugaringKind;
|
use rustc_span::hygiene::DesugaringKind;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
|
||||||
use rustc_span::source_map::{Span, Spanned};
|
use rustc_span::source_map::{Span, Spanned};
|
||||||
use rustc_span::symbol::{kw, sym, Ident};
|
use rustc_span::symbol::{kw, sym, Ident};
|
||||||
use rustc_span::{BytePos, DUMMY_SP};
|
use rustc_span::{BytePos, DUMMY_SP};
|
||||||
|
|
|
@ -14,8 +14,8 @@ use rustc_session::filesearch::sysroot_candidates;
|
||||||
use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer};
|
use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer};
|
||||||
use rustc_session::parse::CrateConfig;
|
use rustc_session::parse::CrateConfig;
|
||||||
use rustc_session::{early_error, filesearch, output, Session};
|
use rustc_session::{early_error, filesearch, output, Session};
|
||||||
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
|
||||||
use rustc_span::source_map::FileLoader;
|
use rustc_span::source_map::FileLoader;
|
||||||
use rustc_span::symbol::{sym, Symbol};
|
use rustc_span::symbol::{sym, Symbol};
|
||||||
use session::CompilerIO;
|
use session::CompilerIO;
|
||||||
|
|
|
@ -39,7 +39,7 @@ use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools,
|
||||||
use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
|
use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
|
||||||
use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
|
use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
use rustc_span::symbol::{sym, Ident, Symbol};
|
||||||
use rustc_span::{BytePos, Span};
|
use rustc_span::{BytePos, Span};
|
||||||
use rustc_target::abi;
|
use rustc_target::abi;
|
||||||
|
|
|
@ -19,8 +19,8 @@ use rustc_errors::{
|
||||||
struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult,
|
struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult,
|
||||||
StashKey,
|
StashKey,
|
||||||
};
|
};
|
||||||
|
use rustc_span::edit_distance::edit_distance;
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::lev_distance::lev_distance;
|
|
||||||
use rustc_span::source_map::{self, Span};
|
use rustc_span::source_map::{self, Span};
|
||||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
|
@ -459,7 +459,8 @@ impl<'a> Parser<'a> {
|
||||||
// Maybe the user misspelled `macro_rules` (issue #91227)
|
// Maybe the user misspelled `macro_rules` (issue #91227)
|
||||||
if self.token.is_ident()
|
if self.token.is_ident()
|
||||||
&& path.segments.len() == 1
|
&& path.segments.len() == 1
|
||||||
&& lev_distance("macro_rules", &path.segments[0].ident.to_string(), 3).is_some()
|
&& edit_distance("macro_rules", &path.segments[0].ident.to_string(), 3)
|
||||||
|
.is_some()
|
||||||
{
|
{
|
||||||
err.span_suggestion(
|
err.span_suggestion(
|
||||||
path.span,
|
path.span,
|
||||||
|
|
|
@ -21,9 +21,9 @@ use rustc_session::lint::builtin::ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE;
|
||||||
use rustc_session::lint::builtin::MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS;
|
use rustc_session::lint::builtin::MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS;
|
||||||
use rustc_session::lint::BuiltinLintDiagnostics;
|
use rustc_session::lint::BuiltinLintDiagnostics;
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::hygiene::MacroKind;
|
use rustc_span::hygiene::MacroKind;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
|
||||||
use rustc_span::source_map::SourceMap;
|
use rustc_span::source_map::SourceMap;
|
||||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||||
use rustc_span::{BytePos, Span, SyntaxContext};
|
use rustc_span::{BytePos, Span, SyntaxContext};
|
||||||
|
|
|
@ -21,8 +21,8 @@ use rustc_middle::span_bug;
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
use rustc_session::lint::builtin::{PUB_USE_OF_PRIVATE_EXTERN_CRATE, UNUSED_IMPORTS};
|
use rustc_session::lint::builtin::{PUB_USE_OF_PRIVATE_EXTERN_CRATE, UNUSED_IMPORTS};
|
||||||
use rustc_session::lint::BuiltinLintDiagnostics;
|
use rustc_session::lint::BuiltinLintDiagnostics;
|
||||||
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::hygiene::LocalExpnId;
|
use rustc_span::hygiene::LocalExpnId;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
|
||||||
use rustc_span::symbol::{kw, Ident, Symbol};
|
use rustc_span::symbol::{kw, Ident, Symbol};
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,9 @@ use rustc_middle::ty::DefIdTree;
|
||||||
use rustc_session::lint;
|
use rustc_session::lint;
|
||||||
use rustc_session::parse::feature_err;
|
use rustc_session::parse::feature_err;
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
|
use rustc_span::edit_distance::find_best_match_for_name;
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::hygiene::MacroKind;
|
use rustc_span::hygiene::MacroKind;
|
||||||
use rustc_span::lev_distance::find_best_match_for_name;
|
|
||||||
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
use rustc_span::symbol::{kw, sym, Ident, Symbol};
|
||||||
use rustc_span::{BytePos, Span};
|
use rustc_span::{BytePos, Span};
|
||||||
|
|
||||||
|
@ -542,7 +542,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try Levenshtein algorithm.
|
// Try finding a suitable replacement.
|
||||||
let typo_sugg =
|
let typo_sugg =
|
||||||
self.lookup_typo_candidate(path, source.namespace(), is_expected).to_opt_suggestion();
|
self.lookup_typo_candidate(path, source.namespace(), is_expected).to_opt_suggestion();
|
||||||
if path.len() == 1 && self.self_type_is_available() {
|
if path.len() == 1 && self.self_type_is_available() {
|
||||||
|
@ -770,7 +770,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the trait has a single item (which wasn't matched by Levenshtein), suggest it
|
// If the trait has a single item (which wasn't matched by the algorithm), suggest it
|
||||||
let suggestion = self.get_single_associated_item(&path, &source, is_expected);
|
let suggestion = self.get_single_associated_item(&path, &source, is_expected);
|
||||||
if !self.r.add_typo_suggestion(err, suggestion, ident_span) {
|
if !self.r.add_typo_suggestion(err, suggestion, ident_span) {
|
||||||
fallback = !self.let_binding_suggestion(err, ident_span);
|
fallback = !self.let_binding_suggestion(err, ident_span);
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
//! Damerau-Levenshtein distances.
|
//! Edit distances.
|
||||||
//!
|
//!
|
||||||
//! The [Damerau-Levenshtein distance] is a metric for measuring the difference between two strings.
|
//! The [edit distance] is a metric for measuring the difference between two strings.
|
||||||
//! This implementation is a restricted version of the algorithm, as it does not permit modifying
|
|
||||||
//! characters that have already been transposed.
|
|
||||||
//!
|
//!
|
||||||
//! [Damerau-Levenshtein distance]: https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
|
//! [edit distance]: https://en.wikipedia.org/wiki/Edit_distance
|
||||||
|
|
||||||
|
// The current implementation is the restricted Damerau-Levenshtein algorithm. It is restricted
|
||||||
|
// because it does not permit modifying characters that have already been transposed. The specific
|
||||||
|
// algorithm should not matter to the caller of the methods, which is why it is not noted in the
|
||||||
|
// documentation.
|
||||||
|
|
||||||
use crate::symbol::Symbol;
|
use crate::symbol::Symbol;
|
||||||
use std::{cmp, mem};
|
use std::{cmp, mem};
|
||||||
|
@ -12,11 +15,12 @@ use std::{cmp, mem};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// Finds the restricted Damerau-Levenshtein distance between two strings. Characters that have
|
/// Finds the [edit distance] between two strings.
|
||||||
/// already been transposed may not be modified.
|
|
||||||
///
|
///
|
||||||
/// Returns None if the distance exceeds the limit.
|
/// Returns `None` if the distance exceeds the limit.
|
||||||
pub fn lev_distance(a: &str, b: &str, limit: usize) -> Option<usize> {
|
///
|
||||||
|
/// [edit distance]: https://en.wikipedia.org/wiki/Edit_distance
|
||||||
|
pub fn edit_distance(a: &str, b: &str, limit: usize) -> Option<usize> {
|
||||||
let mut a = &a.chars().collect::<Vec<_>>()[..];
|
let mut a = &a.chars().collect::<Vec<_>>()[..];
|
||||||
let mut b = &b.chars().collect::<Vec<_>>()[..];
|
let mut b = &b.chars().collect::<Vec<_>>()[..];
|
||||||
|
|
||||||
|
@ -95,18 +99,18 @@ pub fn lev_distance(a: &str, b: &str, limit: usize) -> Option<usize> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides a word similarity score between two words that accounts for substrings being more
|
/// Provides a word similarity score between two words that accounts for substrings being more
|
||||||
/// meaningful than a typical Levenshtein distance. The lower the score, the closer the match.
|
/// meaningful than a typical edit distance. The lower the score, the closer the match. 0 is an
|
||||||
/// 0 is an identical match.
|
/// identical match.
|
||||||
///
|
///
|
||||||
/// Uses the Levenshtein distance between the two strings and removes the cost of the length
|
/// Uses the edit distance between the two strings and removes the cost of the length difference.
|
||||||
/// difference. If this is 0 then it is either a substring match or a full word match, in the
|
/// If this is 0 then it is either a substring match or a full word match, in the substring match
|
||||||
/// substring match case we detect this and return `1`. To prevent finding meaningless substrings,
|
/// case we detect this and return `1`. To prevent finding meaningless substrings, eg. "in" in
|
||||||
/// eg. "in" in "shrink", we only perform this subtraction of length difference if one of the words
|
/// "shrink", we only perform this subtraction of length difference if one of the words is not
|
||||||
/// is not greater than twice the length of the other. For cases where the words are close in size
|
/// greater than twice the length of the other. For cases where the words are close in size but not
|
||||||
/// but not an exact substring then the cost of the length difference is discounted by half.
|
/// an exact substring then the cost of the length difference is discounted by half.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the distance exceeds the limit.
|
/// Returns `None` if the distance exceeds the limit.
|
||||||
pub fn lev_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<usize> {
|
pub fn edit_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<usize> {
|
||||||
let n = a.chars().count();
|
let n = a.chars().count();
|
||||||
let m = b.chars().count();
|
let m = b.chars().count();
|
||||||
|
|
||||||
|
@ -114,10 +118,10 @@ pub fn lev_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<us
|
||||||
// big difference in length.
|
// big difference in length.
|
||||||
let big_len_diff = (n * 2) < m || (m * 2) < n;
|
let big_len_diff = (n * 2) < m || (m * 2) < n;
|
||||||
let len_diff = if n < m { m - n } else { n - m };
|
let len_diff = if n < m { m - n } else { n - m };
|
||||||
let lev = lev_distance(a, b, limit + len_diff)?;
|
let distance = edit_distance(a, b, limit + len_diff)?;
|
||||||
|
|
||||||
// This is the crux, subtracting length difference means exact substring matches will now be 0
|
// This is the crux, subtracting length difference means exact substring matches will now be 0
|
||||||
let score = lev - len_diff;
|
let score = distance - len_diff;
|
||||||
|
|
||||||
// If the score is 0 but the words have different lengths then it's a substring match not a full
|
// If the score is 0 but the words have different lengths then it's a substring match not a full
|
||||||
// word match
|
// word match
|
||||||
|
@ -136,12 +140,12 @@ pub fn lev_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<us
|
||||||
|
|
||||||
/// Finds the best match for given word in the given iterator where substrings are meaningful.
|
/// Finds the best match for given word in the given iterator where substrings are meaningful.
|
||||||
///
|
///
|
||||||
/// A version of [`find_best_match_for_name`] that uses [`lev_distance_with_substrings`] as the score
|
/// A version of [`find_best_match_for_name`] that uses [`edit_distance_with_substrings`] as the
|
||||||
/// for word similarity. This takes an optional distance limit which defaults to one-third of the
|
/// score for word similarity. This takes an optional distance limit which defaults to one-third of
|
||||||
/// given word.
|
/// the given word.
|
||||||
///
|
///
|
||||||
/// Besides the modified Levenshtein, we use case insensitive comparison to improve accuracy
|
/// We use case insensitive comparison to improve accuracy on an edge case with a lower(upper)case
|
||||||
/// on an edge case with a lower(upper)case letters mismatch.
|
/// letters mismatch.
|
||||||
pub fn find_best_match_for_name_with_substrings(
|
pub fn find_best_match_for_name_with_substrings(
|
||||||
candidates: &[Symbol],
|
candidates: &[Symbol],
|
||||||
lookup: Symbol,
|
lookup: Symbol,
|
||||||
|
@ -156,8 +160,8 @@ pub fn find_best_match_for_name_with_substrings(
|
||||||
/// an optional limit for the maximum allowable edit distance, which defaults
|
/// an optional limit for the maximum allowable edit distance, which defaults
|
||||||
/// to one-third of the given word.
|
/// to one-third of the given word.
|
||||||
///
|
///
|
||||||
/// Besides Levenshtein, we use case insensitive comparison to improve accuracy
|
/// We use case insensitive comparison to improve accuracy on an edge case with a lower(upper)case
|
||||||
/// on an edge case with a lower(upper)case letters mismatch.
|
/// letters mismatch.
|
||||||
pub fn find_best_match_for_name(
|
pub fn find_best_match_for_name(
|
||||||
candidates: &[Symbol],
|
candidates: &[Symbol],
|
||||||
lookup: Symbol,
|
lookup: Symbol,
|
||||||
|
@ -178,7 +182,7 @@ fn find_best_match_for_name_impl(
|
||||||
|
|
||||||
// Priority of matches:
|
// Priority of matches:
|
||||||
// 1. Exact case insensitive match
|
// 1. Exact case insensitive match
|
||||||
// 2. Levenshtein distance match
|
// 2. Edit distance match
|
||||||
// 3. Sorted word match
|
// 3. Sorted word match
|
||||||
if let Some(c) = candidates.iter().find(|c| c.as_str().to_uppercase() == lookup_uppercase) {
|
if let Some(c) = candidates.iter().find(|c| c.as_str().to_uppercase() == lookup_uppercase) {
|
||||||
return Some(*c);
|
return Some(*c);
|
||||||
|
@ -188,9 +192,9 @@ fn find_best_match_for_name_impl(
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
for c in candidates {
|
for c in candidates {
|
||||||
match if use_substring_score {
|
match if use_substring_score {
|
||||||
lev_distance_with_substrings(lookup, c.as_str(), dist)
|
edit_distance_with_substrings(lookup, c.as_str(), dist)
|
||||||
} else {
|
} else {
|
||||||
lev_distance(lookup, c.as_str(), dist)
|
edit_distance(lookup, c.as_str(), dist)
|
||||||
} {
|
} {
|
||||||
Some(0) => return Some(*c),
|
Some(0) => return Some(*c),
|
||||||
Some(d) => {
|
Some(d) => {
|
|
@ -1,40 +1,40 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lev_distance() {
|
fn test_edit_distance() {
|
||||||
// Test bytelength agnosticity
|
// Test bytelength agnosticity
|
||||||
for c in (0..char::MAX as u32).filter_map(char::from_u32).map(|i| i.to_string()) {
|
for c in (0..char::MAX as u32).filter_map(char::from_u32).map(|i| i.to_string()) {
|
||||||
assert_eq!(lev_distance(&c[..], &c[..], usize::MAX), Some(0));
|
assert_eq!(edit_distance(&c[..], &c[..], usize::MAX), Some(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
let a = "\nMäry häd ä little lämb\n\nLittle lämb\n";
|
let a = "\nMäry häd ä little lämb\n\nLittle lämb\n";
|
||||||
let b = "\nMary häd ä little lämb\n\nLittle lämb\n";
|
let b = "\nMary häd ä little lämb\n\nLittle lämb\n";
|
||||||
let c = "Mary häd ä little lämb\n\nLittle lämb\n";
|
let c = "Mary häd ä little lämb\n\nLittle lämb\n";
|
||||||
assert_eq!(lev_distance(a, b, usize::MAX), Some(1));
|
assert_eq!(edit_distance(a, b, usize::MAX), Some(1));
|
||||||
assert_eq!(lev_distance(b, a, usize::MAX), Some(1));
|
assert_eq!(edit_distance(b, a, usize::MAX), Some(1));
|
||||||
assert_eq!(lev_distance(a, c, usize::MAX), Some(2));
|
assert_eq!(edit_distance(a, c, usize::MAX), Some(2));
|
||||||
assert_eq!(lev_distance(c, a, usize::MAX), Some(2));
|
assert_eq!(edit_distance(c, a, usize::MAX), Some(2));
|
||||||
assert_eq!(lev_distance(b, c, usize::MAX), Some(1));
|
assert_eq!(edit_distance(b, c, usize::MAX), Some(1));
|
||||||
assert_eq!(lev_distance(c, b, usize::MAX), Some(1));
|
assert_eq!(edit_distance(c, b, usize::MAX), Some(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lev_distance_limit() {
|
fn test_edit_distance_limit() {
|
||||||
assert_eq!(lev_distance("abc", "abcd", 1), Some(1));
|
assert_eq!(edit_distance("abc", "abcd", 1), Some(1));
|
||||||
assert_eq!(lev_distance("abc", "abcd", 0), None);
|
assert_eq!(edit_distance("abc", "abcd", 0), None);
|
||||||
assert_eq!(lev_distance("abc", "xyz", 3), Some(3));
|
assert_eq!(edit_distance("abc", "xyz", 3), Some(3));
|
||||||
assert_eq!(lev_distance("abc", "xyz", 2), None);
|
assert_eq!(edit_distance("abc", "xyz", 2), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_method_name_similarity_score() {
|
fn test_method_name_similarity_score() {
|
||||||
assert_eq!(lev_distance_with_substrings("empty", "is_empty", 1), Some(1));
|
assert_eq!(edit_distance_with_substrings("empty", "is_empty", 1), Some(1));
|
||||||
assert_eq!(lev_distance_with_substrings("shrunk", "rchunks", 2), None);
|
assert_eq!(edit_distance_with_substrings("shrunk", "rchunks", 2), None);
|
||||||
assert_eq!(lev_distance_with_substrings("abc", "abcd", 1), Some(1));
|
assert_eq!(edit_distance_with_substrings("abc", "abcd", 1), Some(1));
|
||||||
assert_eq!(lev_distance_with_substrings("a", "abcd", 1), None);
|
assert_eq!(edit_distance_with_substrings("a", "abcd", 1), None);
|
||||||
assert_eq!(lev_distance_with_substrings("edf", "eq", 1), None);
|
assert_eq!(edit_distance_with_substrings("edf", "eq", 1), None);
|
||||||
assert_eq!(lev_distance_with_substrings("abc", "xyz", 3), Some(3));
|
assert_eq!(edit_distance_with_substrings("abc", "xyz", 3), Some(3));
|
||||||
assert_eq!(lev_distance_with_substrings("abcdef", "abcdef", 2), Some(0));
|
assert_eq!(edit_distance_with_substrings("abcdef", "abcdef", 2), Some(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
|
@ -47,7 +47,7 @@ pub use hygiene::{ExpnData, ExpnHash, ExpnId, LocalExpnId, SyntaxContext};
|
||||||
use rustc_data_structures::stable_hasher::HashingControls;
|
use rustc_data_structures::stable_hasher::HashingControls;
|
||||||
pub mod def_id;
|
pub mod def_id;
|
||||||
use def_id::{CrateNum, DefId, DefPathHash, LocalDefId, LOCAL_CRATE};
|
use def_id::{CrateNum, DefId, DefPathHash, LocalDefId, LOCAL_CRATE};
|
||||||
pub mod lev_distance;
|
pub mod edit_distance;
|
||||||
mod span_encoding;
|
mod span_encoding;
|
||||||
pub use span_encoding::{Span, DUMMY_SP};
|
pub use span_encoding::{Span, DUMMY_SP};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue