diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 6bafbfbc14c73..4a0e005b8b902 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -13,7 +13,7 @@ use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID}; use rustc_hir::PredicateOrigin; use rustc_index::vec::{Idx, IndexVec}; 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::symbol::{kw, sym, Ident}; use rustc_span::{Span, Symbol}; diff --git a/compiler/rustc_hir_analysis/src/astconv/errors.rs b/compiler/rustc_hir_analysis/src/astconv/errors.rs index a9c2886b41448..a68e0e0ac5bd0 100644 --- a/compiler/rustc_hir_analysis/src/astconv/errors.rs +++ b/compiler/rustc_hir_analysis/src/astconv/errors.rs @@ -7,7 +7,7 @@ use rustc_hir::def_id::DefId; use rustc_infer::traits::FulfillmentError; use rustc_middle::ty::{self, Ty}; 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::{Span, Symbol, DUMMY_SP}; diff --git a/compiler/rustc_hir_analysis/src/astconv/mod.rs b/compiler/rustc_hir_analysis/src/astconv/mod.rs index 716b4fc6ae36f..c0cd54cc916e0 100644 --- a/compiler/rustc_hir_analysis/src/astconv/mod.rs +++ b/compiler/rustc_hir_analysis/src/astconv/mod.rs @@ -37,8 +37,8 @@ use rustc_middle::ty::DynKind; use rustc_middle::ty::GenericParamDefKind; use rustc_middle::ty::{self, Const, DefIdTree, IsSuggestable, Ty, TyCtxt, TypeVisitable}; 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::lev_distance::find_best_match_for_name; use rustc_span::symbol::{kw, Ident, Symbol}; use rustc_span::{sym, Span, DUMMY_SP}; use rustc_target::spec::abi; diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 2f79071f6dc58..08cbfffdd171b 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -45,8 +45,8 @@ use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitable}; use rustc_session::errors::ExprParenthesesNeeded; use rustc_session::parse::feature_err; +use rustc_span::edit_distance::find_best_match_for_name; 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::symbol::{kw, sym, Ident, Symbol}; use rustc_target::spec::abi::Abi::RustIntrinsic; diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index eb6c0b7686b6d..88af554483b8e 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -24,8 +24,8 @@ use rustc_middle::ty::{InternalSubsts, SubstsRef}; use rustc_session::lint; use rustc_span::def_id::DefId; use rustc_span::def_id::LocalDefId; -use rustc_span::lev_distance::{ - find_best_match_for_name_with_substrings, lev_distance_with_substrings, +use rustc_span::edit_distance::{ + edit_distance_with_substrings, find_best_match_for_name_with_substrings, }; use rustc_span::symbol::sym; use rustc_span::{symbol::Ident, Span, Symbol, DUMMY_SP}; @@ -69,7 +69,7 @@ struct ProbeContext<'a, 'tcx> { impl_dups: FxHashSet, /// 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, /// Some(candidate) if there is a private candidate @@ -1793,7 +1793,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { /// 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 - /// 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, MethodError<'tcx>> { debug!("probing for method names similar to {:?}", self.method_name); @@ -2024,8 +2024,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { if self.matches_by_doc_alias(x.def_id) { 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, None => false, } diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 6a7b1f6646abe..4f3dbe03c052a 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -31,7 +31,7 @@ use rustc_middle::ty::{self, DefIdTree, GenericArgKind, Ty, TyCtxt, TypeVisitabl use rustc_middle::ty::{IsSuggestable, ToPolyTraitRef}; use rustc_span::symbol::{kw, sym, Ident}; 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::TypeErrCtxtExt 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 if unsatisfied_predicates.is_empty() && rcvr_ty.is_enum() { 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::>(), item_name.name, None, diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 3881efe87db5c..ab6e76ef8aa35 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -14,8 +14,8 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_middle::middle::stability::EvalResult; use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeVisitable}; 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::lev_distance::find_best_match_for_name; use rustc_span::source_map::{Span, Spanned}; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{BytePos, DUMMY_SP}; diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index e4b4d5375e64a..475d3601b52ab 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -14,8 +14,8 @@ use rustc_session::filesearch::sysroot_candidates; use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer}; use rustc_session::parse::CrateConfig; 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::lev_distance::find_best_match_for_name; use rustc_span::source_map::FileLoader; use rustc_span::symbol::{sym, Symbol}; use session::CompilerIO; diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 9a9e2de7b5c7f..aace4974cc935 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -39,7 +39,7 @@ use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId}; use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; 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::{BytePos, Span}; use rustc_target::abi; diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index fd46a1292a823..f164bb330f3da 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -19,8 +19,8 @@ use rustc_errors::{ struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult, StashKey, }; +use rustc_span::edit_distance::edit_distance; use rustc_span::edition::Edition; -use rustc_span::lev_distance::lev_distance; use rustc_span::source_map::{self, Span}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::DUMMY_SP; @@ -459,7 +459,8 @@ impl<'a> Parser<'a> { // Maybe the user misspelled `macro_rules` (issue #91227) if self.token.is_ident() && 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(), 2) + .is_some() { err.span_suggestion( path.span, diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 934d60589d4e2..cd26dbd61901d 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -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::BuiltinLintDiagnostics; use rustc_session::Session; +use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; use rustc_span::hygiene::MacroKind; -use rustc_span::lev_distance::find_best_match_for_name; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{BytePos, Span, SyntaxContext}; diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index da3e5095e531d..48188b4ba355e 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -21,8 +21,8 @@ use rustc_middle::span_bug; use rustc_middle::ty; use rustc_session::lint::builtin::{PUB_USE_OF_PRIVATE_EXTERN_CRATE, UNUSED_IMPORTS}; use rustc_session::lint::BuiltinLintDiagnostics; +use rustc_span::edit_distance::find_best_match_for_name; 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::Span; diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 162f91e05f8c8..f8bd289d72f6a 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -4135,9 +4135,9 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { fn record_candidate_traits_for_expr_if_necessary(&mut self, expr: &'ast Expr) { match expr.kind { ExprKind::Field(_, ident) => { - // FIXME(#6890): Even though you can't treat a method like a - // field, we need to add any trait methods we find that match - // the field name so that we can do some nice error reporting + // #6890: Even though you can't treat a method like a field, + // we need to add any trait methods we find that match the + // field name so that we can do some nice error reporting // later on in typeck. let traits = self.traits_in_scope(ident, ValueNS); self.r.trait_map.insert(expr.id, traits); diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 5205d055cf9ad..174a543fe466d 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -25,9 +25,9 @@ use rustc_middle::ty::DefIdTree; use rustc_session::lint; use rustc_session::parse::feature_err; use rustc_session::Session; +use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; 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::{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 = self.lookup_typo_candidate(path, source.namespace(), is_expected).to_opt_suggestion(); 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); if !self.r.add_typo_suggestion(err, suggestion, ident_span) { fallback = !self.let_binding_suggestion(err, ident_span); diff --git a/compiler/rustc_span/src/edit_distance.rs b/compiler/rustc_span/src/edit_distance.rs new file mode 100644 index 0000000000000..89f0386e3e97f --- /dev/null +++ b/compiler/rustc_span/src/edit_distance.rs @@ -0,0 +1,229 @@ +//! Edit distances. +//! +//! The [edit distance] is a metric for measuring the difference between two strings. +//! +//! [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 std::{cmp, mem}; + +#[cfg(test)] +mod tests; + +/// Finds the [edit distance] between two strings. +/// +/// Returns `None` if the distance exceeds the limit. +/// +/// [edit distance]: https://en.wikipedia.org/wiki/Edit_distance +pub fn edit_distance(a: &str, b: &str, limit: usize) -> Option { + let mut a = &a.chars().collect::>()[..]; + let mut b = &b.chars().collect::>()[..]; + + // Ensure that `b` is the shorter string, minimizing memory use. + if a.len() < b.len() { + mem::swap(&mut a, &mut b); + } + + let min_dist = a.len() - b.len(); + // If we know the limit will be exceeded, we can return early. + if min_dist > limit { + return None; + } + + // Strip common prefix. + while let Some(((b_char, b_rest), (a_char, a_rest))) = b.split_first().zip(a.split_first()) + && a_char == b_char + { + a = a_rest; + b = b_rest; + } + // Strip common suffix. + while let Some(((b_char, b_rest), (a_char, a_rest))) = b.split_last().zip(a.split_last()) + && a_char == b_char + { + a = a_rest; + b = b_rest; + } + + // If either string is empty, the distance is the length of the other. + // We know that `b` is the shorter string, so we don't need to check `a`. + if b.len() == 0 { + return Some(min_dist); + } + + let mut prev_prev = vec![usize::MAX; b.len() + 1]; + let mut prev = (0..=b.len()).collect::>(); + let mut current = vec![0; b.len() + 1]; + + // row by row + for i in 1..=a.len() { + current[0] = i; + let a_idx = i - 1; + + // column by column + for j in 1..=b.len() { + let b_idx = j - 1; + + // There is no cost to substitute a character with itself. + let substitution_cost = if a[a_idx] == b[b_idx] { 0 } else { 1 }; + + current[j] = cmp::min( + // deletion + prev[j] + 1, + cmp::min( + // insertion + current[j - 1] + 1, + // substitution + prev[j - 1] + substitution_cost, + ), + ); + + if (i > 1) && (j > 1) && (a[a_idx] == b[b_idx - 1]) && (a[a_idx - 1] == b[b_idx]) { + // transposition + current[j] = cmp::min(current[j], prev_prev[j - 2] + 1); + } + } + + // Rotate the buffers, reusing the memory. + [prev_prev, prev, current] = [prev, current, prev_prev]; + } + + // `prev` because we already rotated the buffers. + let distance = prev[b.len()]; + (distance <= limit).then_some(distance) +} + +/// Provides a word similarity score between two words that accounts for substrings being more +/// meaningful than a typical edit distance. The lower the score, the closer the match. 0 is an +/// identical match. +/// +/// Uses the edit distance between the two strings and removes the cost of the length difference. +/// If this is 0 then it is either a substring match or a full word match, in the substring match +/// case we detect this and return `1`. To prevent finding meaningless substrings, eg. "in" in +/// "shrink", we only perform this subtraction of length difference if one of the words is not +/// greater than twice the length of the other. For cases where the words are close in size but not +/// an exact substring then the cost of the length difference is discounted by half. +/// +/// Returns `None` if the distance exceeds the limit. +pub fn edit_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option { + let n = a.chars().count(); + let m = b.chars().count(); + + // Check one isn't less than half the length of the other. If this is true then there is a + // big difference in length. + let big_len_diff = (n * 2) < m || (m * 2) < n; + let len_diff = if n < m { m - n } else { n - m }; + let distance = edit_distance(a, b, limit + len_diff)?; + + // This is the crux, subtracting length difference means exact substring matches will now be 0 + 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 + // word match + let score = if score == 0 && len_diff > 0 && !big_len_diff { + 1 // Exact substring match, but not a total word match so return non-zero + } else if !big_len_diff { + // Not a big difference in length, discount cost of length difference + score + (len_diff + 1) / 2 + } else { + // A big difference in length, add back the difference in length to the score + score + len_diff + }; + + (score <= limit).then_some(score) +} + +/// 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 [`edit_distance_with_substrings`] as the +/// score for word similarity. This takes an optional distance limit which defaults to one-third of +/// the given word. +/// +/// We use case insensitive comparison to improve accuracy on an edge case with a lower(upper)case +/// letters mismatch. +pub fn find_best_match_for_name_with_substrings( + candidates: &[Symbol], + lookup: Symbol, + dist: Option, +) -> Option { + find_best_match_for_name_impl(true, candidates, lookup, dist) +} + +/// Finds the best match for a given word in the given iterator. +/// +/// As a loose rule to avoid the obviously incorrect suggestions, it takes +/// an optional limit for the maximum allowable edit distance, which defaults +/// to one-third of the given word. +/// +/// We use case insensitive comparison to improve accuracy on an edge case with a lower(upper)case +/// letters mismatch. +pub fn find_best_match_for_name( + candidates: &[Symbol], + lookup: Symbol, + dist: Option, +) -> Option { + find_best_match_for_name_impl(false, candidates, lookup, dist) +} + +#[cold] +fn find_best_match_for_name_impl( + use_substring_score: bool, + candidates: &[Symbol], + lookup: Symbol, + dist: Option, +) -> Option { + let lookup = lookup.as_str(); + let lookup_uppercase = lookup.to_uppercase(); + + // Priority of matches: + // 1. Exact case insensitive match + // 2. Edit distance match + // 3. Sorted word match + if let Some(c) = candidates.iter().find(|c| c.as_str().to_uppercase() == lookup_uppercase) { + return Some(*c); + } + + let mut dist = dist.unwrap_or_else(|| cmp::max(lookup.len(), 3) / 3); + let mut best = None; + for c in candidates { + match if use_substring_score { + edit_distance_with_substrings(lookup, c.as_str(), dist) + } else { + edit_distance(lookup, c.as_str(), dist) + } { + Some(0) => return Some(*c), + Some(d) => { + dist = d - 1; + best = Some(*c); + } + None => {} + } + } + if best.is_some() { + return best; + } + + find_match_by_sorted_words(candidates, lookup) +} + +fn find_match_by_sorted_words(iter_names: &[Symbol], lookup: &str) -> Option { + iter_names.iter().fold(None, |result, candidate| { + if sort_by_words(candidate.as_str()) == sort_by_words(lookup) { + Some(*candidate) + } else { + result + } + }) +} + +fn sort_by_words(name: &str) -> String { + let mut split_words: Vec<&str> = name.split('_').collect(); + // We are sorting primitive &strs and can use unstable sort here. + split_words.sort_unstable(); + split_words.join("_") +} diff --git a/compiler/rustc_span/src/edit_distance/tests.rs b/compiler/rustc_span/src/edit_distance/tests.rs new file mode 100644 index 0000000000000..c9c7a1f1bf2f6 --- /dev/null +++ b/compiler/rustc_span/src/edit_distance/tests.rs @@ -0,0 +1,80 @@ +use super::*; + +#[test] +fn test_edit_distance() { + // Test bytelength agnosticity + for c in (0..char::MAX as u32).filter_map(char::from_u32).map(|i| i.to_string()) { + 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 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"; + assert_eq!(edit_distance(a, b, usize::MAX), Some(1)); + assert_eq!(edit_distance(b, a, usize::MAX), Some(1)); + assert_eq!(edit_distance(a, c, usize::MAX), Some(2)); + assert_eq!(edit_distance(c, a, usize::MAX), Some(2)); + assert_eq!(edit_distance(b, c, usize::MAX), Some(1)); + assert_eq!(edit_distance(c, b, usize::MAX), Some(1)); +} + +#[test] +fn test_edit_distance_limit() { + assert_eq!(edit_distance("abc", "abcd", 1), Some(1)); + assert_eq!(edit_distance("abc", "abcd", 0), None); + assert_eq!(edit_distance("abc", "xyz", 3), Some(3)); + assert_eq!(edit_distance("abc", "xyz", 2), None); +} + +#[test] +fn test_method_name_similarity_score() { + assert_eq!(edit_distance_with_substrings("empty", "is_empty", 1), Some(1)); + assert_eq!(edit_distance_with_substrings("shrunk", "rchunks", 2), None); + assert_eq!(edit_distance_with_substrings("abc", "abcd", 1), Some(1)); + assert_eq!(edit_distance_with_substrings("a", "abcd", 1), None); + assert_eq!(edit_distance_with_substrings("edf", "eq", 1), None); + assert_eq!(edit_distance_with_substrings("abc", "xyz", 3), Some(3)); + assert_eq!(edit_distance_with_substrings("abcdef", "abcdef", 2), Some(0)); +} + +#[test] +fn test_find_best_match_for_name() { + use crate::create_default_session_globals_then; + create_default_session_globals_then(|| { + let input = vec![Symbol::intern("aaab"), Symbol::intern("aaabc")]; + assert_eq!( + find_best_match_for_name(&input, Symbol::intern("aaaa"), None), + Some(Symbol::intern("aaab")) + ); + + assert_eq!(find_best_match_for_name(&input, Symbol::intern("1111111111"), None), None); + + let input = vec![Symbol::intern("AAAA")]; + assert_eq!( + find_best_match_for_name(&input, Symbol::intern("aaaa"), None), + Some(Symbol::intern("AAAA")) + ); + + let input = vec![Symbol::intern("AAAA")]; + assert_eq!( + find_best_match_for_name(&input, Symbol::intern("aaaa"), Some(4)), + Some(Symbol::intern("AAAA")) + ); + + let input = vec![Symbol::intern("a_longer_variable_name")]; + assert_eq!( + find_best_match_for_name(&input, Symbol::intern("a_variable_longer_name"), None), + Some(Symbol::intern("a_longer_variable_name")) + ); + }) +} + +#[test] +fn test_precise_algorithm() { + // Not Levenshtein distance. + assert_ne!(edit_distance("ab", "ba", usize::MAX), Some(2)); + // Not unrestricted Damerau-Levenshtein distance. + assert_ne!(edit_distance("abde", "bcaed", usize::MAX), Some(3)); + // The current implementation is a restricted Damerau-Levenshtein distance. + assert_eq!(edit_distance("abde", "bcaed", usize::MAX), Some(4)); +} diff --git a/compiler/rustc_span/src/lev_distance.rs b/compiler/rustc_span/src/lev_distance.rs deleted file mode 100644 index 61e4b98a8d28b..0000000000000 --- a/compiler/rustc_span/src/lev_distance.rs +++ /dev/null @@ -1,177 +0,0 @@ -//! Levenshtein distances. -//! -//! The [Levenshtein distance] is a metric for measuring the difference between two strings. -//! -//! [Levenshtein distance]: https://en.wikipedia.org/wiki/Levenshtein_distance - -use crate::symbol::Symbol; -use std::cmp; - -#[cfg(test)] -mod tests; - -/// Finds the Levenshtein distance between two strings. -/// -/// Returns None if the distance exceeds the limit. -pub fn lev_distance(a: &str, b: &str, limit: usize) -> Option { - let n = a.chars().count(); - let m = b.chars().count(); - let min_dist = if n < m { m - n } else { n - m }; - - if min_dist > limit { - return None; - } - if n == 0 || m == 0 { - return (min_dist <= limit).then_some(min_dist); - } - - let mut dcol: Vec<_> = (0..=m).collect(); - - for (i, sc) in a.chars().enumerate() { - let mut current = i; - dcol[0] = current + 1; - - for (j, tc) in b.chars().enumerate() { - let next = dcol[j + 1]; - if sc == tc { - dcol[j + 1] = current; - } else { - dcol[j + 1] = cmp::min(current, next); - dcol[j + 1] = cmp::min(dcol[j + 1], dcol[j]) + 1; - } - current = next; - } - } - - (dcol[m] <= limit).then_some(dcol[m]) -} - -/// 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. -/// 0 is an identical match. -/// -/// Uses the Levenshtein distance between the two strings and removes the cost of the length -/// difference. If this is 0 then it is either a substring match or a full word match, in the -/// substring match case we detect this and return `1`. To prevent finding meaningless substrings, -/// eg. "in" in "shrink", we only perform this subtraction of length difference if one of the words -/// is not greater than twice the length of the other. For cases where the words are close in size -/// but not an exact substring then the cost of the length difference is discounted by half. -/// -/// Returns `None` if the distance exceeds the limit. -pub fn lev_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option { - let n = a.chars().count(); - let m = b.chars().count(); - - // Check one isn't less than half the length of the other. If this is true then there is a - // big difference in length. - let big_len_diff = (n * 2) < m || (m * 2) < n; - let len_diff = if n < m { m - n } else { n - m }; - let lev = lev_distance(a, b, limit + len_diff)?; - - // This is the crux, subtracting length difference means exact substring matches will now be 0 - let score = lev - len_diff; - - // If the score is 0 but the words have different lengths then it's a substring match not a full - // word match - let score = if score == 0 && len_diff > 0 && !big_len_diff { - 1 // Exact substring match, but not a total word match so return non-zero - } else if !big_len_diff { - // Not a big difference in length, discount cost of length difference - score + (len_diff + 1) / 2 - } else { - // A big difference in length, add back the difference in length to the score - score + len_diff - }; - - (score <= limit).then_some(score) -} - -/// 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 -/// for word similarity. This takes an optional distance limit which defaults to one-third of the -/// given word. -/// -/// Besides the modified Levenshtein, we use case insensitive comparison to improve accuracy -/// on an edge case with a lower(upper)case letters mismatch. -pub fn find_best_match_for_name_with_substrings( - candidates: &[Symbol], - lookup: Symbol, - dist: Option, -) -> Option { - find_best_match_for_name_impl(true, candidates, lookup, dist) -} - -/// Finds the best match for a given word in the given iterator. -/// -/// As a loose rule to avoid the obviously incorrect suggestions, it takes -/// an optional limit for the maximum allowable edit distance, which defaults -/// to one-third of the given word. -/// -/// Besides Levenshtein, we use case insensitive comparison to improve accuracy -/// on an edge case with a lower(upper)case letters mismatch. -pub fn find_best_match_for_name( - candidates: &[Symbol], - lookup: Symbol, - dist: Option, -) -> Option { - find_best_match_for_name_impl(false, candidates, lookup, dist) -} - -#[cold] -fn find_best_match_for_name_impl( - use_substring_score: bool, - candidates: &[Symbol], - lookup: Symbol, - dist: Option, -) -> Option { - let lookup = lookup.as_str(); - let lookup_uppercase = lookup.to_uppercase(); - - // Priority of matches: - // 1. Exact case insensitive match - // 2. Levenshtein distance match - // 3. Sorted word match - if let Some(c) = candidates.iter().find(|c| c.as_str().to_uppercase() == lookup_uppercase) { - return Some(*c); - } - - let mut dist = dist.unwrap_or_else(|| cmp::max(lookup.len(), 3) / 3); - let mut best = None; - for c in candidates { - match if use_substring_score { - lev_distance_with_substrings(lookup, c.as_str(), dist) - } else { - lev_distance(lookup, c.as_str(), dist) - } { - Some(0) => return Some(*c), - Some(d) => { - dist = d - 1; - best = Some(*c); - } - None => {} - } - } - if best.is_some() { - return best; - } - - find_match_by_sorted_words(candidates, lookup) -} - -fn find_match_by_sorted_words(iter_names: &[Symbol], lookup: &str) -> Option { - iter_names.iter().fold(None, |result, candidate| { - if sort_by_words(candidate.as_str()) == sort_by_words(lookup) { - Some(*candidate) - } else { - result - } - }) -} - -fn sort_by_words(name: &str) -> String { - let mut split_words: Vec<&str> = name.split('_').collect(); - // We are sorting primitive &strs and can use unstable sort here. - split_words.sort_unstable(); - split_words.join("_") -} diff --git a/compiler/rustc_span/src/lev_distance/tests.rs b/compiler/rustc_span/src/lev_distance/tests.rs deleted file mode 100644 index ed03b22c61fd6..0000000000000 --- a/compiler/rustc_span/src/lev_distance/tests.rs +++ /dev/null @@ -1,70 +0,0 @@ -use super::*; - -#[test] -fn test_lev_distance() { - // Test bytelength agnosticity - 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)); - } - - 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 c = "Mary häd ä little lämb\n\nLittle lämb\n"; - assert_eq!(lev_distance(a, b, usize::MAX), Some(1)); - assert_eq!(lev_distance(b, a, usize::MAX), Some(1)); - assert_eq!(lev_distance(a, c, usize::MAX), Some(2)); - assert_eq!(lev_distance(c, a, usize::MAX), Some(2)); - assert_eq!(lev_distance(b, c, usize::MAX), Some(1)); - assert_eq!(lev_distance(c, b, usize::MAX), Some(1)); -} - -#[test] -fn test_lev_distance_limit() { - assert_eq!(lev_distance("abc", "abcd", 1), Some(1)); - assert_eq!(lev_distance("abc", "abcd", 0), None); - assert_eq!(lev_distance("abc", "xyz", 3), Some(3)); - assert_eq!(lev_distance("abc", "xyz", 2), None); -} - -#[test] -fn test_method_name_similarity_score() { - assert_eq!(lev_distance_with_substrings("empty", "is_empty", 1), Some(1)); - assert_eq!(lev_distance_with_substrings("shrunk", "rchunks", 2), None); - assert_eq!(lev_distance_with_substrings("abc", "abcd", 1), Some(1)); - assert_eq!(lev_distance_with_substrings("a", "abcd", 1), None); - assert_eq!(lev_distance_with_substrings("edf", "eq", 1), None); - assert_eq!(lev_distance_with_substrings("abc", "xyz", 3), Some(3)); - assert_eq!(lev_distance_with_substrings("abcdef", "abcdef", 2), Some(0)); -} - -#[test] -fn test_find_best_match_for_name() { - use crate::create_default_session_globals_then; - create_default_session_globals_then(|| { - let input = vec![Symbol::intern("aaab"), Symbol::intern("aaabc")]; - assert_eq!( - find_best_match_for_name(&input, Symbol::intern("aaaa"), None), - Some(Symbol::intern("aaab")) - ); - - assert_eq!(find_best_match_for_name(&input, Symbol::intern("1111111111"), None), None); - - let input = vec![Symbol::intern("AAAA")]; - assert_eq!( - find_best_match_for_name(&input, Symbol::intern("aaaa"), None), - Some(Symbol::intern("AAAA")) - ); - - let input = vec![Symbol::intern("AAAA")]; - assert_eq!( - find_best_match_for_name(&input, Symbol::intern("aaaa"), Some(4)), - Some(Symbol::intern("AAAA")) - ); - - let input = vec![Symbol::intern("a_longer_variable_name")]; - assert_eq!( - find_best_match_for_name(&input, Symbol::intern("a_variable_longer_name"), None), - Some(Symbol::intern("a_longer_variable_name")) - ); - }) -} diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index e095cf3fda20d..e112100aa5fcd 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -19,6 +19,7 @@ #![feature(negative_impls)] #![feature(min_specialization)] #![feature(rustc_attrs)] +#![feature(let_chains)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] @@ -46,7 +47,7 @@ pub use hygiene::{ExpnData, ExpnHash, ExpnId, LocalExpnId, SyntaxContext}; use rustc_data_structures::stable_hasher::HashingControls; pub mod def_id; use def_id::{CrateNum, DefId, DefPathHash, LocalDefId, LOCAL_CRATE}; -pub mod lev_distance; +pub mod edit_distance; mod span_encoding; pub use span_encoding::{Span, DUMMY_SP}; diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index cd00fd0daf9e1..87f077325f8be 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -457,6 +457,10 @@ impl CStr { /// to a contiguous region of memory terminated with a 0 byte to represent /// the end of the string. /// + /// The type of the returned pointer is + /// [`*const c_char`][crate::ffi::c_char], and whether it's + /// an alias for `*const i8` or `*const u8` is platform-specific. + /// /// **WARNING** /// /// The returned pointer is read-only; writing to it (including passing it @@ -470,6 +474,7 @@ impl CStr { /// # #![allow(unused_must_use)] #![allow(temporary_cstring_as_ptr)] /// use std::ffi::CString; /// + /// // Do not do this: /// let ptr = CString::new("Hello").expect("CString::new failed").as_ptr(); /// unsafe { /// // `ptr` is dangling diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 839fdc96632d1..909d9bf4093b3 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1595,3 +1595,19 @@ fn test_read_dir_infinite_loop() { // Check for duplicate errors assert!(dir.filter(|e| e.is_err()).take(2).count() < 2); } + +#[test] +fn rename_directory() { + let tmpdir = tmpdir(); + let old_path = tmpdir.join("foo/bar/baz"); + fs::create_dir_all(&old_path).unwrap(); + let test_file = &old_path.join("temp.txt"); + + File::create(test_file).unwrap(); + + let new_path = tmpdir.join("quux/blat"); + fs::create_dir_all(&new_path).unwrap(); + fs::rename(&old_path, &new_path.join("newdir")).unwrap(); + assert!(new_path.join("newdir").is_dir()); + assert!(new_path.join("newdir/temp.txt").exists()); +} diff --git a/library/std/tests/common/mod.rs b/library/std/tests/common/mod.rs new file mode 100644 index 0000000000000..fce220223a055 --- /dev/null +++ b/library/std/tests/common/mod.rs @@ -0,0 +1,58 @@ +#![allow(unused)] + +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::thread; +use rand::RngCore; + +/// Copied from `std::test_helpers::test_rng`, since these tests rely on the +/// seed not being the same for every RNG invocation too. +#[track_caller] +pub(crate) fn test_rng() -> rand_xorshift::XorShiftRng { + use core::hash::{BuildHasher, Hash, Hasher}; + let mut hasher = std::collections::hash_map::RandomState::new().build_hasher(); + core::panic::Location::caller().hash(&mut hasher); + let hc64 = hasher.finish(); + let seed_vec = hc64.to_le_bytes().into_iter().chain(0u8..8).collect::>(); + let seed: [u8; 16] = seed_vec.as_slice().try_into().unwrap(); + rand::SeedableRng::from_seed(seed) +} + +// Copied from std::sys_common::io +pub struct TempDir(PathBuf); + +impl TempDir { + pub fn join(&self, path: &str) -> PathBuf { + let TempDir(ref p) = *self; + p.join(path) + } + + pub fn path(&self) -> &Path { + let TempDir(ref p) = *self; + p + } +} + +impl Drop for TempDir { + fn drop(&mut self) { + // Gee, seeing how we're testing the fs module I sure hope that we + // at least implement this correctly! + let TempDir(ref p) = *self; + let result = fs::remove_dir_all(p); + // Avoid panicking while panicking as this causes the process to + // immediately abort, without displaying test results. + if !thread::panicking() { + result.unwrap(); + } + } +} + +#[track_caller] // for `test_rng` +pub fn tmpdir() -> TempDir { + let p = env::temp_dir(); + let mut r = test_rng(); + let ret = p.join(&format!("rust-{}", r.next_u32())); + fs::create_dir(&ret).unwrap(); + TempDir(ret) +} diff --git a/library/std/tests/create_dir_all_bare.rs b/library/std/tests/create_dir_all_bare.rs new file mode 100644 index 0000000000000..cfff421c0bd21 --- /dev/null +++ b/library/std/tests/create_dir_all_bare.rs @@ -0,0 +1,33 @@ +//! Note that this test changes the current directory so +//! should not be in the same process as other tests. +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +mod common; + +// On some platforms, setting the current directory will prevent deleting it. +// So this helper ensures the current directory is reset. +struct CurrentDir(PathBuf); +impl CurrentDir { + fn new() -> Self { + Self(env::current_dir().unwrap()) + } + fn set(&self, path: &Path) { + env::set_current_dir(path).unwrap(); + } +} +impl Drop for CurrentDir { + fn drop(&mut self) { + env::set_current_dir(&self.0).unwrap(); + } +} + +#[test] +fn create_dir_all_bare() { + let current_dir = CurrentDir::new(); + let tmpdir = common::tmpdir(); + + current_dir.set(tmpdir.path()); + fs::create_dir_all("create-dir-all-bare").unwrap(); +} diff --git a/library/std/tests/env.rs b/library/std/tests/env.rs index aae2c49d8982e..96b4f372b8b10 100644 --- a/library/std/tests/env.rs +++ b/library/std/tests/env.rs @@ -3,18 +3,8 @@ use std::ffi::{OsStr, OsString}; use rand::distributions::{Alphanumeric, DistString}; -/// Copied from `std::test_helpers::test_rng`, since these tests rely on the -/// seed not being the same for every RNG invocation too. -#[track_caller] -pub(crate) fn test_rng() -> rand_xorshift::XorShiftRng { - use core::hash::{BuildHasher, Hash, Hasher}; - let mut hasher = std::collections::hash_map::RandomState::new().build_hasher(); - core::panic::Location::caller().hash(&mut hasher); - let hc64 = hasher.finish(); - let seed_vec = hc64.to_le_bytes().into_iter().chain(0u8..8).collect::>(); - let seed: [u8; 16] = seed_vec.as_slice().try_into().unwrap(); - rand::SeedableRng::from_seed(seed) -} +mod common; +use common::test_rng; #[track_caller] fn make_rand_name() -> OsString { diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs index e7d7215166b42..f0fcdf0d5a0cd 100644 --- a/src/bootstrap/compile.rs +++ b/src/bootstrap/compile.rs @@ -111,10 +111,18 @@ impl Step for Std { let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target); if compiler_to_use != compiler { builder.ensure(Std::new(compiler_to_use, target)); - builder.info(&format!( - "Uplifting stage1 library ({} -> {})", - compiler_to_use.host, target - )); + let msg = if compiler_to_use.host == target { + format!( + "Uplifting library (stage{} -> stage{})", + compiler_to_use.stage, compiler.stage + ) + } else { + format!( + "Uplifting library (stage{}:{} -> stage{}:{})", + compiler_to_use.stage, compiler_to_use.host, compiler.stage, target + ) + }; + builder.info(&msg); // Even if we're not building std this stage, the new sysroot must // still contain the third party objects needed by various targets. @@ -134,13 +142,23 @@ impl Step for Std { cargo.arg("-p").arg(krate); } - builder.info(&format!( - "Building{} stage{} library artifacts ({} -> {})", - crate_description(&self.crates), - compiler.stage, - &compiler.host, - target, - )); + let msg = if compiler.host == target { + format!( + "Building{} stage{} library artifacts ({}) ", + crate_description(&self.crates), + compiler.stage, + compiler.host + ) + } else { + format!( + "Building{} stage{} library artifacts ({} -> {})", + crate_description(&self.crates), + compiler.stage, + compiler.host, + target, + ) + }; + builder.info(&msg); run_cargo( builder, cargo, @@ -438,10 +456,6 @@ impl Step for StdLink { let compiler = self.compiler; let target_compiler = self.target_compiler; let target = self.target; - builder.info(&format!( - "Copying stage{} library from stage{} ({} -> {} / {})", - target_compiler.stage, compiler.stage, &compiler.host, target_compiler.host, target - )); let libdir = builder.sysroot_libdir(target_compiler, target); let hostdir = builder.sysroot_libdir(target_compiler, compiler.host); add_to_sysroot(builder, &libdir, &hostdir, &libstd_stamp(builder, compiler, target)); @@ -715,8 +729,22 @@ impl Step for Rustc { let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target); if compiler_to_use != compiler { builder.ensure(Rustc::new(compiler_to_use, target)); - builder - .info(&format!("Uplifting stage1 rustc ({} -> {})", builder.config.build, target)); + let msg = if compiler_to_use.host == target { + format!( + "Uplifting rustc (stage{} -> stage{})", + compiler_to_use.stage, + compiler.stage + 1 + ) + } else { + format!( + "Uplifting rustc (stage{}:{} -> stage{}:{})", + compiler_to_use.stage, + compiler_to_use.host, + compiler.stage + 1, + target + ) + }; + builder.info(&msg); builder.ensure(RustcLink::from_rustc(self, compiler_to_use)); return; } @@ -810,13 +838,24 @@ impl Step for Rustc { cargo.arg("-p").arg(krate); } - builder.info(&format!( - "Building{} stage{} compiler artifacts ({} -> {})", - crate_description(&self.crates), - compiler.stage, - &compiler.host, - target, - )); + let msg = if compiler.host == target { + format!( + "Building{} compiler artifacts (stage{} -> stage{})", + crate_description(&self.crates), + compiler.stage, + compiler.stage + 1 + ) + } else { + format!( + "Building{} compiler artifacts (stage{}:{} -> stage{}:{})", + crate_description(&self.crates), + compiler.stage, + compiler.host, + compiler.stage + 1, + target, + ) + }; + builder.info(&msg); run_cargo( builder, cargo, @@ -1000,10 +1039,6 @@ impl Step for RustcLink { let compiler = self.compiler; let target_compiler = self.target_compiler; let target = self.target; - builder.info(&format!( - "Copying stage{} rustc from stage{} ({} -> {} / {})", - target_compiler.stage, compiler.stage, &compiler.host, target_compiler.host, target - )); add_to_sysroot( builder, &builder.sysroot_libdir(target_compiler, target), @@ -1077,10 +1112,15 @@ impl Step for CodegenBackend { let tmp_stamp = out_dir.join(".tmp.stamp"); - builder.info(&format!( - "Building stage{} codegen backend {} ({} -> {})", - compiler.stage, backend, &compiler.host, target - )); + let msg = if compiler.host == target { + format!("Building stage{} codegen backend {}", compiler.stage, backend) + } else { + format!( + "Building stage{} codegen backend {} ({} -> {})", + compiler.stage, backend, compiler.host, target + ) + }; + builder.info(&msg); let files = run_cargo(builder, cargo, vec![], &tmp_stamp, vec![], false, false); if builder.config.dry_run() { return; @@ -1386,7 +1426,12 @@ impl Step for Assemble { let stage = target_compiler.stage; let host = target_compiler.host; - builder.info(&format!("Assembling stage{} compiler ({})", stage, host)); + let msg = if build_compiler.host == host { + format!("Assembling stage{} compiler", stage) + } else { + format!("Assembling stage{} compiler ({})", stage, host) + }; + builder.info(&msg); // Link in all dylibs to the libdir let stamp = librustc_stamp(builder, build_compiler, target_compiler.host); diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index ca5f500f93bc4..d30532ef3c658 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -33,6 +33,44 @@ struct ToolBuild { allow_features: &'static str, } +fn tooling_output( + mode: Mode, + tool: &str, + build_stage: u32, + host: &TargetSelection, + target: &TargetSelection, +) -> String { + match mode { + // depends on compiler stage, different to host compiler + Mode::ToolRustc => { + if host == target { + format!("Building tool {} (stage{} -> stage{})", tool, build_stage, build_stage + 1) + } else { + format!( + "Building tool {} (stage{}:{} -> stage{}:{})", + tool, + build_stage, + host, + build_stage + 1, + target + ) + } + } + // doesn't depend on compiler, same as host compiler + Mode::ToolStd => { + if host == target { + format!("Building tool {} (stage{})", tool, build_stage) + } else { + format!( + "Building tool {} (stage{}:{} -> stage{}:{})", + tool, build_stage, host, build_stage, target + ) + } + } + _ => format!("Building tool {} (stage{})", tool, build_stage), + } +} + impl Step for ToolBuild { type Output = Option; @@ -74,8 +112,14 @@ impl Step for ToolBuild { if !self.allow_features.is_empty() { cargo.allow_features(self.allow_features); } - - builder.info(&format!("Building stage{} tool {} ({})", compiler.stage, tool, target)); + let msg = tooling_output( + self.mode, + self.tool, + self.compiler.stage, + &self.compiler.host, + &self.target, + ); + builder.info(&msg); let mut duplicates = Vec::new(); let is_expected = compile::stream_cargo(builder, cargo, vec![], &mut |msg| { // Only care about big things like the RLS/Cargo for now @@ -562,10 +606,14 @@ impl Step for Rustdoc { features.as_slice(), ); - builder.info(&format!( - "Building rustdoc for stage{} ({})", - target_compiler.stage, target_compiler.host - )); + let msg = tooling_output( + Mode::ToolRustc, + "rustdoc", + build_compiler.stage, + &self.compiler.host, + &target, + ); + builder.info(&msg); builder.run(&mut cargo.into()); // Cargo adds a number of paths to the dylib search path on windows, which results in diff --git a/tests/ui-fulldeps/create-dir-all-bare.rs b/tests/ui-fulldeps/create-dir-all-bare.rs deleted file mode 100644 index 4554680ec2470..0000000000000 --- a/tests/ui-fulldeps/create-dir-all-bare.rs +++ /dev/null @@ -1,11 +0,0 @@ -// run-pass - -use std::env; -use std::fs; -use std::path::PathBuf; - -fn main() { - let path = PathBuf::from(env::var_os("RUST_TEST_TMPDIR").unwrap()); - env::set_current_dir(&path).unwrap(); - fs::create_dir_all("create-dir-all-bare").unwrap(); -} diff --git a/tests/ui-fulldeps/rename-directory.rs b/tests/ui-fulldeps/rename-directory.rs deleted file mode 100644 index 8fc340cb91872..0000000000000 --- a/tests/ui-fulldeps/rename-directory.rs +++ /dev/null @@ -1,30 +0,0 @@ -// run-pass - -#![allow(unused_must_use)] -#![allow(unused_imports)] -// This test can't be a unit test in std, -// because it needs TempDir, which is in extra - -// ignore-cross-compile - -use std::env; -use std::ffi::CString; -use std::fs::{self, File}; -use std::path::PathBuf; - -fn rename_directory() { - let tmpdir = PathBuf::from(env::var_os("RUST_TEST_TMPDIR").unwrap()); - let old_path = tmpdir.join("foo/bar/baz"); - fs::create_dir_all(&old_path).unwrap(); - let test_file = &old_path.join("temp.txt"); - - File::create(test_file).unwrap(); - - let new_path = tmpdir.join("quux/blat"); - fs::create_dir_all(&new_path).unwrap(); - fs::rename(&old_path, &new_path.join("newdir")); - assert!(new_path.join("newdir").is_dir()); - assert!(new_path.join("newdir/temp.txt").exists()); -} - -pub fn main() { rename_directory() } diff --git a/tests/ui-fulldeps/issue-15149.rs b/tests/ui-fulldeps/std/issue-15149.rs similarity index 100% rename from tests/ui-fulldeps/issue-15149.rs rename to tests/ui-fulldeps/std/issue-15149.rs diff --git a/tests/ui-fulldeps/stdio-from.rs b/tests/ui-fulldeps/std/stdio-from.rs similarity index 100% rename from tests/ui-fulldeps/stdio-from.rs rename to tests/ui-fulldeps/std/stdio-from.rs diff --git a/tests/ui-fulldeps/switch-stdout.rs b/tests/ui-fulldeps/std/switch-stdout.rs similarity index 100% rename from tests/ui-fulldeps/switch-stdout.rs rename to tests/ui-fulldeps/std/switch-stdout.rs diff --git a/tests/ui/check-cfg/invalid-cfg-value.stderr b/tests/ui/check-cfg/invalid-cfg-value.stderr index 60abcb18824ba..83383ea61a4ab 100644 --- a/tests/ui/check-cfg/invalid-cfg-value.stderr +++ b/tests/ui/check-cfg/invalid-cfg-value.stderr @@ -2,7 +2,9 @@ warning: unexpected `cfg` condition value --> $DIR/invalid-cfg-value.rs:7:7 | LL | #[cfg(feature = "sedre")] - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^------- + | | + | help: did you mean: `"serde"` | = note: expected values for `feature` are: full, serde = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/did_you_mean/println-typo.rs b/tests/ui/did_you_mean/println-typo.rs new file mode 100644 index 0000000000000..685b5e1f28403 --- /dev/null +++ b/tests/ui/did_you_mean/println-typo.rs @@ -0,0 +1,6 @@ +// https://internals.rust-lang.org/t/18227 + +fn main() { + prinltn!(); //~ ERROR cannot find macro `prinltn` in this scope + //^ a macro with a similar name exists: `println` +} diff --git a/tests/ui/did_you_mean/println-typo.stderr b/tests/ui/did_you_mean/println-typo.stderr new file mode 100644 index 0000000000000..43b7b1894e2ae --- /dev/null +++ b/tests/ui/did_you_mean/println-typo.stderr @@ -0,0 +1,11 @@ +error: cannot find macro `prinltn` in this scope + --> $DIR/println-typo.rs:4:5 + | +LL | prinltn!(); + | ^^^^^^^ help: a macro with a similar name exists: `println` + --> $SRC_DIR/std/src/macros.rs:LL:COL + | + = note: similarly named macro `println` defined here + +error: aborting due to previous error +