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:
Matthias Krüger 2022-12-09 07:25:46 +01:00 committed by GitHub
commit 6111a7345b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1756 additions and 1095 deletions

View File

@ -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,
}
}
}

View 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),
}
}
}

View 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,
}
}
}

View 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,
}
}
}

View File

@ -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

View File

@ -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 })
}

View File

@ -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)
};
}

View File

@ -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`.

View 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
};
}

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}

View 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;
}