mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 16:24:46 +00:00
Rollup merge of #105443 - compiler-errors:move-more, r=oli-obk
Move some queries and methods Each commit's title should be self-explanatory. Motivated to break up some large, general files and move queries into leaf crates.
This commit is contained in:
commit
6111a7345b
@ -56,22 +56,17 @@ use crate::infer::ExpectedFound;
|
||||
use crate::traits::error_reporting::report_object_safety_error;
|
||||
use crate::traits::{
|
||||
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
|
||||
StatementAsExpression,
|
||||
};
|
||||
|
||||
use crate::errors::SuggAddLetForLetChains;
|
||||
use hir::intravisit::{walk_expr, walk_stmt};
|
||||
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
|
||||
use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg};
|
||||
use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{CtorKind, DefKind};
|
||||
use rustc_hir::def::DefKind;
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::intravisit::Visitor;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_hir::Node;
|
||||
use rustc_middle::dep_graph::DepContext;
|
||||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||
use rustc_middle::ty::relate::{self, RelateResult, TypeRelation};
|
||||
use rustc_middle::ty::{
|
||||
self, error::TypeError, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable,
|
||||
@ -84,6 +79,7 @@ use std::path::PathBuf;
|
||||
use std::{cmp, fmt, iter};
|
||||
|
||||
mod note;
|
||||
mod suggest;
|
||||
|
||||
pub(crate) mod need_type_info;
|
||||
pub use need_type_info::TypeAnnotationNeeded;
|
||||
@ -807,87 +803,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest_remove_semi_or_return_binding(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
first_id: Option<hir::HirId>,
|
||||
first_ty: Ty<'tcx>,
|
||||
first_span: Span,
|
||||
second_id: Option<hir::HirId>,
|
||||
second_ty: Ty<'tcx>,
|
||||
second_span: Span,
|
||||
) {
|
||||
let remove_semicolon = [
|
||||
(first_id, self.resolve_vars_if_possible(second_ty)),
|
||||
(second_id, self.resolve_vars_if_possible(first_ty)),
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|(id, ty)| {
|
||||
let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None };
|
||||
self.could_remove_semicolon(blk, ty)
|
||||
});
|
||||
match remove_semicolon {
|
||||
Some((sp, StatementAsExpression::NeedsBoxing)) => {
|
||||
err.multipart_suggestion(
|
||||
"consider removing this semicolon and boxing the expressions",
|
||||
vec![
|
||||
(first_span.shrink_to_lo(), "Box::new(".to_string()),
|
||||
(first_span.shrink_to_hi(), ")".to_string()),
|
||||
(second_span.shrink_to_lo(), "Box::new(".to_string()),
|
||||
(second_span.shrink_to_hi(), ")".to_string()),
|
||||
(sp, String::new()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
Some((sp, StatementAsExpression::CorrectType)) => {
|
||||
err.span_suggestion_short(
|
||||
sp,
|
||||
"consider removing this semicolon",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
|
||||
if let Some(id) = id
|
||||
&& let hir::Node::Block(blk) = self.tcx.hir().get(id)
|
||||
&& self.consider_returning_binding(blk, ty, err)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest_boxing_for_return_impl_trait(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
return_sp: Span,
|
||||
arm_spans: impl Iterator<Item = Span>,
|
||||
) {
|
||||
err.multipart_suggestion(
|
||||
"you could change the return type to be a boxed trait object",
|
||||
vec![
|
||||
(return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
|
||||
(return_sp.shrink_to_hi(), ">".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
let sugg = arm_spans
|
||||
.flat_map(|sp| {
|
||||
[(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())]
|
||||
.into_iter()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
err.multipart_suggestion(
|
||||
"if you change the return type to expect trait objects, box the returned expressions",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
/// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value`
|
||||
/// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and
|
||||
/// populate `other_value` with `other_ty`.
|
||||
@ -1944,310 +1859,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
debug!(?diag);
|
||||
}
|
||||
|
||||
fn suggest_tuple_pattern(
|
||||
&self,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
// Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
|
||||
// some modifications due to that being in typeck and this being in infer.
|
||||
if let ObligationCauseCode::Pattern { .. } = cause.code() {
|
||||
if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() {
|
||||
let compatible_variants: Vec<_> = expected_adt
|
||||
.variants()
|
||||
.iter()
|
||||
.filter(|variant| {
|
||||
variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
|
||||
})
|
||||
.filter_map(|variant| {
|
||||
let sole_field = &variant.fields[0];
|
||||
let sole_field_ty = sole_field.ty(self.tcx, substs);
|
||||
if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
|
||||
let variant_path =
|
||||
with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
|
||||
// FIXME #56861: DRYer prelude filtering
|
||||
if let Some(path) = variant_path.strip_prefix("std::prelude::") {
|
||||
if let Some((_, path)) = path.split_once("::") {
|
||||
return Some(path.to_string());
|
||||
}
|
||||
}
|
||||
Some(variant_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
match &compatible_variants[..] {
|
||||
[] => {}
|
||||
[variant] => {
|
||||
diag.multipart_suggestion_verbose(
|
||||
&format!("try wrapping the pattern in `{}`", variant),
|
||||
vec![
|
||||
(cause.span.shrink_to_lo(), format!("{}(", variant)),
|
||||
(cause.span.shrink_to_hi(), ")".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
// More than one matching variant.
|
||||
diag.multipart_suggestions(
|
||||
&format!(
|
||||
"try wrapping the pattern in a variant of `{}`",
|
||||
self.tcx.def_path_str(expected_adt.did())
|
||||
),
|
||||
compatible_variants.into_iter().map(|variant| {
|
||||
vec![
|
||||
(cause.span.shrink_to_lo(), format!("{}(", variant)),
|
||||
(cause.span.shrink_to_hi(), ")".to_string()),
|
||||
]
|
||||
}),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A possible error is to forget to add `.await` when using futures:
|
||||
///
|
||||
/// ```compile_fail,E0308
|
||||
/// async fn make_u32() -> u32 {
|
||||
/// 22
|
||||
/// }
|
||||
///
|
||||
/// fn take_u32(x: u32) {}
|
||||
///
|
||||
/// async fn foo() {
|
||||
/// let x = make_u32();
|
||||
/// take_u32(x);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
|
||||
/// expected type. If this is the case, and we are inside of an async body, it suggests adding
|
||||
/// `.await` to the tail of the expression.
|
||||
fn suggest_await_on_expect_found(
|
||||
&self,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
exp_span: Span,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
debug!(
|
||||
"suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
|
||||
exp_span, exp_found.expected, exp_found.found,
|
||||
);
|
||||
|
||||
if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() {
|
||||
return;
|
||||
}
|
||||
|
||||
match (
|
||||
self.get_impl_future_output_ty(exp_found.expected),
|
||||
self.get_impl_future_output_ty(exp_found.found),
|
||||
) {
|
||||
(Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause
|
||||
.code()
|
||||
{
|
||||
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
|
||||
let then_span = self.find_block_span_from_hir_id(*then_id);
|
||||
diag.multipart_suggestion(
|
||||
"consider `await`ing on both `Future`s",
|
||||
vec![
|
||||
(then_span.shrink_to_hi(), ".await".to_string()),
|
||||
(exp_span.shrink_to_hi(), ".await".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
|
||||
prior_arms,
|
||||
..
|
||||
}) => {
|
||||
if let [.., arm_span] = &prior_arms[..] {
|
||||
diag.multipart_suggestion(
|
||||
"consider `await`ing on both `Future`s",
|
||||
vec![
|
||||
(arm_span.shrink_to_hi(), ".await".to_string()),
|
||||
(exp_span.shrink_to_hi(), ".await".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
diag.help("consider `await`ing on both `Future`s");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
diag.help("consider `await`ing on both `Future`s");
|
||||
}
|
||||
},
|
||||
(_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
|
||||
diag.span_suggestion_verbose(
|
||||
exp_span.shrink_to_hi(),
|
||||
"consider `await`ing on the `Future`",
|
||||
".await",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
(Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
|
||||
{
|
||||
ObligationCauseCode::Pattern { span: Some(then_span), .. } => {
|
||||
diag.span_suggestion_verbose(
|
||||
then_span.shrink_to_hi(),
|
||||
"consider `await`ing on the `Future`",
|
||||
".await",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
|
||||
let then_span = self.find_block_span_from_hir_id(*then_id);
|
||||
diag.span_suggestion_verbose(
|
||||
then_span.shrink_to_hi(),
|
||||
"consider `await`ing on the `Future`",
|
||||
".await",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
|
||||
ref prior_arms,
|
||||
..
|
||||
}) => {
|
||||
diag.multipart_suggestion_verbose(
|
||||
"consider `await`ing on the `Future`",
|
||||
prior_arms
|
||||
.iter()
|
||||
.map(|arm| (arm.shrink_to_hi(), ".await".to_string()))
|
||||
.collect(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn suggest_accessing_field_where_appropriate(
|
||||
&self,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
debug!(
|
||||
"suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})",
|
||||
cause, exp_found
|
||||
);
|
||||
if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() {
|
||||
if expected_def.is_enum() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((name, ty)) = expected_def
|
||||
.non_enum_variant()
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| field.vis.is_accessible_from(field.did, self.tcx))
|
||||
.map(|field| (field.name, field.ty(self.tcx, expected_substs)))
|
||||
.find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found))
|
||||
{
|
||||
if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
|
||||
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
|
||||
let suggestion = if expected_def.is_struct() {
|
||||
format!("{}.{}", snippet, name)
|
||||
} else if expected_def.is_union() {
|
||||
format!("unsafe {{ {}.{} }}", snippet, name)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
&format!(
|
||||
"you might have meant to use field `{}` whose type is `{}`",
|
||||
name, ty
|
||||
),
|
||||
suggestion,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
|
||||
/// suggests it.
|
||||
fn suggest_as_ref_where_appropriate(
|
||||
&self,
|
||||
span: Span,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
|
||||
&& let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found)
|
||||
{
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
msg,
|
||||
// HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
|
||||
format!("{}.as_ref()", snippet.trim_start_matches('&')),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
|
||||
if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
|
||||
(expected.kind(), found.kind())
|
||||
{
|
||||
if let ty::Adt(found_def, found_substs) = *found_ty.kind() {
|
||||
if exp_def == &found_def {
|
||||
let have_as_ref = &[
|
||||
(
|
||||
sym::Option,
|
||||
"you can convert from `&Option<T>` to `Option<&T>` using \
|
||||
`.as_ref()`",
|
||||
),
|
||||
(
|
||||
sym::Result,
|
||||
"you can convert from `&Result<T, E>` to \
|
||||
`Result<&T, &E>` using `.as_ref()`",
|
||||
),
|
||||
];
|
||||
if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
|
||||
self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
|
||||
}) {
|
||||
let mut show_suggestion = true;
|
||||
for (exp_ty, found_ty) in
|
||||
iter::zip(exp_substs.types(), found_substs.types())
|
||||
{
|
||||
match *exp_ty.kind() {
|
||||
ty::Ref(_, exp_ty, _) => {
|
||||
match (exp_ty.kind(), found_ty.kind()) {
|
||||
(_, ty::Param(_))
|
||||
| (_, ty::Infer(_))
|
||||
| (ty::Param(_), _)
|
||||
| (ty::Infer(_), _) => {}
|
||||
_ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
|
||||
_ => show_suggestion = false,
|
||||
};
|
||||
}
|
||||
ty::Param(_) | ty::Infer(_) => {}
|
||||
_ => show_suggestion = false,
|
||||
}
|
||||
}
|
||||
if show_suggestion {
|
||||
return Some(*msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn report_and_explain_type_error(
|
||||
&self,
|
||||
trace: TypeTrace<'tcx>,
|
||||
@ -2361,67 +1972,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
diag
|
||||
}
|
||||
|
||||
/// Try to find code with pattern `if Some(..) = expr`
|
||||
/// use a `visitor` to mark the `if` which its span contains given error span,
|
||||
/// and then try to find a assignment in the `cond` part, which span is equal with error span
|
||||
fn suggest_let_for_letchains(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
cause: &ObligationCause<'_>,
|
||||
span: Span,
|
||||
) {
|
||||
let hir = self.tcx.hir();
|
||||
let fn_hir_id = hir.get_parent_node(cause.body_id);
|
||||
if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
|
||||
let hir::Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Fn(_sig, _, body_id), ..
|
||||
}) = node {
|
||||
let body = hir.body(*body_id);
|
||||
|
||||
/// Find the if expression with given span
|
||||
struct IfVisitor {
|
||||
pub result: bool,
|
||||
pub found_if: bool,
|
||||
pub err_span: Span,
|
||||
}
|
||||
|
||||
impl<'v> Visitor<'v> for IfVisitor {
|
||||
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
|
||||
if self.result { return; }
|
||||
match ex.kind {
|
||||
hir::ExprKind::If(cond, _, _) => {
|
||||
self.found_if = true;
|
||||
walk_expr(self, cond);
|
||||
self.found_if = false;
|
||||
}
|
||||
_ => walk_expr(self, ex),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
|
||||
if let hir::StmtKind::Local(hir::Local {
|
||||
span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
|
||||
}) = &ex.kind
|
||||
&& self.found_if
|
||||
&& span.eq(&self.err_span) {
|
||||
self.result = true;
|
||||
}
|
||||
walk_stmt(self, ex);
|
||||
}
|
||||
|
||||
fn visit_body(&mut self, body: &'v hir::Body<'v>) {
|
||||
hir::intravisit::walk_body(self, body);
|
||||
}
|
||||
}
|
||||
|
||||
let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
|
||||
visitor.visit_body(&body);
|
||||
if visitor.result {
|
||||
err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_tuple_wrap_err(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
@ -3253,211 +2803,3 @@ impl<'tcx> InferCtxt<'tcx> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
/// Be helpful when the user wrote `{... expr; }` and taking the `;` off
|
||||
/// is enough to fix the error.
|
||||
pub fn could_remove_semicolon(
|
||||
&self,
|
||||
blk: &'tcx hir::Block<'tcx>,
|
||||
expected_ty: Ty<'tcx>,
|
||||
) -> Option<(Span, StatementAsExpression)> {
|
||||
let blk = blk.innermost_block();
|
||||
// Do not suggest if we have a tail expr.
|
||||
if blk.expr.is_some() {
|
||||
return None;
|
||||
}
|
||||
let last_stmt = blk.stmts.last()?;
|
||||
let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else {
|
||||
return None;
|
||||
};
|
||||
let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?;
|
||||
let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
|
||||
_ if last_expr_ty.references_error() => return None,
|
||||
_ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
|
||||
StatementAsExpression::CorrectType
|
||||
}
|
||||
(ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _))
|
||||
if last_def_id == exp_def_id =>
|
||||
{
|
||||
StatementAsExpression::CorrectType
|
||||
}
|
||||
(ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => {
|
||||
debug!(
|
||||
"both opaque, likely future {:?} {:?} {:?} {:?}",
|
||||
last_def_id, last_bounds, exp_def_id, exp_bounds
|
||||
);
|
||||
|
||||
let last_local_id = last_def_id.as_local()?;
|
||||
let exp_local_id = exp_def_id.as_local()?;
|
||||
|
||||
match (
|
||||
&self.tcx.hir().expect_item(last_local_id).kind,
|
||||
&self.tcx.hir().expect_item(exp_local_id).kind,
|
||||
) {
|
||||
(
|
||||
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }),
|
||||
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }),
|
||||
) if iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| {
|
||||
match (left, right) {
|
||||
(
|
||||
hir::GenericBound::Trait(tl, ml),
|
||||
hir::GenericBound::Trait(tr, mr),
|
||||
) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
|
||||
&& ml == mr =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(
|
||||
hir::GenericBound::LangItemTrait(langl, _, _, argsl),
|
||||
hir::GenericBound::LangItemTrait(langr, _, _, argsr),
|
||||
) if langl == langr => {
|
||||
// FIXME: consider the bounds!
|
||||
debug!("{:?} {:?}", argsl, argsr);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}) =>
|
||||
{
|
||||
StatementAsExpression::NeedsBoxing
|
||||
}
|
||||
_ => StatementAsExpression::CorrectType,
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let span = if last_stmt.span.from_expansion() {
|
||||
let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
|
||||
self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
|
||||
} else {
|
||||
last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1))
|
||||
};
|
||||
Some((span, needs_box))
|
||||
}
|
||||
|
||||
/// Suggest returning a local binding with a compatible type if the block
|
||||
/// has no return expression.
|
||||
pub fn consider_returning_binding(
|
||||
&self,
|
||||
blk: &'tcx hir::Block<'tcx>,
|
||||
expected_ty: Ty<'tcx>,
|
||||
err: &mut Diagnostic,
|
||||
) -> bool {
|
||||
let blk = blk.innermost_block();
|
||||
// Do not suggest if we have a tail expr.
|
||||
if blk.expr.is_some() {
|
||||
return false;
|
||||
}
|
||||
let mut shadowed = FxIndexSet::default();
|
||||
let mut candidate_idents = vec![];
|
||||
let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
|
||||
if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
|
||||
&& let Some(pat_ty) = self
|
||||
.typeck_results
|
||||
.as_ref()
|
||||
.and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
|
||||
{
|
||||
let pat_ty = self.resolve_vars_if_possible(pat_ty);
|
||||
if self.same_type_modulo_infer(pat_ty, expected_ty)
|
||||
&& !(pat_ty, expected_ty).references_error()
|
||||
&& shadowed.insert(ident.name)
|
||||
{
|
||||
candidate_idents.push((*ident, pat_ty));
|
||||
}
|
||||
}
|
||||
true
|
||||
};
|
||||
|
||||
let hir = self.tcx.hir();
|
||||
for stmt in blk.stmts.iter().rev() {
|
||||
let hir::StmtKind::Local(local) = &stmt.kind else { continue; };
|
||||
local.pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
match hir.find(hir.get_parent_node(blk.hir_id)) {
|
||||
Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => {
|
||||
match hir.find(hir.get_parent_node(*hir_id)) {
|
||||
Some(hir::Node::Arm(hir::Arm { pat, .. })) => {
|
||||
pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
Some(
|
||||
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. })
|
||||
| hir::Node::ImplItem(hir::ImplItem {
|
||||
kind: hir::ImplItemKind::Fn(_, body),
|
||||
..
|
||||
})
|
||||
| hir::Node::TraitItem(hir::TraitItem {
|
||||
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
|
||||
..
|
||||
})
|
||||
| hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
for param in hir.body(*body).params {
|
||||
param.pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
}
|
||||
Some(hir::Node::Expr(hir::Expr {
|
||||
kind:
|
||||
hir::ExprKind::If(
|
||||
hir::Expr { kind: hir::ExprKind::Let(let_), .. },
|
||||
then_block,
|
||||
_,
|
||||
),
|
||||
..
|
||||
})) if then_block.hir_id == *hir_id => {
|
||||
let_.pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match &candidate_idents[..] {
|
||||
[(ident, _ty)] => {
|
||||
let sm = self.tcx.sess.source_map();
|
||||
if let Some(stmt) = blk.stmts.last() {
|
||||
let stmt_span = sm.stmt_span(stmt.span, blk.span);
|
||||
let sugg = if sm.is_multiline(blk.span)
|
||||
&& let Some(spacing) = sm.indentation_before(stmt_span)
|
||||
{
|
||||
format!("\n{spacing}{ident}")
|
||||
} else {
|
||||
format!(" {ident}")
|
||||
};
|
||||
err.span_suggestion_verbose(
|
||||
stmt_span.shrink_to_hi(),
|
||||
format!("consider returning the local binding `{ident}`"),
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
let sugg = if sm.is_multiline(blk.span)
|
||||
&& let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
|
||||
{
|
||||
format!("\n{spacing} {ident}\n{spacing}")
|
||||
} else {
|
||||
format!(" {ident} ")
|
||||
};
|
||||
let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
|
||||
err.span_suggestion_verbose(
|
||||
sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
|
||||
format!("consider returning the local binding `{ident}`"),
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
values if (1..3).contains(&values.len()) => {
|
||||
let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
|
||||
err.span_note(spans, "consider returning one of these bindings");
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
427
compiler/rustc_infer/src/infer/error_reporting/note_region.rs
Normal file
427
compiler/rustc_infer/src/infer/error_reporting/note_region.rs
Normal file
@ -0,0 +1,427 @@
|
||||
use crate::errors::RegionOriginNote;
|
||||
use crate::infer::error_reporting::{note_and_explain_region, TypeErrCtxt};
|
||||
use crate::infer::{self, SubregionOrigin};
|
||||
use rustc_errors::{
|
||||
fluent, struct_span_err, AddToDiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
|
||||
};
|
||||
use rustc_middle::traits::ObligationCauseCode;
|
||||
use rustc_middle::ty::error::TypeError;
|
||||
use rustc_middle::ty::{self, Region};
|
||||
|
||||
use super::ObligationCauseAsDiagArg;
|
||||
|
||||
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
pub(super) fn note_region_origin(&self, err: &mut Diagnostic, origin: &SubregionOrigin<'tcx>) {
|
||||
match *origin {
|
||||
infer::Subtype(ref trace) => RegionOriginNote::WithRequirement {
|
||||
span: trace.cause.span,
|
||||
requirement: ObligationCauseAsDiagArg(trace.cause.clone()),
|
||||
expected_found: self.values_str(trace.values),
|
||||
}
|
||||
.add_to_diagnostic(err),
|
||||
infer::Reborrow(span) => {
|
||||
RegionOriginNote::Plain { span, msg: fluent::infer_reborrow }.add_to_diagnostic(err)
|
||||
}
|
||||
infer::ReborrowUpvar(span, ref upvar_id) => {
|
||||
let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id);
|
||||
RegionOriginNote::WithName {
|
||||
span,
|
||||
msg: fluent::infer_reborrow,
|
||||
name: &var_name.to_string(),
|
||||
continues: false,
|
||||
}
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
infer::RelateObjectBound(span) => {
|
||||
RegionOriginNote::Plain { span, msg: fluent::infer_relate_object_bound }
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
infer::DataBorrowed(ty, span) => {
|
||||
RegionOriginNote::WithName {
|
||||
span,
|
||||
msg: fluent::infer_data_borrowed,
|
||||
name: &self.ty_to_string(ty),
|
||||
continues: false,
|
||||
}
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
infer::ReferenceOutlivesReferent(ty, span) => {
|
||||
RegionOriginNote::WithName {
|
||||
span,
|
||||
msg: fluent::infer_reference_outlives_referent,
|
||||
name: &self.ty_to_string(ty),
|
||||
continues: false,
|
||||
}
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
infer::RelateParamBound(span, ty, opt_span) => {
|
||||
RegionOriginNote::WithName {
|
||||
span,
|
||||
msg: fluent::infer_relate_param_bound,
|
||||
name: &self.ty_to_string(ty),
|
||||
continues: opt_span.is_some(),
|
||||
}
|
||||
.add_to_diagnostic(err);
|
||||
if let Some(span) = opt_span {
|
||||
RegionOriginNote::Plain { span, msg: fluent::infer_relate_param_bound_2 }
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
}
|
||||
infer::RelateRegionParamBound(span) => {
|
||||
RegionOriginNote::Plain { span, msg: fluent::infer_relate_region_param_bound }
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
infer::CompareImplItemObligation { span, .. } => {
|
||||
RegionOriginNote::Plain { span, msg: fluent::infer_compare_impl_item_obligation }
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
infer::CheckAssociatedTypeBounds { ref parent, .. } => {
|
||||
self.note_region_origin(err, &parent);
|
||||
}
|
||||
infer::AscribeUserTypeProvePredicate(span) => {
|
||||
RegionOriginNote::Plain {
|
||||
span,
|
||||
msg: fluent::infer_ascribe_user_type_prove_predicate,
|
||||
}
|
||||
.add_to_diagnostic(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn report_concrete_failure(
|
||||
&self,
|
||||
origin: SubregionOrigin<'tcx>,
|
||||
sub: Region<'tcx>,
|
||||
sup: Region<'tcx>,
|
||||
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
|
||||
match origin {
|
||||
infer::Subtype(box trace) => {
|
||||
let terr = TypeError::RegionsDoesNotOutlive(sup, sub);
|
||||
let mut err = self.report_and_explain_type_error(trace, terr);
|
||||
match (*sub, *sup) {
|
||||
(ty::RePlaceholder(_), ty::RePlaceholder(_)) => {}
|
||||
(ty::RePlaceholder(_), _) => {
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"",
|
||||
sup,
|
||||
" doesn't meet the lifetime requirements",
|
||||
None,
|
||||
);
|
||||
}
|
||||
(_, ty::RePlaceholder(_)) => {
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"the required lifetime does not necessarily outlive ",
|
||||
sub,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
note_and_explain_region(self.tcx, &mut err, "", sup, "...", None);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"...does not necessarily outlive ",
|
||||
sub,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
err
|
||||
}
|
||||
infer::Reborrow(span) => {
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
span,
|
||||
E0312,
|
||||
"lifetime of reference outlives lifetime of borrowed content..."
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"...the reference is valid for ",
|
||||
sub,
|
||||
"...",
|
||||
None,
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"...but the borrowed content is only valid for ",
|
||||
sup,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
err
|
||||
}
|
||||
infer::ReborrowUpvar(span, ref upvar_id) => {
|
||||
let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id);
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
span,
|
||||
E0313,
|
||||
"lifetime of borrowed pointer outlives lifetime of captured variable `{}`...",
|
||||
var_name
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"...the borrowed pointer is valid for ",
|
||||
sub,
|
||||
"...",
|
||||
None,
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
&format!("...but `{}` is only valid for ", var_name),
|
||||
sup,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
err
|
||||
}
|
||||
infer::RelateObjectBound(span) => {
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
span,
|
||||
E0476,
|
||||
"lifetime of the source pointer does not outlive lifetime bound of the \
|
||||
object type"
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"object type is valid for ",
|
||||
sub,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"source pointer is only valid for ",
|
||||
sup,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
err
|
||||
}
|
||||
infer::RelateParamBound(span, ty, opt_span) => {
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
span,
|
||||
E0477,
|
||||
"the type `{}` does not fulfill the required lifetime",
|
||||
self.ty_to_string(ty)
|
||||
);
|
||||
match *sub {
|
||||
ty::ReStatic => note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"type must satisfy ",
|
||||
sub,
|
||||
if opt_span.is_some() { " as required by this binding" } else { "" },
|
||||
opt_span,
|
||||
),
|
||||
_ => note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"type must outlive ",
|
||||
sub,
|
||||
if opt_span.is_some() { " as required by this binding" } else { "" },
|
||||
opt_span,
|
||||
),
|
||||
}
|
||||
err
|
||||
}
|
||||
infer::RelateRegionParamBound(span) => {
|
||||
let mut err =
|
||||
struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied");
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"lifetime parameter instantiated with ",
|
||||
sup,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"but lifetime parameter must outlive ",
|
||||
sub,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
err
|
||||
}
|
||||
infer::DataBorrowed(ty, span) => {
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
span,
|
||||
E0490,
|
||||
"a value of type `{}` is borrowed for too long",
|
||||
self.ty_to_string(ty)
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"the type is valid for ",
|
||||
sub,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"but the borrow lasts for ",
|
||||
sup,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
err
|
||||
}
|
||||
infer::ReferenceOutlivesReferent(ty, span) => {
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
span,
|
||||
E0491,
|
||||
"in type `{}`, reference has a longer lifetime than the data it references",
|
||||
self.ty_to_string(ty)
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"the pointer is valid for ",
|
||||
sub,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"but the referenced data is only valid for ",
|
||||
sup,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
err
|
||||
}
|
||||
infer::CompareImplItemObligation { span, impl_item_def_id, trait_item_def_id } => self
|
||||
.report_extra_impl_obligation(
|
||||
span,
|
||||
impl_item_def_id,
|
||||
trait_item_def_id,
|
||||
&format!("`{}: {}`", sup, sub),
|
||||
),
|
||||
infer::CheckAssociatedTypeBounds { impl_item_def_id, trait_item_def_id, parent } => {
|
||||
let mut err = self.report_concrete_failure(*parent, sub, sup);
|
||||
|
||||
let trait_item_span = self.tcx.def_span(trait_item_def_id);
|
||||
let item_name = self.tcx.item_name(impl_item_def_id.to_def_id());
|
||||
err.span_label(
|
||||
trait_item_span,
|
||||
format!("definition of `{}` from trait", item_name),
|
||||
);
|
||||
|
||||
let trait_predicates = self.tcx.explicit_predicates_of(trait_item_def_id);
|
||||
let impl_predicates = self.tcx.explicit_predicates_of(impl_item_def_id);
|
||||
|
||||
let impl_predicates: rustc_data_structures::fx::FxHashSet<_> =
|
||||
impl_predicates.predicates.into_iter().map(|(pred, _)| pred).collect();
|
||||
let clauses: Vec<_> = trait_predicates
|
||||
.predicates
|
||||
.into_iter()
|
||||
.filter(|&(pred, _)| !impl_predicates.contains(pred))
|
||||
.map(|(pred, _)| format!("{}", pred))
|
||||
.collect();
|
||||
|
||||
if !clauses.is_empty() {
|
||||
let generics = self.tcx.hir().get_generics(impl_item_def_id).unwrap();
|
||||
let where_clause_span = generics.tail_span_for_predicate_suggestion();
|
||||
|
||||
let suggestion = format!(
|
||||
"{} {}",
|
||||
generics.add_where_or_trailing_comma(),
|
||||
clauses.join(", "),
|
||||
);
|
||||
err.span_suggestion(
|
||||
where_clause_span,
|
||||
&format!(
|
||||
"try copying {} from the trait",
|
||||
if clauses.len() > 1 { "these clauses" } else { "this clause" }
|
||||
),
|
||||
suggestion,
|
||||
rustc_errors::Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
err
|
||||
}
|
||||
infer::AscribeUserTypeProvePredicate(span) => {
|
||||
let mut err =
|
||||
struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied");
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"lifetime instantiated with ",
|
||||
sup,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
note_and_explain_region(
|
||||
self.tcx,
|
||||
&mut err,
|
||||
"but lifetime must outlive ",
|
||||
sub,
|
||||
"",
|
||||
None,
|
||||
);
|
||||
err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn report_placeholder_failure(
|
||||
&self,
|
||||
placeholder_origin: SubregionOrigin<'tcx>,
|
||||
sub: Region<'tcx>,
|
||||
sup: Region<'tcx>,
|
||||
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
|
||||
// I can't think how to do better than this right now. -nikomatsakis
|
||||
debug!(?placeholder_origin, ?sub, ?sup, "report_placeholder_failure");
|
||||
match placeholder_origin {
|
||||
infer::Subtype(box ref trace)
|
||||
if matches!(
|
||||
&trace.cause.code().peel_derives(),
|
||||
ObligationCauseCode::BindingObligation(..)
|
||||
| ObligationCauseCode::ExprBindingObligation(..)
|
||||
) =>
|
||||
{
|
||||
// Hack to get around the borrow checker because trace.cause has an `Rc`.
|
||||
if let ObligationCauseCode::BindingObligation(_, span)
|
||||
| ObligationCauseCode::ExprBindingObligation(_, span, ..) =
|
||||
&trace.cause.code().peel_derives()
|
||||
{
|
||||
let span = *span;
|
||||
let mut err = self.report_concrete_failure(placeholder_origin, sub, sup);
|
||||
err.span_note(span, "the lifetime requirement is introduced here");
|
||||
err
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
infer::Subtype(box trace) => {
|
||||
let terr = TypeError::RegionsPlaceholderMismatch;
|
||||
return self.report_and_explain_type_error(trace, terr);
|
||||
}
|
||||
_ => return self.report_concrete_failure(placeholder_origin, sub, sup),
|
||||
}
|
||||
}
|
||||
}
|
672
compiler/rustc_infer/src/infer/error_reporting/suggest.rs
Normal file
672
compiler/rustc_infer/src/infer/error_reporting/suggest.rs
Normal file
@ -0,0 +1,672 @@
|
||||
use hir::def::CtorKind;
|
||||
use hir::intravisit::{walk_expr, walk_stmt, Visitor};
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
use rustc_errors::{Applicability, Diagnostic};
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::traits::{
|
||||
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
|
||||
StatementAsExpression,
|
||||
};
|
||||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||
use rustc_middle::ty::{self as ty, Ty, TypeVisitable};
|
||||
use rustc_span::{sym, BytePos, Span};
|
||||
|
||||
use crate::errors::SuggAddLetForLetChains;
|
||||
|
||||
use super::TypeErrCtxt;
|
||||
|
||||
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
pub(super) fn suggest_remove_semi_or_return_binding(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
first_id: Option<hir::HirId>,
|
||||
first_ty: Ty<'tcx>,
|
||||
first_span: Span,
|
||||
second_id: Option<hir::HirId>,
|
||||
second_ty: Ty<'tcx>,
|
||||
second_span: Span,
|
||||
) {
|
||||
let remove_semicolon = [
|
||||
(first_id, self.resolve_vars_if_possible(second_ty)),
|
||||
(second_id, self.resolve_vars_if_possible(first_ty)),
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|(id, ty)| {
|
||||
let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None };
|
||||
self.could_remove_semicolon(blk, ty)
|
||||
});
|
||||
match remove_semicolon {
|
||||
Some((sp, StatementAsExpression::NeedsBoxing)) => {
|
||||
err.multipart_suggestion(
|
||||
"consider removing this semicolon and boxing the expressions",
|
||||
vec![
|
||||
(first_span.shrink_to_lo(), "Box::new(".to_string()),
|
||||
(first_span.shrink_to_hi(), ")".to_string()),
|
||||
(second_span.shrink_to_lo(), "Box::new(".to_string()),
|
||||
(second_span.shrink_to_hi(), ")".to_string()),
|
||||
(sp, String::new()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
Some((sp, StatementAsExpression::CorrectType)) => {
|
||||
err.span_suggestion_short(
|
||||
sp,
|
||||
"consider removing this semicolon",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] {
|
||||
if let Some(id) = id
|
||||
&& let hir::Node::Block(blk) = self.tcx.hir().get(id)
|
||||
&& self.consider_returning_binding(blk, ty, err)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn suggest_boxing_for_return_impl_trait(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
return_sp: Span,
|
||||
arm_spans: impl Iterator<Item = Span>,
|
||||
) {
|
||||
err.multipart_suggestion(
|
||||
"you could change the return type to be a boxed trait object",
|
||||
vec![
|
||||
(return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()),
|
||||
(return_sp.shrink_to_hi(), ">".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
let sugg = arm_spans
|
||||
.flat_map(|sp| {
|
||||
[(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())]
|
||||
.into_iter()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
err.multipart_suggestion(
|
||||
"if you change the return type to expect trait objects, box the returned expressions",
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn suggest_tuple_pattern(
|
||||
&self,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
// Heavily inspired by `FnCtxt::suggest_compatible_variants`, with
|
||||
// some modifications due to that being in typeck and this being in infer.
|
||||
if let ObligationCauseCode::Pattern { .. } = cause.code() {
|
||||
if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() {
|
||||
let compatible_variants: Vec<_> = expected_adt
|
||||
.variants()
|
||||
.iter()
|
||||
.filter(|variant| {
|
||||
variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn)
|
||||
})
|
||||
.filter_map(|variant| {
|
||||
let sole_field = &variant.fields[0];
|
||||
let sole_field_ty = sole_field.ty(self.tcx, substs);
|
||||
if self.same_type_modulo_infer(sole_field_ty, exp_found.found) {
|
||||
let variant_path =
|
||||
with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id));
|
||||
// FIXME #56861: DRYer prelude filtering
|
||||
if let Some(path) = variant_path.strip_prefix("std::prelude::") {
|
||||
if let Some((_, path)) = path.split_once("::") {
|
||||
return Some(path.to_string());
|
||||
}
|
||||
}
|
||||
Some(variant_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
match &compatible_variants[..] {
|
||||
[] => {}
|
||||
[variant] => {
|
||||
diag.multipart_suggestion_verbose(
|
||||
&format!("try wrapping the pattern in `{}`", variant),
|
||||
vec![
|
||||
(cause.span.shrink_to_lo(), format!("{}(", variant)),
|
||||
(cause.span.shrink_to_hi(), ")".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
// More than one matching variant.
|
||||
diag.multipart_suggestions(
|
||||
&format!(
|
||||
"try wrapping the pattern in a variant of `{}`",
|
||||
self.tcx.def_path_str(expected_adt.did())
|
||||
),
|
||||
compatible_variants.into_iter().map(|variant| {
|
||||
vec![
|
||||
(cause.span.shrink_to_lo(), format!("{}(", variant)),
|
||||
(cause.span.shrink_to_hi(), ")".to_string()),
|
||||
]
|
||||
}),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A possible error is to forget to add `.await` when using futures:
|
||||
///
|
||||
/// ```compile_fail,E0308
|
||||
/// async fn make_u32() -> u32 {
|
||||
/// 22
|
||||
/// }
|
||||
///
|
||||
/// fn take_u32(x: u32) {}
|
||||
///
|
||||
/// async fn foo() {
|
||||
/// let x = make_u32();
|
||||
/// take_u32(x);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the
|
||||
/// expected type. If this is the case, and we are inside of an async body, it suggests adding
|
||||
/// `.await` to the tail of the expression.
|
||||
pub(super) fn suggest_await_on_expect_found(
|
||||
&self,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
exp_span: Span,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
debug!(
|
||||
"suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}",
|
||||
exp_span, exp_found.expected, exp_found.found,
|
||||
);
|
||||
|
||||
if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() {
|
||||
return;
|
||||
}
|
||||
|
||||
match (
|
||||
self.get_impl_future_output_ty(exp_found.expected),
|
||||
self.get_impl_future_output_ty(exp_found.found),
|
||||
) {
|
||||
(Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause
|
||||
.code()
|
||||
{
|
||||
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
|
||||
let then_span = self.find_block_span_from_hir_id(*then_id);
|
||||
diag.multipart_suggestion(
|
||||
"consider `await`ing on both `Future`s",
|
||||
vec![
|
||||
(then_span.shrink_to_hi(), ".await".to_string()),
|
||||
(exp_span.shrink_to_hi(), ".await".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
|
||||
prior_arms,
|
||||
..
|
||||
}) => {
|
||||
if let [.., arm_span] = &prior_arms[..] {
|
||||
diag.multipart_suggestion(
|
||||
"consider `await`ing on both `Future`s",
|
||||
vec![
|
||||
(arm_span.shrink_to_hi(), ".await".to_string()),
|
||||
(exp_span.shrink_to_hi(), ".await".to_string()),
|
||||
],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
diag.help("consider `await`ing on both `Future`s");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
diag.help("consider `await`ing on both `Future`s");
|
||||
}
|
||||
},
|
||||
(_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => {
|
||||
diag.span_suggestion_verbose(
|
||||
exp_span.shrink_to_hi(),
|
||||
"consider `await`ing on the `Future`",
|
||||
".await",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
(Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code()
|
||||
{
|
||||
ObligationCauseCode::Pattern { span: Some(then_span), .. } => {
|
||||
diag.span_suggestion_verbose(
|
||||
then_span.shrink_to_hi(),
|
||||
"consider `await`ing on the `Future`",
|
||||
".await",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => {
|
||||
let then_span = self.find_block_span_from_hir_id(*then_id);
|
||||
diag.span_suggestion_verbose(
|
||||
then_span.shrink_to_hi(),
|
||||
"consider `await`ing on the `Future`",
|
||||
".await",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
|
||||
ref prior_arms,
|
||||
..
|
||||
}) => {
|
||||
diag.multipart_suggestion_verbose(
|
||||
"consider `await`ing on the `Future`",
|
||||
prior_arms
|
||||
.iter()
|
||||
.map(|arm| (arm.shrink_to_hi(), ".await".to_string()))
|
||||
.collect(),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn suggest_accessing_field_where_appropriate(
|
||||
&self,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
debug!(
|
||||
"suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})",
|
||||
cause, exp_found
|
||||
);
|
||||
if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() {
|
||||
if expected_def.is_enum() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((name, ty)) = expected_def
|
||||
.non_enum_variant()
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|field| field.vis.is_accessible_from(field.did, self.tcx))
|
||||
.map(|field| (field.name, field.ty(self.tcx, expected_substs)))
|
||||
.find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found))
|
||||
{
|
||||
if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() {
|
||||
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
|
||||
let suggestion = if expected_def.is_struct() {
|
||||
format!("{}.{}", snippet, name)
|
||||
} else if expected_def.is_union() {
|
||||
format!("unsafe {{ {}.{} }}", snippet, name)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
&format!(
|
||||
"you might have meant to use field `{}` whose type is `{}`",
|
||||
name, ty
|
||||
),
|
||||
suggestion,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,
|
||||
/// suggests it.
|
||||
pub(super) fn suggest_as_ref_where_appropriate(
|
||||
&self,
|
||||
span: Span,
|
||||
exp_found: &ty::error::ExpectedFound<Ty<'tcx>>,
|
||||
diag: &mut Diagnostic,
|
||||
) {
|
||||
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
|
||||
&& let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found)
|
||||
{
|
||||
diag.span_suggestion(
|
||||
span,
|
||||
msg,
|
||||
// HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&`
|
||||
format!("{}.as_ref()", snippet.trim_start_matches('&')),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> {
|
||||
if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) =
|
||||
(expected.kind(), found.kind())
|
||||
{
|
||||
if let ty::Adt(found_def, found_substs) = *found_ty.kind() {
|
||||
if exp_def == &found_def {
|
||||
let have_as_ref = &[
|
||||
(
|
||||
sym::Option,
|
||||
"you can convert from `&Option<T>` to `Option<&T>` using \
|
||||
`.as_ref()`",
|
||||
),
|
||||
(
|
||||
sym::Result,
|
||||
"you can convert from `&Result<T, E>` to \
|
||||
`Result<&T, &E>` using `.as_ref()`",
|
||||
),
|
||||
];
|
||||
if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| {
|
||||
self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg)
|
||||
}) {
|
||||
let mut show_suggestion = true;
|
||||
for (exp_ty, found_ty) in
|
||||
std::iter::zip(exp_substs.types(), found_substs.types())
|
||||
{
|
||||
match *exp_ty.kind() {
|
||||
ty::Ref(_, exp_ty, _) => {
|
||||
match (exp_ty.kind(), found_ty.kind()) {
|
||||
(_, ty::Param(_))
|
||||
| (_, ty::Infer(_))
|
||||
| (ty::Param(_), _)
|
||||
| (ty::Infer(_), _) => {}
|
||||
_ if self.same_type_modulo_infer(exp_ty, found_ty) => {}
|
||||
_ => show_suggestion = false,
|
||||
};
|
||||
}
|
||||
ty::Param(_) | ty::Infer(_) => {}
|
||||
_ => show_suggestion = false,
|
||||
}
|
||||
}
|
||||
if show_suggestion {
|
||||
return Some(*msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Try to find code with pattern `if Some(..) = expr`
|
||||
/// use a `visitor` to mark the `if` which its span contains given error span,
|
||||
/// and then try to find a assignment in the `cond` part, which span is equal with error span
|
||||
pub(super) fn suggest_let_for_letchains(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
cause: &ObligationCause<'_>,
|
||||
span: Span,
|
||||
) {
|
||||
let hir = self.tcx.hir();
|
||||
let fn_hir_id = hir.get_parent_node(cause.body_id);
|
||||
if let Some(node) = self.tcx.hir().find(fn_hir_id) &&
|
||||
let hir::Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Fn(_sig, _, body_id), ..
|
||||
}) = node {
|
||||
let body = hir.body(*body_id);
|
||||
|
||||
/// Find the if expression with given span
|
||||
struct IfVisitor {
|
||||
pub result: bool,
|
||||
pub found_if: bool,
|
||||
pub err_span: Span,
|
||||
}
|
||||
|
||||
impl<'v> Visitor<'v> for IfVisitor {
|
||||
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
|
||||
if self.result { return; }
|
||||
match ex.kind {
|
||||
hir::ExprKind::If(cond, _, _) => {
|
||||
self.found_if = true;
|
||||
walk_expr(self, cond);
|
||||
self.found_if = false;
|
||||
}
|
||||
_ => walk_expr(self, ex),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {
|
||||
if let hir::StmtKind::Local(hir::Local {
|
||||
span, pat: hir::Pat{..}, ty: None, init: Some(_), ..
|
||||
}) = &ex.kind
|
||||
&& self.found_if
|
||||
&& span.eq(&self.err_span) {
|
||||
self.result = true;
|
||||
}
|
||||
walk_stmt(self, ex);
|
||||
}
|
||||
|
||||
fn visit_body(&mut self, body: &'v hir::Body<'v>) {
|
||||
hir::intravisit::walk_body(self, body);
|
||||
}
|
||||
}
|
||||
|
||||
let mut visitor = IfVisitor { err_span: span, found_if: false, result: false };
|
||||
visitor.visit_body(&body);
|
||||
if visitor.result {
|
||||
err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
/// Be helpful when the user wrote `{... expr; }` and taking the `;` off
|
||||
/// is enough to fix the error.
|
||||
pub fn could_remove_semicolon(
|
||||
&self,
|
||||
blk: &'tcx hir::Block<'tcx>,
|
||||
expected_ty: Ty<'tcx>,
|
||||
) -> Option<(Span, StatementAsExpression)> {
|
||||
let blk = blk.innermost_block();
|
||||
// Do not suggest if we have a tail expr.
|
||||
if blk.expr.is_some() {
|
||||
return None;
|
||||
}
|
||||
let last_stmt = blk.stmts.last()?;
|
||||
let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else {
|
||||
return None;
|
||||
};
|
||||
let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?;
|
||||
let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) {
|
||||
_ if last_expr_ty.references_error() => return None,
|
||||
_ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => {
|
||||
StatementAsExpression::CorrectType
|
||||
}
|
||||
(ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _))
|
||||
if last_def_id == exp_def_id =>
|
||||
{
|
||||
StatementAsExpression::CorrectType
|
||||
}
|
||||
(ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => {
|
||||
debug!(
|
||||
"both opaque, likely future {:?} {:?} {:?} {:?}",
|
||||
last_def_id, last_bounds, exp_def_id, exp_bounds
|
||||
);
|
||||
|
||||
let last_local_id = last_def_id.as_local()?;
|
||||
let exp_local_id = exp_def_id.as_local()?;
|
||||
|
||||
match (
|
||||
&self.tcx.hir().expect_item(last_local_id).kind,
|
||||
&self.tcx.hir().expect_item(exp_local_id).kind,
|
||||
) {
|
||||
(
|
||||
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }),
|
||||
hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }),
|
||||
) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| {
|
||||
match (left, right) {
|
||||
(
|
||||
hir::GenericBound::Trait(tl, ml),
|
||||
hir::GenericBound::Trait(tr, mr),
|
||||
) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id()
|
||||
&& ml == mr =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(
|
||||
hir::GenericBound::LangItemTrait(langl, _, _, argsl),
|
||||
hir::GenericBound::LangItemTrait(langr, _, _, argsr),
|
||||
) if langl == langr => {
|
||||
// FIXME: consider the bounds!
|
||||
debug!("{:?} {:?}", argsl, argsr);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}) =>
|
||||
{
|
||||
StatementAsExpression::NeedsBoxing
|
||||
}
|
||||
_ => StatementAsExpression::CorrectType,
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let span = if last_stmt.span.from_expansion() {
|
||||
let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span);
|
||||
self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)?
|
||||
} else {
|
||||
last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1))
|
||||
};
|
||||
Some((span, needs_box))
|
||||
}
|
||||
|
||||
/// Suggest returning a local binding with a compatible type if the block
|
||||
/// has no return expression.
|
||||
pub fn consider_returning_binding(
|
||||
&self,
|
||||
blk: &'tcx hir::Block<'tcx>,
|
||||
expected_ty: Ty<'tcx>,
|
||||
err: &mut Diagnostic,
|
||||
) -> bool {
|
||||
let blk = blk.innermost_block();
|
||||
// Do not suggest if we have a tail expr.
|
||||
if blk.expr.is_some() {
|
||||
return false;
|
||||
}
|
||||
let mut shadowed = FxIndexSet::default();
|
||||
let mut candidate_idents = vec![];
|
||||
let mut find_compatible_candidates = |pat: &hir::Pat<'_>| {
|
||||
if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind
|
||||
&& let Some(pat_ty) = self
|
||||
.typeck_results
|
||||
.as_ref()
|
||||
.and_then(|typeck_results| typeck_results.node_type_opt(*hir_id))
|
||||
{
|
||||
let pat_ty = self.resolve_vars_if_possible(pat_ty);
|
||||
if self.same_type_modulo_infer(pat_ty, expected_ty)
|
||||
&& !(pat_ty, expected_ty).references_error()
|
||||
&& shadowed.insert(ident.name)
|
||||
{
|
||||
candidate_idents.push((*ident, pat_ty));
|
||||
}
|
||||
}
|
||||
true
|
||||
};
|
||||
|
||||
let hir = self.tcx.hir();
|
||||
for stmt in blk.stmts.iter().rev() {
|
||||
let hir::StmtKind::Local(local) = &stmt.kind else { continue; };
|
||||
local.pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
match hir.find(hir.get_parent_node(blk.hir_id)) {
|
||||
Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => {
|
||||
match hir.find(hir.get_parent_node(*hir_id)) {
|
||||
Some(hir::Node::Arm(hir::Arm { pat, .. })) => {
|
||||
pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
Some(
|
||||
hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. })
|
||||
| hir::Node::ImplItem(hir::ImplItem {
|
||||
kind: hir::ImplItemKind::Fn(_, body),
|
||||
..
|
||||
})
|
||||
| hir::Node::TraitItem(hir::TraitItem {
|
||||
kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)),
|
||||
..
|
||||
})
|
||||
| hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(hir::Closure { body, .. }),
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
for param in hir.body(*body).params {
|
||||
param.pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
}
|
||||
Some(hir::Node::Expr(hir::Expr {
|
||||
kind:
|
||||
hir::ExprKind::If(
|
||||
hir::Expr { kind: hir::ExprKind::Let(let_), .. },
|
||||
then_block,
|
||||
_,
|
||||
),
|
||||
..
|
||||
})) if then_block.hir_id == *hir_id => {
|
||||
let_.pat.walk(&mut find_compatible_candidates);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match &candidate_idents[..] {
|
||||
[(ident, _ty)] => {
|
||||
let sm = self.tcx.sess.source_map();
|
||||
if let Some(stmt) = blk.stmts.last() {
|
||||
let stmt_span = sm.stmt_span(stmt.span, blk.span);
|
||||
let sugg = if sm.is_multiline(blk.span)
|
||||
&& let Some(spacing) = sm.indentation_before(stmt_span)
|
||||
{
|
||||
format!("\n{spacing}{ident}")
|
||||
} else {
|
||||
format!(" {ident}")
|
||||
};
|
||||
err.span_suggestion_verbose(
|
||||
stmt_span.shrink_to_hi(),
|
||||
format!("consider returning the local binding `{ident}`"),
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
let sugg = if sm.is_multiline(blk.span)
|
||||
&& let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo())
|
||||
{
|
||||
format!("\n{spacing} {ident}\n{spacing}")
|
||||
} else {
|
||||
format!(" {ident} ")
|
||||
};
|
||||
let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi();
|
||||
err.span_suggestion_verbose(
|
||||
sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span),
|
||||
format!("consider returning the local binding `{ident}`"),
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
values if (1..3).contains(&values.len()) => {
|
||||
let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>();
|
||||
err.span_note(spans, "consider returning one of these bindings");
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
203
compiler/rustc_infer/src/infer/note.rs
Normal file
203
compiler/rustc_infer/src/infer/note.rs
Normal file
@ -0,0 +1,203 @@
|
||||
impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||
fn note_error_origin(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
cause: &ObligationCause<'tcx>,
|
||||
exp_found: Option<ty::error::ExpectedFound<Ty<'tcx>>>,
|
||||
terr: TypeError<'tcx>,
|
||||
) {
|
||||
match *cause.code() {
|
||||
ObligationCauseCode::Pattern { origin_expr: true, span: Some(span), root_ty } => {
|
||||
let ty = self.resolve_vars_if_possible(root_ty);
|
||||
if !matches!(ty.kind(), ty::Infer(ty::InferTy::TyVar(_) | ty::InferTy::FreshTy(_)))
|
||||
{
|
||||
// don't show type `_`
|
||||
if span.desugaring_kind() == Some(DesugaringKind::ForLoop)
|
||||
&& let ty::Adt(def, substs) = ty.kind()
|
||||
&& Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option)
|
||||
{
|
||||
err.span_label(span, format!("this is an iterator with items of type `{}`", substs.type_at(0)));
|
||||
} else {
|
||||
err.span_label(span, format!("this expression has type `{}`", ty));
|
||||
}
|
||||
}
|
||||
if let Some(ty::error::ExpectedFound { found, .. }) = exp_found
|
||||
&& ty.is_box() && ty.boxed_ty() == found
|
||||
&& let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span)
|
||||
{
|
||||
err.span_suggestion(
|
||||
span,
|
||||
"consider dereferencing the boxed value",
|
||||
format!("*{}", snippet),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => {
|
||||
err.span_label(span, "expected due to this");
|
||||
}
|
||||
ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause {
|
||||
arm_block_id,
|
||||
arm_span,
|
||||
arm_ty,
|
||||
prior_arm_block_id,
|
||||
prior_arm_span,
|
||||
prior_arm_ty,
|
||||
source,
|
||||
ref prior_arms,
|
||||
scrut_hir_id,
|
||||
opt_suggest_box_span,
|
||||
scrut_span,
|
||||
..
|
||||
}) => match source {
|
||||
hir::MatchSource::TryDesugar => {
|
||||
if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found {
|
||||
let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id);
|
||||
let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind {
|
||||
let arg_expr = args.first().expect("try desugaring call w/out arg");
|
||||
self.typeck_results.as_ref().and_then(|typeck_results| {
|
||||
typeck_results.expr_ty_opt(arg_expr)
|
||||
})
|
||||
} else {
|
||||
bug!("try desugaring w/out call expr as scrutinee");
|
||||
};
|
||||
|
||||
match scrut_ty {
|
||||
Some(ty) if expected == ty => {
|
||||
let source_map = self.tcx.sess.source_map();
|
||||
err.span_suggestion(
|
||||
source_map.end_point(cause.span),
|
||||
"try removing this `?`",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// `prior_arm_ty` can be `!`, `expected` will have better info when present.
|
||||
let t = self.resolve_vars_if_possible(match exp_found {
|
||||
Some(ty::error::ExpectedFound { expected, .. }) => expected,
|
||||
_ => prior_arm_ty,
|
||||
});
|
||||
let source_map = self.tcx.sess.source_map();
|
||||
let mut any_multiline_arm = source_map.is_multiline(arm_span);
|
||||
if prior_arms.len() <= 4 {
|
||||
for sp in prior_arms {
|
||||
any_multiline_arm |= source_map.is_multiline(*sp);
|
||||
err.span_label(*sp, format!("this is found to be of type `{}`", t));
|
||||
}
|
||||
} else if let Some(sp) = prior_arms.last() {
|
||||
any_multiline_arm |= source_map.is_multiline(*sp);
|
||||
err.span_label(
|
||||
*sp,
|
||||
format!("this and all prior arms are found to be of type `{}`", t),
|
||||
);
|
||||
}
|
||||
let outer_error_span = if any_multiline_arm {
|
||||
// Cover just `match` and the scrutinee expression, not
|
||||
// the entire match body, to reduce diagram noise.
|
||||
cause.span.shrink_to_lo().to(scrut_span)
|
||||
} else {
|
||||
cause.span
|
||||
};
|
||||
let msg = "`match` arms have incompatible types";
|
||||
err.span_label(outer_error_span, msg);
|
||||
self.suggest_remove_semi_or_return_binding(
|
||||
err,
|
||||
prior_arm_block_id,
|
||||
prior_arm_ty,
|
||||
prior_arm_span,
|
||||
arm_block_id,
|
||||
arm_ty,
|
||||
arm_span,
|
||||
);
|
||||
if let Some(ret_sp) = opt_suggest_box_span {
|
||||
// Get return type span and point to it.
|
||||
self.suggest_boxing_for_return_impl_trait(
|
||||
err,
|
||||
ret_sp,
|
||||
prior_arms.iter().chain(std::iter::once(&arm_span)).map(|s| *s),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
ObligationCauseCode::IfExpression(box IfExpressionCause {
|
||||
then_id,
|
||||
else_id,
|
||||
then_ty,
|
||||
else_ty,
|
||||
outer_span,
|
||||
opt_suggest_box_span,
|
||||
}) => {
|
||||
let then_span = self.find_block_span_from_hir_id(then_id);
|
||||
let else_span = self.find_block_span_from_hir_id(else_id);
|
||||
err.span_label(then_span, "expected because of this");
|
||||
if let Some(sp) = outer_span {
|
||||
err.span_label(sp, "`if` and `else` have incompatible types");
|
||||
}
|
||||
self.suggest_remove_semi_or_return_binding(
|
||||
err,
|
||||
Some(then_id),
|
||||
then_ty,
|
||||
then_span,
|
||||
Some(else_id),
|
||||
else_ty,
|
||||
else_span,
|
||||
);
|
||||
if let Some(ret_sp) = opt_suggest_box_span {
|
||||
self.suggest_boxing_for_return_impl_trait(
|
||||
err,
|
||||
ret_sp,
|
||||
[then_span, else_span].into_iter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
ObligationCauseCode::LetElse => {
|
||||
err.help("try adding a diverging expression, such as `return` or `panic!(..)`");
|
||||
err.help("...or use `match` instead of `let...else`");
|
||||
}
|
||||
_ => {
|
||||
if let ObligationCauseCode::BindingObligation(_, span)
|
||||
| ObligationCauseCode::ExprBindingObligation(_, span, ..)
|
||||
= cause.code().peel_derives()
|
||||
&& let TypeError::RegionsPlaceholderMismatch = terr
|
||||
{
|
||||
err.span_note( * span,
|
||||
"the lifetime requirement is introduced here");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> InferCtxt<'tcx> {
|
||||
/// Given a [`hir::Block`], get the span of its last expression or
|
||||
/// statement, peeling off any inner blocks.
|
||||
pub fn find_block_span(&self, block: &'tcx hir::Block<'tcx>) -> Span {
|
||||
let block = block.innermost_block();
|
||||
if let Some(expr) = &block.expr {
|
||||
expr.span
|
||||
} else if let Some(stmt) = block.stmts.last() {
|
||||
// possibly incorrect trailing `;` in the else arm
|
||||
stmt.span
|
||||
} else {
|
||||
// empty block; point at its entirety
|
||||
block.span
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a [`hir::HirId`] for a block, get the span of its last expression
|
||||
/// or statement, peeling off any inner blocks.
|
||||
pub fn find_block_span_from_hir_id(&self, hir_id: hir::HirId) -> Span {
|
||||
match self.tcx.hir().get(hir_id) {
|
||||
hir::Node::Block(blk) => self.find_block_span(blk),
|
||||
// The parser was in a weird state if either of these happen, but
|
||||
// it's better not to panic.
|
||||
hir::Node::Expr(e) => e.span,
|
||||
_ => rustc_span::DUMMY_SP,
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
|
||||
pub mod auto_trait;
|
||||
mod chalk_fulfill;
|
||||
pub mod codegen;
|
||||
mod coherence;
|
||||
pub mod const_evaluatable;
|
||||
mod engine;
|
||||
@ -20,9 +19,9 @@ mod select;
|
||||
mod specialize;
|
||||
mod structural_match;
|
||||
mod util;
|
||||
mod vtable;
|
||||
pub mod wf;
|
||||
|
||||
use crate::errors::DumpVTableEntries;
|
||||
use crate::infer::outlives::env::OutlivesEnvironment;
|
||||
use crate::infer::{InferCtxt, TyCtxtInferExt};
|
||||
use crate::traits::error_reporting::TypeErrCtxtExt as _;
|
||||
@ -30,15 +29,11 @@ use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_middle::ty::fold::TypeFoldable;
|
||||
use rustc_middle::ty::visit::TypeVisitable;
|
||||
use rustc_middle::ty::{
|
||||
self, DefIdTree, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeSuperVisitable, VtblEntry,
|
||||
};
|
||||
use rustc_middle::ty::{self, DefIdTree, ToPredicate, Ty, TyCtxt, TypeSuperVisitable};
|
||||
use rustc_middle::ty::{InternalSubsts, SubstsRef};
|
||||
use rustc_span::{sym, Span};
|
||||
use smallvec::SmallVec;
|
||||
use rustc_span::Span;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::ops::ControlFlow;
|
||||
@ -567,369 +562,12 @@ fn is_impossible_method<'tcx>(
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum VtblSegment<'tcx> {
|
||||
MetadataDSA,
|
||||
TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool },
|
||||
}
|
||||
|
||||
/// Prepare the segments for a vtable
|
||||
fn prepare_vtable_segments<'tcx, T>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow<T>,
|
||||
) -> Option<T> {
|
||||
// The following constraints holds for the final arrangement.
|
||||
// 1. The whole virtual table of the first direct super trait is included as the
|
||||
// the prefix. If this trait doesn't have any super traits, then this step
|
||||
// consists of the dsa metadata.
|
||||
// 2. Then comes the proper pointer metadata(vptr) and all own methods for all
|
||||
// other super traits except those already included as part of the first
|
||||
// direct super trait virtual table.
|
||||
// 3. finally, the own methods of this trait.
|
||||
|
||||
// This has the advantage that trait upcasting to the first direct super trait on each level
|
||||
// is zero cost, and to another trait includes only replacing the pointer with one level indirection,
|
||||
// while not using too much extra memory.
|
||||
|
||||
// For a single inheritance relationship like this,
|
||||
// D --> C --> B --> A
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, C, D
|
||||
|
||||
// For a multiple inheritance relationship like this,
|
||||
// D --> C --> A
|
||||
// \-> B
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, B-vptr, C, D
|
||||
|
||||
// For a diamond inheritance relationship like this,
|
||||
// D --> B --> A
|
||||
// \-> C -/
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, C, C-vptr, D
|
||||
|
||||
// For a more complex inheritance relationship like this:
|
||||
// O --> G --> C --> A
|
||||
// \ \ \-> B
|
||||
// | |-> F --> D
|
||||
// | \-> E
|
||||
// |-> N --> J --> H
|
||||
// \ \-> I
|
||||
// |-> M --> K
|
||||
// \-> L
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, B-vptr, C, D, D-vptr, E, E-vptr, F, F-vptr, G,
|
||||
// H, H-vptr, I, I-vptr, J, J-vptr, K, K-vptr, L, L-vptr, M, M-vptr,
|
||||
// N, N-vptr, O
|
||||
|
||||
// emit dsa segment first.
|
||||
if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::MetadataDSA) {
|
||||
return Some(v);
|
||||
}
|
||||
|
||||
let mut emit_vptr_on_new_entry = false;
|
||||
let mut visited = util::PredicateSet::new(tcx);
|
||||
let predicate = trait_ref.without_const().to_predicate(tcx);
|
||||
let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> =
|
||||
smallvec![(trait_ref, emit_vptr_on_new_entry, None)];
|
||||
visited.insert(predicate);
|
||||
|
||||
// the main traversal loop:
|
||||
// basically we want to cut the inheritance directed graph into a few non-overlapping slices of nodes
|
||||
// that each node is emitted after all its descendents have been emitted.
|
||||
// so we convert the directed graph into a tree by skipping all previously visited nodes using a visited set.
|
||||
// this is done on the fly.
|
||||
// Each loop run emits a slice - it starts by find a "childless" unvisited node, backtracking upwards, and it
|
||||
// stops after it finds a node that has a next-sibling node.
|
||||
// This next-sibling node will used as the starting point of next slice.
|
||||
|
||||
// Example:
|
||||
// For a diamond inheritance relationship like this,
|
||||
// D#1 --> B#0 --> A#0
|
||||
// \-> C#1 -/
|
||||
|
||||
// Starting point 0 stack [D]
|
||||
// Loop run #0: Stack after diving in is [D B A], A is "childless"
|
||||
// after this point, all newly visited nodes won't have a vtable that equals to a prefix of this one.
|
||||
// Loop run #0: Emitting the slice [B A] (in reverse order), B has a next-sibling node, so this slice stops here.
|
||||
// Loop run #0: Stack after exiting out is [D C], C is the next starting point.
|
||||
// Loop run #1: Stack after diving in is [D C], C is "childless", since its child A is skipped(already emitted).
|
||||
// Loop run #1: Emitting the slice [D C] (in reverse order). No one has a next-sibling node.
|
||||
// Loop run #1: Stack after exiting out is []. Now the function exits.
|
||||
|
||||
loop {
|
||||
// dive deeper into the stack, recording the path
|
||||
'diving_in: loop {
|
||||
if let Some((inner_most_trait_ref, _, _)) = stack.last() {
|
||||
let inner_most_trait_ref = *inner_most_trait_ref;
|
||||
let mut direct_super_traits_iter = tcx
|
||||
.super_predicates_of(inner_most_trait_ref.def_id())
|
||||
.predicates
|
||||
.into_iter()
|
||||
.filter_map(move |(pred, _)| {
|
||||
pred.subst_supertrait(tcx, &inner_most_trait_ref).to_opt_poly_trait_pred()
|
||||
});
|
||||
|
||||
'diving_in_skip_visited_traits: loop {
|
||||
if let Some(next_super_trait) = direct_super_traits_iter.next() {
|
||||
if visited.insert(next_super_trait.to_predicate(tcx)) {
|
||||
// We're throwing away potential constness of super traits here.
|
||||
// FIXME: handle ~const super traits
|
||||
let next_super_trait = next_super_trait.map_bound(|t| t.trait_ref);
|
||||
stack.push((
|
||||
next_super_trait,
|
||||
emit_vptr_on_new_entry,
|
||||
Some(direct_super_traits_iter),
|
||||
));
|
||||
break 'diving_in_skip_visited_traits;
|
||||
} else {
|
||||
continue 'diving_in_skip_visited_traits;
|
||||
}
|
||||
} else {
|
||||
break 'diving_in;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Other than the left-most path, vptr should be emitted for each trait.
|
||||
emit_vptr_on_new_entry = true;
|
||||
|
||||
// emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level.
|
||||
'exiting_out: loop {
|
||||
if let Some((inner_most_trait_ref, emit_vptr, siblings_opt)) = stack.last_mut() {
|
||||
if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::TraitOwnEntries {
|
||||
trait_ref: *inner_most_trait_ref,
|
||||
emit_vptr: *emit_vptr,
|
||||
}) {
|
||||
return Some(v);
|
||||
}
|
||||
|
||||
'exiting_out_skip_visited_traits: loop {
|
||||
if let Some(siblings) = siblings_opt {
|
||||
if let Some(next_inner_most_trait_ref) = siblings.next() {
|
||||
if visited.insert(next_inner_most_trait_ref.to_predicate(tcx)) {
|
||||
// We're throwing away potential constness of super traits here.
|
||||
// FIXME: handle ~const super traits
|
||||
let next_inner_most_trait_ref =
|
||||
next_inner_most_trait_ref.map_bound(|t| t.trait_ref);
|
||||
*inner_most_trait_ref = next_inner_most_trait_ref;
|
||||
*emit_vptr = emit_vptr_on_new_entry;
|
||||
break 'exiting_out;
|
||||
} else {
|
||||
continue 'exiting_out_skip_visited_traits;
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
continue 'exiting_out;
|
||||
}
|
||||
}
|
||||
// all done
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_vtable_entries<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
sp: Span,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
entries: &[VtblEntry<'tcx>],
|
||||
) {
|
||||
tcx.sess.emit_err(DumpVTableEntries {
|
||||
span: sp,
|
||||
trait_ref,
|
||||
entries: format!("{:#?}", entries),
|
||||
});
|
||||
}
|
||||
|
||||
fn own_existential_vtable_entries<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> &'tcx [DefId] {
|
||||
let trait_methods = tcx
|
||||
.associated_items(trait_def_id)
|
||||
.in_definition_order()
|
||||
.filter(|item| item.kind == ty::AssocKind::Fn);
|
||||
// Now list each method's DefId (for within its trait).
|
||||
let own_entries = trait_methods.filter_map(move |trait_method| {
|
||||
debug!("own_existential_vtable_entry: trait_method={:?}", trait_method);
|
||||
let def_id = trait_method.def_id;
|
||||
|
||||
// Some methods cannot be called on an object; skip those.
|
||||
if !is_vtable_safe_method(tcx, trait_def_id, &trait_method) {
|
||||
debug!("own_existential_vtable_entry: not vtable safe");
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(def_id)
|
||||
});
|
||||
|
||||
tcx.arena.alloc_from_iter(own_entries.into_iter())
|
||||
}
|
||||
|
||||
/// Given a trait `trait_ref`, iterates the vtable entries
|
||||
/// that come from `trait_ref`, including its supertraits.
|
||||
fn vtable_entries<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
) -> &'tcx [VtblEntry<'tcx>] {
|
||||
debug!("vtable_entries({:?})", trait_ref);
|
||||
|
||||
let mut entries = vec![];
|
||||
|
||||
let vtable_segment_callback = |segment| -> ControlFlow<()> {
|
||||
match segment {
|
||||
VtblSegment::MetadataDSA => {
|
||||
entries.extend(TyCtxt::COMMON_VTABLE_ENTRIES);
|
||||
}
|
||||
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
|
||||
let existential_trait_ref = trait_ref
|
||||
.map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref));
|
||||
|
||||
// Lookup the shape of vtable for the trait.
|
||||
let own_existential_entries =
|
||||
tcx.own_existential_vtable_entries(existential_trait_ref.def_id());
|
||||
|
||||
let own_entries = own_existential_entries.iter().copied().map(|def_id| {
|
||||
debug!("vtable_entries: trait_method={:?}", def_id);
|
||||
|
||||
// The method may have some early-bound lifetimes; add regions for those.
|
||||
let substs = trait_ref.map_bound(|trait_ref| {
|
||||
InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind {
|
||||
GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
|
||||
GenericParamDefKind::Type { .. }
|
||||
| GenericParamDefKind::Const { .. } => {
|
||||
trait_ref.substs[param.index as usize]
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// The trait type may have higher-ranked lifetimes in it;
|
||||
// erase them if they appear, so that we get the type
|
||||
// at some particular call site.
|
||||
let substs = tcx
|
||||
.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs);
|
||||
|
||||
// It's possible that the method relies on where-clauses that
|
||||
// do not hold for this particular set of type parameters.
|
||||
// Note that this method could then never be called, so we
|
||||
// do not want to try and codegen it, in that case (see #23435).
|
||||
let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs);
|
||||
if impossible_predicates(tcx, predicates.predicates) {
|
||||
debug!("vtable_entries: predicates do not hold");
|
||||
return VtblEntry::Vacant;
|
||||
}
|
||||
|
||||
let instance = ty::Instance::resolve_for_vtable(
|
||||
tcx,
|
||||
ty::ParamEnv::reveal_all(),
|
||||
def_id,
|
||||
substs,
|
||||
)
|
||||
.expect("resolution failed during building vtable representation");
|
||||
VtblEntry::Method(instance)
|
||||
});
|
||||
|
||||
entries.extend(own_entries);
|
||||
|
||||
if emit_vptr {
|
||||
entries.push(VtblEntry::TraitVPtr(trait_ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
};
|
||||
|
||||
let _ = prepare_vtable_segments(tcx, trait_ref, vtable_segment_callback);
|
||||
|
||||
if tcx.has_attr(trait_ref.def_id(), sym::rustc_dump_vtable) {
|
||||
let sp = tcx.def_span(trait_ref.def_id());
|
||||
dump_vtable_entries(tcx, sp, trait_ref, &entries);
|
||||
}
|
||||
|
||||
tcx.arena.alloc_from_iter(entries.into_iter())
|
||||
}
|
||||
|
||||
/// Find slot base for trait methods within vtable entries of another trait
|
||||
fn vtable_trait_first_method_offset<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
key: (
|
||||
ty::PolyTraitRef<'tcx>, // trait_to_be_found
|
||||
ty::PolyTraitRef<'tcx>, // trait_owning_vtable
|
||||
),
|
||||
) -> usize {
|
||||
let (trait_to_be_found, trait_owning_vtable) = key;
|
||||
|
||||
// #90177
|
||||
let trait_to_be_found_erased = tcx.erase_regions(trait_to_be_found);
|
||||
|
||||
let vtable_segment_callback = {
|
||||
let mut vtable_base = 0;
|
||||
|
||||
move |segment| {
|
||||
match segment {
|
||||
VtblSegment::MetadataDSA => {
|
||||
vtable_base += TyCtxt::COMMON_VTABLE_ENTRIES.len();
|
||||
}
|
||||
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
|
||||
if tcx.erase_regions(trait_ref) == trait_to_be_found_erased {
|
||||
return ControlFlow::Break(vtable_base);
|
||||
}
|
||||
vtable_base += util::count_own_vtable_entries(tcx, trait_ref);
|
||||
if emit_vptr {
|
||||
vtable_base += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(vtable_base) =
|
||||
prepare_vtable_segments(tcx, trait_owning_vtable, vtable_segment_callback)
|
||||
{
|
||||
vtable_base
|
||||
} else {
|
||||
bug!("Failed to find info for expected trait in vtable");
|
||||
}
|
||||
}
|
||||
|
||||
/// Find slot offset for trait vptr within vtable entries of another trait
|
||||
pub fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
key: (
|
||||
Ty<'tcx>, // trait object type whose trait owning vtable
|
||||
Ty<'tcx>, // trait object for supertrait
|
||||
),
|
||||
) -> Option<usize> {
|
||||
let (source, target) = key;
|
||||
assert!(matches!(&source.kind(), &ty::Dynamic(..)) && !source.needs_infer());
|
||||
assert!(matches!(&target.kind(), &ty::Dynamic(..)) && !target.needs_infer());
|
||||
|
||||
// this has been typecked-before, so diagnostics is not really needed.
|
||||
let unsize_trait_did = tcx.require_lang_item(LangItem::Unsize, None);
|
||||
|
||||
let trait_ref = tcx.mk_trait_ref(unsize_trait_did, [source, target]);
|
||||
|
||||
match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), ty::Binder::dummy(trait_ref))) {
|
||||
Ok(ImplSource::TraitUpcasting(implsrc_traitcasting)) => {
|
||||
implsrc_traitcasting.vtable_vptr_slot
|
||||
}
|
||||
otherwise => bug!("expected TraitUpcasting candidate, got {otherwise:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut ty::query::Providers) {
|
||||
object_safety::provide(providers);
|
||||
structural_match::provide(providers);
|
||||
vtable::provide(providers);
|
||||
*providers = ty::query::Providers {
|
||||
specialization_graph_of: specialize::specialization_graph_provider,
|
||||
specializes: specialize::specializes,
|
||||
codegen_select_candidate: codegen::codegen_select_candidate,
|
||||
own_existential_vtable_entries,
|
||||
vtable_entries,
|
||||
vtable_trait_upcasting_coercion_new_vptr_slot,
|
||||
subst_and_check_impossible_predicates,
|
||||
is_impossible_method,
|
||||
..*providers
|
||||
|
@ -19,6 +19,10 @@ use rustc_span::def_id::DefId;
|
||||
|
||||
use crate::traits::project::{normalize_with_depth, normalize_with_depth_to};
|
||||
use crate::traits::util::{self, closure_trait_ref_and_return_type, predicate_for_trait_def};
|
||||
use crate::traits::vtable::{
|
||||
count_own_vtable_entries, prepare_vtable_segments, vtable_trait_first_method_offset,
|
||||
VtblSegment,
|
||||
};
|
||||
use crate::traits::{
|
||||
BuiltinDerivedObligation, ImplDerivedObligation, ImplDerivedObligationCause, ImplSource,
|
||||
ImplSourceAutoImplData, ImplSourceBuiltinData, ImplSourceClosureData,
|
||||
@ -26,7 +30,7 @@ use crate::traits::{
|
||||
ImplSourceGeneratorData, ImplSourceObjectData, ImplSourceTraitAliasData,
|
||||
ImplSourceTraitUpcastingData, ImplSourceUserDefinedData, Normalized, ObjectCastObligation,
|
||||
Obligation, ObligationCause, OutputTypeParameterMismatch, PredicateObligation, Selection,
|
||||
SelectionError, TraitNotObjectSafe, TraitObligation, Unimplemented, VtblSegment,
|
||||
SelectionError, TraitNotObjectSafe, TraitObligation, Unimplemented,
|
||||
};
|
||||
|
||||
use super::BuiltinImplConditions;
|
||||
@ -583,7 +587,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
|
||||
debug!(?nested, "object nested obligations");
|
||||
|
||||
let vtable_base = super::super::vtable_trait_first_method_offset(
|
||||
let vtable_base = vtable_trait_first_method_offset(
|
||||
tcx,
|
||||
(unnormalized_upcast_trait_ref, ty::Binder::dummy(object_trait_ref)),
|
||||
);
|
||||
@ -904,7 +908,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
vptr_offset += TyCtxt::COMMON_VTABLE_ENTRIES.len();
|
||||
}
|
||||
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
|
||||
vptr_offset += util::count_own_vtable_entries(tcx, trait_ref);
|
||||
vptr_offset += count_own_vtable_entries(tcx, trait_ref);
|
||||
if trait_ref == upcast_trait_ref {
|
||||
if emit_vptr {
|
||||
return ControlFlow::Break(Some(vptr_offset));
|
||||
@ -923,8 +927,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||
};
|
||||
|
||||
let vtable_vptr_slot =
|
||||
super::super::prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback)
|
||||
.unwrap();
|
||||
prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback).unwrap();
|
||||
|
||||
Ok(ImplSourceTraitUpcastingData { upcast_trait_ref, vtable_vptr_slot, nested })
|
||||
}
|
||||
|
@ -1,10 +1,5 @@
|
||||
use crate::infer::{InferCtxt, TyCtxtInferExt};
|
||||
use crate::traits::{ObligationCause, ObligationCtxt};
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_middle::ty::query::Providers;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
|
||||
use rustc_span::Span;
|
||||
use std::ops::ControlFlow;
|
||||
@ -59,41 +54,6 @@ pub fn search_for_adt_const_param_violation<'tcx>(
|
||||
.break_value()
|
||||
}
|
||||
|
||||
/// This method returns true if and only if `adt_ty` itself has been marked as
|
||||
/// eligible for structural-match: namely, if it implements both
|
||||
/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by
|
||||
/// `#[derive(PartialEq)]` and `#[derive(Eq)]`).
|
||||
///
|
||||
/// Note that this does *not* recursively check if the substructure of `adt_ty`
|
||||
/// implements the traits.
|
||||
fn type_marked_structural<'tcx>(
|
||||
infcx: &InferCtxt<'tcx>,
|
||||
adt_ty: Ty<'tcx>,
|
||||
cause: ObligationCause<'tcx>,
|
||||
) -> bool {
|
||||
let ocx = ObligationCtxt::new(infcx);
|
||||
// require `#[derive(PartialEq)]`
|
||||
let structural_peq_def_id =
|
||||
infcx.tcx.require_lang_item(LangItem::StructuralPeq, Some(cause.span));
|
||||
ocx.register_bound(cause.clone(), ty::ParamEnv::empty(), adt_ty, structural_peq_def_id);
|
||||
// for now, require `#[derive(Eq)]`. (Doing so is a hack to work around
|
||||
// the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.)
|
||||
let structural_teq_def_id =
|
||||
infcx.tcx.require_lang_item(LangItem::StructuralTeq, Some(cause.span));
|
||||
ocx.register_bound(cause, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id);
|
||||
|
||||
// We deliberately skip *reporting* fulfillment errors (via
|
||||
// `report_fulfillment_errors`), for two reasons:
|
||||
//
|
||||
// 1. The error messages would mention `std::marker::StructuralPartialEq`
|
||||
// (a trait which is solely meant as an implementation detail
|
||||
// for now), and
|
||||
//
|
||||
// 2. We are sometimes doing future-incompatibility lints for
|
||||
// now, so we do not want unconditional errors here.
|
||||
ocx.select_all_or_error().is_empty()
|
||||
}
|
||||
|
||||
/// This implements the traversal over the structure of a given type to try to
|
||||
/// find instances of ADTs (specifically structs or enums) that do not implement
|
||||
/// the structural-match traits (`StructuralPartialEq` and `StructuralEq`).
|
||||
@ -249,11 +209,3 @@ impl<'tcx> TypeVisitor<'tcx> for Search<'tcx> {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
providers.has_structural_eq_impls = |tcx, ty| {
|
||||
let infcx = tcx.infer_ctxt().build();
|
||||
let cause = ObligationCause::dummy();
|
||||
type_marked_structural(&infcx, ty, cause)
|
||||
};
|
||||
}
|
||||
|
@ -261,16 +261,6 @@ pub fn upcast_choices<'tcx>(
|
||||
supertraits(tcx, source_trait_ref).filter(|r| r.def_id() == target_trait_def_id).collect()
|
||||
}
|
||||
|
||||
/// Given a trait `trait_ref`, returns the number of vtable entries
|
||||
/// that come from `trait_ref`, excluding its supertraits. Used in
|
||||
/// computing the vtable base for an upcast trait of a trait object.
|
||||
pub fn count_own_vtable_entries<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
) -> usize {
|
||||
tcx.own_existential_vtable_entries(trait_ref.def_id()).len()
|
||||
}
|
||||
|
||||
/// Given an upcast trait object described by `object`, returns the
|
||||
/// index of the method `method_def_id` (which should be part of
|
||||
/// `object.upcast_trait_ref`) within the vtable for `object`.
|
||||
|
386
compiler/rustc_trait_selection/src/traits/vtable.rs
Normal file
386
compiler/rustc_trait_selection/src/traits/vtable.rs
Normal file
@ -0,0 +1,386 @@
|
||||
use crate::errors::DumpVTableEntries;
|
||||
use crate::traits::{impossible_predicates, is_vtable_safe_method};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_infer::traits::util::PredicateSet;
|
||||
use rustc_infer::traits::ImplSource;
|
||||
use rustc_middle::ty::visit::TypeVisitable;
|
||||
use rustc_middle::ty::InternalSubsts;
|
||||
use rustc_middle::ty::{self, GenericParamDefKind, ToPredicate, Ty, TyCtxt, VtblEntry};
|
||||
use rustc_span::{sym, Span};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) enum VtblSegment<'tcx> {
|
||||
MetadataDSA,
|
||||
TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool },
|
||||
}
|
||||
|
||||
/// Prepare the segments for a vtable
|
||||
pub(super) fn prepare_vtable_segments<'tcx, T>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow<T>,
|
||||
) -> Option<T> {
|
||||
// The following constraints holds for the final arrangement.
|
||||
// 1. The whole virtual table of the first direct super trait is included as the
|
||||
// the prefix. If this trait doesn't have any super traits, then this step
|
||||
// consists of the dsa metadata.
|
||||
// 2. Then comes the proper pointer metadata(vptr) and all own methods for all
|
||||
// other super traits except those already included as part of the first
|
||||
// direct super trait virtual table.
|
||||
// 3. finally, the own methods of this trait.
|
||||
|
||||
// This has the advantage that trait upcasting to the first direct super trait on each level
|
||||
// is zero cost, and to another trait includes only replacing the pointer with one level indirection,
|
||||
// while not using too much extra memory.
|
||||
|
||||
// For a single inheritance relationship like this,
|
||||
// D --> C --> B --> A
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, C, D
|
||||
|
||||
// For a multiple inheritance relationship like this,
|
||||
// D --> C --> A
|
||||
// \-> B
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, B-vptr, C, D
|
||||
|
||||
// For a diamond inheritance relationship like this,
|
||||
// D --> B --> A
|
||||
// \-> C -/
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, C, C-vptr, D
|
||||
|
||||
// For a more complex inheritance relationship like this:
|
||||
// O --> G --> C --> A
|
||||
// \ \ \-> B
|
||||
// | |-> F --> D
|
||||
// | \-> E
|
||||
// |-> N --> J --> H
|
||||
// \ \-> I
|
||||
// |-> M --> K
|
||||
// \-> L
|
||||
// The resulting vtable will consists of these segments:
|
||||
// DSA, A, B, B-vptr, C, D, D-vptr, E, E-vptr, F, F-vptr, G,
|
||||
// H, H-vptr, I, I-vptr, J, J-vptr, K, K-vptr, L, L-vptr, M, M-vptr,
|
||||
// N, N-vptr, O
|
||||
|
||||
// emit dsa segment first.
|
||||
if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::MetadataDSA) {
|
||||
return Some(v);
|
||||
}
|
||||
|
||||
let mut emit_vptr_on_new_entry = false;
|
||||
let mut visited = PredicateSet::new(tcx);
|
||||
let predicate = trait_ref.without_const().to_predicate(tcx);
|
||||
let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> =
|
||||
smallvec![(trait_ref, emit_vptr_on_new_entry, None)];
|
||||
visited.insert(predicate);
|
||||
|
||||
// the main traversal loop:
|
||||
// basically we want to cut the inheritance directed graph into a few non-overlapping slices of nodes
|
||||
// that each node is emitted after all its descendents have been emitted.
|
||||
// so we convert the directed graph into a tree by skipping all previously visited nodes using a visited set.
|
||||
// this is done on the fly.
|
||||
// Each loop run emits a slice - it starts by find a "childless" unvisited node, backtracking upwards, and it
|
||||
// stops after it finds a node that has a next-sibling node.
|
||||
// This next-sibling node will used as the starting point of next slice.
|
||||
|
||||
// Example:
|
||||
// For a diamond inheritance relationship like this,
|
||||
// D#1 --> B#0 --> A#0
|
||||
// \-> C#1 -/
|
||||
|
||||
// Starting point 0 stack [D]
|
||||
// Loop run #0: Stack after diving in is [D B A], A is "childless"
|
||||
// after this point, all newly visited nodes won't have a vtable that equals to a prefix of this one.
|
||||
// Loop run #0: Emitting the slice [B A] (in reverse order), B has a next-sibling node, so this slice stops here.
|
||||
// Loop run #0: Stack after exiting out is [D C], C is the next starting point.
|
||||
// Loop run #1: Stack after diving in is [D C], C is "childless", since its child A is skipped(already emitted).
|
||||
// Loop run #1: Emitting the slice [D C] (in reverse order). No one has a next-sibling node.
|
||||
// Loop run #1: Stack after exiting out is []. Now the function exits.
|
||||
|
||||
loop {
|
||||
// dive deeper into the stack, recording the path
|
||||
'diving_in: loop {
|
||||
if let Some((inner_most_trait_ref, _, _)) = stack.last() {
|
||||
let inner_most_trait_ref = *inner_most_trait_ref;
|
||||
let mut direct_super_traits_iter = tcx
|
||||
.super_predicates_of(inner_most_trait_ref.def_id())
|
||||
.predicates
|
||||
.into_iter()
|
||||
.filter_map(move |(pred, _)| {
|
||||
pred.subst_supertrait(tcx, &inner_most_trait_ref).to_opt_poly_trait_pred()
|
||||
});
|
||||
|
||||
'diving_in_skip_visited_traits: loop {
|
||||
if let Some(next_super_trait) = direct_super_traits_iter.next() {
|
||||
if visited.insert(next_super_trait.to_predicate(tcx)) {
|
||||
// We're throwing away potential constness of super traits here.
|
||||
// FIXME: handle ~const super traits
|
||||
let next_super_trait = next_super_trait.map_bound(|t| t.trait_ref);
|
||||
stack.push((
|
||||
next_super_trait,
|
||||
emit_vptr_on_new_entry,
|
||||
Some(direct_super_traits_iter),
|
||||
));
|
||||
break 'diving_in_skip_visited_traits;
|
||||
} else {
|
||||
continue 'diving_in_skip_visited_traits;
|
||||
}
|
||||
} else {
|
||||
break 'diving_in;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Other than the left-most path, vptr should be emitted for each trait.
|
||||
emit_vptr_on_new_entry = true;
|
||||
|
||||
// emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level.
|
||||
'exiting_out: loop {
|
||||
if let Some((inner_most_trait_ref, emit_vptr, siblings_opt)) = stack.last_mut() {
|
||||
if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::TraitOwnEntries {
|
||||
trait_ref: *inner_most_trait_ref,
|
||||
emit_vptr: *emit_vptr,
|
||||
}) {
|
||||
return Some(v);
|
||||
}
|
||||
|
||||
'exiting_out_skip_visited_traits: loop {
|
||||
if let Some(siblings) = siblings_opt {
|
||||
if let Some(next_inner_most_trait_ref) = siblings.next() {
|
||||
if visited.insert(next_inner_most_trait_ref.to_predicate(tcx)) {
|
||||
// We're throwing away potential constness of super traits here.
|
||||
// FIXME: handle ~const super traits
|
||||
let next_inner_most_trait_ref =
|
||||
next_inner_most_trait_ref.map_bound(|t| t.trait_ref);
|
||||
*inner_most_trait_ref = next_inner_most_trait_ref;
|
||||
*emit_vptr = emit_vptr_on_new_entry;
|
||||
break 'exiting_out;
|
||||
} else {
|
||||
continue 'exiting_out_skip_visited_traits;
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.pop();
|
||||
continue 'exiting_out;
|
||||
}
|
||||
}
|
||||
// all done
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_vtable_entries<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
sp: Span,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
entries: &[VtblEntry<'tcx>],
|
||||
) {
|
||||
tcx.sess.emit_err(DumpVTableEntries {
|
||||
span: sp,
|
||||
trait_ref,
|
||||
entries: format!("{:#?}", entries),
|
||||
});
|
||||
}
|
||||
|
||||
fn own_existential_vtable_entries<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> &'tcx [DefId] {
|
||||
let trait_methods = tcx
|
||||
.associated_items(trait_def_id)
|
||||
.in_definition_order()
|
||||
.filter(|item| item.kind == ty::AssocKind::Fn);
|
||||
// Now list each method's DefId (for within its trait).
|
||||
let own_entries = trait_methods.filter_map(move |trait_method| {
|
||||
debug!("own_existential_vtable_entry: trait_method={:?}", trait_method);
|
||||
let def_id = trait_method.def_id;
|
||||
|
||||
// Some methods cannot be called on an object; skip those.
|
||||
if !is_vtable_safe_method(tcx, trait_def_id, &trait_method) {
|
||||
debug!("own_existential_vtable_entry: not vtable safe");
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(def_id)
|
||||
});
|
||||
|
||||
tcx.arena.alloc_from_iter(own_entries.into_iter())
|
||||
}
|
||||
|
||||
/// Given a trait `trait_ref`, iterates the vtable entries
|
||||
/// that come from `trait_ref`, including its supertraits.
|
||||
fn vtable_entries<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
) -> &'tcx [VtblEntry<'tcx>] {
|
||||
debug!("vtable_entries({:?})", trait_ref);
|
||||
|
||||
let mut entries = vec![];
|
||||
|
||||
let vtable_segment_callback = |segment| -> ControlFlow<()> {
|
||||
match segment {
|
||||
VtblSegment::MetadataDSA => {
|
||||
entries.extend(TyCtxt::COMMON_VTABLE_ENTRIES);
|
||||
}
|
||||
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
|
||||
let existential_trait_ref = trait_ref
|
||||
.map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref));
|
||||
|
||||
// Lookup the shape of vtable for the trait.
|
||||
let own_existential_entries =
|
||||
tcx.own_existential_vtable_entries(existential_trait_ref.def_id());
|
||||
|
||||
let own_entries = own_existential_entries.iter().copied().map(|def_id| {
|
||||
debug!("vtable_entries: trait_method={:?}", def_id);
|
||||
|
||||
// The method may have some early-bound lifetimes; add regions for those.
|
||||
let substs = trait_ref.map_bound(|trait_ref| {
|
||||
InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind {
|
||||
GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(),
|
||||
GenericParamDefKind::Type { .. }
|
||||
| GenericParamDefKind::Const { .. } => {
|
||||
trait_ref.substs[param.index as usize]
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// The trait type may have higher-ranked lifetimes in it;
|
||||
// erase them if they appear, so that we get the type
|
||||
// at some particular call site.
|
||||
let substs = tcx
|
||||
.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs);
|
||||
|
||||
// It's possible that the method relies on where-clauses that
|
||||
// do not hold for this particular set of type parameters.
|
||||
// Note that this method could then never be called, so we
|
||||
// do not want to try and codegen it, in that case (see #23435).
|
||||
let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs);
|
||||
if impossible_predicates(tcx, predicates.predicates) {
|
||||
debug!("vtable_entries: predicates do not hold");
|
||||
return VtblEntry::Vacant;
|
||||
}
|
||||
|
||||
let instance = ty::Instance::resolve_for_vtable(
|
||||
tcx,
|
||||
ty::ParamEnv::reveal_all(),
|
||||
def_id,
|
||||
substs,
|
||||
)
|
||||
.expect("resolution failed during building vtable representation");
|
||||
VtblEntry::Method(instance)
|
||||
});
|
||||
|
||||
entries.extend(own_entries);
|
||||
|
||||
if emit_vptr {
|
||||
entries.push(VtblEntry::TraitVPtr(trait_ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
};
|
||||
|
||||
let _ = prepare_vtable_segments(tcx, trait_ref, vtable_segment_callback);
|
||||
|
||||
if tcx.has_attr(trait_ref.def_id(), sym::rustc_dump_vtable) {
|
||||
let sp = tcx.def_span(trait_ref.def_id());
|
||||
dump_vtable_entries(tcx, sp, trait_ref, &entries);
|
||||
}
|
||||
|
||||
tcx.arena.alloc_from_iter(entries.into_iter())
|
||||
}
|
||||
|
||||
/// Find slot base for trait methods within vtable entries of another trait
|
||||
pub(super) fn vtable_trait_first_method_offset<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
key: (
|
||||
ty::PolyTraitRef<'tcx>, // trait_to_be_found
|
||||
ty::PolyTraitRef<'tcx>, // trait_owning_vtable
|
||||
),
|
||||
) -> usize {
|
||||
let (trait_to_be_found, trait_owning_vtable) = key;
|
||||
|
||||
// #90177
|
||||
let trait_to_be_found_erased = tcx.erase_regions(trait_to_be_found);
|
||||
|
||||
let vtable_segment_callback = {
|
||||
let mut vtable_base = 0;
|
||||
|
||||
move |segment| {
|
||||
match segment {
|
||||
VtblSegment::MetadataDSA => {
|
||||
vtable_base += TyCtxt::COMMON_VTABLE_ENTRIES.len();
|
||||
}
|
||||
VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
|
||||
if tcx.erase_regions(trait_ref) == trait_to_be_found_erased {
|
||||
return ControlFlow::Break(vtable_base);
|
||||
}
|
||||
vtable_base += count_own_vtable_entries(tcx, trait_ref);
|
||||
if emit_vptr {
|
||||
vtable_base += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(vtable_base) =
|
||||
prepare_vtable_segments(tcx, trait_owning_vtable, vtable_segment_callback)
|
||||
{
|
||||
vtable_base
|
||||
} else {
|
||||
bug!("Failed to find info for expected trait in vtable");
|
||||
}
|
||||
}
|
||||
|
||||
/// Find slot offset for trait vptr within vtable entries of another trait
|
||||
pub(crate) fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
key: (
|
||||
Ty<'tcx>, // trait object type whose trait owning vtable
|
||||
Ty<'tcx>, // trait object for supertrait
|
||||
),
|
||||
) -> Option<usize> {
|
||||
let (source, target) = key;
|
||||
assert!(matches!(&source.kind(), &ty::Dynamic(..)) && !source.needs_infer());
|
||||
assert!(matches!(&target.kind(), &ty::Dynamic(..)) && !target.needs_infer());
|
||||
|
||||
// this has been typecked-before, so diagnostics is not really needed.
|
||||
let unsize_trait_did = tcx.require_lang_item(LangItem::Unsize, None);
|
||||
|
||||
let trait_ref = tcx.mk_trait_ref(unsize_trait_did, [source, target]);
|
||||
|
||||
match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), ty::Binder::dummy(trait_ref))) {
|
||||
Ok(ImplSource::TraitUpcasting(implsrc_traitcasting)) => {
|
||||
implsrc_traitcasting.vtable_vptr_slot
|
||||
}
|
||||
otherwise => bug!("expected TraitUpcasting candidate, got {otherwise:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a trait `trait_ref`, returns the number of vtable entries
|
||||
/// that come from `trait_ref`, excluding its supertraits. Used in
|
||||
/// computing the vtable base for an upcast trait of a trait object.
|
||||
pub(crate) fn count_own_vtable_entries<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
) -> usize {
|
||||
tcx.own_existential_vtable_entries(trait_ref.def_id()).len()
|
||||
}
|
||||
|
||||
pub(super) fn provide(providers: &mut ty::query::Providers) {
|
||||
*providers = ty::query::Providers {
|
||||
own_existential_vtable_entries,
|
||||
vtable_entries,
|
||||
vtable_trait_upcasting_coercion_new_vptr_slot,
|
||||
..*providers
|
||||
};
|
||||
}
|
@ -3,15 +3,15 @@
|
||||
// seems likely that they should eventually be merged into more
|
||||
// general routines.
|
||||
|
||||
use crate::infer::{DefiningAnchor, TyCtxtInferExt};
|
||||
use crate::traits::error_reporting::TypeErrCtxtExt;
|
||||
use crate::traits::{
|
||||
ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt,
|
||||
Unimplemented,
|
||||
};
|
||||
use rustc_infer::infer::{DefiningAnchor, TyCtxtInferExt};
|
||||
use rustc_infer::traits::FulfillmentErrorCode;
|
||||
use rustc_middle::traits::CodegenObligationError;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
|
||||
use rustc_trait_selection::traits::{
|
||||
ImplSource, Obligation, ObligationCause, SelectionContext, TraitEngine, TraitEngineExt,
|
||||
Unimplemented,
|
||||
};
|
||||
|
||||
/// Attempts to resolve an obligation to an `ImplSource`. The result is
|
||||
/// a shallow `ImplSource` resolution, meaning that we do not
|
@ -12,6 +12,7 @@ extern crate tracing;
|
||||
extern crate rustc_middle;
|
||||
|
||||
mod chalk;
|
||||
mod codegen;
|
||||
mod dropck_outlives;
|
||||
mod evaluate_obligation;
|
||||
mod implied_outlives_bounds;
|
||||
@ -31,4 +32,5 @@ pub fn provide(p: &mut Providers) {
|
||||
normalize_projection_ty::provide(p);
|
||||
normalize_erasing_regions::provide(p);
|
||||
type_op::provide(p);
|
||||
p.codegen_select_candidate = codegen::codegen_select_candidate;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ mod layout;
|
||||
mod layout_sanity_check;
|
||||
mod needs_drop;
|
||||
pub mod representability;
|
||||
mod structural_match;
|
||||
mod ty;
|
||||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
@ -42,4 +43,5 @@ pub fn provide(providers: &mut Providers) {
|
||||
representability::provide(providers);
|
||||
ty::provide(providers);
|
||||
instance::provide(providers);
|
||||
structural_match::provide(providers);
|
||||
}
|
||||
|
44
compiler/rustc_ty_utils/src/structural_match.rs
Normal file
44
compiler/rustc_ty_utils/src/structural_match.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_middle::ty::query::Providers;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_trait_selection::traits::{ObligationCause, ObligationCtxt};
|
||||
|
||||
/// This method returns true if and only if `adt_ty` itself has been marked as
|
||||
/// eligible for structural-match: namely, if it implements both
|
||||
/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by
|
||||
/// `#[derive(PartialEq)]` and `#[derive(Eq)]`).
|
||||
///
|
||||
/// Note that this does *not* recursively check if the substructure of `adt_ty`
|
||||
/// implements the traits.
|
||||
fn has_structural_eq_impls<'tcx>(tcx: TyCtxt<'tcx>, adt_ty: Ty<'tcx>) -> bool {
|
||||
let ref infcx = tcx.infer_ctxt().build();
|
||||
let cause = ObligationCause::dummy();
|
||||
|
||||
let ocx = ObligationCtxt::new(infcx);
|
||||
// require `#[derive(PartialEq)]`
|
||||
let structural_peq_def_id =
|
||||
infcx.tcx.require_lang_item(LangItem::StructuralPeq, Some(cause.span));
|
||||
ocx.register_bound(cause.clone(), ty::ParamEnv::empty(), adt_ty, structural_peq_def_id);
|
||||
// for now, require `#[derive(Eq)]`. (Doing so is a hack to work around
|
||||
// the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.)
|
||||
let structural_teq_def_id =
|
||||
infcx.tcx.require_lang_item(LangItem::StructuralTeq, Some(cause.span));
|
||||
ocx.register_bound(cause, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id);
|
||||
|
||||
// We deliberately skip *reporting* fulfillment errors (via
|
||||
// `report_fulfillment_errors`), for two reasons:
|
||||
//
|
||||
// 1. The error messages would mention `std::marker::StructuralPartialEq`
|
||||
// (a trait which is solely meant as an implementation detail
|
||||
// for now), and
|
||||
//
|
||||
// 2. We are sometimes doing future-incompatibility lints for
|
||||
// now, so we do not want unconditional errors here.
|
||||
ocx.select_all_or_error().is_empty()
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut Providers) {
|
||||
providers.has_structural_eq_impls = has_structural_eq_impls;
|
||||
}
|
Loading…
Reference in New Issue
Block a user