blob: 22eef8e53dae063f0c260f326f2561d08ce65f6e [file] [log] [blame]
use crate::{
method::probe::{self, Pick},
FnCtxt,
};
use hir::def_id::DefId;
use hir::HirId;
use hir::ItemKind;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_infer::infer::type_variable::TypeVariableOrigin;
use rustc_middle::ty::{self, Ty};
use rustc_session::lint::builtin::RUST_2021_PRELUDE_COLLISIONS;
use rustc_span::symbol::kw::{Empty, Underscore};
use rustc_span::symbol::{sym, Ident};
use rustc_span::Span;
use rustc_trait_selection::infer::InferCtxtExt;
use std::fmt::Write;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub(super) fn lint_dot_call_from_2018(
&self,
self_ty: Ty<'tcx>,
segment: &hir::PathSegment<'_>,
span: Span,
call_expr: &'tcx hir::Expr<'tcx>,
self_expr: &'tcx hir::Expr<'tcx>,
pick: &Pick<'tcx>,
args: &'tcx [hir::Expr<'tcx>],
) {
debug!(
"lookup(method_name={}, self_ty={:?}, call_expr={:?}, self_expr={:?})",
segment.ident, self_ty, call_expr, self_expr
);
// Rust 2021 and later is already using the new prelude
if span.at_least_rust_2021() {
return;
}
let prelude_or_array_lint = match segment.ident.name {
// `try_into` was added to the prelude in Rust 2021.
sym::try_into => RUST_2021_PRELUDE_COLLISIONS,
// `into_iter` wasn't added to the prelude,
// but `[T; N].into_iter()` doesn't resolve to IntoIterator::into_iter
// before Rust 2021, which results in the same problem.
// It is only a problem for arrays.
sym::into_iter if let ty::Array(..) = self_ty.kind() => {
// In this case, it wasn't really a prelude addition that was the problem.
// Instead, the problem is that the array-into_iter hack will no longer apply in Rust 2021.
rustc_lint::ARRAY_INTO_ITER
}
_ => return,
};
// No need to lint if method came from std/core, as that will now be in the prelude
if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
return;
}
if matches!(pick.kind, probe::PickKind::InherentImplPick | probe::PickKind::ObjectPick) {
// avoid repeatedly adding unneeded `&*`s
if pick.autoderefs == 1
&& matches!(
pick.autoref_or_ptr_adjustment,
Some(probe::AutorefOrPtrAdjustment::Autoref { .. })
)
&& matches!(self_ty.kind(), ty::Ref(..))
{
return;
}
// if it's an inherent `self` method (not `&self` or `&mut self`), it will take
// precedence over the `TryInto` impl, and thus won't break in 2021 edition
if pick.autoderefs == 0 && pick.autoref_or_ptr_adjustment.is_none() {
return;
}
// Inherent impls only require not relying on autoref and autoderef in order to
// ensure that the trait implementation won't be used
self.tcx.node_span_lint(
prelude_or_array_lint,
self_expr.hir_id,
self_expr.span,
format!("trait method `{}` will become ambiguous in Rust 2021", segment.ident.name),
|lint| {
let sp = self_expr.span;
let derefs = "*".repeat(pick.autoderefs);
let autoref = match pick.autoref_or_ptr_adjustment {
Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl, .. }) => {
mutbl.ref_prefix_str()
}
Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
};
if let Ok(self_expr) = self.sess().source_map().span_to_snippet(self_expr.span)
{
let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
pick.autoref_or_ptr_adjustment
{
format!("{derefs}{self_expr} as *const _")
} else {
format!("{autoref}{derefs}{self_expr}")
};
lint.span_suggestion(
sp,
"disambiguate the method call",
format!("({self_adjusted})"),
Applicability::MachineApplicable,
);
} else {
let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
pick.autoref_or_ptr_adjustment
{
format!("{derefs}(...) as *const _")
} else {
format!("{autoref}{derefs}...")
};
lint.span_help(
sp,
format!("disambiguate the method call with `({self_adjusted})`",),
);
}
},
);
} else {
// trait implementations require full disambiguation to not clash with the new prelude
// additions (i.e. convert from dot-call to fully-qualified call)
self.tcx.node_span_lint(
prelude_or_array_lint,
call_expr.hir_id,
call_expr.span,
format!("trait method `{}` will become ambiguous in Rust 2021", segment.ident.name),
|lint| {
let sp = call_expr.span;
let trait_name = self.trait_path_or_bare_name(
span,
call_expr.hir_id,
pick.item.container_id(self.tcx),
);
let (self_adjusted, precise) = self.adjust_expr(pick, self_expr, sp);
if precise {
let args = args.iter().fold(String::new(), |mut string, arg| {
let span = arg.span.find_ancestor_inside(sp).unwrap_or_default();
write!(
string,
", {}",
self.sess().source_map().span_to_snippet(span).unwrap()
)
.unwrap();
string
});
lint.span_suggestion(
sp,
"disambiguate the associated function",
format!(
"{}::{}{}({}{})",
trait_name,
segment.ident.name,
if let Some(args) = segment.args.as_ref().and_then(|args| self
.sess()
.source_map()
.span_to_snippet(args.span_ext)
.ok())
{
// Keep turbofish.
format!("::{args}")
} else {
String::new()
},
self_adjusted,
args,
),
Applicability::MachineApplicable,
);
} else {
lint.span_help(
sp,
format!(
"disambiguate the associated function with `{}::{}(...)`",
trait_name, segment.ident,
),
);
}
},
);
}
}
pub(super) fn lint_fully_qualified_call_from_2018(
&self,
span: Span,
method_name: Ident,
self_ty: Ty<'tcx>,
self_ty_span: Span,
expr_id: hir::HirId,
pick: &Pick<'tcx>,
) {
// Rust 2021 and later is already using the new prelude
if span.at_least_rust_2021() {
return;
}
// These are the fully qualified methods added to prelude in Rust 2021
if !matches!(method_name.name, sym::try_into | sym::try_from | sym::from_iter) {
return;
}
// No need to lint if method came from std/core, as that will now be in the prelude
if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
return;
}
// For from_iter, check if the type actually implements FromIterator.
// If we know it does not, we don't need to warn.
if method_name.name == sym::from_iter {
if let Some(trait_def_id) = self.tcx.get_diagnostic_item(sym::FromIterator) {
let any_type =
self.infcx.next_ty_var(TypeVariableOrigin { param_def_id: None, span });
if !self
.infcx
.type_implements_trait(trait_def_id, [self_ty, any_type], self.param_env)
.may_apply()
{
return;
}
}
}
// No need to lint if this is an inherent method called on a specific type, like `Vec::foo(...)`,
// since such methods take precedence over trait methods.
if matches!(pick.kind, probe::PickKind::InherentImplPick) {
return;
}
self.tcx.node_span_lint(
RUST_2021_PRELUDE_COLLISIONS,
expr_id,
span,
format!(
"trait-associated function `{}` will become ambiguous in Rust 2021",
method_name.name
),
|lint| {
// "type" refers to either a type or, more likely, a trait from which
// the associated function or method is from.
let container_id = pick.item.container_id(self.tcx);
let trait_path = self.trait_path_or_bare_name(span, expr_id, container_id);
let trait_generics = self.tcx.generics_of(container_id);
let trait_name = if trait_generics.params.len() <= trait_generics.has_self as usize
{
trait_path
} else {
let counts = trait_generics.own_counts();
format!(
"{}<{}>",
trait_path,
std::iter::repeat("'_")
.take(counts.lifetimes)
.chain(std::iter::repeat("_").take(
counts.types + counts.consts - trait_generics.has_self as usize
))
.collect::<Vec<_>>()
.join(", ")
)
};
let mut self_ty_name = self_ty_span
.find_ancestor_inside(span)
.and_then(|span| self.sess().source_map().span_to_snippet(span).ok())
.unwrap_or_else(|| self_ty.to_string());
// Get the number of generics the self type has (if an Adt) unless we can determine that
// the user has written the self type with generics already which we (naively) do by looking
// for a "<" in `self_ty_name`.
if !self_ty_name.contains('<') {
if let ty::Adt(def, _) = self_ty.kind() {
let generics = self.tcx.generics_of(def.did());
if !generics.params.is_empty() {
let counts = generics.own_counts();
self_ty_name += &format!(
"<{}>",
std::iter::repeat("'_")
.take(counts.lifetimes)
.chain(
std::iter::repeat("_").take(counts.types + counts.consts)
)
.collect::<Vec<_>>()
.join(", ")
);
}
}
}
lint.span_suggestion(
span,
"disambiguate the associated function",
format!("<{} as {}>::{}", self_ty_name, trait_name, method_name.name,),
Applicability::MachineApplicable,
);
},
);
}
fn trait_path_or_bare_name(
&self,
span: Span,
expr_hir_id: HirId,
trait_def_id: DefId,
) -> String {
self.trait_path(span, expr_hir_id, trait_def_id).unwrap_or_else(|| {
let key = self.tcx.def_key(trait_def_id);
format!("{}", key.disambiguated_data.data)
})
}
fn trait_path(&self, span: Span, expr_hir_id: HirId, trait_def_id: DefId) -> Option<String> {
let applicable_traits = self.tcx.in_scope_traits(expr_hir_id)?;
let applicable_trait = applicable_traits.iter().find(|t| t.def_id == trait_def_id)?;
if applicable_trait.import_ids.is_empty() {
// The trait was declared within the module, we only need to use its name.
return None;
}
let import_items: Vec<_> = applicable_trait
.import_ids
.iter()
.map(|&import_id| self.tcx.hir().expect_item(import_id))
.collect();
// Find an identifier with which this trait was imported (note that `_` doesn't count).
let any_id = import_items
.iter()
.find_map(|item| if item.ident.name != Underscore { Some(item.ident) } else { None });
if let Some(any_id) = any_id {
if any_id.name == Empty {
// Glob import, so just use its name.
return None;
} else {
return Some(format!("{any_id}"));
}
}
// All that is left is `_`! We need to use the full path. It doesn't matter which one we pick,
// so just take the first one.
match import_items[0].kind {
ItemKind::Use(path, _) => Some(
path.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<_>>()
.join("::"),
),
_ => {
span_bug!(span, "unexpected item kind, expected a use: {:?}", import_items[0].kind);
}
}
}
/// Creates a string version of the `expr` that includes explicit adjustments.
/// Returns the string and also a bool indicating whether this is a *precise*
/// suggestion.
fn adjust_expr(
&self,
pick: &Pick<'tcx>,
expr: &hir::Expr<'tcx>,
outer: Span,
) -> (String, bool) {
let derefs = "*".repeat(pick.autoderefs);
let autoref = match pick.autoref_or_ptr_adjustment {
Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl, .. }) => mutbl.ref_prefix_str(),
Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
};
let (expr_text, precise) = if let Some(expr_text) = expr
.span
.find_ancestor_inside(outer)
.and_then(|span| self.sess().source_map().span_to_snippet(span).ok())
{
(expr_text, true)
} else {
("(..)".to_string(), false)
};
let adjusted_text = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
pick.autoref_or_ptr_adjustment
{
format!("{derefs}{expr_text} as *const _")
} else {
format!("{autoref}{derefs}{expr_text}")
};
(adjusted_text, precise)
}
}