use crate::late::lifetimes::{ElisionFailureInfo, LifetimeContext};
use crate::late::{AliasPossibility, LateResolutionVisitor, RibKind};
use crate::path_names_to_string;
-use crate::{CrateLint, Module, ModuleKind, ModuleOrUniformRoot};
+use crate::{Finalize, Module, ModuleKind, ModuleOrUniformRoot};
use crate::{PathResult, PathSource, Segment};
use rustc_ast::visit::FnKind;
-use rustc_ast::{self as ast, Expr, ExprKind, Item, ItemKind, NodeId, Path, Ty, TyKind};
+use rustc_ast::{
+ self as ast, AssocItemKind, Expr, ExprKind, GenericParam, GenericParamKind, Item, ItemKind,
+ NodeId, Path, Ty, TyKind,
+};
use rustc_ast_pretty::pprust::path_segment_to_string;
use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
+use rustc_errors::{
+ pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
+};
use rustc_hir as hir;
use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, CtorOf, DefKind};
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
use rustc_hir::PrimTy;
use rustc_session::parse::feature_err;
+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, MultiSpan, Span, DUMMY_SP};
+use std::iter;
+use std::ops::Deref;
+
use tracing::debug;
type Res = def::Res<ast::NodeId>;
span: Span,
source: PathSource<'_>,
res: Option<Res>,
- ) -> (DiagnosticBuilder<'a>, Vec<ImportSuggestion>) {
+ ) -> (DiagnosticBuilder<'a, ErrorGuaranteed>, Vec<ImportSuggestion>) {
let ident_span = path.last().map_or(span, |ident| ident.ident.span);
let ns = source.namespace();
let is_expected = &|res| source.is_expected(res);
let is_enum_variant = &|res| matches!(res, Res::Def(DefKind::Variant, _));
// Make the base error.
- let expected = source.descr_expected();
+ let mut expected = source.descr_expected();
let path_str = Segment::names_to_string(path);
let item_str = path.last().unwrap().ident;
let (base_msg, fallback_label, base_span, could_be_expr) = if let Some(res) = res {
let (mod_prefix, mod_str) = if path.len() == 1 {
(String::new(), "this scope".to_string())
} else if path.len() == 2 && path[0].ident.name == kw::PathRoot {
+ if self.r.session.edition() > Edition::Edition2015 {
+ // In edition 2018 onwards, the `::foo` syntax may only pull from the extern prelude
+ // which overrides all other expectations of item type
+ expected = "crate";
+ (String::new(), "the list of imported crates".to_string())
+ } else {
+ (String::new(), "the crate root".to_string())
+ }
+ } else if path.len() == 2 && path[0].ident.name == kw::Crate {
(String::new(), "the crate root".to_string())
} else {
let mod_path = &path[..path.len() - 1];
- let mod_prefix =
- match self.resolve_path(mod_path, Some(TypeNS), false, span, CrateLint::No) {
- PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(),
- _ => None,
- }
- .map_or(String::new(), |res| format!("{} ", res.descr()));
+ let mod_prefix = match self.resolve_path(mod_path, Some(TypeNS), Finalize::No) {
+ PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(),
+ _ => None,
+ }
+ .map_or_else(String::new, |res| format!("{} ", res.descr()));
(mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)))
};
(
let code = source.error_code(res.is_some());
let mut err = self.r.session.struct_span_err_with_code(base_span, &base_msg, code);
+ if let Some(span) = self.diagnostic_metadata.current_block_could_be_bare_struct_literal {
+ err.multipart_suggestion(
+ "you might have meant to write a `struct` literal",
+ vec![
+ (span.shrink_to_lo(), "{ SomeStruct ".to_string()),
+ (span.shrink_to_hi(), "}".to_string()),
+ ],
+ Applicability::HasPlaceholders,
+ );
+ }
match (source, self.diagnostic_metadata.in_if_condition) {
(PathSource::Expr(_), Some(Expr { span, kind: ExprKind::Assign(..), .. })) => {
err.span_suggestion_verbose(
"let ".to_string(),
Applicability::MaybeIncorrect,
);
- self.r.session.if_let_suggestions.borrow_mut().insert(*span);
}
_ => {}
}
- let is_assoc_fn = self.self_type_is_available(span);
+ let is_assoc_fn = self.self_type_is_available();
// Emit help message for fake-self from other languages (e.g., `this` in Javascript).
- if ["this", "my"].contains(&&*item_str.as_str()) && is_assoc_fn {
+ if ["this", "my"].contains(&item_str.as_str()) && is_assoc_fn {
err.span_suggestion_short(
span,
"you might have meant to use `self` here instead",
"self".to_string(),
Applicability::MaybeIncorrect,
);
- if !self.self_value_is_available(path[0].ident.span, span) {
+ if !self.self_value_is_available(path[0].ident.span) {
if let Some((FnKind::Fn(_, _, sig, ..), fn_span)) =
&self.diagnostic_metadata.current_function
{
}
}
+ self.detect_assoct_type_constraint_meant_as_path(base_span, &mut err);
+
// Emit special messages for unresolved `Self` and `self`.
if is_self_type(path, ns) {
err.code(rustc_errors::error_code!(E0411));
.get(0)
.map(|p| (p.span.shrink_to_lo(), "&self, "))
.unwrap_or_else(|| {
+ // Try to look for the "(" after the function name, if possible.
+ // This avoids placing the suggestion into the visibility specifier.
+ let span = fn_kind
+ .ident()
+ .map_or(*span, |ident| span.with_lo(ident.span.hi()));
(
self.r
.session
.source_map()
- .span_through_char(*span, '(')
+ .span_through_char(span, '(')
.shrink_to_hi(),
"&self",
)
let candidates = self
.r
.lookup_import_candidates(ident, ns, &self.parent_scope, is_expected)
- .drain(..)
+ .into_iter()
.filter(|ImportSuggestion { did, .. }| {
match (did, res.and_then(|res| res.opt_def_id())) {
(Some(suggestion_did), Some(actual_did)) => *suggestion_did != actual_did,
.collect::<Vec<_>>();
let crate_def_id = DefId::local(CRATE_DEF_INDEX);
if candidates.is_empty() && is_expected(Res::Def(DefKind::Enum, crate_def_id)) {
- let enum_candidates =
- self.r.lookup_import_candidates(ident, ns, &self.parent_scope, is_enum_variant);
-
+ let mut enum_candidates: Vec<_> = self
+ .r
+ .lookup_import_candidates(ident, ns, &self.parent_scope, is_enum_variant)
+ .into_iter()
+ .map(|suggestion| import_candidate_to_enum_paths(&suggestion))
+ .filter(|(_, enum_ty_path)| !enum_ty_path.starts_with("std::prelude::"))
+ .collect();
if !enum_candidates.is_empty() {
if let (PathSource::Type, Some(span)) =
(source, self.diagnostic_metadata.current_type_ascription.last())
}
}
- let mut enum_candidates = enum_candidates
- .iter()
- .map(|suggestion| import_candidate_to_enum_paths(&suggestion))
- .collect::<Vec<_>>();
enum_candidates.sort();
// Contextualize for E0412 "cannot find type", but don't belabor the point
err.span_suggestions(
span,
&msg,
- enum_candidates
- .into_iter()
- .map(|(_variant_path, enum_ty_path)| enum_ty_path)
- // Variants re-exported in prelude doesn't mean `prelude::v1` is the
- // type name!
- // FIXME: is there a more principled way to do this that
- // would work for other re-exports?
- .filter(|enum_ty_path| enum_ty_path != "std::prelude::v1")
- // Also write `Option` rather than `std::prelude::v1::Option`.
- .map(|enum_ty_path| {
- // FIXME #56861: DRY-er prelude filtering.
- enum_ty_path.trim_start_matches("std::prelude::v1::").to_owned()
- }),
+ enum_candidates.into_iter().map(|(_variant_path, enum_ty_path)| enum_ty_path),
Applicability::MachineApplicable,
);
}
}
- if path.len() == 1 && self.self_type_is_available(span) {
+ if path.len() == 1 && self.self_type_is_available() {
if let Some(candidate) = self.lookup_assoc_candidate(ident, ns, is_expected) {
- let self_is_available = self.self_value_is_available(path[0].ident.span, span);
+ let self_is_available = self.self_value_is_available(path[0].ident.span);
match candidate {
AssocSuggestion::Field => {
if self_is_available {
}
// Try Levenshtein algorithm.
- let typo_sugg = self.lookup_typo_candidate(path, ns, is_expected, span);
+ let typo_sugg = self.lookup_typo_candidate(path, ns, is_expected);
// Try context-dependent help if relaxed lookup didn't work.
if let Some(res) = res {
if self.smart_resolve_context_dependent_help(
}
}
+ let is_macro = base_span.from_expansion() && base_span.desugaring_kind().is_none();
if !self.type_ascription_suggestion(&mut err, base_span) {
let mut fallback = false;
if let (
PathSource::Trait(AliasPossibility::Maybe),
Some(Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _)),
- ) = (source, res)
+ false,
+ ) = (source, res, is_macro)
{
if let Some(bounds @ [_, .., _]) = self.diagnostic_metadata.current_trait_object {
fallback = true;
for sp in spans {
let msg = if sp == last_bound_span {
format!(
- "...because of {} bound{}",
- if bounds.len() <= 2 { "this" } else { "these" },
- if bounds.len() <= 2 { "" } else { "s" },
+ "...because of {these} bound{s}",
+ these = pluralize!("this", bounds.len() - 1),
+ s = pluralize!(bounds.len() - 1),
)
} else {
String::new()
}
_ => {}
}
+
+ // If the trait has a single item (which wasn't matched by Levenshtein), suggest it
+ let suggestion = self.get_single_associated_item(&path, &source, is_expected);
+ self.r.add_typo_suggestion(&mut err, suggestion, ident_span);
}
if fallback {
// Fallback label.
}
}
}
+ } else if err_code == &rustc_errors::error_code!(E0412) {
+ if let Some(correct) = Self::likely_rust_type(path) {
+ err.span_suggestion(
+ span,
+ "perhaps you intended to use this type",
+ correct.to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ }
}
}
(err, candidates)
}
- /// Given `where <T as Bar>::Baz: String`, suggest `where T: Bar<Baz = String>`.
- fn restrict_assoc_type_in_where_clause(
+ fn detect_assoct_type_constraint_meant_as_path(&self, base_span: Span, err: &mut Diagnostic) {
+ let Some(ty) = self.diagnostic_metadata.current_type_path else { return; };
+ let TyKind::Path(_, path) = &ty.kind else { return; };
+ for segment in &path.segments {
+ let Some(params) = &segment.args else { continue; };
+ let ast::GenericArgs::AngleBracketed(ref params) = params.deref() else { continue; };
+ for param in ¶ms.args {
+ let ast::AngleBracketedArg::Constraint(constraint) = param else { continue; };
+ let ast::AssocConstraintKind::Bound { bounds } = &constraint.kind else {
+ continue;
+ };
+ for bound in bounds {
+ let ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifier::None)
+ = bound else
+ {
+ continue;
+ };
+ if base_span == trait_ref.span {
+ err.span_suggestion_verbose(
+ constraint.ident.span.between(trait_ref.span),
+ "you might have meant to write a path instead of an associated type bound",
+ "::".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ fn get_single_associated_item(
&mut self,
- span: Span,
- err: &mut DiagnosticBuilder<'_>,
- ) -> bool {
+ path: &[Segment],
+ source: &PathSource<'_>,
+ filter_fn: &impl Fn(Res) -> bool,
+ ) -> Option<TypoSuggestion> {
+ if let crate::PathSource::TraitItem(_) = source {
+ let mod_path = &path[..path.len() - 1];
+ if let PathResult::Module(ModuleOrUniformRoot::Module(module)) =
+ self.resolve_path(mod_path, None, Finalize::No)
+ {
+ let resolutions = self.r.resolutions(module).borrow();
+ let targets: Vec<_> =
+ resolutions
+ .iter()
+ .filter_map(|(key, resolution)| {
+ resolution.borrow().binding.map(|binding| binding.res()).and_then(
+ |res| if filter_fn(res) { Some((key, res)) } else { None },
+ )
+ })
+ .collect();
+ if targets.len() == 1 {
+ let target = targets[0];
+ return Some(TypoSuggestion::single_item_from_res(
+ target.0.ident.name,
+ target.1,
+ ));
+ }
+ }
+ }
+ None
+ }
+
+ /// Given `where <T as Bar>::Baz: String`, suggest `where T: Bar<Baz = String>`.
+ fn restrict_assoc_type_in_where_clause(&mut self, span: Span, err: &mut Diagnostic) -> bool {
// Detect that we are actually in a `where` predicate.
let (bounded_ty, bounds, where_span) =
if let Some(ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
) = &bounded_ty.kind
{
// use this to verify that ident is a type param.
- let partial_res = if let Ok(Some(partial_res)) = self.resolve_qpath_anywhere(
- bounded_ty.id,
- None,
- &Segment::from_path(path),
- Namespace::TypeNS,
- span,
- true,
- CrateLint::No,
- ) {
- partial_res
- } else {
+ let Some(partial_res) = self.r.partial_res_map.get(&bounded_ty.id) else {
return false;
};
if !(matches!(
return false;
};
- if let ast::TyKind::Path(None, type_param_path) = &ty.peel_refs().kind {
+ let peeled_ty = ty.peel_refs();
+ if let ast::TyKind::Path(None, type_param_path) = &peeled_ty.kind {
// Confirm that the `SelfTy` is a type parameter.
- let partial_res = if let Ok(Some(partial_res)) = self.resolve_qpath_anywhere(
- bounded_ty.id,
- None,
- &Segment::from_path(type_param_path),
- Namespace::TypeNS,
- span,
- true,
- CrateLint::No,
- ) {
- partial_res
- } else {
+ let Some(partial_res) = self.r.partial_res_map.get(&peeled_ty.id) else {
return false;
};
if !(matches!(
args[1].span.lo(),
args.last().unwrap().span.hi(),
call_span.ctxt(),
+ None,
))
} else {
None
/// Returns `true` if able to provide context-dependent help.
fn smart_resolve_context_dependent_help(
&mut self,
- err: &mut DiagnosticBuilder<'a>,
+ err: &mut Diagnostic,
span: Span,
source: PathSource<'_>,
res: Res,
let ns = source.namespace();
let is_expected = &|res| source.is_expected(res);
- let path_sep = |err: &mut DiagnosticBuilder<'_>, expr: &Expr| match expr.kind {
+ let path_sep = |err: &mut Diagnostic, expr: &Expr| match expr.kind {
ExprKind::Field(_, ident) => {
err.span_suggestion(
expr.span,
_ => false,
};
+ let find_span = |source: &PathSource<'_>, err: &mut Diagnostic| {
+ match source {
+ PathSource::Expr(Some(Expr { span, kind: ExprKind::Call(_, _), .. }))
+ | PathSource::TupleStruct(span, _) => {
+ // We want the main underline to cover the suggested code as well for
+ // cleaner output.
+ err.set_span(*span);
+ *span
+ }
+ _ => span,
+ }
+ };
+
let mut bad_struct_syntax_suggestion = |def_id: DefId| {
let (followed_by_brace, closing_brace) = self.followed_by_brace(span);
}
}
PathSource::Expr(_) | PathSource::TupleStruct(..) | PathSource::Pat => {
- let span = match &source {
- PathSource::Expr(Some(Expr {
- span, kind: ExprKind::Call(_, _), ..
- }))
- | PathSource::TupleStruct(span, _) => {
- // We want the main underline to cover the suggested code as well for
- // cleaner output.
- err.set_span(*span);
- *span
- }
- _ => span,
- };
+ let span = find_span(&source, err);
if let Some(span) = self.def_span(def_id) {
err.span_label(span, &format!("`{}` defined here", path_str));
}
};
match (res, source) {
- (Res::Def(DefKind::Macro(MacroKind::Bang), _), _) => {
+ (
+ Res::Def(DefKind::Macro(MacroKind::Bang), _),
+ PathSource::Expr(Some(Expr {
+ kind: ExprKind::Index(..) | ExprKind::Call(..), ..
+ }))
+ | PathSource::Struct,
+ ) => {
err.span_label(span, fallback_label);
err.span_suggestion_verbose(
span.shrink_to_hi(),
err.note("if you want the `try` keyword, you need Rust 2018 or later");
}
}
+ (Res::Def(DefKind::Macro(MacroKind::Bang), _), _) => {
+ err.span_label(span, fallback_label);
+ }
(Res::Def(DefKind::TyAlias, def_id), PathSource::Trait(_)) => {
err.span_label(span, "type aliases cannot be used as traits");
if self.r.session.is_nightly_build() {
let msg = "you might have meant to use `#![feature(trait_alias)]` instead of a \
`type` alias";
if let Some(span) = self.def_span(def_id) {
- err.span_help(span, msg);
+ if let Ok(snip) = self.r.session.source_map().span_to_snippet(span) {
+ // The span contains a type alias so we should be able to
+ // replace `type` with `trait`.
+ let snip = snip.replacen("type", "trait", 1);
+ err.span_suggestion(span, msg, snip, Applicability::MaybeIncorrect);
+ } else {
+ err.span_help(span, msg);
+ }
} else {
err.help(msg);
}
})
.unwrap_or(false)
{
- err.delay_as_bug();
+ err.downgrade_to_delayed_bug();
// We already suggested changing `:` into `::` during parsing.
return false;
}
self.suggest_using_enum_variant(err, source, def_id, span);
}
- (Res::Def(DefKind::Struct, def_id), _) if ns == ValueNS => {
+ (Res::Def(DefKind::Struct, def_id), source) if ns == ValueNS => {
let (ctor_def, ctor_vis, fields) =
if let Some(struct_ctor) = self.r.struct_constructors.get(&def_id).cloned() {
+ if let PathSource::Expr(Some(parent)) = source {
+ if let ExprKind::Field(..) | ExprKind::MethodCall(..) = parent.kind {
+ bad_struct_syntax_suggestion(def_id);
+ return true;
+ }
+ }
struct_ctor
} else {
bad_struct_syntax_suggestion(def_id);
if let Some(spans) =
field_spans.filter(|spans| spans.len() > 0 && fields.len() == spans.len())
{
- let non_visible_spans: Vec<Span> = fields
- .iter()
- .zip(spans.iter())
+ let non_visible_spans: Vec<Span> = iter::zip(&fields, &spans)
.filter(|(vis, _)| {
!self.r.is_accessible_from(**vis, self.parent_scope.module)
})
) if ns == ValueNS => {
bad_struct_syntax_suggestion(def_id);
}
+ (Res::Def(DefKind::Ctor(_, CtorKind::Const), def_id), _) if ns == ValueNS => {
+ match source {
+ PathSource::Expr(_) | PathSource::TupleStruct(..) | PathSource::Pat => {
+ let span = find_span(&source, err);
+ if let Some(span) = self.def_span(def_id) {
+ err.span_label(span, &format!("`{}` defined here", path_str));
+ }
+ err.span_suggestion(
+ span,
+ &"use this syntax instead",
+ path_str.to_string(),
+ Applicability::MaybeIncorrect,
+ );
+ }
+ _ => return false,
+ }
+ }
(Res::Def(DefKind::Ctor(_, CtorKind::Fn), def_id), _) if ns == ValueNS => {
if let Some(span) = self.def_span(def_id) {
err.span_label(span, &format!("`{}` defined here", path_str));
}
- let fields =
- self.r.field_names.get(&def_id).map_or("/* fields */".to_string(), |fields| {
- vec!["_"; fields.len()].join(", ")
- });
+ let fields = self.r.field_names.get(&def_id).map_or_else(
+ || "/* fields */".to_string(),
+ |fields| vec!["_"; fields.len()].join(", "),
+ );
err.span_suggestion(
span,
"use the tuple variant pattern syntax instead",
Applicability::HasPlaceholders,
);
}
- (Res::SelfTy(..), _) if ns == ValueNS => {
+ (Res::SelfTy { .. }, _) if ns == ValueNS => {
err.span_label(span, fallback_label);
err.note("can't use `Self` as a constructor, you must use the implemented struct");
}
true
}
+ /// Given the target `ident` and `kind`, search for the similarly named associated item
+ /// in `self.current_trait_ref`.
+ crate fn find_similarly_named_assoc_item(
+ &mut self,
+ ident: Symbol,
+ kind: &AssocItemKind,
+ ) -> Option<Symbol> {
+ let Some((module, _)) = &self.current_trait_ref else {
+ return None;
+ };
+ if ident == kw::Underscore {
+ // We do nothing for `_`.
+ return None;
+ }
+
+ let resolutions = self.r.resolutions(module);
+ let targets = resolutions
+ .borrow()
+ .iter()
+ .filter_map(|(key, res)| res.borrow().binding.map(|binding| (key, binding.res())))
+ .filter(|(_, res)| match (kind, res) {
+ (AssocItemKind::Const(..), Res::Def(DefKind::AssocConst, _)) => true,
+ (AssocItemKind::Fn(_), Res::Def(DefKind::AssocFn, _)) => true,
+ (AssocItemKind::TyAlias(..), Res::Def(DefKind::AssocTy, _)) => true,
+ _ => false,
+ })
+ .map(|(key, _)| key.ident.name)
+ .collect::<Vec<_>>();
+
+ find_best_match_for_name(&targets, ident, None)
+ }
+
fn lookup_assoc_candidate<FilterFn>(
&mut self,
ident: Ident,
}
if let Some(items) = self.diagnostic_metadata.current_trait_assoc_items {
- for assoc_item in &items[..] {
+ for assoc_item in items {
if assoc_item.ident == ident {
return Some(match &assoc_item.kind {
ast::AssocItemKind::Const(..) => AssocSuggestion::AssocConst,
- ast::AssocItemKind::Fn(box ast::FnKind(_, sig, ..))
- if sig.decl.has_self() =>
- {
+ ast::AssocItemKind::Fn(box ast::Fn { sig, .. }) if sig.decl.has_self() => {
AssocSuggestion::MethodWithSelf
}
ast::AssocItemKind::Fn(..) => AssocSuggestion::AssocFn,
ident,
ns,
&self.parent_scope,
- false,
- module.span,
+ None,
) {
let res = binding.res();
if filter_fn(res) {
path: &[Segment],
ns: Namespace,
filter_fn: &impl Fn(Res) -> bool,
- span: Span,
) -> Option<TypoSuggestion> {
let mut names = Vec::new();
if path.len() == 1 {
// Locals and type parameters
for (ident, &res) in &rib.bindings {
if filter_fn(res) {
- names.push(TypoSuggestion::from_res(ident.name, res));
+ names.push(TypoSuggestion::typo_from_res(ident.name, res));
}
}
// Items in scope
);
if filter_fn(crate_mod) {
- Some(TypoSuggestion::from_res(ident.name, crate_mod))
+ Some(TypoSuggestion::typo_from_res(
+ ident.name, crate_mod,
+ ))
} else {
None
}
}
// Add primitive types to the mix
if filter_fn(Res::PrimTy(PrimTy::Bool)) {
- names.extend(
- self.r.primitive_type_table.primitive_types.iter().map(|(name, prim_ty)| {
- TypoSuggestion::from_res(*name, Res::PrimTy(*prim_ty))
- }),
- )
+ names.extend(PrimTy::ALL.iter().map(|prim_ty| {
+ TypoSuggestion::typo_from_res(prim_ty.name(), Res::PrimTy(*prim_ty))
+ }))
}
} else {
// Search in module.
let mod_path = &path[..path.len() - 1];
- if let PathResult::Module(module) =
- self.resolve_path(mod_path, Some(TypeNS), false, span, CrateLint::No)
+ if let PathResult::Module(ModuleOrUniformRoot::Module(module)) =
+ self.resolve_path(mod_path, Some(TypeNS), Finalize::No)
{
- if let ModuleOrUniformRoot::Module(module) = module {
- self.r.add_module_candidates(module, &mut names, &filter_fn);
- }
+ self.r.add_module_candidates(module, &mut names, &filter_fn);
}
}
let name = path[path.len() - 1].ident.name;
// Make sure error reporting is deterministic.
- names.sort_by_cached_key(|suggestion| suggestion.candidate.as_str());
+ names.sort_by(|a, b| a.candidate.as_str().partial_cmp(b.candidate.as_str()).unwrap());
match find_best_match_for_name(
&names.iter().map(|suggestion| suggestion.candidate).collect::<Vec<Symbol>>(),
}
}
+ // Returns the name of the Rust type approximately corresponding to
+ // a type name in another programming language.
+ fn likely_rust_type(path: &[Segment]) -> Option<Symbol> {
+ let name = path[path.len() - 1].ident.as_str();
+ // Common Java types
+ Some(match name {
+ "byte" => sym::u8, // In Java, bytes are signed, but in practice one almost always wants unsigned bytes.
+ "short" => sym::i16,
+ "boolean" => sym::bool,
+ "int" => sym::i32,
+ "long" => sym::i64,
+ "float" => sym::f32,
+ "double" => sym::f64,
+ _ => return None,
+ })
+ }
+
/// Only used in a specific case of type ascription suggestions
fn get_colon_suggestion_span(&self, start: Span) -> Span {
let sm = self.r.session.source_map();
start.to(sm.next_point(start))
}
- fn type_ascription_suggestion(&self, err: &mut DiagnosticBuilder<'_>, base_span: Span) -> bool {
+ fn type_ascription_suggestion(&self, err: &mut Diagnostic, base_span: Span) -> bool {
let sm = self.r.session.source_map();
let base_snippet = sm.span_to_snippet(base_span);
if let Some(&sp) = self.diagnostic_metadata.current_type_ascription.last() {
.borrow_mut()
.insert(colon_sp)
{
- err.delay_as_bug();
+ err.downgrade_to_delayed_bug();
}
}
if let Ok(base_snippet) = base_snippet {
// form the path
let mut path_segments = path_segments.clone();
path_segments.push(ast::PathSegment::from_ident(ident));
- let module_def_id = module.def_id().unwrap();
+ let module_def_id = module.def_id();
if module_def_id == def_id {
let path =
Path { span: name_binding.span, segments: path_segments, tokens: None };
descr: "module",
path,
accessible: true,
+ note: None,
},
));
} else {
/// Adds a suggestion for using an enum's variant when an enum is used instead.
fn suggest_using_enum_variant(
&mut self,
- err: &mut DiagnosticBuilder<'a>,
+ err: &mut Diagnostic,
source: PathSource<'_>,
def_id: DefId,
span: Span,
) {
- let variants = match self.collect_enum_ctors(def_id) {
- Some(variants) => variants,
- None => {
- err.note("you might have meant to use one of the enum's variants");
- return;
- }
+ let Some(variants) = self.collect_enum_ctors(def_id) else {
+ err.note("you might have meant to use one of the enum's variants");
+ return;
};
let suggest_only_tuple_variants =
matches!(source, PathSource::TupleStruct(..)) || source.is_call();
if suggest_only_tuple_variants {
// Suggest only tuple variants regardless of whether they have fields and do not
- // suggest path with added parenthesis.
- let mut suggestable_variants = variants
+ // suggest path with added parentheses.
+ let suggestable_variants = variants
.iter()
.filter(|(.., kind)| *kind == CtorKind::Fn)
.map(|(variant, ..)| path_names_to_string(variant))
err.span_suggestions(
span,
&msg,
- suggestable_variants.drain(..),
+ suggestable_variants.into_iter(),
Applicability::MaybeIncorrect,
);
}
);
}
- let mut suggestable_variants_with_placeholders = variants
+ let suggestable_variants_with_placeholders = variants
.iter()
.filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind))
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
err.span_suggestions(
span,
msg,
- suggestable_variants_with_placeholders.drain(..),
+ suggestable_variants_with_placeholders.into_iter(),
Applicability::HasPlaceholders,
);
}
if !self.diagnostic_metadata.currently_processing_generics && !single_uppercase_char {
return None;
}
- match (self.diagnostic_metadata.current_item, single_uppercase_char) {
- (Some(Item { kind: ItemKind::Fn(..), ident, .. }), _) if ident.name == sym::main => {
+ match (self.diagnostic_metadata.current_item, single_uppercase_char, self.diagnostic_metadata.currently_processing_generics) {
+ (Some(Item { kind: ItemKind::Fn(..), ident, .. }), _, _) if ident.name == sym::main => {
// Ignore `fn main()` as we don't want to suggest `fn main<T>()`
}
(
| kind @ ItemKind::Union(..),
..
}),
- true,
+ true, _
)
- | (Some(Item { kind, .. }), false) => {
+ // Without the 2nd `true`, we'd suggest `impl <T>` for `impl T` when a type `T` isn't found
+ | (Some(Item { kind: kind @ ItemKind::Impl(..), .. }), true, true)
+ | (Some(Item { kind, .. }), false, _) => {
// Likely missing type parameter.
if let Some(generics) = kind.generics() {
if span.overlaps(generics.span) {
let (span, sugg) = if let [.., param] = &generics.params[..] {
let span = if let [.., bound] = ¶m.bounds[..] {
bound.span()
+ } else if let GenericParam {
+ kind: GenericParamKind::Const { ty, kw_span: _, default }, ..
+ } = param {
+ default.as_ref().map(|def| def.value.span).unwrap_or(ty.span)
} else {
param.ident.span
};
(generics.span, format!("<{}>", ident))
};
// Do not suggest if this is coming from macro expansion.
- if !span.from_expansion() {
+ if span.can_be_used_for_suggestions() {
return Some((
span.shrink_to_hi(),
msg,
impl<'tcx> LifetimeContext<'_, 'tcx> {
crate fn report_missing_lifetime_specifiers(
&self,
- span: Span,
+ spans: Vec<Span>,
count: usize,
- ) -> DiagnosticBuilder<'tcx> {
+ ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
struct_span_err!(
self.tcx.sess,
- span,
+ spans,
E0106,
"missing lifetime specifier{}",
pluralize!(count)
lifetime_ref
);
err.span_label(lifetime_ref.span, "undeclared lifetime");
- let mut suggests_in_band = false;
+ let mut suggested_spans = vec![];
for missing in &self.missing_named_lifetime_spots {
match missing {
MissingLifetimeSpot::Generics(generics) => {
let (span, sugg) = if let Some(param) = generics.params.iter().find(|p| {
!matches!(
p.kind,
- hir::GenericParamKind::Type {
- synthetic: Some(hir::SyntheticTyParamKind::ImplTrait),
- ..
- } | hir::GenericParamKind::Lifetime {
- kind: hir::LifetimeParamKind::Elided,
- }
+ hir::GenericParamKind::Type { synthetic: true, .. }
+ | hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Elided,
+ }
)
}) {
(param.span.shrink_to_lo(), format!("{}, ", lifetime_ref))
} else {
- suggests_in_band = true;
(generics.span, format!("<{}>", lifetime_ref))
};
- err.span_suggestion(
- span,
- &format!("consider introducing lifetime `{}` here", lifetime_ref),
- sugg,
- Applicability::MaybeIncorrect,
- );
+ if suggested_spans.contains(&span) {
+ continue;
+ }
+ suggested_spans.push(span);
+ if span.can_be_used_for_suggestions() {
+ err.span_suggestion(
+ span,
+ &format!("consider introducing lifetime `{}` here", lifetime_ref),
+ sugg,
+ Applicability::MaybeIncorrect,
+ );
+ }
}
MissingLifetimeSpot::HigherRanked { span, span_type } => {
err.span_suggestion(
);
err.note(
"for more information on higher-ranked polymorphism, visit \
- https://doc.rust-lang.org/nomicon/hrtb.html",
+ https://doc.rust-lang.org/nomicon/hrtb.html",
);
}
_ => {}
}
}
- if self.tcx.sess.is_nightly_build()
- && !self.tcx.features().in_band_lifetimes
- && suggests_in_band
- {
- err.help(
- "if you want to experiment with in-band lifetime bindings, \
- add `#![feature(in_band_lifetimes)]` to the crate attributes",
+ err.emit();
+ }
+
+ /// Returns whether to add `'static` lifetime to the suggested lifetime list.
+ crate fn report_elision_failure(
+ &mut self,
+ // FIXME(eddyb) rename this since it's no longer a `DiagnosticBuilder`.
+ db: &mut Diagnostic,
+ params: &[ElisionFailureInfo],
+ ) -> bool {
+ let mut m = String::new();
+ let len = params.len();
+
+ let elided_params: Vec<_> =
+ params.iter().cloned().filter(|info| info.lifetime_count > 0).collect();
+
+ let elided_len = elided_params.len();
+
+ for (i, info) in elided_params.into_iter().enumerate() {
+ let ElisionFailureInfo { parent, index, lifetime_count: n, have_bound_regions, span } =
+ info;
+
+ db.span_label(span, "");
+ let help_name = if let Some(ident) =
+ parent.and_then(|body| self.tcx.hir().body(body).params[index].pat.simple_ident())
+ {
+ format!("`{}`", ident)
+ } else {
+ format!("argument {}", index + 1)
+ };
+
+ m.push_str(
+ &(if n == 1 {
+ help_name
+ } else {
+ format!(
+ "one of {}'s {} {}lifetimes",
+ help_name,
+ n,
+ if have_bound_regions { "free " } else { "" }
+ )
+ })[..],
);
+
+ if elided_len == 2 && i == 0 {
+ m.push_str(" or ");
+ } else if i + 2 == elided_len {
+ m.push_str(", or ");
+ } else if i != elided_len - 1 {
+ m.push_str(", ");
+ }
+ }
+
+ if len == 0 {
+ db.help(
+ "this function's return type contains a borrowed value, \
+ but there is no value for it to be borrowed from",
+ );
+ true
+ } else if elided_len == 0 {
+ db.help(
+ "this function's return type contains a borrowed value with \
+ an elided lifetime, but the lifetime cannot be derived from \
+ the arguments",
+ );
+ true
+ } else if elided_len == 1 {
+ db.help(&format!(
+ "this function's return type contains a borrowed value, \
+ but the signature does not say which {} it is borrowed from",
+ m
+ ));
+ false
+ } else {
+ db.help(&format!(
+ "this function's return type contains a borrowed value, \
+ but the signature does not say whether it is borrowed from {}",
+ m
+ ));
+ false
}
- err.emit();
}
- // FIXME(const_generics): This patches over a ICE caused by non-'static lifetimes in const
+ crate fn report_elided_lifetime_in_ty(&self, lifetime_refs: &[&hir::Lifetime]) {
+ let Some(missing_lifetime) = lifetime_refs.iter().find(|lt| {
+ lt.name == hir::LifetimeName::Implicit(true)
+ }) else { return };
+
+ let mut spans: Vec<_> = lifetime_refs.iter().map(|lt| lt.span).collect();
+ spans.sort();
+ let mut spans_dedup = spans.clone();
+ spans_dedup.dedup();
+ let spans_with_counts: Vec<_> = spans_dedup
+ .into_iter()
+ .map(|sp| (sp, spans.iter().filter(|nsp| *nsp == &sp).count()))
+ .collect();
+
+ self.tcx.struct_span_lint_hir(
+ rustc_session::lint::builtin::ELIDED_LIFETIMES_IN_PATHS,
+ missing_lifetime.hir_id,
+ spans,
+ |lint| {
+ let mut db = lint.build("hidden lifetime parameters in types are deprecated");
+ self.add_missing_lifetime_specifiers_label(
+ &mut db,
+ spans_with_counts,
+ &FxHashSet::from_iter([kw::UnderscoreLifetime]),
+ Vec::new(),
+ &[],
+ );
+ db.emit();
+ },
+ );
+ }
+
+ // FIXME(const_generics): This patches over an ICE caused by non-'static lifetimes in const
// generics. We are disallowing this until we can decide on how we want to handle non-'static
// lifetimes in const generics. See issue #74052 for discussion.
crate fn emit_non_static_lt_in_const_generic_error(&self, lifetime_ref: &hir::Lifetime) {
crate fn add_missing_lifetime_specifiers_label(
&self,
- err: &mut DiagnosticBuilder<'_>,
- span: Span,
- count: usize,
+ err: &mut Diagnostic,
+ mut spans_with_counts: Vec<(Span, usize)>,
lifetime_names: &FxHashSet<Symbol>,
lifetime_spans: Vec<Span>,
params: &[ElisionFailureInfo],
) {
- let snippet = self.tcx.sess.source_map().span_to_snippet(span).ok();
-
- err.span_label(
- span,
- &format!(
- "expected {} lifetime parameter{}",
- if count == 1 { "named".to_string() } else { count.to_string() },
- pluralize!(count)
- ),
- );
+ let snippets: Vec<Option<String>> = spans_with_counts
+ .iter()
+ .map(|(span, _)| self.tcx.sess.source_map().span_to_snippet(*span).ok())
+ .collect();
+
+ // Empty generics are marked with a span of "<", but since from now on
+ // that information is in the snippets it can be removed from the spans.
+ for ((span, _), snippet) in spans_with_counts.iter_mut().zip(&snippets) {
+ if snippet.as_deref() == Some("<") {
+ *span = span.shrink_to_hi();
+ }
+ }
- let suggest_existing = |err: &mut DiagnosticBuilder<'_>,
- name: &str,
- formatter: &dyn Fn(&str) -> String| {
- if let Some(MissingLifetimeSpot::HigherRanked { span: for_span, span_type }) =
- self.missing_named_lifetime_spots.iter().rev().next()
- {
- // When we have `struct S<'a>(&'a dyn Fn(&X) -> &X);` we want to not only suggest
- // using `'a`, but also introduce the concept of HRLTs by suggesting
- // `struct S<'a>(&'a dyn for<'b> Fn(&X) -> &'b X);`. (#72404)
- let mut introduce_suggestion = vec![];
+ for &(span, count) in &spans_with_counts {
+ err.span_label(
+ span,
+ format!(
+ "expected {} lifetime parameter{}",
+ if count == 1 { "named".to_string() } else { count.to_string() },
+ pluralize!(count),
+ ),
+ );
+ }
- let a_to_z_repeat_n = |n| {
- (b'a'..=b'z').map(move |c| {
- let mut s = '\''.to_string();
- s.extend(std::iter::repeat(char::from(c)).take(n));
- s
- })
- };
+ let suggest_existing =
+ |err: &mut Diagnostic,
+ name: &str,
+ formatters: Vec<Option<Box<dyn Fn(&str) -> String>>>| {
+ if let Some(MissingLifetimeSpot::HigherRanked { span: for_span, span_type }) =
+ self.missing_named_lifetime_spots.iter().rev().next()
+ {
+ // When we have `struct S<'a>(&'a dyn Fn(&X) -> &X);` we want to not only suggest
+ // using `'a`, but also introduce the concept of HRLTs by suggesting
+ // `struct S<'a>(&'a dyn for<'b> Fn(&X) -> &'b X);`. (#72404)
+ let mut introduce_suggestion = vec![];
+
+ let a_to_z_repeat_n = |n| {
+ (b'a'..=b'z').map(move |c| {
+ let mut s = '\''.to_string();
+ s.extend(std::iter::repeat(char::from(c)).take(n));
+ s
+ })
+ };
- // If all single char lifetime names are present, we wrap around and double the chars.
- let lt_name = (1..)
- .flat_map(a_to_z_repeat_n)
- .find(|lt| !lifetime_names.contains(&Symbol::intern(<)))
- .unwrap();
- let msg = format!(
- "consider making the {} lifetime-generic with a new `{}` lifetime",
- span_type.descr(),
- lt_name,
- );
- err.note(
- "for more information on higher-ranked polymorphism, visit \
+ // If all single char lifetime names are present, we wrap around and double the chars.
+ let lt_name = (1..)
+ .flat_map(a_to_z_repeat_n)
+ .find(|lt| !lifetime_names.contains(&Symbol::intern(<)))
+ .unwrap();
+ let msg = format!(
+ "consider making the {} lifetime-generic with a new `{}` lifetime",
+ span_type.descr(),
+ lt_name,
+ );
+ err.note(
+ "for more information on higher-ranked polymorphism, visit \
https://doc.rust-lang.org/nomicon/hrtb.html",
- );
- let for_sugg = span_type.suggestion(<_name);
- for param in params {
- if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(param.span) {
- if snippet.starts_with('&') && !snippet.starts_with("&'") {
- introduce_suggestion
- .push((param.span, format!("&{} {}", lt_name, &snippet[1..])));
- } else if let Some(stripped) = snippet.strip_prefix("&'_ ") {
- introduce_suggestion
- .push((param.span, format!("&{} {}", lt_name, stripped)));
+ );
+ let for_sugg = span_type.suggestion(<_name);
+ for param in params {
+ if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(param.span)
+ {
+ if snippet.starts_with('&') && !snippet.starts_with("&'") {
+ introduce_suggestion
+ .push((param.span, format!("&{} {}", lt_name, &snippet[1..])));
+ } else if let Some(stripped) = snippet.strip_prefix("&'_ ") {
+ introduce_suggestion
+ .push((param.span, format!("&{} {}", lt_name, stripped)));
+ }
+ }
+ }
+ introduce_suggestion.push((*for_span, for_sugg));
+ for ((span, _), formatter) in spans_with_counts.iter().zip(formatters.iter()) {
+ if let Some(formatter) = formatter {
+ introduce_suggestion.push((*span, formatter(<_name)));
}
}
+ err.multipart_suggestion_verbose(
+ &msg,
+ introduce_suggestion,
+ Applicability::MaybeIncorrect,
+ );
}
- introduce_suggestion.push((*for_span, for_sugg));
- introduce_suggestion.push((span, formatter(<_name)));
- err.multipart_suggestion(&msg, introduce_suggestion, Applicability::MaybeIncorrect);
- }
- err.span_suggestion_verbose(
- span,
- &format!("consider using the `{}` lifetime", lifetime_names.iter().next().unwrap()),
- formatter(name),
- Applicability::MaybeIncorrect,
- );
- };
- let suggest_new = |err: &mut DiagnosticBuilder<'_>, sugg: &str| {
+ let spans_suggs: Vec<_> = formatters
+ .into_iter()
+ .zip(spans_with_counts.iter())
+ .filter_map(|(formatter, (span, _))| {
+ if let Some(formatter) = formatter {
+ Some((*span, formatter(name)))
+ } else {
+ None
+ }
+ })
+ .collect();
+ if spans_suggs.is_empty() {
+ // If all the spans come from macros, we cannot extract snippets and then
+ // `formatters` only contains None and `spans_suggs` is empty.
+ return;
+ }
+ err.multipart_suggestion_verbose(
+ &format!(
+ "consider using the `{}` lifetime",
+ lifetime_names.iter().next().unwrap()
+ ),
+ spans_suggs,
+ Applicability::MaybeIncorrect,
+ );
+ };
+ let suggest_new = |err: &mut Diagnostic, suggs: Vec<Option<String>>| {
for missing in self.missing_named_lifetime_spots.iter().rev() {
let mut introduce_suggestion = vec![];
let msg;
// but if we make a mistake elsewhere, mainly by keeping something in
// `missing_named_lifetime_spots` that we shouldn't, like associated
// `const`s or making a mistake in the AST lowering we would provide
- // non-sensical suggestions. Guard against that by skipping these.
+ // nonsensical suggestions. Guard against that by skipping these.
// (#74264)
continue;
}
if let Some(param) = generics.params.iter().find(|p| {
!matches!(
p.kind,
- hir::GenericParamKind::Type {
- synthetic: Some(hir::SyntheticTyParamKind::ImplTrait),
- ..
- }
+ hir::GenericParamKind::Type { synthetic: true, .. }
+ | hir::GenericParamKind::Lifetime {
+ kind: hir::LifetimeParamKind::Elided
+ }
)
}) {
(param.span.shrink_to_lo(), "'a, ".to_string())
(*span, span_type.suggestion("'a"))
}
MissingLifetimeSpot::Static => {
- let (span, sugg) = match snippet.as_deref() {
- Some("&") => (span.shrink_to_hi(), "'static ".to_owned()),
- Some("'_") => (span, "'static".to_owned()),
- Some(snippet) if !snippet.ends_with('>') => {
- if snippet == "" {
- (
- span,
- std::iter::repeat("'static")
- .take(count)
- .collect::<Vec<_>>()
- .join(", "),
- )
- } else {
- (
- span.shrink_to_hi(),
- format!(
- "<{}>",
+ let mut spans_suggs = Vec::new();
+ for ((span, count), snippet) in
+ spans_with_counts.iter().copied().zip(snippets.iter())
+ {
+ let (span, sugg) = match snippet.as_deref() {
+ Some("&") => (span.shrink_to_hi(), "'static ".to_owned()),
+ Some("'_") => (span, "'static".to_owned()),
+ Some(snippet) if !snippet.ends_with('>') => {
+ if snippet == "" {
+ (
+ span,
std::iter::repeat("'static")
.take(count)
.collect::<Vec<_>>()
- .join(", ")
- ),
- )
+ .join(", "),
+ )
+ } else if snippet == "<" || snippet == "(" {
+ (
+ span.shrink_to_hi(),
+ std::iter::repeat("'static")
+ .take(count)
+ .collect::<Vec<_>>()
+ .join(", "),
+ )
+ } else {
+ (
+ span.shrink_to_hi(),
+ format!(
+ "<{}>",
+ std::iter::repeat("'static")
+ .take(count)
+ .collect::<Vec<_>>()
+ .join(", "),
+ ),
+ )
+ }
}
- }
- _ => continue,
- };
- err.span_suggestion_verbose(
- span,
+ _ => continue,
+ };
+ spans_suggs.push((span, sugg.to_string()));
+ }
+ err.multipart_suggestion_verbose(
"consider using the `'static` lifetime",
- sugg.to_string(),
+ spans_suggs,
Applicability::MaybeIncorrect,
);
continue;
}
});
+
+ struct Lifetime(Span, String);
+ impl Lifetime {
+ fn is_unnamed(&self) -> bool {
+ self.1.starts_with('&') && !self.1.starts_with("&'")
+ }
+ fn is_underscore(&self) -> bool {
+ self.1.starts_with("&'_ ")
+ }
+ fn is_named(&self) -> bool {
+ self.1.starts_with("&'")
+ }
+ fn suggestion(&self, sugg: String) -> Option<(Span, String)> {
+ Some(
+ match (
+ self.is_unnamed(),
+ self.is_underscore(),
+ self.is_named(),
+ sugg.starts_with('&'),
+ ) {
+ (true, _, _, false) => (self.span_unnamed_borrow(), sugg),
+ (true, _, _, true) => {
+ (self.span_unnamed_borrow(), sugg[1..].to_string())
+ }
+ (_, true, _, false) => {
+ (self.span_underscore_borrow(), sugg.trim().to_string())
+ }
+ (_, true, _, true) => {
+ (self.span_underscore_borrow(), sugg[1..].trim().to_string())
+ }
+ (_, _, true, false) => {
+ (self.span_named_borrow(), sugg.trim().to_string())
+ }
+ (_, _, true, true) => {
+ (self.span_named_borrow(), sugg[1..].trim().to_string())
+ }
+ _ => return None,
+ },
+ )
+ }
+ fn span_unnamed_borrow(&self) -> Span {
+ let lo = self.0.lo() + BytePos(1);
+ self.0.with_lo(lo).with_hi(lo)
+ }
+ fn span_named_borrow(&self) -> Span {
+ let lo = self.0.lo() + BytePos(1);
+ self.0.with_lo(lo)
+ }
+ fn span_underscore_borrow(&self) -> Span {
+ let lo = self.0.lo() + BytePos(1);
+ let hi = lo + BytePos(2);
+ self.0.with_lo(lo).with_hi(hi)
+ }
+ }
+
for param in params {
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(param.span) {
- if snippet.starts_with('&') && !snippet.starts_with("&'") {
- introduce_suggestion
- .push((param.span, format!("&'a {}", &snippet[1..])));
- } else if let Some(stripped) = snippet.strip_prefix("&'_ ") {
- introduce_suggestion.push((param.span, format!("&'a {}", &stripped)));
+ if let Some((span, sugg)) =
+ Lifetime(param.span, snippet).suggestion("'a ".to_string())
+ {
+ introduce_suggestion.push((span, sugg));
}
}
}
- introduce_suggestion.push((span, sugg.to_string()));
- err.multipart_suggestion(&msg, introduce_suggestion, Applicability::MaybeIncorrect);
+ for (span, sugg) in spans_with_counts.iter().copied().zip(suggs.iter()).filter_map(
+ |((span, _), sugg)| match &sugg {
+ Some(sugg) => Some((span, sugg.to_string())),
+ _ => None,
+ },
+ ) {
+ let (span, sugg) = self
+ .tcx
+ .sess
+ .source_map()
+ .span_to_snippet(span)
+ .ok()
+ .and_then(|snippet| Lifetime(span, snippet).suggestion(sugg.clone()))
+ .unwrap_or((span, sugg));
+ introduce_suggestion.push((span, sugg.to_string()));
+ }
+ err.multipart_suggestion_verbose(
+ &msg,
+ introduce_suggestion,
+ Applicability::MaybeIncorrect,
+ );
if should_break {
break;
}
};
let lifetime_names: Vec<_> = lifetime_names.iter().collect();
- match (&lifetime_names[..], snippet.as_deref()) {
- ([name], Some("&")) => {
- suggest_existing(err, &name.as_str()[..], &|name| format!("&{} ", name));
- }
- ([name], Some("'_")) => {
- suggest_existing(err, &name.as_str()[..], &|n| n.to_string());
- }
- ([name], Some("")) => {
- suggest_existing(err, &name.as_str()[..], &|n| format!("{}, ", n).repeat(count));
- }
- ([name], Some(snippet)) if !snippet.ends_with('>') => {
- let f = |name: &str| {
- format!(
- "{}<{}>",
- snippet,
- std::iter::repeat(name.to_string())
- .take(count)
- .collect::<Vec<_>>()
- .join(", ")
- )
- };
- suggest_existing(err, &name.as_str()[..], &f);
- }
- ([], Some("&")) if count == 1 => {
- suggest_new(err, "&'a ");
- }
- ([], Some("'_")) if count == 1 => {
- suggest_new(err, "'a");
+ match &lifetime_names[..] {
+ [name] => {
+ let mut suggs: Vec<Option<Box<dyn Fn(&str) -> String>>> = Vec::new();
+ for (snippet, (_, count)) in snippets.iter().zip(spans_with_counts.iter().copied())
+ {
+ suggs.push(match snippet.as_deref() {
+ Some("&") => Some(Box::new(|name| format!("&{} ", name))),
+ Some("'_") => Some(Box::new(|n| n.to_string())),
+ Some("") => Some(Box::new(move |n| format!("{}, ", n).repeat(count))),
+ Some("<") => Some(Box::new(move |n| {
+ std::iter::repeat(n).take(count).collect::<Vec<_>>().join(", ")
+ })),
+ Some(snippet) if !snippet.ends_with('>') => Some(Box::new(move |name| {
+ format!(
+ "{}<{}>",
+ snippet,
+ std::iter::repeat(name.to_string())
+ .take(count)
+ .collect::<Vec<_>>()
+ .join(", ")
+ )
+ })),
+ _ => None,
+ });
+ }
+ suggest_existing(err, name.as_str(), suggs);
}
- ([], Some(snippet)) if !snippet.ends_with('>') => {
- if snippet == "" {
- // This happens when we have `type Bar<'a> = Foo<T>` where we point at the space
- // before `T`. We will suggest `type Bar<'a> = Foo<'a, T>`.
- suggest_new(
- err,
- &std::iter::repeat("'a, ").take(count).collect::<Vec<_>>().join(""),
- );
- } else {
- suggest_new(
- err,
- &format!(
+ [] => {
+ let mut suggs = Vec::new();
+ for (snippet, (_, count)) in
+ snippets.iter().cloned().zip(spans_with_counts.iter().copied())
+ {
+ suggs.push(match snippet.as_deref() {
+ Some("&") => Some("&'a ".to_string()),
+ Some("'_") => Some("'a".to_string()),
+ Some("") => {
+ Some(std::iter::repeat("'a, ").take(count).collect::<Vec<_>>().join(""))
+ }
+ Some("<") => {
+ Some(std::iter::repeat("'a").take(count).collect::<Vec<_>>().join(", "))
+ }
+ Some(snippet) => Some(format!(
"{}<{}>",
snippet,
- std::iter::repeat("'a").take(count).collect::<Vec<_>>().join(", ")
- ),
- );
+ std::iter::repeat("'a").take(count).collect::<Vec<_>>().join(", "),
+ )),
+ None => None,
+ });
}
+ suggest_new(err, suggs);
}
- (lts, ..) if lts.len() > 1 => {
+ lts if lts.len() > 1 => {
err.span_note(lifetime_spans, "these named lifetimes are available to use");
- if Some("") == snippet.as_deref() {
+
+ let mut spans_suggs: Vec<_> = Vec::new();
+ for ((span, _), snippet) in spans_with_counts.iter().copied().zip(snippets.iter()) {
+ match snippet.as_deref() {
+ Some("") => spans_suggs.push((span, "'lifetime, ".to_string())),
+ Some("&") => spans_suggs
+ .push((span.with_lo(span.lo() + BytePos(1)), "'lifetime ".to_string())),
+ _ => {}
+ }
+ }
+
+ if spans_suggs.len() > 0 {
// This happens when we have `Foo<T>` where we point at the space before `T`,
// but this can be confusing so we give a suggestion with placeholders.
- err.span_suggestion_verbose(
- span,
+ err.multipart_suggestion_verbose(
"consider using one of the available lifetimes here",
- "'lifetime, ".repeat(count),
+ spans_suggs,
Applicability::HasPlaceholders,
);
}
}
- _ => {}
+ _ => unreachable!(),
}
}
/// Non-static lifetimes are prohibited in anonymous constants under `min_const_generics`.
- /// This function will emit an error if `const_generics` is not enabled, the body identified by
+ /// This function will emit an error if `generic_const_exprs` is not enabled, the body identified by
/// `body_id` is an anonymous constant and `lifetime_ref` is non-static.
crate fn maybe_emit_forbidden_non_static_lifetime_error(
&self,
);
let is_allowed_lifetime = matches!(
lifetime_ref.name,
- hir::LifetimeName::Implicit | hir::LifetimeName::Static | hir::LifetimeName::Underscore
+ hir::LifetimeName::Implicit(_)
+ | hir::LifetimeName::Static
+ | hir::LifetimeName::Underscore
);
if !self.tcx.lazy_normalization() && is_anon_const && !is_allowed_lifetime {
feature_err(
&self.tcx.sess.parse_sess,
- sym::const_generics,
+ sym::generic_const_exprs,
lifetime_ref.span,
"a non-static lifetime is not allowed in a `const`",
)