rustc_hir_typeck: move whole files
This commit is contained in:
parent
53728ff751
commit
f468a90bad
37 changed files with 0 additions and 0 deletions
|
@ -1,557 +0,0 @@
|
|||
use crate::check::coercion::{AsCoercionSite, CoerceMany};
|
||||
use crate::check::{Diverges, Expectation, FnCtxt, Needs};
|
||||
use rustc_errors::{Applicability, MultiSpan};
|
||||
use rustc_hir::{self as hir, ExprKind};
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_infer::traits::Obligation;
|
||||
use rustc_middle::ty::{self, ToPredicate, Ty};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::{
|
||||
IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode,
|
||||
};
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
#[instrument(skip(self), level = "debug", ret)]
|
||||
pub fn check_match(
|
||||
&self,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
scrut: &'tcx hir::Expr<'tcx>,
|
||||
arms: &'tcx [hir::Arm<'tcx>],
|
||||
orig_expected: Expectation<'tcx>,
|
||||
match_src: hir::MatchSource,
|
||||
) -> Ty<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
|
||||
let acrb = arms_contain_ref_bindings(arms);
|
||||
let scrutinee_ty = self.demand_scrutinee_type(scrut, acrb, arms.is_empty());
|
||||
debug!(?scrutinee_ty);
|
||||
|
||||
// If there are no arms, that is a diverging match; a special case.
|
||||
if arms.is_empty() {
|
||||
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
|
||||
return tcx.types.never;
|
||||
}
|
||||
|
||||
self.warn_arms_when_scrutinee_diverges(arms);
|
||||
|
||||
// Otherwise, we have to union together the types that the arms produce and so forth.
|
||||
let scrut_diverges = self.diverges.replace(Diverges::Maybe);
|
||||
|
||||
// #55810: Type check patterns first so we get types for all bindings.
|
||||
let scrut_span = scrut.span.find_ancestor_inside(expr.span).unwrap_or(scrut.span);
|
||||
for arm in arms {
|
||||
self.check_pat_top(&arm.pat, scrutinee_ty, Some(scrut_span), true);
|
||||
}
|
||||
|
||||
// Now typecheck the blocks.
|
||||
//
|
||||
// The result of the match is the common supertype of all the
|
||||
// arms. Start out the value as bottom, since it's the, well,
|
||||
// bottom the type lattice, and we'll be moving up the lattice as
|
||||
// we process each arm. (Note that any match with 0 arms is matching
|
||||
// on any empty type and is therefore unreachable; should the flow
|
||||
// of execution reach it, we will panic, so bottom is an appropriate
|
||||
// type in that case)
|
||||
let mut all_arms_diverge = Diverges::WarnedAlways;
|
||||
|
||||
let expected = orig_expected.adjust_for_branches(self);
|
||||
debug!(?expected);
|
||||
|
||||
let mut coercion = {
|
||||
let coerce_first = match expected {
|
||||
// We don't coerce to `()` so that if the match expression is a
|
||||
// statement it's branches can have any consistent type. That allows
|
||||
// us to give better error messages (pointing to a usually better
|
||||
// arm for inconsistent arms or to the whole match when a `()` type
|
||||
// is required).
|
||||
Expectation::ExpectHasType(ety) if ety != self.tcx.mk_unit() => ety,
|
||||
_ => self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: expr.span,
|
||||
}),
|
||||
};
|
||||
CoerceMany::with_coercion_sites(coerce_first, arms)
|
||||
};
|
||||
|
||||
let mut other_arms = vec![]; // Used only for diagnostics.
|
||||
let mut prior_arm = None;
|
||||
for arm in arms {
|
||||
if let Some(g) = &arm.guard {
|
||||
self.diverges.set(Diverges::Maybe);
|
||||
match g {
|
||||
hir::Guard::If(e) => {
|
||||
self.check_expr_has_type_or_error(e, tcx.types.bool, |_| {});
|
||||
}
|
||||
hir::Guard::IfLet(l) => {
|
||||
self.check_expr_let(l);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
self.diverges.set(Diverges::Maybe);
|
||||
|
||||
let arm_ty = self.check_expr_with_expectation(&arm.body, expected);
|
||||
all_arms_diverge &= self.diverges.get();
|
||||
|
||||
let opt_suggest_box_span = prior_arm.and_then(|(_, prior_arm_ty, _)| {
|
||||
self.opt_suggest_box_span(prior_arm_ty, arm_ty, orig_expected)
|
||||
});
|
||||
|
||||
let (arm_block_id, arm_span) = if let hir::ExprKind::Block(blk, _) = arm.body.kind {
|
||||
(Some(blk.hir_id), self.find_block_span(blk))
|
||||
} else {
|
||||
(None, arm.body.span)
|
||||
};
|
||||
|
||||
let (span, code) = match prior_arm {
|
||||
// The reason for the first arm to fail is not that the match arms diverge,
|
||||
// but rather that there's a prior obligation that doesn't hold.
|
||||
None => (arm_span, ObligationCauseCode::BlockTailExpression(arm.body.hir_id)),
|
||||
Some((prior_arm_block_id, prior_arm_ty, prior_arm_span)) => (
|
||||
expr.span,
|
||||
ObligationCauseCode::MatchExpressionArm(Box::new(MatchExpressionArmCause {
|
||||
arm_block_id,
|
||||
arm_span,
|
||||
arm_ty,
|
||||
prior_arm_block_id,
|
||||
prior_arm_ty,
|
||||
prior_arm_span,
|
||||
scrut_span: scrut.span,
|
||||
source: match_src,
|
||||
prior_arms: other_arms.clone(),
|
||||
scrut_hir_id: scrut.hir_id,
|
||||
opt_suggest_box_span,
|
||||
})),
|
||||
),
|
||||
};
|
||||
let cause = self.cause(span, code);
|
||||
|
||||
// This is the moral equivalent of `coercion.coerce(self, cause, arm.body, arm_ty)`.
|
||||
// We use it this way to be able to expand on the potential error and detect when a
|
||||
// `match` tail statement could be a tail expression instead. If so, we suggest
|
||||
// removing the stray semicolon.
|
||||
coercion.coerce_inner(
|
||||
self,
|
||||
&cause,
|
||||
Some(&arm.body),
|
||||
arm_ty,
|
||||
Some(&mut |err| {
|
||||
let Some(ret) = self
|
||||
.tcx
|
||||
.hir()
|
||||
.find_by_def_id(self.body_id.owner.def_id)
|
||||
.and_then(|owner| owner.fn_decl())
|
||||
.map(|decl| decl.output.span())
|
||||
else { return; };
|
||||
let Expectation::IsLast(stmt) = orig_expected else {
|
||||
return
|
||||
};
|
||||
let can_coerce_to_return_ty = match self.ret_coercion.as_ref() {
|
||||
Some(ret_coercion) if self.in_tail_expr => {
|
||||
let ret_ty = ret_coercion.borrow().expected_ty();
|
||||
let ret_ty = self.inh.infcx.shallow_resolve(ret_ty);
|
||||
self.can_coerce(arm_ty, ret_ty)
|
||||
&& prior_arm.map_or(true, |(_, t, _)| self.can_coerce(t, ret_ty))
|
||||
// The match arms need to unify for the case of `impl Trait`.
|
||||
&& !matches!(ret_ty.kind(), ty::Opaque(..))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if !can_coerce_to_return_ty {
|
||||
return;
|
||||
}
|
||||
|
||||
let semi_span = expr.span.shrink_to_hi().with_hi(stmt.hi());
|
||||
let mut ret_span: MultiSpan = semi_span.into();
|
||||
ret_span.push_span_label(
|
||||
expr.span,
|
||||
"this could be implicitly returned but it is a statement, not a \
|
||||
tail expression",
|
||||
);
|
||||
ret_span
|
||||
.push_span_label(ret, "the `match` arms can conform to this return type");
|
||||
ret_span.push_span_label(
|
||||
semi_span,
|
||||
"the `match` is a statement because of this semicolon, consider \
|
||||
removing it",
|
||||
);
|
||||
err.span_note(
|
||||
ret_span,
|
||||
"you might have meant to return the `match` expression",
|
||||
);
|
||||
err.tool_only_span_suggestion(
|
||||
semi_span,
|
||||
"remove this semicolon",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}),
|
||||
false,
|
||||
);
|
||||
|
||||
other_arms.push(arm_span);
|
||||
if other_arms.len() > 5 {
|
||||
other_arms.remove(0);
|
||||
}
|
||||
|
||||
prior_arm = Some((arm_block_id, arm_ty, arm_span));
|
||||
}
|
||||
|
||||
// If all of the arms in the `match` diverge,
|
||||
// and we're dealing with an actual `match` block
|
||||
// (as opposed to a `match` desugared from something else'),
|
||||
// we can emit a better note. Rather than pointing
|
||||
// at a diverging expression in an arbitrary arm,
|
||||
// we can point at the entire `match` expression
|
||||
if let (Diverges::Always { .. }, hir::MatchSource::Normal) = (all_arms_diverge, match_src) {
|
||||
all_arms_diverge = Diverges::Always {
|
||||
span: expr.span,
|
||||
custom_note: Some(
|
||||
"any code following this `match` expression is unreachable, as all arms diverge",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// We won't diverge unless the scrutinee or all arms diverge.
|
||||
self.diverges.set(scrut_diverges | all_arms_diverge);
|
||||
|
||||
coercion.complete(self)
|
||||
}
|
||||
|
||||
/// When the previously checked expression (the scrutinee) diverges,
|
||||
/// warn the user about the match arms being unreachable.
|
||||
fn warn_arms_when_scrutinee_diverges(&self, arms: &'tcx [hir::Arm<'tcx>]) {
|
||||
for arm in arms {
|
||||
self.warn_if_unreachable(arm.body.hir_id, arm.body.span, "arm");
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the fallback arm of a desugared if(-let) like a missing else.
|
||||
///
|
||||
/// Returns `true` if there was an error forcing the coercion to the `()` type.
|
||||
pub(super) fn if_fallback_coercion<T>(
|
||||
&self,
|
||||
span: Span,
|
||||
then_expr: &'tcx hir::Expr<'tcx>,
|
||||
coercion: &mut CoerceMany<'tcx, '_, T>,
|
||||
) -> bool
|
||||
where
|
||||
T: AsCoercionSite,
|
||||
{
|
||||
// If this `if` expr is the parent's function return expr,
|
||||
// the cause of the type coercion is the return type, point at it. (#25228)
|
||||
let ret_reason = self.maybe_get_coercion_reason(then_expr.hir_id, span);
|
||||
let cause = self.cause(span, ObligationCauseCode::IfExpressionWithNoElse);
|
||||
let mut error = false;
|
||||
coercion.coerce_forced_unit(
|
||||
self,
|
||||
&cause,
|
||||
&mut |err| {
|
||||
if let Some((span, msg)) = &ret_reason {
|
||||
err.span_label(*span, msg);
|
||||
} else if let ExprKind::Block(block, _) = &then_expr.kind
|
||||
&& let Some(expr) = &block.expr
|
||||
{
|
||||
err.span_label(expr.span, "found here");
|
||||
}
|
||||
err.note("`if` expressions without `else` evaluate to `()`");
|
||||
err.help("consider adding an `else` block that evaluates to the expected type");
|
||||
error = true;
|
||||
},
|
||||
false,
|
||||
);
|
||||
error
|
||||
}
|
||||
|
||||
fn maybe_get_coercion_reason(&self, hir_id: hir::HirId, sp: Span) -> Option<(Span, String)> {
|
||||
let node = {
|
||||
let rslt = self.tcx.hir().get_parent_node(self.tcx.hir().get_parent_node(hir_id));
|
||||
self.tcx.hir().get(rslt)
|
||||
};
|
||||
if let hir::Node::Block(block) = node {
|
||||
// check that the body's parent is an fn
|
||||
let parent = self
|
||||
.tcx
|
||||
.hir()
|
||||
.get(self.tcx.hir().get_parent_node(self.tcx.hir().get_parent_node(block.hir_id)));
|
||||
if let (Some(expr), hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(..), .. })) =
|
||||
(&block.expr, parent)
|
||||
{
|
||||
// check that the `if` expr without `else` is the fn body's expr
|
||||
if expr.span == sp {
|
||||
return self.get_fn_decl(hir_id).and_then(|(fn_decl, _)| {
|
||||
let span = fn_decl.output.span();
|
||||
let snippet = self.tcx.sess.source_map().span_to_snippet(span).ok()?;
|
||||
Some((span, format!("expected `{snippet}` because of this return type")))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if let hir::Node::Local(hir::Local { ty: Some(_), pat, .. }) = node {
|
||||
return Some((pat.span, "expected because of this assignment".to_string()));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn if_cause(
|
||||
&self,
|
||||
span: Span,
|
||||
cond_span: Span,
|
||||
then_expr: &'tcx hir::Expr<'tcx>,
|
||||
else_expr: &'tcx hir::Expr<'tcx>,
|
||||
then_ty: Ty<'tcx>,
|
||||
else_ty: Ty<'tcx>,
|
||||
opt_suggest_box_span: Option<Span>,
|
||||
) -> ObligationCause<'tcx> {
|
||||
let mut outer_span = if self.tcx.sess.source_map().is_multiline(span) {
|
||||
// The `if`/`else` isn't in one line in the output, include some context to make it
|
||||
// clear it is an if/else expression:
|
||||
// ```
|
||||
// LL | let x = if true {
|
||||
// | _____________-
|
||||
// LL || 10i32
|
||||
// || ----- expected because of this
|
||||
// LL || } else {
|
||||
// LL || 10u32
|
||||
// || ^^^^^ expected `i32`, found `u32`
|
||||
// LL || };
|
||||
// ||_____- `if` and `else` have incompatible types
|
||||
// ```
|
||||
Some(span)
|
||||
} else {
|
||||
// The entire expression is in one line, only point at the arms
|
||||
// ```
|
||||
// LL | let x = if true { 10i32 } else { 10u32 };
|
||||
// | ----- ^^^^^ expected `i32`, found `u32`
|
||||
// | |
|
||||
// | expected because of this
|
||||
// ```
|
||||
None
|
||||
};
|
||||
|
||||
let (error_sp, else_id) = if let ExprKind::Block(block, _) = &else_expr.kind {
|
||||
let block = block.innermost_block();
|
||||
|
||||
// Avoid overlapping spans that aren't as readable:
|
||||
// ```
|
||||
// 2 | let x = if true {
|
||||
// | _____________-
|
||||
// 3 | | 3
|
||||
// | | - expected because of this
|
||||
// 4 | | } else {
|
||||
// | |____________^
|
||||
// 5 | ||
|
||||
// 6 | || };
|
||||
// | || ^
|
||||
// | ||_____|
|
||||
// | |______if and else have incompatible types
|
||||
// | expected integer, found `()`
|
||||
// ```
|
||||
// by not pointing at the entire expression:
|
||||
// ```
|
||||
// 2 | let x = if true {
|
||||
// | ------- `if` and `else` have incompatible types
|
||||
// 3 | 3
|
||||
// | - expected because of this
|
||||
// 4 | } else {
|
||||
// | ____________^
|
||||
// 5 | |
|
||||
// 6 | | };
|
||||
// | |_____^ expected integer, found `()`
|
||||
// ```
|
||||
if block.expr.is_none() && block.stmts.is_empty()
|
||||
&& let Some(outer_span) = &mut outer_span
|
||||
&& let Some(cond_span) = cond_span.find_ancestor_inside(*outer_span)
|
||||
{
|
||||
*outer_span = outer_span.with_hi(cond_span.hi())
|
||||
}
|
||||
|
||||
(self.find_block_span(block), block.hir_id)
|
||||
} else {
|
||||
(else_expr.span, else_expr.hir_id)
|
||||
};
|
||||
|
||||
let then_id = if let ExprKind::Block(block, _) = &then_expr.kind {
|
||||
let block = block.innermost_block();
|
||||
// Exclude overlapping spans
|
||||
if block.expr.is_none() && block.stmts.is_empty() {
|
||||
outer_span = None;
|
||||
}
|
||||
block.hir_id
|
||||
} else {
|
||||
then_expr.hir_id
|
||||
};
|
||||
|
||||
// Finally construct the cause:
|
||||
self.cause(
|
||||
error_sp,
|
||||
ObligationCauseCode::IfExpression(Box::new(IfExpressionCause {
|
||||
else_id,
|
||||
then_id,
|
||||
then_ty,
|
||||
else_ty,
|
||||
outer_span,
|
||||
opt_suggest_box_span,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn demand_scrutinee_type(
|
||||
&self,
|
||||
scrut: &'tcx hir::Expr<'tcx>,
|
||||
contains_ref_bindings: Option<hir::Mutability>,
|
||||
no_arms: bool,
|
||||
) -> Ty<'tcx> {
|
||||
// Not entirely obvious: if matches may create ref bindings, we want to
|
||||
// use the *precise* type of the scrutinee, *not* some supertype, as
|
||||
// the "scrutinee type" (issue #23116).
|
||||
//
|
||||
// arielb1 [writes here in this comment thread][c] that there
|
||||
// is certainly *some* potential danger, e.g., for an example
|
||||
// like:
|
||||
//
|
||||
// [c]: https://github.com/rust-lang/rust/pull/43399#discussion_r130223956
|
||||
//
|
||||
// ```
|
||||
// let Foo(x) = f()[0];
|
||||
// ```
|
||||
//
|
||||
// Then if the pattern matches by reference, we want to match
|
||||
// `f()[0]` as a lexpr, so we can't allow it to be
|
||||
// coerced. But if the pattern matches by value, `f()[0]` is
|
||||
// still syntactically a lexpr, but we *do* want to allow
|
||||
// coercions.
|
||||
//
|
||||
// However, *likely* we are ok with allowing coercions to
|
||||
// happen if there are no explicit ref mut patterns - all
|
||||
// implicit ref mut patterns must occur behind a reference, so
|
||||
// they will have the "correct" variance and lifetime.
|
||||
//
|
||||
// This does mean that the following pattern would be legal:
|
||||
//
|
||||
// ```
|
||||
// struct Foo(Bar);
|
||||
// struct Bar(u32);
|
||||
// impl Deref for Foo {
|
||||
// type Target = Bar;
|
||||
// fn deref(&self) -> &Bar { &self.0 }
|
||||
// }
|
||||
// impl DerefMut for Foo {
|
||||
// fn deref_mut(&mut self) -> &mut Bar { &mut self.0 }
|
||||
// }
|
||||
// fn foo(x: &mut Foo) {
|
||||
// {
|
||||
// let Bar(z): &mut Bar = x;
|
||||
// *z = 42;
|
||||
// }
|
||||
// assert_eq!(foo.0.0, 42);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// FIXME(tschottdorf): don't call contains_explicit_ref_binding, which
|
||||
// is problematic as the HIR is being scraped, but ref bindings may be
|
||||
// implicit after #42640. We need to make sure that pat_adjustments
|
||||
// (once introduced) is populated by the time we get here.
|
||||
//
|
||||
// See #44848.
|
||||
if let Some(m) = contains_ref_bindings {
|
||||
self.check_expr_with_needs(scrut, Needs::maybe_mut_place(m))
|
||||
} else if no_arms {
|
||||
self.check_expr(scrut)
|
||||
} else {
|
||||
// ...but otherwise we want to use any supertype of the
|
||||
// scrutinee. This is sort of a workaround, see note (*) in
|
||||
// `check_pat` for some details.
|
||||
let scrut_ty = self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::TypeInference,
|
||||
span: scrut.span,
|
||||
});
|
||||
self.check_expr_has_type_or_error(scrut, scrut_ty, |_| {});
|
||||
scrut_ty
|
||||
}
|
||||
}
|
||||
|
||||
/// When we have a `match` as a tail expression in a `fn` with a returned `impl Trait`
|
||||
/// we check if the different arms would work with boxed trait objects instead and
|
||||
/// provide a structured suggestion in that case.
|
||||
pub(crate) fn opt_suggest_box_span(
|
||||
&self,
|
||||
first_ty: Ty<'tcx>,
|
||||
second_ty: Ty<'tcx>,
|
||||
orig_expected: Expectation<'tcx>,
|
||||
) -> Option<Span> {
|
||||
// FIXME(compiler-errors): This really shouldn't need to be done during the
|
||||
// "good" path of typeck, but here we are.
|
||||
match orig_expected {
|
||||
Expectation::ExpectHasType(expected) => {
|
||||
let TypeVariableOrigin {
|
||||
span,
|
||||
kind: TypeVariableOriginKind::OpaqueTypeInference(rpit_def_id),
|
||||
..
|
||||
} = self.type_var_origin(expected)? else { return None; };
|
||||
|
||||
let sig = *self
|
||||
.typeck_results
|
||||
.borrow()
|
||||
.liberated_fn_sigs()
|
||||
.get(hir::HirId::make_owner(self.body_id.owner.def_id))?;
|
||||
|
||||
let substs = sig.output().walk().find_map(|arg| {
|
||||
if let ty::GenericArgKind::Type(ty) = arg.unpack()
|
||||
&& let ty::Opaque(def_id, substs) = *ty.kind()
|
||||
&& def_id == rpit_def_id
|
||||
{
|
||||
Some(substs)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
let opaque_ty = self.tcx.mk_opaque(rpit_def_id, substs);
|
||||
|
||||
if !self.can_coerce(first_ty, expected) || !self.can_coerce(second_ty, expected) {
|
||||
return None;
|
||||
}
|
||||
|
||||
for ty in [first_ty, second_ty] {
|
||||
for pred in self.tcx.bound_explicit_item_bounds(rpit_def_id).transpose_iter() {
|
||||
let pred = pred.map_bound(|(pred, _)| *pred).subst(self.tcx, substs);
|
||||
let pred = match pred.kind().skip_binder() {
|
||||
ty::PredicateKind::Trait(mut trait_pred) => {
|
||||
assert_eq!(trait_pred.trait_ref.self_ty(), opaque_ty);
|
||||
trait_pred.trait_ref.substs =
|
||||
self.tcx.mk_substs_trait(ty, &trait_pred.trait_ref.substs[1..]);
|
||||
pred.kind().rebind(trait_pred).to_predicate(self.tcx)
|
||||
}
|
||||
ty::PredicateKind::Projection(mut proj_pred) => {
|
||||
assert_eq!(proj_pred.projection_ty.self_ty(), opaque_ty);
|
||||
proj_pred.projection_ty.substs = self
|
||||
.tcx
|
||||
.mk_substs_trait(ty, &proj_pred.projection_ty.substs[1..]);
|
||||
pred.kind().rebind(proj_pred).to_predicate(self.tcx)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
if !self.predicate_must_hold_modulo_regions(&Obligation::new(
|
||||
ObligationCause::misc(span, self.body_id),
|
||||
self.param_env,
|
||||
pred,
|
||||
)) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(span)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn arms_contain_ref_bindings<'tcx>(arms: &'tcx [hir::Arm<'tcx>]) -> Option<hir::Mutability> {
|
||||
arms.iter().filter_map(|a| a.pat.contains_explicit_ref_binding()).max_by_key(|m| match *m {
|
||||
hir::Mutability::Mut => 1,
|
||||
hir::Mutability::Not => 0,
|
||||
})
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
//! Some helper functions for `AutoDeref`
|
||||
use super::method::MethodCallee;
|
||||
use super::{FnCtxt, PlaceOp};
|
||||
|
||||
use rustc_infer::infer::InferOk;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::autoderef::{Autoderef, AutoderefKind};
|
||||
|
||||
use std::iter;
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
pub fn autoderef(&'a self, span: Span, base_ty: Ty<'tcx>) -> Autoderef<'a, 'tcx> {
|
||||
Autoderef::new(self, self.param_env, self.body_id, span, base_ty, span)
|
||||
}
|
||||
|
||||
/// Like `autoderef`, but provides a custom `Span` to use for calls to
|
||||
/// an overloaded `Deref` operator
|
||||
pub fn autoderef_overloaded_span(
|
||||
&'a self,
|
||||
span: Span,
|
||||
base_ty: Ty<'tcx>,
|
||||
overloaded_span: Span,
|
||||
) -> Autoderef<'a, 'tcx> {
|
||||
Autoderef::new(self, self.param_env, self.body_id, span, base_ty, overloaded_span)
|
||||
}
|
||||
|
||||
pub fn try_overloaded_deref(
|
||||
&self,
|
||||
span: Span,
|
||||
base_ty: Ty<'tcx>,
|
||||
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
|
||||
self.try_overloaded_place_op(span, base_ty, &[], PlaceOp::Deref)
|
||||
}
|
||||
|
||||
/// Returns the adjustment steps.
|
||||
pub fn adjust_steps(&self, autoderef: &Autoderef<'a, 'tcx>) -> Vec<Adjustment<'tcx>> {
|
||||
self.register_infer_ok_obligations(self.adjust_steps_as_infer_ok(autoderef))
|
||||
}
|
||||
|
||||
pub fn adjust_steps_as_infer_ok(
|
||||
&self,
|
||||
autoderef: &Autoderef<'a, 'tcx>,
|
||||
) -> InferOk<'tcx, Vec<Adjustment<'tcx>>> {
|
||||
let mut obligations = vec![];
|
||||
let steps = autoderef.steps();
|
||||
let targets =
|
||||
steps.iter().skip(1).map(|&(ty, _)| ty).chain(iter::once(autoderef.final_ty(false)));
|
||||
let steps: Vec<_> = steps
|
||||
.iter()
|
||||
.map(|&(source, kind)| {
|
||||
if let AutoderefKind::Overloaded = kind {
|
||||
self.try_overloaded_deref(autoderef.span(), source).and_then(
|
||||
|InferOk { value: method, obligations: o }| {
|
||||
obligations.extend(o);
|
||||
if let ty::Ref(region, _, mutbl) = *method.sig.output().kind() {
|
||||
Some(OverloadedDeref {
|
||||
region,
|
||||
mutbl,
|
||||
span: autoderef.overloaded_span(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.zip(targets)
|
||||
.map(|(autoderef, target)| Adjustment { kind: Adjust::Deref(autoderef), target })
|
||||
.collect();
|
||||
|
||||
InferOk { obligations, value: steps }
|
||||
}
|
||||
}
|
|
@ -1,831 +0,0 @@
|
|||
use super::method::probe::{IsSuggestion, Mode, ProbeScope};
|
||||
use super::method::MethodCallee;
|
||||
use super::{Expectation, FnCtxt, TupleArgumentsFlag};
|
||||
use crate::type_error_struct;
|
||||
|
||||
use rustc_ast::util::parser::PREC_POSTFIX;
|
||||
use rustc_errors::{struct_span_err, Applicability, Diagnostic, StashKey};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{self, Namespace, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_infer::{
|
||||
infer,
|
||||
traits::{self, Obligation},
|
||||
};
|
||||
use rustc_infer::{
|
||||
infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
|
||||
traits::ObligationCause,
|
||||
};
|
||||
use rustc_middle::ty::adjustment::{
|
||||
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
|
||||
};
|
||||
use rustc_middle::ty::SubstsRef;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
|
||||
use rustc_span::def_id::LocalDefId;
|
||||
use rustc_span::symbol::{sym, Ident};
|
||||
use rustc_span::Span;
|
||||
use rustc_target::spec::abi;
|
||||
use rustc_trait_selection::autoderef::Autoderef;
|
||||
use rustc_trait_selection::infer::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::error_reporting::DefIdOrName;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
|
||||
use std::iter;
|
||||
|
||||
/// Checks that it is legal to call methods of the trait corresponding
|
||||
/// to `trait_id` (this only cares about the trait, not the specific
|
||||
/// method that is called).
|
||||
pub fn check_legal_trait_for_method_call(
|
||||
tcx: TyCtxt<'_>,
|
||||
span: Span,
|
||||
receiver: Option<Span>,
|
||||
expr_span: Span,
|
||||
trait_id: DefId,
|
||||
) {
|
||||
if tcx.lang_items().drop_trait() == Some(trait_id) {
|
||||
let mut err = struct_span_err!(tcx.sess, span, E0040, "explicit use of destructor method");
|
||||
err.span_label(span, "explicit destructor calls not allowed");
|
||||
|
||||
let (sp, suggestion) = receiver
|
||||
.and_then(|s| tcx.sess.source_map().span_to_snippet(s).ok())
|
||||
.filter(|snippet| !snippet.is_empty())
|
||||
.map(|snippet| (expr_span, format!("drop({snippet})")))
|
||||
.unwrap_or_else(|| (span, "drop".to_string()));
|
||||
|
||||
err.span_suggestion(
|
||||
sp,
|
||||
"consider using `drop` function",
|
||||
suggestion,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
err.emit();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CallStep<'tcx> {
|
||||
Builtin(Ty<'tcx>),
|
||||
DeferredClosure(LocalDefId, ty::FnSig<'tcx>),
|
||||
/// E.g., enum variant constructors.
|
||||
Overloaded(MethodCallee<'tcx>),
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
pub fn check_call(
|
||||
&self,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_expr: &'tcx hir::Expr<'tcx>,
|
||||
arg_exprs: &'tcx [hir::Expr<'tcx>],
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let original_callee_ty = match &callee_expr.kind {
|
||||
hir::ExprKind::Path(hir::QPath::Resolved(..) | hir::QPath::TypeRelative(..)) => self
|
||||
.check_expr_with_expectation_and_args(
|
||||
callee_expr,
|
||||
Expectation::NoExpectation,
|
||||
arg_exprs,
|
||||
),
|
||||
_ => self.check_expr(callee_expr),
|
||||
};
|
||||
|
||||
let expr_ty = self.structurally_resolved_type(call_expr.span, original_callee_ty);
|
||||
|
||||
let mut autoderef = self.autoderef(callee_expr.span, expr_ty);
|
||||
let mut result = None;
|
||||
while result.is_none() && autoderef.next().is_some() {
|
||||
result = self.try_overloaded_call_step(call_expr, callee_expr, arg_exprs, &autoderef);
|
||||
}
|
||||
self.register_predicates(autoderef.into_obligations());
|
||||
|
||||
let output = match result {
|
||||
None => {
|
||||
// this will report an error since original_callee_ty is not a fn
|
||||
self.confirm_builtin_call(
|
||||
call_expr,
|
||||
callee_expr,
|
||||
original_callee_ty,
|
||||
arg_exprs,
|
||||
expected,
|
||||
)
|
||||
}
|
||||
|
||||
Some(CallStep::Builtin(callee_ty)) => {
|
||||
self.confirm_builtin_call(call_expr, callee_expr, callee_ty, arg_exprs, expected)
|
||||
}
|
||||
|
||||
Some(CallStep::DeferredClosure(def_id, fn_sig)) => {
|
||||
self.confirm_deferred_closure_call(call_expr, arg_exprs, expected, def_id, fn_sig)
|
||||
}
|
||||
|
||||
Some(CallStep::Overloaded(method_callee)) => {
|
||||
self.confirm_overloaded_call(call_expr, arg_exprs, expected, method_callee)
|
||||
}
|
||||
};
|
||||
|
||||
// we must check that return type of called functions is WF:
|
||||
self.register_wf_obligation(output.into(), call_expr.span, traits::WellFormed(None));
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn try_overloaded_call_step(
|
||||
&self,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_expr: &'tcx hir::Expr<'tcx>,
|
||||
arg_exprs: &'tcx [hir::Expr<'tcx>],
|
||||
autoderef: &Autoderef<'a, 'tcx>,
|
||||
) -> Option<CallStep<'tcx>> {
|
||||
let adjusted_ty =
|
||||
self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false));
|
||||
debug!(
|
||||
"try_overloaded_call_step(call_expr={:?}, adjusted_ty={:?})",
|
||||
call_expr, adjusted_ty
|
||||
);
|
||||
|
||||
// If the callee is a bare function or a closure, then we're all set.
|
||||
match *adjusted_ty.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(_) => {
|
||||
let adjustments = self.adjust_steps(autoderef);
|
||||
self.apply_adjustments(callee_expr, adjustments);
|
||||
return Some(CallStep::Builtin(adjusted_ty));
|
||||
}
|
||||
|
||||
ty::Closure(def_id, substs) => {
|
||||
let def_id = def_id.expect_local();
|
||||
|
||||
// Check whether this is a call to a closure where we
|
||||
// haven't yet decided on whether the closure is fn vs
|
||||
// fnmut vs fnonce. If so, we have to defer further processing.
|
||||
if self.closure_kind(substs).is_none() {
|
||||
let closure_sig = substs.as_closure().sig();
|
||||
let closure_sig = self.replace_bound_vars_with_fresh_vars(
|
||||
call_expr.span,
|
||||
infer::FnCall,
|
||||
closure_sig,
|
||||
);
|
||||
let adjustments = self.adjust_steps(autoderef);
|
||||
self.record_deferred_call_resolution(
|
||||
def_id,
|
||||
DeferredCallResolution {
|
||||
call_expr,
|
||||
callee_expr,
|
||||
adjusted_ty,
|
||||
adjustments,
|
||||
fn_sig: closure_sig,
|
||||
closure_substs: substs,
|
||||
},
|
||||
);
|
||||
return Some(CallStep::DeferredClosure(def_id, closure_sig));
|
||||
}
|
||||
}
|
||||
|
||||
// Hack: we know that there are traits implementing Fn for &F
|
||||
// where F:Fn and so forth. In the particular case of types
|
||||
// like `x: &mut FnMut()`, if there is a call `x()`, we would
|
||||
// normally translate to `FnMut::call_mut(&mut x, ())`, but
|
||||
// that winds up requiring `mut x: &mut FnMut()`. A little
|
||||
// over the top. The simplest fix by far is to just ignore
|
||||
// this case and deref again, so we wind up with
|
||||
// `FnMut::call_mut(&mut *x, ())`.
|
||||
ty::Ref(..) if autoderef.step_count() == 0 => {
|
||||
return None;
|
||||
}
|
||||
|
||||
ty::Error(_) => {
|
||||
return None;
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Now, we look for the implementation of a Fn trait on the object's type.
|
||||
// We first do it with the explicit instruction to look for an impl of
|
||||
// `Fn<Tuple>`, with the tuple `Tuple` having an arity corresponding
|
||||
// to the number of call parameters.
|
||||
// If that fails (or_else branch), we try again without specifying the
|
||||
// shape of the tuple (hence the None). This allows to detect an Fn trait
|
||||
// is implemented, and use this information for diagnostic.
|
||||
self.try_overloaded_call_traits(call_expr, adjusted_ty, Some(arg_exprs))
|
||||
.or_else(|| self.try_overloaded_call_traits(call_expr, adjusted_ty, None))
|
||||
.map(|(autoref, method)| {
|
||||
let mut adjustments = self.adjust_steps(autoderef);
|
||||
adjustments.extend(autoref);
|
||||
self.apply_adjustments(callee_expr, adjustments);
|
||||
CallStep::Overloaded(method)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_overloaded_call_traits(
|
||||
&self,
|
||||
call_expr: &hir::Expr<'_>,
|
||||
adjusted_ty: Ty<'tcx>,
|
||||
opt_arg_exprs: Option<&'tcx [hir::Expr<'tcx>]>,
|
||||
) -> Option<(Option<Adjustment<'tcx>>, MethodCallee<'tcx>)> {
|
||||
// Try the options that are least restrictive on the caller first.
|
||||
for (opt_trait_def_id, method_name, borrow) in [
|
||||
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
|
||||
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
|
||||
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
|
||||
] {
|
||||
let Some(trait_def_id) = opt_trait_def_id else { continue };
|
||||
|
||||
let opt_input_types = opt_arg_exprs.map(|arg_exprs| {
|
||||
[self.tcx.mk_tup(arg_exprs.iter().map(|e| {
|
||||
self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::TypeInference,
|
||||
span: e.span,
|
||||
})
|
||||
}))]
|
||||
});
|
||||
let opt_input_types = opt_input_types.as_ref().map(AsRef::as_ref);
|
||||
|
||||
if let Some(ok) = self.lookup_method_in_trait(
|
||||
call_expr.span,
|
||||
method_name,
|
||||
trait_def_id,
|
||||
adjusted_ty,
|
||||
opt_input_types,
|
||||
) {
|
||||
let method = self.register_infer_ok_obligations(ok);
|
||||
let mut autoref = None;
|
||||
if borrow {
|
||||
// Check for &self vs &mut self in the method signature. Since this is either
|
||||
// the Fn or FnMut trait, it should be one of those.
|
||||
let ty::Ref(region, _, mutbl) = method.sig.inputs()[0].kind() else {
|
||||
// The `fn`/`fn_mut` lang item is ill-formed, which should have
|
||||
// caused an error elsewhere.
|
||||
self.tcx
|
||||
.sess
|
||||
.delay_span_bug(call_expr.span, "input to call/call_mut is not a ref?");
|
||||
return None;
|
||||
};
|
||||
|
||||
let mutbl = match mutbl {
|
||||
hir::Mutability::Not => AutoBorrowMutability::Not,
|
||||
hir::Mutability::Mut => AutoBorrowMutability::Mut {
|
||||
// For initial two-phase borrow
|
||||
// deployment, conservatively omit
|
||||
// overloaded function call ops.
|
||||
allow_two_phase_borrow: AllowTwoPhase::No,
|
||||
},
|
||||
};
|
||||
autoref = Some(Adjustment {
|
||||
kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)),
|
||||
target: method.sig.inputs()[0],
|
||||
});
|
||||
}
|
||||
return Some((autoref, method));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Give appropriate suggestion when encountering `||{/* not callable */}()`, where the
|
||||
/// likely intention is to call the closure, suggest `(||{})()`. (#55851)
|
||||
fn identify_bad_closure_def_and_call(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
hir_id: hir::HirId,
|
||||
callee_node: &hir::ExprKind<'_>,
|
||||
callee_span: Span,
|
||||
) {
|
||||
let hir = self.tcx.hir();
|
||||
let parent_hir_id = hir.get_parent_node(hir_id);
|
||||
let parent_node = hir.get(parent_hir_id);
|
||||
if let (
|
||||
hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, body, .. }),
|
||||
..
|
||||
}),
|
||||
hir::ExprKind::Block(..),
|
||||
) = (parent_node, callee_node)
|
||||
{
|
||||
let fn_decl_span = if hir.body(body).generator_kind
|
||||
== Some(hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Closure))
|
||||
{
|
||||
// Actually need to unwrap a few more layers of HIR to get to
|
||||
// the _real_ closure...
|
||||
let async_closure = hir.get_parent_node(hir.get_parent_node(parent_hir_id));
|
||||
if let hir::Node::Expr(hir::Expr {
|
||||
kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }),
|
||||
..
|
||||
}) = hir.get(async_closure)
|
||||
{
|
||||
fn_decl_span
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
fn_decl_span
|
||||
};
|
||||
|
||||
let start = fn_decl_span.shrink_to_lo();
|
||||
let end = callee_span.shrink_to_hi();
|
||||
err.multipart_suggestion(
|
||||
"if you meant to create this closure and immediately call it, surround the \
|
||||
closure with parentheses",
|
||||
vec![(start, "(".to_string()), (end, ")".to_string())],
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Give appropriate suggestion when encountering `[("a", 0) ("b", 1)]`, where the
|
||||
/// likely intention is to create an array containing tuples.
|
||||
fn maybe_suggest_bad_array_definition(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_expr: &'tcx hir::Expr<'tcx>,
|
||||
) -> bool {
|
||||
let hir_id = self.tcx.hir().get_parent_node(call_expr.hir_id);
|
||||
let parent_node = self.tcx.hir().get(hir_id);
|
||||
if let (
|
||||
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Array(_), .. }),
|
||||
hir::ExprKind::Tup(exp),
|
||||
hir::ExprKind::Call(_, args),
|
||||
) = (parent_node, &callee_expr.kind, &call_expr.kind)
|
||||
&& args.len() == exp.len()
|
||||
{
|
||||
let start = callee_expr.span.shrink_to_hi();
|
||||
err.span_suggestion(
|
||||
start,
|
||||
"consider separating array elements with a comma",
|
||||
",",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn confirm_builtin_call(
|
||||
&self,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_ty: Ty<'tcx>,
|
||||
arg_exprs: &'tcx [hir::Expr<'tcx>],
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let (fn_sig, def_id) = match *callee_ty.kind() {
|
||||
ty::FnDef(def_id, subst) => {
|
||||
let fn_sig = self.tcx.bound_fn_sig(def_id).subst(self.tcx, subst);
|
||||
|
||||
// Unit testing: function items annotated with
|
||||
// `#[rustc_evaluate_where_clauses]` trigger special output
|
||||
// to let us test the trait evaluation system.
|
||||
if self.tcx.has_attr(def_id, sym::rustc_evaluate_where_clauses) {
|
||||
let predicates = self.tcx.predicates_of(def_id);
|
||||
let predicates = predicates.instantiate(self.tcx, subst);
|
||||
for (predicate, predicate_span) in
|
||||
predicates.predicates.iter().zip(&predicates.spans)
|
||||
{
|
||||
let obligation = Obligation::new(
|
||||
ObligationCause::dummy_with_span(callee_expr.span),
|
||||
self.param_env,
|
||||
*predicate,
|
||||
);
|
||||
let result = self.evaluate_obligation(&obligation);
|
||||
self.tcx
|
||||
.sess
|
||||
.struct_span_err(
|
||||
callee_expr.span,
|
||||
&format!("evaluate({:?}) = {:?}", predicate, result),
|
||||
)
|
||||
.span_label(*predicate_span, "predicate")
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
(fn_sig, Some(def_id))
|
||||
}
|
||||
ty::FnPtr(sig) => (sig, None),
|
||||
_ => {
|
||||
if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &callee_expr.kind
|
||||
&& let [segment] = path.segments
|
||||
&& let Some(mut diag) = self
|
||||
.tcx
|
||||
.sess
|
||||
.diagnostic()
|
||||
.steal_diagnostic(segment.ident.span, StashKey::CallIntoMethod)
|
||||
{
|
||||
// Try suggesting `foo(a)` -> `a.foo()` if possible.
|
||||
if let Some(ty) =
|
||||
self.suggest_call_as_method(
|
||||
&mut diag,
|
||||
segment,
|
||||
arg_exprs,
|
||||
call_expr,
|
||||
expected
|
||||
)
|
||||
{
|
||||
diag.emit();
|
||||
return ty;
|
||||
} else {
|
||||
diag.emit();
|
||||
}
|
||||
}
|
||||
|
||||
self.report_invalid_callee(call_expr, callee_expr, callee_ty, arg_exprs);
|
||||
|
||||
// This is the "default" function signature, used in case of error.
|
||||
// In that case, we check each argument against "error" in order to
|
||||
// set up all the node type bindings.
|
||||
(
|
||||
ty::Binder::dummy(self.tcx.mk_fn_sig(
|
||||
self.err_args(arg_exprs.len()).into_iter(),
|
||||
self.tcx.ty_error(),
|
||||
false,
|
||||
hir::Unsafety::Normal,
|
||||
abi::Abi::Rust,
|
||||
)),
|
||||
None,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Replace any late-bound regions that appear in the function
|
||||
// signature with region variables. We also have to
|
||||
// renormalize the associated types at this point, since they
|
||||
// previously appeared within a `Binder<>` and hence would not
|
||||
// have been normalized before.
|
||||
let fn_sig = self.replace_bound_vars_with_fresh_vars(call_expr.span, infer::FnCall, fn_sig);
|
||||
let fn_sig = self.normalize_associated_types_in(call_expr.span, fn_sig);
|
||||
|
||||
// Call the generic checker.
|
||||
let expected_arg_tys = self.expected_inputs_for_expected_output(
|
||||
call_expr.span,
|
||||
expected,
|
||||
fn_sig.output(),
|
||||
fn_sig.inputs(),
|
||||
);
|
||||
self.check_argument_types(
|
||||
call_expr.span,
|
||||
call_expr,
|
||||
fn_sig.inputs(),
|
||||
expected_arg_tys,
|
||||
arg_exprs,
|
||||
fn_sig.c_variadic,
|
||||
TupleArgumentsFlag::DontTupleArguments,
|
||||
def_id,
|
||||
);
|
||||
|
||||
fn_sig.output()
|
||||
}
|
||||
|
||||
/// Attempts to reinterpret `method(rcvr, args...)` as `rcvr.method(args...)`
|
||||
/// and suggesting the fix if the method probe is successful.
|
||||
fn suggest_call_as_method(
|
||||
&self,
|
||||
diag: &mut Diagnostic,
|
||||
segment: &'tcx hir::PathSegment<'tcx>,
|
||||
arg_exprs: &'tcx [hir::Expr<'tcx>],
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Option<Ty<'tcx>> {
|
||||
if let [callee_expr, rest @ ..] = arg_exprs {
|
||||
let callee_ty = self.check_expr(callee_expr);
|
||||
// First, do a probe with `IsSuggestion(true)` to avoid emitting
|
||||
// any strange errors. If it's successful, then we'll do a true
|
||||
// method lookup.
|
||||
let Ok(pick) = self
|
||||
.probe_for_name(
|
||||
call_expr.span,
|
||||
Mode::MethodCall,
|
||||
segment.ident,
|
||||
IsSuggestion(true),
|
||||
callee_ty,
|
||||
call_expr.hir_id,
|
||||
// We didn't record the in scope traits during late resolution
|
||||
// so we need to probe AllTraits unfortunately
|
||||
ProbeScope::AllTraits,
|
||||
) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let pick = self.confirm_method(
|
||||
call_expr.span,
|
||||
callee_expr,
|
||||
call_expr,
|
||||
callee_ty,
|
||||
pick,
|
||||
segment,
|
||||
);
|
||||
if pick.illegal_sized_bound.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let up_to_rcvr_span = segment.ident.span.until(callee_expr.span);
|
||||
let rest_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
|
||||
let rest_snippet = if let Some(first) = rest.first() {
|
||||
self.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.span_to_snippet(first.span.to(call_expr.span.shrink_to_hi()))
|
||||
} else {
|
||||
Ok(")".to_string())
|
||||
};
|
||||
|
||||
if let Ok(rest_snippet) = rest_snippet {
|
||||
let sugg = if callee_expr.precedence().order() >= PREC_POSTFIX {
|
||||
vec![
|
||||
(up_to_rcvr_span, "".to_string()),
|
||||
(rest_span, format!(".{}({rest_snippet}", segment.ident)),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
(up_to_rcvr_span, "(".to_string()),
|
||||
(rest_span, format!(").{}({rest_snippet}", segment.ident)),
|
||||
]
|
||||
};
|
||||
let self_ty = self.resolve_vars_if_possible(pick.callee.sig.inputs()[0]);
|
||||
diag.multipart_suggestion(
|
||||
format!(
|
||||
"use the `.` operator to call the method `{}{}` on `{self_ty}`",
|
||||
self.tcx
|
||||
.associated_item(pick.callee.def_id)
|
||||
.trait_container(self.tcx)
|
||||
.map_or_else(
|
||||
|| String::new(),
|
||||
|trait_def_id| self.tcx.def_path_str(trait_def_id) + "::"
|
||||
),
|
||||
segment.ident
|
||||
),
|
||||
sugg,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
// Let's check the method fully now
|
||||
let return_ty = self.check_method_argument_types(
|
||||
segment.ident.span,
|
||||
call_expr,
|
||||
Ok(pick.callee),
|
||||
rest,
|
||||
TupleArgumentsFlag::DontTupleArguments,
|
||||
expected,
|
||||
);
|
||||
|
||||
return Some(return_ty);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn report_invalid_callee(
|
||||
&self,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_ty: Ty<'tcx>,
|
||||
arg_exprs: &'tcx [hir::Expr<'tcx>],
|
||||
) {
|
||||
let mut unit_variant = None;
|
||||
if let hir::ExprKind::Path(qpath) = &callee_expr.kind
|
||||
&& let Res::Def(def::DefKind::Ctor(kind, def::CtorKind::Const), _)
|
||||
= self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id)
|
||||
// Only suggest removing parens if there are no arguments
|
||||
&& arg_exprs.is_empty()
|
||||
{
|
||||
let descr = match kind {
|
||||
def::CtorOf::Struct => "struct",
|
||||
def::CtorOf::Variant => "enum variant",
|
||||
};
|
||||
let removal_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
|
||||
unit_variant = Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(qpath)));
|
||||
}
|
||||
|
||||
let callee_ty = self.resolve_vars_if_possible(callee_ty);
|
||||
let mut err = type_error_struct!(
|
||||
self.tcx.sess,
|
||||
callee_expr.span,
|
||||
callee_ty,
|
||||
E0618,
|
||||
"expected function, found {}",
|
||||
match &unit_variant {
|
||||
Some((_, kind, path)) => format!("{kind} `{path}`"),
|
||||
None => format!("`{callee_ty}`"),
|
||||
}
|
||||
);
|
||||
|
||||
self.identify_bad_closure_def_and_call(
|
||||
&mut err,
|
||||
call_expr.hir_id,
|
||||
&callee_expr.kind,
|
||||
callee_expr.span,
|
||||
);
|
||||
|
||||
if let Some((removal_span, kind, path)) = &unit_variant {
|
||||
err.span_suggestion_verbose(
|
||||
*removal_span,
|
||||
&format!(
|
||||
"`{path}` is a unit {kind}, and does not take parentheses to be constructed",
|
||||
),
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
let mut inner_callee_path = None;
|
||||
let def = match callee_expr.kind {
|
||||
hir::ExprKind::Path(ref qpath) => {
|
||||
self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id)
|
||||
}
|
||||
hir::ExprKind::Call(ref inner_callee, _) => {
|
||||
// If the call spans more than one line and the callee kind is
|
||||
// itself another `ExprCall`, that's a clue that we might just be
|
||||
// missing a semicolon (Issue #51055)
|
||||
let call_is_multiline = self.tcx.sess.source_map().is_multiline(call_expr.span);
|
||||
if call_is_multiline {
|
||||
err.span_suggestion(
|
||||
callee_expr.span.shrink_to_hi(),
|
||||
"consider using a semicolon here",
|
||||
";",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
if let hir::ExprKind::Path(ref inner_qpath) = inner_callee.kind {
|
||||
inner_callee_path = Some(inner_qpath);
|
||||
self.typeck_results.borrow().qpath_res(inner_qpath, inner_callee.hir_id)
|
||||
} else {
|
||||
Res::Err
|
||||
}
|
||||
}
|
||||
_ => Res::Err,
|
||||
};
|
||||
|
||||
if !self.maybe_suggest_bad_array_definition(&mut err, call_expr, callee_expr) {
|
||||
if let Some((maybe_def, output_ty, _)) =
|
||||
self.extract_callable_info(callee_expr, callee_ty)
|
||||
&& !self.type_is_sized_modulo_regions(self.param_env, output_ty, callee_expr.span)
|
||||
{
|
||||
let descr = match maybe_def {
|
||||
DefIdOrName::DefId(def_id) => self.tcx.def_kind(def_id).descr(def_id),
|
||||
DefIdOrName::Name(name) => name,
|
||||
};
|
||||
err.span_label(
|
||||
callee_expr.span,
|
||||
format!("this {descr} returns an unsized value `{output_ty}`, so it cannot be called")
|
||||
);
|
||||
if let DefIdOrName::DefId(def_id) = maybe_def
|
||||
&& let Some(def_span) = self.tcx.hir().span_if_local(def_id)
|
||||
{
|
||||
err.span_label(def_span, "the callable type is defined here");
|
||||
}
|
||||
} else {
|
||||
err.span_label(call_expr.span, "call expression requires function");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(span) = self.tcx.hir().res_span(def) {
|
||||
let callee_ty = callee_ty.to_string();
|
||||
let label = match (unit_variant, inner_callee_path) {
|
||||
(Some((_, kind, path)), _) => Some(format!("{kind} `{path}` defined here")),
|
||||
(_, Some(hir::QPath::Resolved(_, path))) => self
|
||||
.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.span_to_snippet(path.span)
|
||||
.ok()
|
||||
.map(|p| format!("`{p}` defined here returns `{callee_ty}`")),
|
||||
_ => {
|
||||
match def {
|
||||
// Emit a different diagnostic for local variables, as they are not
|
||||
// type definitions themselves, but rather variables *of* that type.
|
||||
Res::Local(hir_id) => Some(format!(
|
||||
"`{}` has type `{}`",
|
||||
self.tcx.hir().name(hir_id),
|
||||
callee_ty
|
||||
)),
|
||||
Res::Def(kind, def_id) if kind.ns() == Some(Namespace::ValueNS) => {
|
||||
Some(format!("`{}` defined here", self.tcx.def_path_str(def_id),))
|
||||
}
|
||||
_ => Some(format!("`{callee_ty}` defined here")),
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(label) = label {
|
||||
err.span_label(span, label);
|
||||
}
|
||||
}
|
||||
err.emit();
|
||||
}
|
||||
|
||||
fn confirm_deferred_closure_call(
|
||||
&self,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
arg_exprs: &'tcx [hir::Expr<'tcx>],
|
||||
expected: Expectation<'tcx>,
|
||||
closure_def_id: LocalDefId,
|
||||
fn_sig: ty::FnSig<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
// `fn_sig` is the *signature* of the closure being called. We
|
||||
// don't know the full details yet (`Fn` vs `FnMut` etc), but we
|
||||
// do know the types expected for each argument and the return
|
||||
// type.
|
||||
|
||||
let expected_arg_tys = self.expected_inputs_for_expected_output(
|
||||
call_expr.span,
|
||||
expected,
|
||||
fn_sig.output(),
|
||||
fn_sig.inputs(),
|
||||
);
|
||||
|
||||
self.check_argument_types(
|
||||
call_expr.span,
|
||||
call_expr,
|
||||
fn_sig.inputs(),
|
||||
expected_arg_tys,
|
||||
arg_exprs,
|
||||
fn_sig.c_variadic,
|
||||
TupleArgumentsFlag::TupleArguments,
|
||||
Some(closure_def_id.to_def_id()),
|
||||
);
|
||||
|
||||
fn_sig.output()
|
||||
}
|
||||
|
||||
fn confirm_overloaded_call(
|
||||
&self,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
arg_exprs: &'tcx [hir::Expr<'tcx>],
|
||||
expected: Expectation<'tcx>,
|
||||
method_callee: MethodCallee<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let output_type = self.check_method_argument_types(
|
||||
call_expr.span,
|
||||
call_expr,
|
||||
Ok(method_callee),
|
||||
arg_exprs,
|
||||
TupleArgumentsFlag::TupleArguments,
|
||||
expected,
|
||||
);
|
||||
|
||||
self.write_method_call(call_expr.hir_id, method_callee);
|
||||
output_type
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeferredCallResolution<'tcx> {
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
callee_expr: &'tcx hir::Expr<'tcx>,
|
||||
adjusted_ty: Ty<'tcx>,
|
||||
adjustments: Vec<Adjustment<'tcx>>,
|
||||
fn_sig: ty::FnSig<'tcx>,
|
||||
closure_substs: SubstsRef<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DeferredCallResolution<'tcx> {
|
||||
pub fn resolve(self, fcx: &FnCtxt<'a, 'tcx>) {
|
||||
debug!("DeferredCallResolution::resolve() {:?}", self);
|
||||
|
||||
// we should not be invoked until the closure kind has been
|
||||
// determined by upvar inference
|
||||
assert!(fcx.closure_kind(self.closure_substs).is_some());
|
||||
|
||||
// We may now know enough to figure out fn vs fnmut etc.
|
||||
match fcx.try_overloaded_call_traits(self.call_expr, self.adjusted_ty, None) {
|
||||
Some((autoref, method_callee)) => {
|
||||
// One problem is that when we get here, we are going
|
||||
// to have a newly instantiated function signature
|
||||
// from the call trait. This has to be reconciled with
|
||||
// the older function signature we had before. In
|
||||
// principle we *should* be able to fn_sigs(), but we
|
||||
// can't because of the annoying need for a TypeTrace.
|
||||
// (This always bites me, should find a way to
|
||||
// refactor it.)
|
||||
let method_sig = method_callee.sig;
|
||||
|
||||
debug!("attempt_resolution: method_callee={:?}", method_callee);
|
||||
|
||||
for (method_arg_ty, self_arg_ty) in
|
||||
iter::zip(method_sig.inputs().iter().skip(1), self.fn_sig.inputs())
|
||||
{
|
||||
fcx.demand_eqtype(self.call_expr.span, *self_arg_ty, *method_arg_ty);
|
||||
}
|
||||
|
||||
fcx.demand_eqtype(self.call_expr.span, method_sig.output(), self.fn_sig.output());
|
||||
|
||||
let mut adjustments = self.adjustments;
|
||||
adjustments.extend(autoref);
|
||||
fcx.apply_adjustments(self.callee_expr, adjustments);
|
||||
|
||||
fcx.write_method_call(self.call_expr.hir_id, method_callee);
|
||||
}
|
||||
None => {
|
||||
// This can happen if `#![no_core]` is used and the `fn/fn_mut/fn_once`
|
||||
// lang items are not defined (issue #86238).
|
||||
let mut err = fcx.inh.tcx.sess.struct_span_err(
|
||||
self.call_expr.span,
|
||||
"failed to find an overloaded call trait for closure call",
|
||||
);
|
||||
err.help(
|
||||
"make sure the `fn`/`fn_mut`/`fn_once` lang items are defined \
|
||||
and have associated `call`/`call_mut`/`call_once` functions",
|
||||
);
|
||||
err.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,827 +0,0 @@
|
|||
//! Code for type-checking closure expressions.
|
||||
|
||||
use super::{check_fn, Expectation, FnCtxt, GeneratorTypes};
|
||||
|
||||
use crate::astconv::AstConv;
|
||||
use hir::def::DefKind;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_infer::infer::LateBoundRegionConversionTime;
|
||||
use rustc_infer::infer::{InferOk, InferResult};
|
||||
use rustc_middle::ty::subst::InternalSubsts;
|
||||
use rustc_middle::ty::visit::TypeVisitable;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
use rustc_trait_selection::traits::error_reporting::ArgKind;
|
||||
use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
|
||||
use std::cmp;
|
||||
use std::iter;
|
||||
|
||||
/// What signature do we *expect* the closure to have from context?
|
||||
#[derive(Debug)]
|
||||
struct ExpectedSig<'tcx> {
|
||||
/// Span that gave us this expectation, if we know that.
|
||||
cause_span: Option<Span>,
|
||||
sig: ty::PolyFnSig<'tcx>,
|
||||
}
|
||||
|
||||
struct ClosureSignatures<'tcx> {
|
||||
/// The signature users of the closure see.
|
||||
bound_sig: ty::PolyFnSig<'tcx>,
|
||||
/// The signature within the function body.
|
||||
/// This mostly differs in the sense that lifetimes are now early bound and any
|
||||
/// opaque types from the signature expectation are overriden in case there are
|
||||
/// explicit hidden types written by the user in the closure signature.
|
||||
liberated_sig: ty::FnSig<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
#[instrument(skip(self, expr, _capture, decl, body_id), level = "debug")]
|
||||
pub fn check_expr_closure(
|
||||
&self,
|
||||
expr: &hir::Expr<'_>,
|
||||
_capture: hir::CaptureBy,
|
||||
decl: &'tcx hir::FnDecl<'tcx>,
|
||||
body_id: hir::BodyId,
|
||||
gen: Option<hir::Movability>,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
trace!("decl = {:#?}", decl);
|
||||
trace!("expr = {:#?}", expr);
|
||||
|
||||
// It's always helpful for inference if we know the kind of
|
||||
// closure sooner rather than later, so first examine the expected
|
||||
// type, and see if can glean a closure kind from there.
|
||||
let (expected_sig, expected_kind) = match expected.to_option(self) {
|
||||
Some(ty) => self.deduce_expectations_from_expected_type(ty),
|
||||
None => (None, None),
|
||||
};
|
||||
let body = self.tcx.hir().body(body_id);
|
||||
self.check_closure(expr, expected_kind, decl, body, gen, expected_sig)
|
||||
}
|
||||
|
||||
#[instrument(skip(self, expr, body, decl), level = "debug", ret)]
|
||||
fn check_closure(
|
||||
&self,
|
||||
expr: &hir::Expr<'_>,
|
||||
opt_kind: Option<ty::ClosureKind>,
|
||||
decl: &'tcx hir::FnDecl<'tcx>,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
gen: Option<hir::Movability>,
|
||||
expected_sig: Option<ExpectedSig<'tcx>>,
|
||||
) -> Ty<'tcx> {
|
||||
trace!("decl = {:#?}", decl);
|
||||
let expr_def_id = self.tcx.hir().local_def_id(expr.hir_id);
|
||||
debug!(?expr_def_id);
|
||||
|
||||
let ClosureSignatures { bound_sig, liberated_sig } =
|
||||
self.sig_of_closure(expr.hir_id, expr_def_id.to_def_id(), decl, body, expected_sig);
|
||||
|
||||
debug!(?bound_sig, ?liberated_sig);
|
||||
|
||||
let return_type_pre_known = !liberated_sig.output().is_ty_infer();
|
||||
|
||||
let generator_types = check_fn(
|
||||
self,
|
||||
self.param_env.without_const(),
|
||||
liberated_sig,
|
||||
decl,
|
||||
expr.hir_id,
|
||||
body,
|
||||
gen,
|
||||
return_type_pre_known,
|
||||
)
|
||||
.1;
|
||||
|
||||
let parent_substs = InternalSubsts::identity_for_item(
|
||||
self.tcx,
|
||||
self.tcx.typeck_root_def_id(expr_def_id.to_def_id()),
|
||||
);
|
||||
|
||||
let tupled_upvars_ty = self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::ClosureSynthetic,
|
||||
span: self.tcx.hir().span(expr.hir_id),
|
||||
});
|
||||
|
||||
if let Some(GeneratorTypes { resume_ty, yield_ty, interior, movability }) = generator_types
|
||||
{
|
||||
let generator_substs = ty::GeneratorSubsts::new(
|
||||
self.tcx,
|
||||
ty::GeneratorSubstsParts {
|
||||
parent_substs,
|
||||
resume_ty,
|
||||
yield_ty,
|
||||
return_ty: liberated_sig.output(),
|
||||
witness: interior,
|
||||
tupled_upvars_ty,
|
||||
},
|
||||
);
|
||||
|
||||
return self.tcx.mk_generator(
|
||||
expr_def_id.to_def_id(),
|
||||
generator_substs.substs,
|
||||
movability,
|
||||
);
|
||||
}
|
||||
|
||||
// Tuple up the arguments and insert the resulting function type into
|
||||
// the `closures` table.
|
||||
let sig = bound_sig.map_bound(|sig| {
|
||||
self.tcx.mk_fn_sig(
|
||||
iter::once(self.tcx.intern_tup(sig.inputs())),
|
||||
sig.output(),
|
||||
sig.c_variadic,
|
||||
sig.unsafety,
|
||||
sig.abi,
|
||||
)
|
||||
});
|
||||
|
||||
debug!(?sig, ?opt_kind);
|
||||
|
||||
let closure_kind_ty = match opt_kind {
|
||||
Some(kind) => kind.to_ty(self.tcx),
|
||||
|
||||
// Create a type variable (for now) to represent the closure kind.
|
||||
// It will be unified during the upvar inference phase (`upvar.rs`)
|
||||
None => self.next_ty_var(TypeVariableOrigin {
|
||||
// FIXME(eddyb) distinguish closure kind inference variables from the rest.
|
||||
kind: TypeVariableOriginKind::ClosureSynthetic,
|
||||
span: expr.span,
|
||||
}),
|
||||
};
|
||||
|
||||
let closure_substs = ty::ClosureSubsts::new(
|
||||
self.tcx,
|
||||
ty::ClosureSubstsParts {
|
||||
parent_substs,
|
||||
closure_kind_ty,
|
||||
closure_sig_as_fn_ptr_ty: self.tcx.mk_fn_ptr(sig),
|
||||
tupled_upvars_ty,
|
||||
},
|
||||
);
|
||||
|
||||
self.tcx.mk_closure(expr_def_id.to_def_id(), closure_substs.substs)
|
||||
}
|
||||
|
||||
/// Given the expected type, figures out what it can about this closure we
|
||||
/// are about to type check:
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
fn deduce_expectations_from_expected_type(
|
||||
&self,
|
||||
expected_ty: Ty<'tcx>,
|
||||
) -> (Option<ExpectedSig<'tcx>>, Option<ty::ClosureKind>) {
|
||||
match *expected_ty.kind() {
|
||||
ty::Opaque(def_id, substs) => {
|
||||
let bounds = self.tcx.bound_explicit_item_bounds(def_id);
|
||||
let sig = bounds
|
||||
.transpose_iter()
|
||||
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||
.find_map(|(pred, span)| match pred.0.kind().skip_binder() {
|
||||
ty::PredicateKind::Projection(proj_predicate) => self
|
||||
.deduce_sig_from_projection(
|
||||
Some(span.0),
|
||||
pred.0
|
||||
.kind()
|
||||
.rebind(pred.rebind(proj_predicate).subst(self.tcx, substs)),
|
||||
),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let kind = bounds
|
||||
.transpose_iter()
|
||||
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||
.filter_map(|(pred, _)| match pred.0.kind().skip_binder() {
|
||||
ty::PredicateKind::Trait(tp) => {
|
||||
self.tcx.fn_trait_kind_from_lang_item(tp.def_id())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.fold(None, |best, cur| Some(best.map_or(cur, |best| cmp::min(best, cur))));
|
||||
trace!(?sig, ?kind);
|
||||
(sig, kind)
|
||||
}
|
||||
ty::Dynamic(ref object_type, ..) => {
|
||||
let sig = object_type.projection_bounds().find_map(|pb| {
|
||||
let pb = pb.with_self_ty(self.tcx, self.tcx.types.trait_object_dummy_self);
|
||||
self.deduce_sig_from_projection(None, pb)
|
||||
});
|
||||
let kind = object_type
|
||||
.principal_def_id()
|
||||
.and_then(|did| self.tcx.fn_trait_kind_from_lang_item(did));
|
||||
(sig, kind)
|
||||
}
|
||||
ty::Infer(ty::TyVar(vid)) => self.deduce_expectations_from_obligations(vid),
|
||||
ty::FnPtr(sig) => {
|
||||
let expected_sig = ExpectedSig { cause_span: None, sig };
|
||||
(Some(expected_sig), Some(ty::ClosureKind::Fn))
|
||||
}
|
||||
_ => (None, None),
|
||||
}
|
||||
}
|
||||
|
||||
fn deduce_expectations_from_obligations(
|
||||
&self,
|
||||
expected_vid: ty::TyVid,
|
||||
) -> (Option<ExpectedSig<'tcx>>, Option<ty::ClosureKind>) {
|
||||
let expected_sig =
|
||||
self.obligations_for_self_ty(expected_vid).find_map(|(_, obligation)| {
|
||||
debug!(?obligation.predicate);
|
||||
|
||||
let bound_predicate = obligation.predicate.kind();
|
||||
if let ty::PredicateKind::Projection(proj_predicate) =
|
||||
obligation.predicate.kind().skip_binder()
|
||||
{
|
||||
// Given a Projection predicate, we can potentially infer
|
||||
// the complete signature.
|
||||
self.deduce_sig_from_projection(
|
||||
Some(obligation.cause.span),
|
||||
bound_predicate.rebind(proj_predicate),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// Even if we can't infer the full signature, we may be able to
|
||||
// infer the kind. This can occur when we elaborate a predicate
|
||||
// like `F : Fn<A>`. Note that due to subtyping we could encounter
|
||||
// many viable options, so pick the most restrictive.
|
||||
let expected_kind = self
|
||||
.obligations_for_self_ty(expected_vid)
|
||||
.filter_map(|(tr, _)| self.tcx.fn_trait_kind_from_lang_item(tr.def_id()))
|
||||
.fold(None, |best, cur| Some(best.map_or(cur, |best| cmp::min(best, cur))));
|
||||
|
||||
(expected_sig, expected_kind)
|
||||
}
|
||||
|
||||
/// Given a projection like "<F as Fn(X)>::Result == Y", we can deduce
|
||||
/// everything we need to know about a closure or generator.
|
||||
///
|
||||
/// The `cause_span` should be the span that caused us to
|
||||
/// have this expected signature, or `None` if we can't readily
|
||||
/// know that.
|
||||
#[instrument(level = "debug", skip(self, cause_span), ret)]
|
||||
fn deduce_sig_from_projection(
|
||||
&self,
|
||||
cause_span: Option<Span>,
|
||||
projection: ty::PolyProjectionPredicate<'tcx>,
|
||||
) -> Option<ExpectedSig<'tcx>> {
|
||||
let tcx = self.tcx;
|
||||
|
||||
let trait_def_id = projection.trait_def_id(tcx);
|
||||
|
||||
let is_fn = tcx.fn_trait_kind_from_lang_item(trait_def_id).is_some();
|
||||
let gen_trait = tcx.require_lang_item(LangItem::Generator, cause_span);
|
||||
let is_gen = gen_trait == trait_def_id;
|
||||
if !is_fn && !is_gen {
|
||||
debug!("not fn or generator");
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_gen {
|
||||
// Check that we deduce the signature from the `<_ as std::ops::Generator>::Return`
|
||||
// associated item and not yield.
|
||||
let return_assoc_item = self.tcx.associated_item_def_ids(gen_trait)[1];
|
||||
if return_assoc_item != projection.projection_def_id() {
|
||||
debug!("not return assoc item of generator");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let input_tys = if is_fn {
|
||||
let arg_param_ty = projection.skip_binder().projection_ty.substs.type_at(1);
|
||||
let arg_param_ty = self.resolve_vars_if_possible(arg_param_ty);
|
||||
debug!(?arg_param_ty);
|
||||
|
||||
match arg_param_ty.kind() {
|
||||
&ty::Tuple(tys) => tys,
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
// Generators with a `()` resume type may be defined with 0 or 1 explicit arguments,
|
||||
// else they must have exactly 1 argument. For now though, just give up in this case.
|
||||
return None;
|
||||
};
|
||||
|
||||
// Since this is a return parameter type it is safe to unwrap.
|
||||
let ret_param_ty = projection.skip_binder().term.ty().unwrap();
|
||||
let ret_param_ty = self.resolve_vars_if_possible(ret_param_ty);
|
||||
debug!(?ret_param_ty);
|
||||
|
||||
let sig = projection.rebind(self.tcx.mk_fn_sig(
|
||||
input_tys.iter(),
|
||||
ret_param_ty,
|
||||
false,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::Rust,
|
||||
));
|
||||
|
||||
Some(ExpectedSig { cause_span, sig })
|
||||
}
|
||||
|
||||
fn sig_of_closure(
|
||||
&self,
|
||||
hir_id: hir::HirId,
|
||||
expr_def_id: DefId,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
body: &hir::Body<'_>,
|
||||
expected_sig: Option<ExpectedSig<'tcx>>,
|
||||
) -> ClosureSignatures<'tcx> {
|
||||
if let Some(e) = expected_sig {
|
||||
self.sig_of_closure_with_expectation(hir_id, expr_def_id, decl, body, e)
|
||||
} else {
|
||||
self.sig_of_closure_no_expectation(hir_id, expr_def_id, decl, body)
|
||||
}
|
||||
}
|
||||
|
||||
/// If there is no expected signature, then we will convert the
|
||||
/// types that the user gave into a signature.
|
||||
#[instrument(skip(self, hir_id, expr_def_id, decl, body), level = "debug")]
|
||||
fn sig_of_closure_no_expectation(
|
||||
&self,
|
||||
hir_id: hir::HirId,
|
||||
expr_def_id: DefId,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
body: &hir::Body<'_>,
|
||||
) -> ClosureSignatures<'tcx> {
|
||||
let bound_sig = self.supplied_sig_of_closure(hir_id, expr_def_id, decl, body);
|
||||
|
||||
self.closure_sigs(expr_def_id, body, bound_sig)
|
||||
}
|
||||
|
||||
/// Invoked to compute the signature of a closure expression. This
|
||||
/// combines any user-provided type annotations (e.g., `|x: u32|
|
||||
/// -> u32 { .. }`) with the expected signature.
|
||||
///
|
||||
/// The approach is as follows:
|
||||
///
|
||||
/// - Let `S` be the (higher-ranked) signature that we derive from the user's annotations.
|
||||
/// - Let `E` be the (higher-ranked) signature that we derive from the expectations, if any.
|
||||
/// - If we have no expectation `E`, then the signature of the closure is `S`.
|
||||
/// - Otherwise, the signature of the closure is E. Moreover:
|
||||
/// - Skolemize the late-bound regions in `E`, yielding `E'`.
|
||||
/// - Instantiate all the late-bound regions bound in the closure within `S`
|
||||
/// with fresh (existential) variables, yielding `S'`
|
||||
/// - Require that `E' = S'`
|
||||
/// - We could use some kind of subtyping relationship here,
|
||||
/// I imagine, but equality is easier and works fine for
|
||||
/// our purposes.
|
||||
///
|
||||
/// The key intuition here is that the user's types must be valid
|
||||
/// from "the inside" of the closure, but the expectation
|
||||
/// ultimately drives the overall signature.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore (illustrative)
|
||||
/// fn with_closure<F>(_: F)
|
||||
/// where F: Fn(&u32) -> &u32 { .. }
|
||||
///
|
||||
/// with_closure(|x: &u32| { ... })
|
||||
/// ```
|
||||
///
|
||||
/// Here:
|
||||
/// - E would be `fn(&u32) -> &u32`.
|
||||
/// - S would be `fn(&u32) ->
|
||||
/// - E' is `&'!0 u32 -> &'!0 u32`
|
||||
/// - S' is `&'?0 u32 -> ?T`
|
||||
///
|
||||
/// S' can be unified with E' with `['?0 = '!0, ?T = &'!10 u32]`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `expr_def_id`: the `DefId` of the closure expression
|
||||
/// - `decl`: the HIR declaration of the closure
|
||||
/// - `body`: the body of the closure
|
||||
/// - `expected_sig`: the expected signature (if any). Note that
|
||||
/// this is missing a binder: that is, there may be late-bound
|
||||
/// regions with depth 1, which are bound then by the closure.
|
||||
#[instrument(skip(self, hir_id, expr_def_id, decl, body), level = "debug")]
|
||||
fn sig_of_closure_with_expectation(
|
||||
&self,
|
||||
hir_id: hir::HirId,
|
||||
expr_def_id: DefId,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
body: &hir::Body<'_>,
|
||||
expected_sig: ExpectedSig<'tcx>,
|
||||
) -> ClosureSignatures<'tcx> {
|
||||
// Watch out for some surprises and just ignore the
|
||||
// expectation if things don't see to match up with what we
|
||||
// expect.
|
||||
if expected_sig.sig.c_variadic() != decl.c_variadic {
|
||||
return self.sig_of_closure_no_expectation(hir_id, expr_def_id, decl, body);
|
||||
} else if expected_sig.sig.skip_binder().inputs_and_output.len() != decl.inputs.len() + 1 {
|
||||
return self.sig_of_closure_with_mismatched_number_of_arguments(
|
||||
expr_def_id,
|
||||
decl,
|
||||
body,
|
||||
expected_sig,
|
||||
);
|
||||
}
|
||||
|
||||
// Create a `PolyFnSig`. Note the oddity that late bound
|
||||
// regions appearing free in `expected_sig` are now bound up
|
||||
// in this binder we are creating.
|
||||
assert!(!expected_sig.sig.skip_binder().has_vars_bound_above(ty::INNERMOST));
|
||||
let bound_sig = expected_sig.sig.map_bound(|sig| {
|
||||
self.tcx.mk_fn_sig(
|
||||
sig.inputs().iter().cloned(),
|
||||
sig.output(),
|
||||
sig.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::RustCall,
|
||||
)
|
||||
});
|
||||
|
||||
// `deduce_expectations_from_expected_type` introduces
|
||||
// late-bound lifetimes defined elsewhere, which we now
|
||||
// anonymize away, so as not to confuse the user.
|
||||
let bound_sig = self.tcx.anonymize_late_bound_regions(bound_sig);
|
||||
|
||||
let closure_sigs = self.closure_sigs(expr_def_id, body, bound_sig);
|
||||
|
||||
// Up till this point, we have ignored the annotations that the user
|
||||
// gave. This function will check that they unify successfully.
|
||||
// Along the way, it also writes out entries for types that the user
|
||||
// wrote into our typeck results, which are then later used by the privacy
|
||||
// check.
|
||||
match self.merge_supplied_sig_with_expectation(
|
||||
hir_id,
|
||||
expr_def_id,
|
||||
decl,
|
||||
body,
|
||||
closure_sigs,
|
||||
) {
|
||||
Ok(infer_ok) => self.register_infer_ok_obligations(infer_ok),
|
||||
Err(_) => self.sig_of_closure_no_expectation(hir_id, expr_def_id, decl, body),
|
||||
}
|
||||
}
|
||||
|
||||
fn sig_of_closure_with_mismatched_number_of_arguments(
|
||||
&self,
|
||||
expr_def_id: DefId,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
body: &hir::Body<'_>,
|
||||
expected_sig: ExpectedSig<'tcx>,
|
||||
) -> ClosureSignatures<'tcx> {
|
||||
let hir = self.tcx.hir();
|
||||
let expr_map_node = hir.get_if_local(expr_def_id).unwrap();
|
||||
let expected_args: Vec<_> = expected_sig
|
||||
.sig
|
||||
.skip_binder()
|
||||
.inputs()
|
||||
.iter()
|
||||
.map(|ty| ArgKind::from_expected_ty(*ty, None))
|
||||
.collect();
|
||||
let (closure_span, found_args) = match self.get_fn_like_arguments(expr_map_node) {
|
||||
Some((sp, args)) => (Some(sp), args),
|
||||
None => (None, Vec::new()),
|
||||
};
|
||||
let expected_span =
|
||||
expected_sig.cause_span.unwrap_or_else(|| hir.span_if_local(expr_def_id).unwrap());
|
||||
self.report_arg_count_mismatch(
|
||||
expected_span,
|
||||
closure_span,
|
||||
expected_args,
|
||||
found_args,
|
||||
true,
|
||||
)
|
||||
.emit();
|
||||
|
||||
let error_sig = self.error_sig_of_closure(decl);
|
||||
|
||||
self.closure_sigs(expr_def_id, body, error_sig)
|
||||
}
|
||||
|
||||
/// Enforce the user's types against the expectation. See
|
||||
/// `sig_of_closure_with_expectation` for details on the overall
|
||||
/// strategy.
|
||||
#[instrument(level = "debug", skip(self, hir_id, expr_def_id, decl, body, expected_sigs))]
|
||||
fn merge_supplied_sig_with_expectation(
|
||||
&self,
|
||||
hir_id: hir::HirId,
|
||||
expr_def_id: DefId,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
body: &hir::Body<'_>,
|
||||
mut expected_sigs: ClosureSignatures<'tcx>,
|
||||
) -> InferResult<'tcx, ClosureSignatures<'tcx>> {
|
||||
// Get the signature S that the user gave.
|
||||
//
|
||||
// (See comment on `sig_of_closure_with_expectation` for the
|
||||
// meaning of these letters.)
|
||||
let supplied_sig = self.supplied_sig_of_closure(hir_id, expr_def_id, decl, body);
|
||||
|
||||
debug!(?supplied_sig);
|
||||
|
||||
// FIXME(#45727): As discussed in [this comment][c1], naively
|
||||
// forcing equality here actually results in suboptimal error
|
||||
// messages in some cases. For now, if there would have been
|
||||
// an obvious error, we fallback to declaring the type of the
|
||||
// closure to be the one the user gave, which allows other
|
||||
// error message code to trigger.
|
||||
//
|
||||
// However, I think [there is potential to do even better
|
||||
// here][c2], since in *this* code we have the precise span of
|
||||
// the type parameter in question in hand when we report the
|
||||
// error.
|
||||
//
|
||||
// [c1]: https://github.com/rust-lang/rust/pull/45072#issuecomment-341089706
|
||||
// [c2]: https://github.com/rust-lang/rust/pull/45072#issuecomment-341096796
|
||||
self.commit_if_ok(|_| {
|
||||
let mut all_obligations = vec![];
|
||||
let inputs: Vec<_> = iter::zip(
|
||||
decl.inputs,
|
||||
supplied_sig.inputs().skip_binder(), // binder moved to (*) below
|
||||
)
|
||||
.map(|(hir_ty, &supplied_ty)| {
|
||||
// Instantiate (this part of..) S to S', i.e., with fresh variables.
|
||||
self.replace_bound_vars_with_fresh_vars(
|
||||
hir_ty.span,
|
||||
LateBoundRegionConversionTime::FnCall,
|
||||
// (*) binder moved to here
|
||||
supplied_sig.inputs().rebind(supplied_ty),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// The liberated version of this signature should be a subtype
|
||||
// of the liberated form of the expectation.
|
||||
for ((hir_ty, &supplied_ty), expected_ty) in iter::zip(
|
||||
iter::zip(decl.inputs, &inputs),
|
||||
expected_sigs.liberated_sig.inputs(), // `liberated_sig` is E'.
|
||||
) {
|
||||
// Check that E' = S'.
|
||||
let cause = self.misc(hir_ty.span);
|
||||
let InferOk { value: (), obligations } =
|
||||
self.at(&cause, self.param_env).eq(*expected_ty, supplied_ty)?;
|
||||
all_obligations.extend(obligations);
|
||||
}
|
||||
|
||||
let supplied_output_ty = self.replace_bound_vars_with_fresh_vars(
|
||||
decl.output.span(),
|
||||
LateBoundRegionConversionTime::FnCall,
|
||||
supplied_sig.output(),
|
||||
);
|
||||
let cause = &self.misc(decl.output.span());
|
||||
let InferOk { value: (), obligations } = self
|
||||
.at(cause, self.param_env)
|
||||
.eq(expected_sigs.liberated_sig.output(), supplied_output_ty)?;
|
||||
all_obligations.extend(obligations);
|
||||
|
||||
let inputs = inputs.into_iter().map(|ty| self.resolve_vars_if_possible(ty));
|
||||
|
||||
expected_sigs.liberated_sig = self.tcx.mk_fn_sig(
|
||||
inputs,
|
||||
supplied_output_ty,
|
||||
expected_sigs.liberated_sig.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::RustCall,
|
||||
);
|
||||
|
||||
Ok(InferOk { value: expected_sigs, obligations: all_obligations })
|
||||
})
|
||||
}
|
||||
|
||||
/// If there is no expected signature, then we will convert the
|
||||
/// types that the user gave into a signature.
|
||||
///
|
||||
/// Also, record this closure signature for later.
|
||||
#[instrument(skip(self, decl, body), level = "debug", ret)]
|
||||
fn supplied_sig_of_closure(
|
||||
&self,
|
||||
hir_id: hir::HirId,
|
||||
expr_def_id: DefId,
|
||||
decl: &hir::FnDecl<'_>,
|
||||
body: &hir::Body<'_>,
|
||||
) -> ty::PolyFnSig<'tcx> {
|
||||
let astconv: &dyn AstConv<'_> = self;
|
||||
|
||||
trace!("decl = {:#?}", decl);
|
||||
debug!(?body.generator_kind);
|
||||
|
||||
let bound_vars = self.tcx.late_bound_vars(hir_id);
|
||||
|
||||
// First, convert the types that the user supplied (if any).
|
||||
let supplied_arguments = decl.inputs.iter().map(|a| astconv.ast_ty_to_ty(a));
|
||||
let supplied_return = match decl.output {
|
||||
hir::FnRetTy::Return(ref output) => astconv.ast_ty_to_ty(&output),
|
||||
hir::FnRetTy::DefaultReturn(_) => match body.generator_kind {
|
||||
// In the case of the async block that we create for a function body,
|
||||
// we expect the return type of the block to match that of the enclosing
|
||||
// function.
|
||||
Some(hir::GeneratorKind::Async(hir::AsyncGeneratorKind::Fn)) => {
|
||||
debug!("closure is async fn body");
|
||||
self.deduce_future_output_from_obligations(expr_def_id, body.id().hir_id)
|
||||
.unwrap_or_else(|| {
|
||||
// AFAIK, deducing the future output
|
||||
// always succeeds *except* in error cases
|
||||
// like #65159. I'd like to return Error
|
||||
// here, but I can't because I can't
|
||||
// easily (and locally) prove that we
|
||||
// *have* reported an
|
||||
// error. --nikomatsakis
|
||||
astconv.ty_infer(None, decl.output.span())
|
||||
})
|
||||
}
|
||||
|
||||
_ => astconv.ty_infer(None, decl.output.span()),
|
||||
},
|
||||
};
|
||||
|
||||
let result = ty::Binder::bind_with_vars(
|
||||
self.tcx.mk_fn_sig(
|
||||
supplied_arguments,
|
||||
supplied_return,
|
||||
decl.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::RustCall,
|
||||
),
|
||||
bound_vars,
|
||||
);
|
||||
// Astconv can't normalize inputs or outputs with escaping bound vars,
|
||||
// so normalize them here, after we've wrapped them in a binder.
|
||||
let result = self.normalize_associated_types_in(self.tcx.hir().span(hir_id), result);
|
||||
|
||||
let c_result = self.inh.infcx.canonicalize_response(result);
|
||||
self.typeck_results.borrow_mut().user_provided_sigs.insert(expr_def_id, c_result);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Invoked when we are translating the generator that results
|
||||
/// from desugaring an `async fn`. Returns the "sugared" return
|
||||
/// type of the `async fn` -- that is, the return type that the
|
||||
/// user specified. The "desugared" return type is an `impl
|
||||
/// Future<Output = T>`, so we do this by searching through the
|
||||
/// obligations to extract the `T`.
|
||||
#[instrument(skip(self), level = "debug", ret)]
|
||||
fn deduce_future_output_from_obligations(
|
||||
&self,
|
||||
expr_def_id: DefId,
|
||||
body_id: hir::HirId,
|
||||
) -> Option<Ty<'tcx>> {
|
||||
let ret_coercion = self.ret_coercion.as_ref().unwrap_or_else(|| {
|
||||
span_bug!(self.tcx.def_span(expr_def_id), "async fn generator outside of a fn")
|
||||
});
|
||||
|
||||
let ret_ty = ret_coercion.borrow().expected_ty();
|
||||
let ret_ty = self.inh.infcx.shallow_resolve(ret_ty);
|
||||
|
||||
let get_future_output = |predicate: ty::Predicate<'tcx>, span| {
|
||||
// Search for a pending obligation like
|
||||
//
|
||||
// `<R as Future>::Output = T`
|
||||
//
|
||||
// where R is the return type we are expecting. This type `T`
|
||||
// will be our output.
|
||||
let bound_predicate = predicate.kind();
|
||||
if let ty::PredicateKind::Projection(proj_predicate) = bound_predicate.skip_binder() {
|
||||
self.deduce_future_output_from_projection(
|
||||
span,
|
||||
bound_predicate.rebind(proj_predicate),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let output_ty = match *ret_ty.kind() {
|
||||
ty::Infer(ty::TyVar(ret_vid)) => {
|
||||
self.obligations_for_self_ty(ret_vid).find_map(|(_, obligation)| {
|
||||
get_future_output(obligation.predicate, obligation.cause.span)
|
||||
})?
|
||||
}
|
||||
ty::Opaque(def_id, substs) => self
|
||||
.tcx
|
||||
.bound_explicit_item_bounds(def_id)
|
||||
.transpose_iter()
|
||||
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||
.find_map(|(p, s)| get_future_output(p.subst(self.tcx, substs), s.0))?,
|
||||
ty::Error(_) => return None,
|
||||
ty::Projection(proj)
|
||||
if self.tcx.def_kind(proj.item_def_id) == DefKind::ImplTraitPlaceholder =>
|
||||
{
|
||||
self.tcx
|
||||
.bound_explicit_item_bounds(proj.item_def_id)
|
||||
.transpose_iter()
|
||||
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||
.find_map(|(p, s)| get_future_output(p.subst(self.tcx, proj.substs), s.0))?
|
||||
}
|
||||
_ => span_bug!(
|
||||
self.tcx.def_span(expr_def_id),
|
||||
"async fn generator return type not an inference variable: {ret_ty}"
|
||||
),
|
||||
};
|
||||
|
||||
// async fn that have opaque types in their return type need to redo the conversion to inference variables
|
||||
// as they fetch the still opaque version from the signature.
|
||||
let InferOk { value: output_ty, obligations } = self
|
||||
.replace_opaque_types_with_inference_vars(
|
||||
output_ty,
|
||||
body_id,
|
||||
self.tcx.def_span(expr_def_id),
|
||||
self.param_env,
|
||||
);
|
||||
self.register_predicates(obligations);
|
||||
|
||||
Some(output_ty)
|
||||
}
|
||||
|
||||
/// Given a projection like
|
||||
///
|
||||
/// `<X as Future>::Output = T`
|
||||
///
|
||||
/// where `X` is some type that has no late-bound regions, returns
|
||||
/// `Some(T)`. If the projection is for some other trait, returns
|
||||
/// `None`.
|
||||
fn deduce_future_output_from_projection(
|
||||
&self,
|
||||
cause_span: Span,
|
||||
predicate: ty::PolyProjectionPredicate<'tcx>,
|
||||
) -> Option<Ty<'tcx>> {
|
||||
debug!("deduce_future_output_from_projection(predicate={:?})", predicate);
|
||||
|
||||
// We do not expect any bound regions in our predicate, so
|
||||
// skip past the bound vars.
|
||||
let Some(predicate) = predicate.no_bound_vars() else {
|
||||
debug!("deduce_future_output_from_projection: has late-bound regions");
|
||||
return None;
|
||||
};
|
||||
|
||||
// Check that this is a projection from the `Future` trait.
|
||||
let trait_def_id = predicate.projection_ty.trait_def_id(self.tcx);
|
||||
let future_trait = self.tcx.require_lang_item(LangItem::Future, Some(cause_span));
|
||||
if trait_def_id != future_trait {
|
||||
debug!("deduce_future_output_from_projection: not a future");
|
||||
return None;
|
||||
}
|
||||
|
||||
// The `Future` trait has only one associated item, `Output`,
|
||||
// so check that this is what we see.
|
||||
let output_assoc_item = self.tcx.associated_item_def_ids(future_trait)[0];
|
||||
if output_assoc_item != predicate.projection_ty.item_def_id {
|
||||
span_bug!(
|
||||
cause_span,
|
||||
"projecting associated item `{:?}` from future, which is not Output `{:?}`",
|
||||
predicate.projection_ty.item_def_id,
|
||||
output_assoc_item,
|
||||
);
|
||||
}
|
||||
|
||||
// Extract the type from the projection. Note that there can
|
||||
// be no bound variables in this type because the "self type"
|
||||
// does not have any regions in it.
|
||||
let output_ty = self.resolve_vars_if_possible(predicate.term);
|
||||
debug!("deduce_future_output_from_projection: output_ty={:?}", output_ty);
|
||||
// This is a projection on a Fn trait so will always be a type.
|
||||
Some(output_ty.ty().unwrap())
|
||||
}
|
||||
|
||||
/// Converts the types that the user supplied, in case that doing
|
||||
/// so should yield an error, but returns back a signature where
|
||||
/// all parameters are of type `TyErr`.
|
||||
fn error_sig_of_closure(&self, decl: &hir::FnDecl<'_>) -> ty::PolyFnSig<'tcx> {
|
||||
let astconv: &dyn AstConv<'_> = self;
|
||||
|
||||
let supplied_arguments = decl.inputs.iter().map(|a| {
|
||||
// Convert the types that the user supplied (if any), but ignore them.
|
||||
astconv.ast_ty_to_ty(a);
|
||||
self.tcx.ty_error()
|
||||
});
|
||||
|
||||
if let hir::FnRetTy::Return(ref output) = decl.output {
|
||||
astconv.ast_ty_to_ty(&output);
|
||||
}
|
||||
|
||||
let result = ty::Binder::dummy(self.tcx.mk_fn_sig(
|
||||
supplied_arguments,
|
||||
self.tcx.ty_error(),
|
||||
decl.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::RustCall,
|
||||
));
|
||||
|
||||
debug!("supplied_sig_of_closure: result={:?}", result);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn closure_sigs(
|
||||
&self,
|
||||
expr_def_id: DefId,
|
||||
body: &hir::Body<'_>,
|
||||
bound_sig: ty::PolyFnSig<'tcx>,
|
||||
) -> ClosureSignatures<'tcx> {
|
||||
let liberated_sig = self.tcx().liberate_late_bound_regions(expr_def_id, bound_sig);
|
||||
let liberated_sig = self.inh.normalize_associated_types_in(
|
||||
body.value.span,
|
||||
body.value.hir_id,
|
||||
self.param_env,
|
||||
liberated_sig,
|
||||
);
|
||||
ClosureSignatures { bound_sig, liberated_sig }
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,78 +0,0 @@
|
|||
use rustc_span::source_map::DUMMY_SP;
|
||||
use rustc_span::{self, Span};
|
||||
use std::{cmp, ops};
|
||||
|
||||
/// Tracks whether executing a node may exit normally (versus
|
||||
/// return/break/panic, which "diverge", leaving dead code in their
|
||||
/// wake). Tracked semi-automatically (through type variables marked
|
||||
/// as diverging), with some manual adjustments for control-flow
|
||||
/// primitives (approximating a CFG).
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Diverges {
|
||||
/// Potentially unknown, some cases converge,
|
||||
/// others require a CFG to determine them.
|
||||
Maybe,
|
||||
|
||||
/// Definitely known to diverge and therefore
|
||||
/// not reach the next sibling or its parent.
|
||||
Always {
|
||||
/// The `Span` points to the expression
|
||||
/// that caused us to diverge
|
||||
/// (e.g. `return`, `break`, etc).
|
||||
span: Span,
|
||||
/// In some cases (e.g. a `match` expression
|
||||
/// where all arms diverge), we may be
|
||||
/// able to provide a more informative
|
||||
/// message to the user.
|
||||
/// If this is `None`, a default message
|
||||
/// will be generated, which is suitable
|
||||
/// for most cases.
|
||||
custom_note: Option<&'static str>,
|
||||
},
|
||||
|
||||
/// Same as `Always` but with a reachability
|
||||
/// warning already emitted.
|
||||
WarnedAlways,
|
||||
}
|
||||
|
||||
// Convenience impls for combining `Diverges`.
|
||||
|
||||
impl ops::BitAnd for Diverges {
|
||||
type Output = Self;
|
||||
fn bitand(self, other: Self) -> Self {
|
||||
cmp::min(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOr for Diverges {
|
||||
type Output = Self;
|
||||
fn bitor(self, other: Self) -> Self {
|
||||
cmp::max(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitAndAssign for Diverges {
|
||||
fn bitand_assign(&mut self, other: Self) {
|
||||
*self = *self & other;
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::BitOrAssign for Diverges {
|
||||
fn bitor_assign(&mut self, other: Self) {
|
||||
*self = *self | other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Diverges {
|
||||
/// Creates a `Diverges::Always` with the provided `span` and the default note message.
|
||||
pub(super) fn always(span: Span) -> Diverges {
|
||||
Diverges::Always { span, custom_note: None }
|
||||
}
|
||||
|
||||
pub(super) fn is_always(self) -> bool {
|
||||
// Enum comparison ignores the
|
||||
// contents of fields, so we just
|
||||
// fill them in with garbage here.
|
||||
self >= Diverges::Always { span: DUMMY_SP, custom_note: None }
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::{self, Span};
|
||||
|
||||
use super::Expectation::*;
|
||||
use super::FnCtxt;
|
||||
|
||||
/// When type-checking an expression, we propagate downward
|
||||
/// whatever type hint we are able in the form of an `Expectation`.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Expectation<'tcx> {
|
||||
/// We know nothing about what type this expression should have.
|
||||
NoExpectation,
|
||||
|
||||
/// This expression should have the type given (or some subtype).
|
||||
ExpectHasType(Ty<'tcx>),
|
||||
|
||||
/// This expression will be cast to the `Ty`.
|
||||
ExpectCastableToType(Ty<'tcx>),
|
||||
|
||||
/// This rvalue expression will be wrapped in `&` or `Box` and coerced
|
||||
/// to `&Ty` or `Box<Ty>`, respectively. `Ty` is `[A]` or `Trait`.
|
||||
ExpectRvalueLikeUnsized(Ty<'tcx>),
|
||||
|
||||
IsLast(Span),
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Expectation<'tcx> {
|
||||
// Disregard "castable to" expectations because they
|
||||
// can lead us astray. Consider for example `if cond
|
||||
// {22} else {c} as u8` -- if we propagate the
|
||||
// "castable to u8" constraint to 22, it will pick the
|
||||
// type 22u8, which is overly constrained (c might not
|
||||
// be a u8). In effect, the problem is that the
|
||||
// "castable to" expectation is not the tightest thing
|
||||
// we can say, so we want to drop it in this case.
|
||||
// The tightest thing we can say is "must unify with
|
||||
// else branch". Note that in the case of a "has type"
|
||||
// constraint, this limitation does not hold.
|
||||
|
||||
// If the expected type is just a type variable, then don't use
|
||||
// an expected type. Otherwise, we might write parts of the type
|
||||
// when checking the 'then' block which are incompatible with the
|
||||
// 'else' branch.
|
||||
pub(super) fn adjust_for_branches(&self, fcx: &FnCtxt<'a, 'tcx>) -> Expectation<'tcx> {
|
||||
match *self {
|
||||
ExpectHasType(ety) => {
|
||||
let ety = fcx.shallow_resolve(ety);
|
||||
if !ety.is_ty_var() { ExpectHasType(ety) } else { NoExpectation }
|
||||
}
|
||||
ExpectRvalueLikeUnsized(ety) => ExpectRvalueLikeUnsized(ety),
|
||||
_ => NoExpectation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an expectation for an rvalue expression given an *optional*
|
||||
/// hint, which is not required for type safety (the resulting type might
|
||||
/// be checked higher up, as is the case with `&expr` and `box expr`), but
|
||||
/// is useful in determining the concrete type.
|
||||
///
|
||||
/// The primary use case is where the expected type is a fat pointer,
|
||||
/// like `&[isize]`. For example, consider the following statement:
|
||||
///
|
||||
/// let x: &[isize] = &[1, 2, 3];
|
||||
///
|
||||
/// In this case, the expected type for the `&[1, 2, 3]` expression is
|
||||
/// `&[isize]`. If however we were to say that `[1, 2, 3]` has the
|
||||
/// expectation `ExpectHasType([isize])`, that would be too strong --
|
||||
/// `[1, 2, 3]` does not have the type `[isize]` but rather `[isize; 3]`.
|
||||
/// It is only the `&[1, 2, 3]` expression as a whole that can be coerced
|
||||
/// to the type `&[isize]`. Therefore, we propagate this more limited hint,
|
||||
/// which still is useful, because it informs integer literals and the like.
|
||||
/// See the test case `test/ui/coerce-expect-unsized.rs` and #20169
|
||||
/// for examples of where this comes up,.
|
||||
pub(super) fn rvalue_hint(fcx: &FnCtxt<'a, 'tcx>, ty: Ty<'tcx>) -> Expectation<'tcx> {
|
||||
match fcx.tcx.struct_tail_without_normalization(ty).kind() {
|
||||
ty::Slice(_) | ty::Str | ty::Dynamic(..) => ExpectRvalueLikeUnsized(ty),
|
||||
_ => ExpectHasType(ty),
|
||||
}
|
||||
}
|
||||
|
||||
// Resolves `expected` by a single level if it is a variable. If
|
||||
// there is no expected type or resolution is not possible (e.g.,
|
||||
// no constraints yet present), just returns `self`.
|
||||
fn resolve(self, fcx: &FnCtxt<'a, 'tcx>) -> Expectation<'tcx> {
|
||||
match self {
|
||||
NoExpectation => NoExpectation,
|
||||
ExpectCastableToType(t) => ExpectCastableToType(fcx.resolve_vars_if_possible(t)),
|
||||
ExpectHasType(t) => ExpectHasType(fcx.resolve_vars_if_possible(t)),
|
||||
ExpectRvalueLikeUnsized(t) => ExpectRvalueLikeUnsized(fcx.resolve_vars_if_possible(t)),
|
||||
IsLast(sp) => IsLast(sp),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn to_option(self, fcx: &FnCtxt<'a, 'tcx>) -> Option<Ty<'tcx>> {
|
||||
match self.resolve(fcx) {
|
||||
NoExpectation | IsLast(_) => None,
|
||||
ExpectCastableToType(ty) | ExpectHasType(ty) | ExpectRvalueLikeUnsized(ty) => Some(ty),
|
||||
}
|
||||
}
|
||||
|
||||
/// It sometimes happens that we want to turn an expectation into
|
||||
/// a **hard constraint** (i.e., something that must be satisfied
|
||||
/// for the program to type-check). `only_has_type` will return
|
||||
/// such a constraint, if it exists.
|
||||
pub(super) fn only_has_type(self, fcx: &FnCtxt<'a, 'tcx>) -> Option<Ty<'tcx>> {
|
||||
match self {
|
||||
ExpectHasType(ty) => Some(fcx.resolve_vars_if_possible(ty)),
|
||||
NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) | IsLast(_) => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `only_has_type`, but instead of returning `None` if no
|
||||
/// hard constraint exists, creates a fresh type variable.
|
||||
pub(super) fn coercion_target_type(self, fcx: &FnCtxt<'a, 'tcx>, span: Span) -> Ty<'tcx> {
|
||||
self.only_has_type(fcx).unwrap_or_else(|| {
|
||||
fcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::MiscVariable, span })
|
||||
})
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,398 +0,0 @@
|
|||
use crate::check::FnCtxt;
|
||||
use rustc_data_structures::{
|
||||
fx::{FxHashMap, FxHashSet},
|
||||
graph::WithSuccessors,
|
||||
graph::{iterate::DepthFirstSearch, vec_graph::VecGraph},
|
||||
};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
/// Performs type inference fallback, returning true if any fallback
|
||||
/// occurs.
|
||||
pub(super) fn type_inference_fallback(&self) -> bool {
|
||||
debug!(
|
||||
"type-inference-fallback start obligations: {:#?}",
|
||||
self.fulfillment_cx.borrow_mut().pending_obligations()
|
||||
);
|
||||
|
||||
// All type checking constraints were added, try to fallback unsolved variables.
|
||||
self.select_obligations_where_possible(false, |_| {});
|
||||
|
||||
debug!(
|
||||
"type-inference-fallback post selection obligations: {:#?}",
|
||||
self.fulfillment_cx.borrow_mut().pending_obligations()
|
||||
);
|
||||
|
||||
// Check if we have any unsolved variables. If not, no need for fallback.
|
||||
let unsolved_variables = self.unsolved_variables();
|
||||
if unsolved_variables.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let diverging_fallback = self.calculate_diverging_fallback(&unsolved_variables);
|
||||
|
||||
let mut fallback_has_occurred = false;
|
||||
// We do fallback in two passes, to try to generate
|
||||
// better error messages.
|
||||
// The first time, we do *not* replace opaque types.
|
||||
for ty in unsolved_variables {
|
||||
debug!("unsolved_variable = {:?}", ty);
|
||||
fallback_has_occurred |= self.fallback_if_possible(ty, &diverging_fallback);
|
||||
}
|
||||
|
||||
// We now see if we can make progress. This might cause us to
|
||||
// unify inference variables for opaque types, since we may
|
||||
// have unified some other type variables during the first
|
||||
// phase of fallback. This means that we only replace
|
||||
// inference variables with their underlying opaque types as a
|
||||
// last resort.
|
||||
//
|
||||
// In code like this:
|
||||
//
|
||||
// ```rust
|
||||
// type MyType = impl Copy;
|
||||
// fn produce() -> MyType { true }
|
||||
// fn bad_produce() -> MyType { panic!() }
|
||||
// ```
|
||||
//
|
||||
// we want to unify the opaque inference variable in `bad_produce`
|
||||
// with the diverging fallback for `panic!` (e.g. `()` or `!`).
|
||||
// This will produce a nice error message about conflicting concrete
|
||||
// types for `MyType`.
|
||||
//
|
||||
// If we had tried to fallback the opaque inference variable to `MyType`,
|
||||
// we will generate a confusing type-check error that does not explicitly
|
||||
// refer to opaque types.
|
||||
self.select_obligations_where_possible(fallback_has_occurred, |_| {});
|
||||
|
||||
fallback_has_occurred
|
||||
}
|
||||
|
||||
// Tries to apply a fallback to `ty` if it is an unsolved variable.
|
||||
//
|
||||
// - Unconstrained ints are replaced with `i32`.
|
||||
//
|
||||
// - Unconstrained floats are replaced with `f64`.
|
||||
//
|
||||
// - Non-numerics may get replaced with `()` or `!`, depending on
|
||||
// how they were categorized by `calculate_diverging_fallback`
|
||||
// (and the setting of `#![feature(never_type_fallback)]`).
|
||||
//
|
||||
// Fallback becomes very dubious if we have encountered
|
||||
// type-checking errors. In that case, fallback to Error.
|
||||
//
|
||||
// The return value indicates whether fallback has occurred.
|
||||
fn fallback_if_possible(
|
||||
&self,
|
||||
ty: Ty<'tcx>,
|
||||
diverging_fallback: &FxHashMap<Ty<'tcx>, Ty<'tcx>>,
|
||||
) -> bool {
|
||||
// Careful: we do NOT shallow-resolve `ty`. We know that `ty`
|
||||
// is an unsolved variable, and we determine its fallback
|
||||
// based solely on how it was created, not what other type
|
||||
// variables it may have been unified with since then.
|
||||
//
|
||||
// The reason this matters is that other attempts at fallback
|
||||
// may (in principle) conflict with this fallback, and we wish
|
||||
// to generate a type error in that case. (However, this
|
||||
// actually isn't true right now, because we're only using the
|
||||
// builtin fallback rules. This would be true if we were using
|
||||
// user-supplied fallbacks. But it's still useful to write the
|
||||
// code to detect bugs.)
|
||||
//
|
||||
// (Note though that if we have a general type variable `?T`
|
||||
// that is then unified with an integer type variable `?I`
|
||||
// that ultimately never gets resolved to a special integral
|
||||
// type, `?T` is not considered unsolved, but `?I` is. The
|
||||
// same is true for float variables.)
|
||||
let fallback = match ty.kind() {
|
||||
_ if self.is_tainted_by_errors() => self.tcx.ty_error(),
|
||||
ty::Infer(ty::IntVar(_)) => self.tcx.types.i32,
|
||||
ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64,
|
||||
_ => match diverging_fallback.get(&ty) {
|
||||
Some(&fallback_ty) => fallback_ty,
|
||||
None => return false,
|
||||
},
|
||||
};
|
||||
debug!("fallback_if_possible(ty={:?}): defaulting to `{:?}`", ty, fallback);
|
||||
|
||||
let span = self
|
||||
.infcx
|
||||
.type_var_origin(ty)
|
||||
.map(|origin| origin.span)
|
||||
.unwrap_or(rustc_span::DUMMY_SP);
|
||||
self.demand_eqtype(span, ty, fallback);
|
||||
true
|
||||
}
|
||||
|
||||
/// The "diverging fallback" system is rather complicated. This is
|
||||
/// a result of our need to balance 'do the right thing' with
|
||||
/// backwards compatibility.
|
||||
///
|
||||
/// "Diverging" type variables are variables created when we
|
||||
/// coerce a `!` type into an unbound type variable `?X`. If they
|
||||
/// never wind up being constrained, the "right and natural" thing
|
||||
/// is that `?X` should "fallback" to `!`. This means that e.g. an
|
||||
/// expression like `Some(return)` will ultimately wind up with a
|
||||
/// type like `Option<!>` (presuming it is not assigned or
|
||||
/// constrained to have some other type).
|
||||
///
|
||||
/// However, the fallback used to be `()` (before the `!` type was
|
||||
/// added). Moreover, there are cases where the `!` type 'leaks
|
||||
/// out' from dead code into type variables that affect live
|
||||
/// code. The most common case is something like this:
|
||||
///
|
||||
/// ```rust
|
||||
/// # fn foo() -> i32 { 4 }
|
||||
/// match foo() {
|
||||
/// 22 => Default::default(), // call this type `?D`
|
||||
/// _ => return, // return has type `!`
|
||||
/// } // call the type of this match `?M`
|
||||
/// ```
|
||||
///
|
||||
/// Here, coercing the type `!` into `?M` will create a diverging
|
||||
/// type variable `?X` where `?X <: ?M`. We also have that `?D <:
|
||||
/// ?M`. If `?M` winds up unconstrained, then `?X` will
|
||||
/// fallback. If it falls back to `!`, then all the type variables
|
||||
/// will wind up equal to `!` -- this includes the type `?D`
|
||||
/// (since `!` doesn't implement `Default`, we wind up a "trait
|
||||
/// not implemented" error in code like this). But since the
|
||||
/// original fallback was `()`, this code used to compile with `?D
|
||||
/// = ()`. This is somewhat surprising, since `Default::default()`
|
||||
/// on its own would give an error because the types are
|
||||
/// insufficiently constrained.
|
||||
///
|
||||
/// Our solution to this dilemma is to modify diverging variables
|
||||
/// so that they can *either* fallback to `!` (the default) or to
|
||||
/// `()` (the backwards compatibility case). We decide which
|
||||
/// fallback to use based on whether there is a coercion pattern
|
||||
/// like this:
|
||||
///
|
||||
/// ```ignore (not-rust)
|
||||
/// ?Diverging -> ?V
|
||||
/// ?NonDiverging -> ?V
|
||||
/// ?V != ?NonDiverging
|
||||
/// ```
|
||||
///
|
||||
/// Here `?Diverging` represents some diverging type variable and
|
||||
/// `?NonDiverging` represents some non-diverging type
|
||||
/// variable. `?V` can be any type variable (diverging or not), so
|
||||
/// long as it is not equal to `?NonDiverging`.
|
||||
///
|
||||
/// Intuitively, what we are looking for is a case where a
|
||||
/// "non-diverging" type variable (like `?M` in our example above)
|
||||
/// is coerced *into* some variable `?V` that would otherwise
|
||||
/// fallback to `!`. In that case, we make `?V` fallback to `!`,
|
||||
/// along with anything that would flow into `?V`.
|
||||
///
|
||||
/// The algorithm we use:
|
||||
/// * Identify all variables that are coerced *into* by a
|
||||
/// diverging variable. Do this by iterating over each
|
||||
/// diverging, unsolved variable and finding all variables
|
||||
/// reachable from there. Call that set `D`.
|
||||
/// * Walk over all unsolved, non-diverging variables, and find
|
||||
/// any variable that has an edge into `D`.
|
||||
fn calculate_diverging_fallback(
|
||||
&self,
|
||||
unsolved_variables: &[Ty<'tcx>],
|
||||
) -> FxHashMap<Ty<'tcx>, Ty<'tcx>> {
|
||||
debug!("calculate_diverging_fallback({:?})", unsolved_variables);
|
||||
|
||||
let relationships = self.fulfillment_cx.borrow_mut().relationships().clone();
|
||||
|
||||
// Construct a coercion graph where an edge `A -> B` indicates
|
||||
// a type variable is that is coerced
|
||||
let coercion_graph = self.create_coercion_graph();
|
||||
|
||||
// Extract the unsolved type inference variable vids; note that some
|
||||
// unsolved variables are integer/float variables and are excluded.
|
||||
let unsolved_vids = unsolved_variables.iter().filter_map(|ty| ty.ty_vid());
|
||||
|
||||
// Compute the diverging root vids D -- that is, the root vid of
|
||||
// those type variables that (a) are the target of a coercion from
|
||||
// a `!` type and (b) have not yet been solved.
|
||||
//
|
||||
// These variables are the ones that are targets for fallback to
|
||||
// either `!` or `()`.
|
||||
let diverging_roots: FxHashSet<ty::TyVid> = self
|
||||
.diverging_type_vars
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|&ty| self.shallow_resolve(ty))
|
||||
.filter_map(|ty| ty.ty_vid())
|
||||
.map(|vid| self.root_var(vid))
|
||||
.collect();
|
||||
debug!(
|
||||
"calculate_diverging_fallback: diverging_type_vars={:?}",
|
||||
self.diverging_type_vars.borrow()
|
||||
);
|
||||
debug!("calculate_diverging_fallback: diverging_roots={:?}", diverging_roots);
|
||||
|
||||
// Find all type variables that are reachable from a diverging
|
||||
// type variable. These will typically default to `!`, unless
|
||||
// we find later that they are *also* reachable from some
|
||||
// other type variable outside this set.
|
||||
let mut roots_reachable_from_diverging = DepthFirstSearch::new(&coercion_graph);
|
||||
let mut diverging_vids = vec![];
|
||||
let mut non_diverging_vids = vec![];
|
||||
for unsolved_vid in unsolved_vids {
|
||||
let root_vid = self.root_var(unsolved_vid);
|
||||
debug!(
|
||||
"calculate_diverging_fallback: unsolved_vid={:?} root_vid={:?} diverges={:?}",
|
||||
unsolved_vid,
|
||||
root_vid,
|
||||
diverging_roots.contains(&root_vid),
|
||||
);
|
||||
if diverging_roots.contains(&root_vid) {
|
||||
diverging_vids.push(unsolved_vid);
|
||||
roots_reachable_from_diverging.push_start_node(root_vid);
|
||||
|
||||
debug!(
|
||||
"calculate_diverging_fallback: root_vid={:?} reaches {:?}",
|
||||
root_vid,
|
||||
coercion_graph.depth_first_search(root_vid).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// drain the iterator to visit all nodes reachable from this node
|
||||
roots_reachable_from_diverging.complete_search();
|
||||
} else {
|
||||
non_diverging_vids.push(unsolved_vid);
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"calculate_diverging_fallback: roots_reachable_from_diverging={:?}",
|
||||
roots_reachable_from_diverging,
|
||||
);
|
||||
|
||||
// Find all type variables N0 that are not reachable from a
|
||||
// diverging variable, and then compute the set reachable from
|
||||
// N0, which we call N. These are the *non-diverging* type
|
||||
// variables. (Note that this set consists of "root variables".)
|
||||
let mut roots_reachable_from_non_diverging = DepthFirstSearch::new(&coercion_graph);
|
||||
for &non_diverging_vid in &non_diverging_vids {
|
||||
let root_vid = self.root_var(non_diverging_vid);
|
||||
if roots_reachable_from_diverging.visited(root_vid) {
|
||||
continue;
|
||||
}
|
||||
roots_reachable_from_non_diverging.push_start_node(root_vid);
|
||||
roots_reachable_from_non_diverging.complete_search();
|
||||
}
|
||||
debug!(
|
||||
"calculate_diverging_fallback: roots_reachable_from_non_diverging={:?}",
|
||||
roots_reachable_from_non_diverging,
|
||||
);
|
||||
|
||||
debug!("inherited: {:#?}", self.inh.fulfillment_cx.borrow_mut().pending_obligations());
|
||||
debug!("obligations: {:#?}", self.fulfillment_cx.borrow_mut().pending_obligations());
|
||||
debug!("relationships: {:#?}", relationships);
|
||||
|
||||
// For each diverging variable, figure out whether it can
|
||||
// reach a member of N. If so, it falls back to `()`. Else
|
||||
// `!`.
|
||||
let mut diverging_fallback = FxHashMap::default();
|
||||
diverging_fallback.reserve(diverging_vids.len());
|
||||
for &diverging_vid in &diverging_vids {
|
||||
let diverging_ty = self.tcx.mk_ty_var(diverging_vid);
|
||||
let root_vid = self.root_var(diverging_vid);
|
||||
let can_reach_non_diverging = coercion_graph
|
||||
.depth_first_search(root_vid)
|
||||
.any(|n| roots_reachable_from_non_diverging.visited(n));
|
||||
|
||||
let mut relationship = ty::FoundRelationships { self_in_trait: false, output: false };
|
||||
|
||||
for (vid, rel) in relationships.iter() {
|
||||
if self.root_var(*vid) == root_vid {
|
||||
relationship.self_in_trait |= rel.self_in_trait;
|
||||
relationship.output |= rel.output;
|
||||
}
|
||||
}
|
||||
|
||||
if relationship.self_in_trait && relationship.output {
|
||||
// This case falls back to () to ensure that the code pattern in
|
||||
// src/test/ui/never_type/fallback-closure-ret.rs continues to
|
||||
// compile when never_type_fallback is enabled.
|
||||
//
|
||||
// This rule is not readily explainable from first principles,
|
||||
// but is rather intended as a patchwork fix to ensure code
|
||||
// which compiles before the stabilization of never type
|
||||
// fallback continues to work.
|
||||
//
|
||||
// Typically this pattern is encountered in a function taking a
|
||||
// closure as a parameter, where the return type of that closure
|
||||
// (checked by `relationship.output`) is expected to implement
|
||||
// some trait (checked by `relationship.self_in_trait`). This
|
||||
// can come up in non-closure cases too, so we do not limit this
|
||||
// rule to specifically `FnOnce`.
|
||||
//
|
||||
// When the closure's body is something like `panic!()`, the
|
||||
// return type would normally be inferred to `!`. However, it
|
||||
// needs to fall back to `()` in order to still compile, as the
|
||||
// trait is specifically implemented for `()` but not `!`.
|
||||
//
|
||||
// For details on the requirements for these relationships to be
|
||||
// set, see the relationship finding module in
|
||||
// compiler/rustc_trait_selection/src/traits/relationships.rs.
|
||||
debug!("fallback to () - found trait and projection: {:?}", diverging_vid);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
|
||||
} else if can_reach_non_diverging {
|
||||
debug!("fallback to () - reached non-diverging: {:?}", diverging_vid);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
|
||||
} else {
|
||||
debug!("fallback to ! - all diverging: {:?}", diverging_vid);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.mk_diverging_default());
|
||||
}
|
||||
}
|
||||
|
||||
diverging_fallback
|
||||
}
|
||||
|
||||
/// Returns a graph whose nodes are (unresolved) inference variables and where
|
||||
/// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
|
||||
fn create_coercion_graph(&self) -> VecGraph<ty::TyVid> {
|
||||
let pending_obligations = self.fulfillment_cx.borrow_mut().pending_obligations();
|
||||
debug!("create_coercion_graph: pending_obligations={:?}", pending_obligations);
|
||||
let coercion_edges: Vec<(ty::TyVid, ty::TyVid)> = pending_obligations
|
||||
.into_iter()
|
||||
.filter_map(|obligation| {
|
||||
// The predicates we are looking for look like `Coerce(?A -> ?B)`.
|
||||
// They will have no bound variables.
|
||||
obligation.predicate.kind().no_bound_vars()
|
||||
})
|
||||
.filter_map(|atom| {
|
||||
// We consider both subtyping and coercion to imply 'flow' from
|
||||
// some position in the code `a` to a different position `b`.
|
||||
// This is then used to determine which variables interact with
|
||||
// live code, and as such must fall back to `()` to preserve
|
||||
// soundness.
|
||||
//
|
||||
// In practice currently the two ways that this happens is
|
||||
// coercion and subtyping.
|
||||
let (a, b) = if let ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) = atom {
|
||||
(a, b)
|
||||
} else if let ty::PredicateKind::Subtype(ty::SubtypePredicate {
|
||||
a_is_expected: _,
|
||||
a,
|
||||
b,
|
||||
}) = atom
|
||||
{
|
||||
(a, b)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let a_vid = self.root_vid(a)?;
|
||||
let b_vid = self.root_vid(b)?;
|
||||
Some((a_vid, b_vid))
|
||||
})
|
||||
.collect();
|
||||
debug!("create_coercion_graph: coercion_edges={:?}", coercion_edges);
|
||||
let num_ty_vars = self.num_ty_vars();
|
||||
VecGraph::new(num_ty_vars, coercion_edges)
|
||||
}
|
||||
|
||||
/// If `ty` is an unresolved type variable, returns its root vid.
|
||||
fn root_vid(&self, ty: Ty<'tcx>) -> Option<ty::TyVid> {
|
||||
Some(self.root_var(self.shallow_resolve(ty).ty_vid()?))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,383 +0,0 @@
|
|||
use std::cmp;
|
||||
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_middle::ty::error::TypeError;
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
pub(crate) struct ExpectedIdx {
|
||||
DEBUG_FORMAT = "ExpectedIdx({})",
|
||||
}
|
||||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
pub(crate) struct ProvidedIdx {
|
||||
DEBUG_FORMAT = "ProvidedIdx({})",
|
||||
}
|
||||
}
|
||||
|
||||
impl ExpectedIdx {
|
||||
pub fn to_provided_idx(self) -> ProvidedIdx {
|
||||
ProvidedIdx::from_usize(self.as_usize())
|
||||
}
|
||||
}
|
||||
|
||||
// An issue that might be found in the compatibility matrix
|
||||
#[derive(Debug)]
|
||||
enum Issue {
|
||||
/// The given argument is the invalid type for the input
|
||||
Invalid(usize),
|
||||
/// There is a missing input
|
||||
Missing(usize),
|
||||
/// There's a superfluous argument
|
||||
Extra(usize),
|
||||
/// Two arguments should be swapped
|
||||
Swap(usize, usize),
|
||||
/// Several arguments should be reordered
|
||||
Permutation(Vec<Option<usize>>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Compatibility<'tcx> {
|
||||
Compatible,
|
||||
Incompatible(Option<TypeError<'tcx>>),
|
||||
}
|
||||
|
||||
/// Similar to `Issue`, but contains some extra information
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Error<'tcx> {
|
||||
/// The provided argument is the invalid type for the expected input
|
||||
Invalid(ProvidedIdx, ExpectedIdx, Compatibility<'tcx>),
|
||||
/// There is a missing input
|
||||
Missing(ExpectedIdx),
|
||||
/// There's a superfluous argument
|
||||
Extra(ProvidedIdx),
|
||||
/// Two arguments should be swapped
|
||||
Swap(ProvidedIdx, ProvidedIdx, ExpectedIdx, ExpectedIdx),
|
||||
/// Several arguments should be reordered
|
||||
Permutation(Vec<(ExpectedIdx, ProvidedIdx)>),
|
||||
}
|
||||
|
||||
pub(crate) struct ArgMatrix<'tcx> {
|
||||
/// Maps the indices in the `compatibility_matrix` rows to the indices of
|
||||
/// the *user provided* inputs
|
||||
provided_indices: Vec<ProvidedIdx>,
|
||||
/// Maps the indices in the `compatibility_matrix` columns to the indices
|
||||
/// of the *expected* args
|
||||
expected_indices: Vec<ExpectedIdx>,
|
||||
/// The first dimension (rows) are the remaining user provided inputs to
|
||||
/// match and the second dimension (cols) are the remaining expected args
|
||||
/// to match
|
||||
compatibility_matrix: Vec<Vec<Compatibility<'tcx>>>,
|
||||
}
|
||||
|
||||
impl<'tcx> ArgMatrix<'tcx> {
|
||||
pub(crate) fn new<F: FnMut(ProvidedIdx, ExpectedIdx) -> Compatibility<'tcx>>(
|
||||
provided_count: usize,
|
||||
expected_input_count: usize,
|
||||
mut is_compatible: F,
|
||||
) -> Self {
|
||||
let compatibility_matrix = (0..provided_count)
|
||||
.map(|i| {
|
||||
(0..expected_input_count)
|
||||
.map(|j| is_compatible(ProvidedIdx::from_usize(i), ExpectedIdx::from_usize(j)))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
ArgMatrix {
|
||||
provided_indices: (0..provided_count).map(ProvidedIdx::from_usize).collect(),
|
||||
expected_indices: (0..expected_input_count).map(ExpectedIdx::from_usize).collect(),
|
||||
compatibility_matrix,
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a given input from consideration
|
||||
fn eliminate_provided(&mut self, idx: usize) {
|
||||
self.provided_indices.remove(idx);
|
||||
self.compatibility_matrix.remove(idx);
|
||||
}
|
||||
|
||||
/// Remove a given argument from consideration
|
||||
fn eliminate_expected(&mut self, idx: usize) {
|
||||
self.expected_indices.remove(idx);
|
||||
for row in &mut self.compatibility_matrix {
|
||||
row.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
/// "satisfy" an input with a given arg, removing both from consideration
|
||||
fn satisfy_input(&mut self, provided_idx: usize, expected_idx: usize) {
|
||||
self.eliminate_provided(provided_idx);
|
||||
self.eliminate_expected(expected_idx);
|
||||
}
|
||||
|
||||
// Returns a `Vec` of (user input, expected arg) of matched arguments. These
|
||||
// are inputs on the remaining diagonal that match.
|
||||
fn eliminate_satisfied(&mut self) -> Vec<(ProvidedIdx, ExpectedIdx)> {
|
||||
let num_args = cmp::min(self.provided_indices.len(), self.expected_indices.len());
|
||||
let mut eliminated = vec![];
|
||||
for i in (0..num_args).rev() {
|
||||
if matches!(self.compatibility_matrix[i][i], Compatibility::Compatible) {
|
||||
eliminated.push((self.provided_indices[i], self.expected_indices[i]));
|
||||
self.satisfy_input(i, i);
|
||||
}
|
||||
}
|
||||
eliminated
|
||||
}
|
||||
|
||||
// Find some issue in the compatibility matrix
|
||||
fn find_issue(&self) -> Option<Issue> {
|
||||
let mat = &self.compatibility_matrix;
|
||||
let ai = &self.expected_indices;
|
||||
let ii = &self.provided_indices;
|
||||
|
||||
// Issue: 100478, when we end the iteration,
|
||||
// `next_unmatched_idx` will point to the index of the first unmatched
|
||||
let mut next_unmatched_idx = 0;
|
||||
for i in 0..cmp::max(ai.len(), ii.len()) {
|
||||
// If we eliminate the last row, any left-over arguments are considered missing
|
||||
if i >= mat.len() {
|
||||
return Some(Issue::Missing(next_unmatched_idx));
|
||||
}
|
||||
// If we eliminate the last column, any left-over inputs are extra
|
||||
if mat[i].len() == 0 {
|
||||
return Some(Issue::Extra(next_unmatched_idx));
|
||||
}
|
||||
|
||||
// Make sure we don't pass the bounds of our matrix
|
||||
let is_arg = i < ai.len();
|
||||
let is_input = i < ii.len();
|
||||
if is_arg && is_input && matches!(mat[i][i], Compatibility::Compatible) {
|
||||
// This is a satisfied input, so move along
|
||||
next_unmatched_idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut useless = true;
|
||||
let mut unsatisfiable = true;
|
||||
if is_arg {
|
||||
for j in 0..ii.len() {
|
||||
// If we find at least one input this argument could satisfy
|
||||
// this argument isn't unsatisfiable
|
||||
if matches!(mat[j][i], Compatibility::Compatible) {
|
||||
unsatisfiable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_input {
|
||||
for j in 0..ai.len() {
|
||||
// If we find at least one argument that could satisfy this input
|
||||
// this input isn't useless
|
||||
if matches!(mat[i][j], Compatibility::Compatible) {
|
||||
useless = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (is_input, is_arg, useless, unsatisfiable) {
|
||||
// If an argument is unsatisfied, and the input in its position is useless
|
||||
// then the most likely explanation is that we just got the types wrong
|
||||
(true, true, true, true) => return Some(Issue::Invalid(i)),
|
||||
// Otherwise, if an input is useless, then indicate that this is an extra argument
|
||||
(true, _, true, _) => return Some(Issue::Extra(i)),
|
||||
// Otherwise, if an argument is unsatisfiable, indicate that it's missing
|
||||
(_, true, _, true) => return Some(Issue::Missing(i)),
|
||||
(true, true, _, _) => {
|
||||
// The argument isn't useless, and the input isn't unsatisfied,
|
||||
// so look for a parameter we might swap it with
|
||||
// We look for swaps explicitly, instead of just falling back on permutations
|
||||
// so that cases like (A,B,C,D) given (B,A,D,C) show up as two swaps,
|
||||
// instead of a large permutation of 4 elements.
|
||||
for j in 0..cmp::min(ai.len(), ii.len()) {
|
||||
if i == j || matches!(mat[j][j], Compatibility::Compatible) {
|
||||
continue;
|
||||
}
|
||||
if matches!(mat[i][j], Compatibility::Compatible)
|
||||
&& matches!(mat[j][i], Compatibility::Compatible)
|
||||
{
|
||||
return Some(Issue::Swap(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any of the individual issues above, but
|
||||
// there might be a larger permutation of parameters, so we now check for that
|
||||
// by checking for cycles
|
||||
// We use a double option at position i in this vec to represent:
|
||||
// - None: We haven't computed anything about this argument yet
|
||||
// - Some(None): This argument definitely doesn't participate in a cycle
|
||||
// - Some(Some(x)): the i-th argument could permute to the x-th position
|
||||
let mut permutation: Vec<Option<Option<usize>>> = vec![None; mat.len()];
|
||||
let mut permutation_found = false;
|
||||
for i in 0..mat.len() {
|
||||
if permutation[i].is_some() {
|
||||
// We've already decided whether this argument is or is not in a loop
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut stack = vec![];
|
||||
let mut j = i;
|
||||
let mut last = i;
|
||||
let mut is_cycle = true;
|
||||
loop {
|
||||
stack.push(j);
|
||||
// Look for params this one could slot into
|
||||
let compat: Vec<_> =
|
||||
mat[j]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, c)| {
|
||||
if matches!(c, Compatibility::Compatible) { Some(i) } else { None }
|
||||
})
|
||||
.collect();
|
||||
if compat.len() < 1 {
|
||||
// try to find a cycle even when this could go into multiple slots, see #101097
|
||||
is_cycle = false;
|
||||
break;
|
||||
}
|
||||
j = compat[0];
|
||||
if stack.contains(&j) {
|
||||
last = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if stack.len() <= 2 {
|
||||
// If we encounter a cycle of 1 or 2 elements, we'll let the
|
||||
// "satisfy" and "swap" code above handle those
|
||||
is_cycle = false;
|
||||
}
|
||||
// We've built up some chain, some of which might be a cycle
|
||||
// ex: [1,2,3,4]; last = 2; j = 2;
|
||||
// So, we want to mark 4, 3, and 2 as part of a permutation
|
||||
permutation_found = is_cycle;
|
||||
while let Some(x) = stack.pop() {
|
||||
if is_cycle {
|
||||
permutation[x] = Some(Some(j));
|
||||
j = x;
|
||||
if j == last {
|
||||
// From here on out, we're a tail leading into a cycle,
|
||||
// not the cycle itself
|
||||
is_cycle = false;
|
||||
}
|
||||
} else {
|
||||
// Some(None) ensures we save time by skipping this argument again
|
||||
permutation[x] = Some(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if permutation_found {
|
||||
// Map unwrap to remove the first layer of Some
|
||||
let final_permutation: Vec<Option<usize>> =
|
||||
permutation.into_iter().map(|x| x.unwrap()).collect();
|
||||
return Some(Issue::Permutation(final_permutation));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// Obviously, detecting exact user intention is impossible, so the goal here is to
|
||||
// come up with as likely of a story as we can to be helpful.
|
||||
//
|
||||
// We'll iteratively removed "satisfied" input/argument pairs,
|
||||
// then check for the cases above, until we've eliminated the entire grid
|
||||
//
|
||||
// We'll want to know which arguments and inputs these rows and columns correspond to
|
||||
// even after we delete them.
|
||||
pub(crate) fn find_errors(
|
||||
mut self,
|
||||
) -> (Vec<Error<'tcx>>, IndexVec<ExpectedIdx, Option<ProvidedIdx>>) {
|
||||
let provided_arg_count = self.provided_indices.len();
|
||||
|
||||
let mut errors: Vec<Error<'tcx>> = vec![];
|
||||
// For each expected argument, the matched *actual* input
|
||||
let mut matched_inputs: IndexVec<ExpectedIdx, Option<ProvidedIdx>> =
|
||||
IndexVec::from_elem_n(None, self.expected_indices.len());
|
||||
|
||||
// Before we start looking for issues, eliminate any arguments that are already satisfied,
|
||||
// so that an argument which is already spoken for by the input it's in doesn't
|
||||
// spill over into another similarly typed input
|
||||
// ex:
|
||||
// fn some_func(_a: i32, _b: i32) {}
|
||||
// some_func(1, "");
|
||||
// Without this elimination, the first argument causes the second argument
|
||||
// to show up as both a missing input and extra argument, rather than
|
||||
// just an invalid type.
|
||||
for (provided, expected) in self.eliminate_satisfied() {
|
||||
matched_inputs[expected] = Some(provided);
|
||||
}
|
||||
|
||||
while !self.provided_indices.is_empty() || !self.expected_indices.is_empty() {
|
||||
let res = self.find_issue();
|
||||
match res {
|
||||
Some(Issue::Invalid(idx)) => {
|
||||
let compatibility = self.compatibility_matrix[idx][idx].clone();
|
||||
let input_idx = self.provided_indices[idx];
|
||||
let arg_idx = self.expected_indices[idx];
|
||||
self.satisfy_input(idx, idx);
|
||||
errors.push(Error::Invalid(input_idx, arg_idx, compatibility));
|
||||
}
|
||||
Some(Issue::Extra(idx)) => {
|
||||
let input_idx = self.provided_indices[idx];
|
||||
self.eliminate_provided(idx);
|
||||
errors.push(Error::Extra(input_idx));
|
||||
}
|
||||
Some(Issue::Missing(idx)) => {
|
||||
let arg_idx = self.expected_indices[idx];
|
||||
self.eliminate_expected(idx);
|
||||
errors.push(Error::Missing(arg_idx));
|
||||
}
|
||||
Some(Issue::Swap(idx, other)) => {
|
||||
let input_idx = self.provided_indices[idx];
|
||||
let other_input_idx = self.provided_indices[other];
|
||||
let arg_idx = self.expected_indices[idx];
|
||||
let other_arg_idx = self.expected_indices[other];
|
||||
let (min, max) = (cmp::min(idx, other), cmp::max(idx, other));
|
||||
self.satisfy_input(min, max);
|
||||
// Subtract 1 because we already removed the "min" row
|
||||
self.satisfy_input(max - 1, min);
|
||||
errors.push(Error::Swap(input_idx, other_input_idx, arg_idx, other_arg_idx));
|
||||
matched_inputs[other_arg_idx] = Some(input_idx);
|
||||
matched_inputs[arg_idx] = Some(other_input_idx);
|
||||
}
|
||||
Some(Issue::Permutation(args)) => {
|
||||
let mut idxs: Vec<usize> = args.iter().filter_map(|&a| a).collect();
|
||||
|
||||
let mut real_idxs: IndexVec<ProvidedIdx, Option<(ExpectedIdx, ProvidedIdx)>> =
|
||||
IndexVec::from_elem_n(None, provided_arg_count);
|
||||
for (src, dst) in
|
||||
args.iter().enumerate().filter_map(|(src, dst)| dst.map(|dst| (src, dst)))
|
||||
{
|
||||
let src_input_idx = self.provided_indices[src];
|
||||
let dst_input_idx = self.provided_indices[dst];
|
||||
let dest_arg_idx = self.expected_indices[dst];
|
||||
real_idxs[src_input_idx] = Some((dest_arg_idx, dst_input_idx));
|
||||
matched_inputs[dest_arg_idx] = Some(src_input_idx);
|
||||
}
|
||||
idxs.sort();
|
||||
idxs.reverse();
|
||||
for i in idxs {
|
||||
self.satisfy_input(i, i);
|
||||
}
|
||||
errors.push(Error::Permutation(real_idxs.into_iter().flatten().collect()));
|
||||
}
|
||||
None => {
|
||||
// We didn't find any issues, so we need to push the algorithm forward
|
||||
// First, eliminate any arguments that currently satisfy their inputs
|
||||
let eliminated = self.eliminate_satisfied();
|
||||
assert!(!eliminated.is_empty(), "didn't eliminated any indice in this round");
|
||||
for (inp, arg) in eliminated {
|
||||
matched_inputs[arg] = Some(inp);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (errors, matched_inputs);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,313 +0,0 @@
|
|||
mod _impl;
|
||||
mod arg_matrix;
|
||||
mod checks;
|
||||
mod suggestions;
|
||||
|
||||
pub use _impl::*;
|
||||
pub use suggestions::*;
|
||||
|
||||
use crate::astconv::AstConv;
|
||||
use crate::check::coercion::DynamicCoerceMany;
|
||||
use crate::check::{Diverges, EnclosingBreakables, Inherited, UnsafetyState};
|
||||
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_infer::infer;
|
||||
use rustc_infer::infer::error_reporting::TypeErrCtxt;
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
|
||||
use rustc_middle::ty::subst::GenericArgKind;
|
||||
use rustc_middle::ty::visit::TypeVisitable;
|
||||
use rustc_middle::ty::{self, Const, Ty, TyCtxt};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{self, Span};
|
||||
use rustc_trait_selection::traits::{ObligationCause, ObligationCauseCode};
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// The `FnCtxt` stores type-checking context needed to type-check bodies of
|
||||
/// functions, closures, and `const`s, including performing type inference
|
||||
/// with [`InferCtxt`].
|
||||
///
|
||||
/// This is in contrast to [`ItemCtxt`], which is used to type-check item *signatures*
|
||||
/// and thus does not perform type inference.
|
||||
///
|
||||
/// See [`ItemCtxt`]'s docs for more.
|
||||
///
|
||||
/// [`ItemCtxt`]: crate::collect::ItemCtxt
|
||||
/// [`InferCtxt`]: infer::InferCtxt
|
||||
pub struct FnCtxt<'a, 'tcx> {
|
||||
pub(super) body_id: hir::HirId,
|
||||
|
||||
/// The parameter environment used for proving trait obligations
|
||||
/// in this function. This can change when we descend into
|
||||
/// closures (as they bring new things into scope), hence it is
|
||||
/// not part of `Inherited` (as of the time of this writing,
|
||||
/// closures do not yet change the environment, but they will
|
||||
/// eventually).
|
||||
pub(super) param_env: ty::ParamEnv<'tcx>,
|
||||
|
||||
/// Number of errors that had been reported when we started
|
||||
/// checking this function. On exit, if we find that *more* errors
|
||||
/// have been reported, we will skip regionck and other work that
|
||||
/// expects the types within the function to be consistent.
|
||||
// FIXME(matthewjasper) This should not exist, and it's not correct
|
||||
// if type checking is run in parallel.
|
||||
err_count_on_creation: usize,
|
||||
|
||||
/// If `Some`, this stores coercion information for returned
|
||||
/// expressions. If `None`, this is in a context where return is
|
||||
/// inappropriate, such as a const expression.
|
||||
///
|
||||
/// This is a `RefCell<DynamicCoerceMany>`, which means that we
|
||||
/// can track all the return expressions and then use them to
|
||||
/// compute a useful coercion from the set, similar to a match
|
||||
/// expression or other branching context. You can use methods
|
||||
/// like `expected_ty` to access the declared return type (if
|
||||
/// any).
|
||||
pub(super) ret_coercion: Option<RefCell<DynamicCoerceMany<'tcx>>>,
|
||||
|
||||
/// Used exclusively to reduce cost of advanced evaluation used for
|
||||
/// more helpful diagnostics.
|
||||
pub(super) in_tail_expr: bool,
|
||||
|
||||
/// First span of a return site that we find. Used in error messages.
|
||||
pub(super) ret_coercion_span: Cell<Option<Span>>,
|
||||
|
||||
pub(super) resume_yield_tys: Option<(Ty<'tcx>, Ty<'tcx>)>,
|
||||
|
||||
pub(super) ps: Cell<UnsafetyState>,
|
||||
|
||||
/// Whether the last checked node generates a divergence (e.g.,
|
||||
/// `return` will set this to `Always`). In general, when entering
|
||||
/// an expression or other node in the tree, the initial value
|
||||
/// indicates whether prior parts of the containing expression may
|
||||
/// have diverged. It is then typically set to `Maybe` (and the
|
||||
/// old value remembered) for processing the subparts of the
|
||||
/// current expression. As each subpart is processed, they may set
|
||||
/// the flag to `Always`, etc. Finally, at the end, we take the
|
||||
/// result and "union" it with the original value, so that when we
|
||||
/// return the flag indicates if any subpart of the parent
|
||||
/// expression (up to and including this part) has diverged. So,
|
||||
/// if you read it after evaluating a subexpression `X`, the value
|
||||
/// you get indicates whether any subexpression that was
|
||||
/// evaluating up to and including `X` diverged.
|
||||
///
|
||||
/// We currently use this flag only for diagnostic purposes:
|
||||
///
|
||||
/// - To warn about unreachable code: if, after processing a
|
||||
/// sub-expression but before we have applied the effects of the
|
||||
/// current node, we see that the flag is set to `Always`, we
|
||||
/// can issue a warning. This corresponds to something like
|
||||
/// `foo(return)`; we warn on the `foo()` expression. (We then
|
||||
/// update the flag to `WarnedAlways` to suppress duplicate
|
||||
/// reports.) Similarly, if we traverse to a fresh statement (or
|
||||
/// tail expression) from an `Always` setting, we will issue a
|
||||
/// warning. This corresponds to something like `{return;
|
||||
/// foo();}` or `{return; 22}`, where we would warn on the
|
||||
/// `foo()` or `22`.
|
||||
///
|
||||
/// An expression represents dead code if, after checking it,
|
||||
/// the diverges flag is set to something other than `Maybe`.
|
||||
pub(super) diverges: Cell<Diverges>,
|
||||
|
||||
/// Whether any child nodes have any type errors.
|
||||
pub(super) has_errors: Cell<bool>,
|
||||
|
||||
pub(super) enclosing_breakables: RefCell<EnclosingBreakables<'tcx>>,
|
||||
|
||||
pub(super) inh: &'a Inherited<'tcx>,
|
||||
|
||||
/// True if the function or closure's return type is known before
|
||||
/// entering the function/closure, i.e. if the return type is
|
||||
/// either given explicitly or inferred from, say, an `Fn*` trait
|
||||
/// bound. Used for diagnostic purposes only.
|
||||
pub(super) return_type_pre_known: bool,
|
||||
|
||||
/// True if the return type has an Opaque type
|
||||
pub(super) return_type_has_opaque: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
pub fn new(
|
||||
inh: &'a Inherited<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
body_id: hir::HirId,
|
||||
) -> FnCtxt<'a, 'tcx> {
|
||||
FnCtxt {
|
||||
body_id,
|
||||
param_env,
|
||||
err_count_on_creation: inh.tcx.sess.err_count(),
|
||||
ret_coercion: None,
|
||||
in_tail_expr: false,
|
||||
ret_coercion_span: Cell::new(None),
|
||||
resume_yield_tys: None,
|
||||
ps: Cell::new(UnsafetyState::function(hir::Unsafety::Normal, hir::CRATE_HIR_ID)),
|
||||
diverges: Cell::new(Diverges::Maybe),
|
||||
has_errors: Cell::new(false),
|
||||
enclosing_breakables: RefCell::new(EnclosingBreakables {
|
||||
stack: Vec::new(),
|
||||
by_id: Default::default(),
|
||||
}),
|
||||
inh,
|
||||
return_type_pre_known: true,
|
||||
return_type_has_opaque: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cause(&self, span: Span, code: ObligationCauseCode<'tcx>) -> ObligationCause<'tcx> {
|
||||
ObligationCause::new(span, self.body_id, code)
|
||||
}
|
||||
|
||||
pub fn misc(&self, span: Span) -> ObligationCause<'tcx> {
|
||||
self.cause(span, ObligationCauseCode::MiscObligation)
|
||||
}
|
||||
|
||||
pub fn sess(&self) -> &Session {
|
||||
&self.tcx.sess
|
||||
}
|
||||
|
||||
/// Creates an `TypeErrCtxt` with a reference to the in-progress
|
||||
/// `TypeckResults` which is used for diagnostics.
|
||||
/// Use [`InferCtxt::err_ctxt`] to start one without a `TypeckResults`.
|
||||
///
|
||||
/// [`InferCtxt::err_ctxt`]: infer::InferCtxt::err_ctxt
|
||||
pub fn err_ctxt(&'a self) -> TypeErrCtxt<'a, 'tcx> {
|
||||
TypeErrCtxt { infcx: &self.infcx, typeck_results: Some(self.typeck_results.borrow()) }
|
||||
}
|
||||
|
||||
pub fn errors_reported_since_creation(&self) -> bool {
|
||||
self.tcx.sess.err_count() > self.err_count_on_creation
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Deref for FnCtxt<'a, 'tcx> {
|
||||
type Target = Inherited<'tcx>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inh
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> AstConv<'tcx> for FnCtxt<'a, 'tcx> {
|
||||
fn tcx<'b>(&'b self) -> TyCtxt<'tcx> {
|
||||
self.tcx
|
||||
}
|
||||
|
||||
fn item_def_id(&self) -> Option<DefId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_type_parameter_bounds(
|
||||
&self,
|
||||
_: Span,
|
||||
def_id: DefId,
|
||||
_: Ident,
|
||||
) -> ty::GenericPredicates<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
let item_def_id = tcx.hir().ty_param_owner(def_id.expect_local());
|
||||
let generics = tcx.generics_of(item_def_id);
|
||||
let index = generics.param_def_id_to_index[&def_id];
|
||||
ty::GenericPredicates {
|
||||
parent: None,
|
||||
predicates: tcx.arena.alloc_from_iter(
|
||||
self.param_env.caller_bounds().iter().filter_map(|predicate| {
|
||||
match predicate.kind().skip_binder() {
|
||||
ty::PredicateKind::Trait(data) if data.self_ty().is_param(index) => {
|
||||
// HACK(eddyb) should get the original `Span`.
|
||||
let span = tcx.def_span(def_id);
|
||||
Some((predicate, span))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn re_infer(&self, def: Option<&ty::GenericParamDef>, span: Span) -> Option<ty::Region<'tcx>> {
|
||||
let v = match def {
|
||||
Some(def) => infer::EarlyBoundRegion(span, def.name),
|
||||
None => infer::MiscVariable(span),
|
||||
};
|
||||
Some(self.next_region_var(v))
|
||||
}
|
||||
|
||||
fn allow_ty_infer(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn ty_infer(&self, param: Option<&ty::GenericParamDef>, span: Span) -> Ty<'tcx> {
|
||||
if let Some(param) = param {
|
||||
if let GenericArgKind::Type(ty) = self.var_for_def(span, param).unpack() {
|
||||
return ty;
|
||||
}
|
||||
unreachable!()
|
||||
} else {
|
||||
self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::TypeInference,
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn ct_infer(
|
||||
&self,
|
||||
ty: Ty<'tcx>,
|
||||
param: Option<&ty::GenericParamDef>,
|
||||
span: Span,
|
||||
) -> Const<'tcx> {
|
||||
if let Some(param) = param {
|
||||
if let GenericArgKind::Const(ct) = self.var_for_def(span, param).unpack() {
|
||||
return ct;
|
||||
}
|
||||
unreachable!()
|
||||
} else {
|
||||
self.next_const_var(
|
||||
ty,
|
||||
ConstVariableOrigin { kind: ConstVariableOriginKind::ConstInference, span },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn projected_ty_from_poly_trait_ref(
|
||||
&self,
|
||||
span: Span,
|
||||
item_def_id: DefId,
|
||||
item_segment: &hir::PathSegment<'_>,
|
||||
poly_trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let trait_ref = self.replace_bound_vars_with_fresh_vars(
|
||||
span,
|
||||
infer::LateBoundRegionConversionTime::AssocTypeProjection(item_def_id),
|
||||
poly_trait_ref,
|
||||
);
|
||||
|
||||
let item_substs = <dyn AstConv<'tcx>>::create_substs_for_associated_item(
|
||||
self,
|
||||
span,
|
||||
item_def_id,
|
||||
item_segment,
|
||||
trait_ref.substs,
|
||||
);
|
||||
|
||||
self.tcx().mk_projection(item_def_id, item_substs)
|
||||
}
|
||||
|
||||
fn normalize_ty(&self, span: Span, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
if ty.has_escaping_bound_vars() {
|
||||
ty // FIXME: normalization and escaping regions
|
||||
} else {
|
||||
self.normalize_associated_types_in(span, ty)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_tainted_by_errors(&self) {
|
||||
self.infcx.set_tainted_by_errors()
|
||||
}
|
||||
|
||||
fn record_ty(&self, hir_id: hir::HirId, ty: Ty<'tcx>, _span: Span) {
|
||||
self.write_ty(hir_id, ty)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,160 +0,0 @@
|
|||
use crate::check::{FnCtxt, LocalTy, UserType};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::PatKind;
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::traits;
|
||||
|
||||
/// A declaration is an abstraction of [hir::Local] and [hir::Let].
|
||||
///
|
||||
/// It must have a hir_id, as this is how we connect gather_locals to the check functions.
|
||||
pub(super) struct Declaration<'a> {
|
||||
pub hir_id: hir::HirId,
|
||||
pub pat: &'a hir::Pat<'a>,
|
||||
pub ty: Option<&'a hir::Ty<'a>>,
|
||||
pub span: Span,
|
||||
pub init: Option<&'a hir::Expr<'a>>,
|
||||
pub els: Option<&'a hir::Block<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a hir::Local<'a>> for Declaration<'a> {
|
||||
fn from(local: &'a hir::Local<'a>) -> Self {
|
||||
let hir::Local { hir_id, pat, ty, span, init, els, source: _ } = *local;
|
||||
Declaration { hir_id, pat, ty, span, init, els }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a hir::Let<'a>> for Declaration<'a> {
|
||||
fn from(let_expr: &'a hir::Let<'a>) -> Self {
|
||||
let hir::Let { hir_id, pat, ty, span, init } = *let_expr;
|
||||
Declaration { hir_id, pat, ty, span, init: Some(init), els: None }
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct GatherLocalsVisitor<'a, 'tcx> {
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
// parameters are special cases of patterns, but we want to handle them as
|
||||
// *distinct* cases. so track when we are hitting a pattern *within* an fn
|
||||
// parameter.
|
||||
outermost_fn_param_pat: Option<Span>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> {
|
||||
pub(super) fn new(fcx: &'a FnCtxt<'a, 'tcx>) -> Self {
|
||||
Self { fcx, outermost_fn_param_pat: None }
|
||||
}
|
||||
|
||||
fn assign(&mut self, span: Span, nid: hir::HirId, ty_opt: Option<LocalTy<'tcx>>) -> Ty<'tcx> {
|
||||
match ty_opt {
|
||||
None => {
|
||||
// Infer the variable's type.
|
||||
let var_ty = self.fcx.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::TypeInference,
|
||||
span,
|
||||
});
|
||||
self.fcx
|
||||
.locals
|
||||
.borrow_mut()
|
||||
.insert(nid, LocalTy { decl_ty: var_ty, revealed_ty: var_ty });
|
||||
var_ty
|
||||
}
|
||||
Some(typ) => {
|
||||
// Take type that the user specified.
|
||||
self.fcx.locals.borrow_mut().insert(nid, typ);
|
||||
typ.revealed_ty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a [LocalTy] for a declaration, which may have a type annotation. If it does have
|
||||
/// a type annotation, then the LocalTy stored will be the resolved type. This may be found
|
||||
/// again during type checking by querying [FnCtxt::local_ty] for the same hir_id.
|
||||
fn declare(&mut self, decl: Declaration<'tcx>) {
|
||||
let local_ty = match decl.ty {
|
||||
Some(ref ty) => {
|
||||
let o_ty = self.fcx.to_ty(&ty);
|
||||
|
||||
let c_ty = self.fcx.inh.infcx.canonicalize_user_type_annotation(UserType::Ty(o_ty));
|
||||
debug!("visit_local: ty.hir_id={:?} o_ty={:?} c_ty={:?}", ty.hir_id, o_ty, c_ty);
|
||||
self.fcx
|
||||
.typeck_results
|
||||
.borrow_mut()
|
||||
.user_provided_types_mut()
|
||||
.insert(ty.hir_id, c_ty);
|
||||
|
||||
Some(LocalTy { decl_ty: o_ty, revealed_ty: o_ty })
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
self.assign(decl.span, decl.hir_id, local_ty);
|
||||
|
||||
debug!(
|
||||
"local variable {:?} is assigned type {}",
|
||||
decl.pat,
|
||||
self.fcx.ty_to_string(self.fcx.locals.borrow().get(&decl.hir_id).unwrap().decl_ty)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> {
|
||||
// Add explicitly-declared locals.
|
||||
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
|
||||
self.declare(local.into());
|
||||
intravisit::walk_local(self, local)
|
||||
}
|
||||
|
||||
fn visit_let_expr(&mut self, let_expr: &'tcx hir::Let<'tcx>) {
|
||||
self.declare(let_expr.into());
|
||||
intravisit::walk_let_expr(self, let_expr);
|
||||
}
|
||||
|
||||
fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
|
||||
let old_outermost_fn_param_pat = self.outermost_fn_param_pat.replace(param.ty_span);
|
||||
intravisit::walk_param(self, param);
|
||||
self.outermost_fn_param_pat = old_outermost_fn_param_pat;
|
||||
}
|
||||
|
||||
// Add pattern bindings.
|
||||
fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
|
||||
if let PatKind::Binding(_, _, ident, _) = p.kind {
|
||||
let var_ty = self.assign(p.span, p.hir_id, None);
|
||||
|
||||
if let Some(ty_span) = self.outermost_fn_param_pat {
|
||||
if !self.fcx.tcx.features().unsized_fn_params {
|
||||
self.fcx.require_type_is_sized(
|
||||
var_ty,
|
||||
p.span,
|
||||
traits::SizedArgumentType(Some(ty_span)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if !self.fcx.tcx.features().unsized_locals {
|
||||
self.fcx.require_type_is_sized(var_ty, p.span, traits::VariableType(p.hir_id));
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"pattern binding {} is assigned to {} with type {:?}",
|
||||
ident,
|
||||
self.fcx.ty_to_string(self.fcx.locals.borrow().get(&p.hir_id).unwrap().decl_ty),
|
||||
var_ty
|
||||
);
|
||||
}
|
||||
let old_outermost_fn_param_pat = self.outermost_fn_param_pat.take();
|
||||
intravisit::walk_pat(self, p);
|
||||
self.outermost_fn_param_pat = old_outermost_fn_param_pat;
|
||||
}
|
||||
|
||||
// Don't descend into the bodies of nested closures.
|
||||
fn visit_fn(
|
||||
&mut self,
|
||||
_: intravisit::FnKind<'tcx>,
|
||||
_: &'tcx hir::FnDecl<'tcx>,
|
||||
_: hir::BodyId,
|
||||
_: Span,
|
||||
_: hir::HirId,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -1,647 +0,0 @@
|
|||
//! This calculates the types which has storage which lives across a suspension point in a
|
||||
//! generator from the perspective of typeck. The actual types used at runtime
|
||||
//! is calculated in `rustc_mir_transform::generator` and may be a subset of the
|
||||
//! types computed here.
|
||||
|
||||
use self::drop_ranges::DropRanges;
|
||||
use super::FnCtxt;
|
||||
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
|
||||
use rustc_errors::{pluralize, DelayDm};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{CtorKind, DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::hir_id::HirIdSet;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind};
|
||||
use rustc_middle::middle::region::{self, Scope, ScopeData, YieldData};
|
||||
use rustc_middle::ty::{self, RvalueScopes, Ty, TyCtxt, TypeVisitable};
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
|
||||
mod drop_ranges;
|
||||
|
||||
struct InteriorVisitor<'a, 'tcx> {
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
region_scope_tree: &'a region::ScopeTree,
|
||||
types: FxIndexSet<ty::GeneratorInteriorTypeCause<'tcx>>,
|
||||
rvalue_scopes: &'a RvalueScopes,
|
||||
expr_count: usize,
|
||||
kind: hir::GeneratorKind,
|
||||
prev_unresolved_span: Option<Span>,
|
||||
linted_values: HirIdSet,
|
||||
drop_ranges: DropRanges,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
|
||||
fn record(
|
||||
&mut self,
|
||||
ty: Ty<'tcx>,
|
||||
hir_id: HirId,
|
||||
scope: Option<region::Scope>,
|
||||
expr: Option<&'tcx Expr<'tcx>>,
|
||||
source_span: Span,
|
||||
) {
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
let ty = self.fcx.resolve_vars_if_possible(ty);
|
||||
|
||||
debug!(
|
||||
"attempting to record type ty={:?}; hir_id={:?}; scope={:?}; expr={:?}; source_span={:?}; expr_count={:?}",
|
||||
ty, hir_id, scope, expr, source_span, self.expr_count,
|
||||
);
|
||||
|
||||
let live_across_yield = scope
|
||||
.map(|s| {
|
||||
self.region_scope_tree.yield_in_scope(s).and_then(|yield_data| {
|
||||
// If we are recording an expression that is the last yield
|
||||
// in the scope, or that has a postorder CFG index larger
|
||||
// than the one of all of the yields, then its value can't
|
||||
// be storage-live (and therefore live) at any of the yields.
|
||||
//
|
||||
// See the mega-comment at `yield_in_scope` for a proof.
|
||||
|
||||
yield_data
|
||||
.iter()
|
||||
.find(|yield_data| {
|
||||
debug!(
|
||||
"comparing counts yield: {} self: {}, source_span = {:?}",
|
||||
yield_data.expr_and_pat_count, self.expr_count, source_span
|
||||
);
|
||||
|
||||
if self.fcx.sess().opts.unstable_opts.drop_tracking
|
||||
&& self
|
||||
.drop_ranges
|
||||
.is_dropped_at(hir_id, yield_data.expr_and_pat_count)
|
||||
{
|
||||
debug!("value is dropped at yield point; not recording");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If it is a borrowing happening in the guard,
|
||||
// it needs to be recorded regardless because they
|
||||
// do live across this yield point.
|
||||
yield_data.expr_and_pat_count >= self.expr_count
|
||||
})
|
||||
.cloned()
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
Some(YieldData { span: DUMMY_SP, expr_and_pat_count: 0, source: self.kind.into() })
|
||||
});
|
||||
|
||||
if let Some(yield_data) = live_across_yield {
|
||||
debug!(
|
||||
"type in expr = {:?}, scope = {:?}, type = {:?}, count = {}, yield_span = {:?}",
|
||||
expr, scope, ty, self.expr_count, yield_data.span
|
||||
);
|
||||
|
||||
if let Some((unresolved_type, unresolved_type_span)) =
|
||||
self.fcx.unresolved_type_vars(&ty)
|
||||
{
|
||||
// If unresolved type isn't a ty_var then unresolved_type_span is None
|
||||
let span = self
|
||||
.prev_unresolved_span
|
||||
.unwrap_or_else(|| unresolved_type_span.unwrap_or(source_span));
|
||||
|
||||
// If we encounter an int/float variable, then inference fallback didn't
|
||||
// finish due to some other error. Don't emit spurious additional errors.
|
||||
if let ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(_)) =
|
||||
unresolved_type.kind()
|
||||
{
|
||||
self.fcx
|
||||
.tcx
|
||||
.sess
|
||||
.delay_span_bug(span, &format!("Encountered var {:?}", unresolved_type));
|
||||
} else {
|
||||
let note = format!(
|
||||
"the type is part of the {} because of this {}",
|
||||
self.kind, yield_data.source
|
||||
);
|
||||
|
||||
self.fcx
|
||||
.need_type_info_err_in_generator(self.kind, span, unresolved_type)
|
||||
.span_note(yield_data.span, &*note)
|
||||
.emit();
|
||||
}
|
||||
} else {
|
||||
// Insert the type into the ordered set.
|
||||
let scope_span = scope.map(|s| s.span(self.fcx.tcx, self.region_scope_tree));
|
||||
|
||||
if !self.linted_values.contains(&hir_id) {
|
||||
check_must_not_suspend_ty(
|
||||
self.fcx,
|
||||
ty,
|
||||
hir_id,
|
||||
SuspendCheckData {
|
||||
expr,
|
||||
source_span,
|
||||
yield_span: yield_data.span,
|
||||
plural_len: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
self.linted_values.insert(hir_id);
|
||||
}
|
||||
|
||||
self.types.insert(ty::GeneratorInteriorTypeCause {
|
||||
span: source_span,
|
||||
ty,
|
||||
scope_span,
|
||||
yield_span: yield_data.span,
|
||||
expr: expr.map(|e| e.hir_id),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"no type in expr = {:?}, count = {:?}, span = {:?}",
|
||||
expr,
|
||||
self.expr_count,
|
||||
expr.map(|e| e.span)
|
||||
);
|
||||
if let Some((unresolved_type, unresolved_type_span)) =
|
||||
self.fcx.unresolved_type_vars(&ty)
|
||||
{
|
||||
debug!(
|
||||
"remained unresolved_type = {:?}, unresolved_type_span: {:?}",
|
||||
unresolved_type, unresolved_type_span
|
||||
);
|
||||
self.prev_unresolved_span = unresolved_type_span;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_interior<'a, 'tcx>(
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
def_id: DefId,
|
||||
body_id: hir::BodyId,
|
||||
interior: Ty<'tcx>,
|
||||
kind: hir::GeneratorKind,
|
||||
) {
|
||||
let body = fcx.tcx.hir().body(body_id);
|
||||
let typeck_results = fcx.inh.typeck_results.borrow();
|
||||
let mut visitor = InteriorVisitor {
|
||||
fcx,
|
||||
types: FxIndexSet::default(),
|
||||
region_scope_tree: fcx.tcx.region_scope_tree(def_id),
|
||||
rvalue_scopes: &typeck_results.rvalue_scopes,
|
||||
expr_count: 0,
|
||||
kind,
|
||||
prev_unresolved_span: None,
|
||||
linted_values: <_>::default(),
|
||||
drop_ranges: drop_ranges::compute_drop_ranges(fcx, def_id, body),
|
||||
};
|
||||
intravisit::walk_body(&mut visitor, body);
|
||||
|
||||
// Check that we visited the same amount of expressions as the RegionResolutionVisitor
|
||||
let region_expr_count = fcx.tcx.region_scope_tree(def_id).body_expr_count(body_id).unwrap();
|
||||
assert_eq!(region_expr_count, visitor.expr_count);
|
||||
|
||||
// The types are already kept in insertion order.
|
||||
let types = visitor.types;
|
||||
|
||||
// The types in the generator interior contain lifetimes local to the generator itself,
|
||||
// which should not be exposed outside of the generator. Therefore, we replace these
|
||||
// lifetimes with existentially-bound lifetimes, which reflect the exact value of the
|
||||
// lifetimes not being known by users.
|
||||
//
|
||||
// These lifetimes are used in auto trait impl checking (for example,
|
||||
// if a Sync generator contains an &'α T, we need to check whether &'α T: Sync),
|
||||
// so knowledge of the exact relationships between them isn't particularly important.
|
||||
|
||||
debug!("types in generator {:?}, span = {:?}", types, body.value.span);
|
||||
|
||||
let mut counter = 0;
|
||||
let mut captured_tys = FxHashSet::default();
|
||||
let type_causes: Vec<_> = types
|
||||
.into_iter()
|
||||
.filter_map(|mut cause| {
|
||||
// Erase regions and canonicalize late-bound regions to deduplicate as many types as we
|
||||
// can.
|
||||
let ty = fcx.normalize_associated_types_in(cause.span, cause.ty);
|
||||
let erased = fcx.tcx.erase_regions(ty);
|
||||
if captured_tys.insert(erased) {
|
||||
// Replace all regions inside the generator interior with late bound regions.
|
||||
// Note that each region slot in the types gets a new fresh late bound region,
|
||||
// which means that none of the regions inside relate to any other, even if
|
||||
// typeck had previously found constraints that would cause them to be related.
|
||||
let folded = fcx.tcx.fold_regions(erased, |_, current_depth| {
|
||||
let br = ty::BoundRegion {
|
||||
var: ty::BoundVar::from_u32(counter),
|
||||
kind: ty::BrAnon(counter),
|
||||
};
|
||||
let r = fcx.tcx.mk_region(ty::ReLateBound(current_depth, br));
|
||||
counter += 1;
|
||||
r
|
||||
});
|
||||
|
||||
cause.ty = folded;
|
||||
Some(cause)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Extract type components to build the witness type.
|
||||
let type_list = fcx.tcx.mk_type_list(type_causes.iter().map(|cause| cause.ty));
|
||||
let bound_vars = fcx.tcx.mk_bound_variable_kinds(
|
||||
(0..counter).map(|i| ty::BoundVariableKind::Region(ty::BrAnon(i))),
|
||||
);
|
||||
let witness =
|
||||
fcx.tcx.mk_generator_witness(ty::Binder::bind_with_vars(type_list, bound_vars.clone()));
|
||||
|
||||
drop(typeck_results);
|
||||
// Store the generator types and spans into the typeck results for this generator.
|
||||
fcx.inh.typeck_results.borrow_mut().generator_interior_types =
|
||||
ty::Binder::bind_with_vars(type_causes, bound_vars);
|
||||
|
||||
debug!(
|
||||
"types in generator after region replacement {:?}, span = {:?}",
|
||||
witness, body.value.span
|
||||
);
|
||||
|
||||
// Unify the type variable inside the generator with the new witness
|
||||
match fcx.at(&fcx.misc(body.value.span), fcx.param_env).eq(interior, witness) {
|
||||
Ok(ok) => fcx.register_infer_ok_obligations(ok),
|
||||
_ => bug!("failed to relate {interior} and {witness}"),
|
||||
}
|
||||
}
|
||||
|
||||
// This visitor has to have the same visit_expr calls as RegionResolutionVisitor in
|
||||
// librustc_middle/middle/region.rs since `expr_count` is compared against the results
|
||||
// there.
|
||||
impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
|
||||
fn visit_arm(&mut self, arm: &'tcx Arm<'tcx>) {
|
||||
let Arm { guard, pat, body, .. } = arm;
|
||||
self.visit_pat(pat);
|
||||
if let Some(ref g) = guard {
|
||||
{
|
||||
// If there is a guard, we need to count all variables bound in the pattern as
|
||||
// borrowed for the entire guard body, regardless of whether they are accessed.
|
||||
// We do this by walking the pattern bindings and recording `&T` for any `x: T`
|
||||
// that is bound.
|
||||
|
||||
struct ArmPatCollector<'a, 'b, 'tcx> {
|
||||
interior_visitor: &'a mut InteriorVisitor<'b, 'tcx>,
|
||||
scope: Scope,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> Visitor<'tcx> for ArmPatCollector<'a, 'b, 'tcx> {
|
||||
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
|
||||
intravisit::walk_pat(self, pat);
|
||||
if let PatKind::Binding(_, id, ident, ..) = pat.kind {
|
||||
let ty =
|
||||
self.interior_visitor.fcx.typeck_results.borrow().node_type(id);
|
||||
let tcx = self.interior_visitor.fcx.tcx;
|
||||
let ty = tcx.mk_ref(
|
||||
// Use `ReErased` as `resolve_interior` is going to replace all the
|
||||
// regions anyway.
|
||||
tcx.mk_region(ty::ReErased),
|
||||
ty::TypeAndMut { ty, mutbl: hir::Mutability::Not },
|
||||
);
|
||||
self.interior_visitor.record(
|
||||
ty,
|
||||
id,
|
||||
Some(self.scope),
|
||||
None,
|
||||
ident.span,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArmPatCollector {
|
||||
interior_visitor: self,
|
||||
scope: Scope { id: g.body().hir_id.local_id, data: ScopeData::Node },
|
||||
}
|
||||
.visit_pat(pat);
|
||||
}
|
||||
|
||||
match g {
|
||||
Guard::If(ref e) => {
|
||||
self.visit_expr(e);
|
||||
}
|
||||
Guard::IfLet(ref l) => {
|
||||
self.visit_let_expr(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.visit_expr(body);
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
|
||||
intravisit::walk_pat(self, pat);
|
||||
|
||||
self.expr_count += 1;
|
||||
|
||||
if let PatKind::Binding(..) = pat.kind {
|
||||
let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id).unwrap();
|
||||
let ty = self.fcx.typeck_results.borrow().pat_ty(pat);
|
||||
self.record(ty, pat.hir_id, Some(scope), None, pat.span);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
match &expr.kind {
|
||||
ExprKind::Call(callee, args) => match &callee.kind {
|
||||
ExprKind::Path(qpath) => {
|
||||
let res = self.fcx.typeck_results.borrow().qpath_res(qpath, callee.hir_id);
|
||||
match res {
|
||||
// Direct calls never need to keep the callee `ty::FnDef`
|
||||
// ZST in a temporary, so skip its type, just in case it
|
||||
// can significantly complicate the generator type.
|
||||
Res::Def(
|
||||
DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn),
|
||||
_,
|
||||
) => {
|
||||
// NOTE(eddyb) this assumes a path expression has
|
||||
// no nested expressions to keep track of.
|
||||
self.expr_count += 1;
|
||||
|
||||
// Record the rest of the call expression normally.
|
||||
for arg in *args {
|
||||
self.visit_expr(arg);
|
||||
}
|
||||
}
|
||||
_ => intravisit::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
_ => intravisit::walk_expr(self, expr),
|
||||
},
|
||||
_ => intravisit::walk_expr(self, expr),
|
||||
}
|
||||
|
||||
self.expr_count += 1;
|
||||
|
||||
debug!("is_borrowed_temporary: {:?}", self.drop_ranges.is_borrowed_temporary(expr));
|
||||
|
||||
let ty = self.fcx.typeck_results.borrow().expr_ty_adjusted_opt(expr);
|
||||
let may_need_drop = |ty: Ty<'tcx>| {
|
||||
// Avoid ICEs in needs_drop.
|
||||
let ty = self.fcx.resolve_vars_if_possible(ty);
|
||||
let ty = self.fcx.tcx.erase_regions(ty);
|
||||
if ty.needs_infer() {
|
||||
return true;
|
||||
}
|
||||
ty.needs_drop(self.fcx.tcx, self.fcx.param_env)
|
||||
};
|
||||
|
||||
// Typically, the value produced by an expression is consumed by its parent in some way,
|
||||
// so we only have to check if the parent contains a yield (note that the parent may, for
|
||||
// example, store the value into a local variable, but then we already consider local
|
||||
// variables to be live across their scope).
|
||||
//
|
||||
// However, in the case of temporary values, we are going to store the value into a
|
||||
// temporary on the stack that is live for the current temporary scope and then return a
|
||||
// reference to it. That value may be live across the entire temporary scope.
|
||||
//
|
||||
// There's another subtlety: if the type has an observable drop, it must be dropped after
|
||||
// the yield, even if it's not borrowed or referenced after the yield. Ideally this would
|
||||
// *only* happen for types with observable drop, not all types which wrap them, but that
|
||||
// doesn't match the behavior of MIR borrowck and causes ICEs. See the FIXME comment in
|
||||
// src/test/ui/generator/drop-tracking-parent-expression.rs.
|
||||
let scope = if self.drop_ranges.is_borrowed_temporary(expr)
|
||||
|| ty.map_or(true, |ty| {
|
||||
let needs_drop = may_need_drop(ty);
|
||||
debug!(?needs_drop, ?ty);
|
||||
needs_drop
|
||||
}) {
|
||||
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id)
|
||||
} else {
|
||||
let parent_expr = self
|
||||
.fcx
|
||||
.tcx
|
||||
.hir()
|
||||
.parent_iter(expr.hir_id)
|
||||
.find(|(_, node)| matches!(node, hir::Node::Expr(_)))
|
||||
.map(|(id, _)| id);
|
||||
debug!("parent_expr: {:?}", parent_expr);
|
||||
match parent_expr {
|
||||
Some(parent) => Some(Scope { id: parent.local_id, data: ScopeData::Node }),
|
||||
None => {
|
||||
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If there are adjustments, then record the final type --
|
||||
// this is the actual value that is being produced.
|
||||
if let Some(adjusted_ty) = ty {
|
||||
self.record(adjusted_ty, expr.hir_id, scope, Some(expr), expr.span);
|
||||
}
|
||||
|
||||
// Also record the unadjusted type (which is the only type if
|
||||
// there are no adjustments). The reason for this is that the
|
||||
// unadjusted value is sometimes a "temporary" that would wind
|
||||
// up in a MIR temporary.
|
||||
//
|
||||
// As an example, consider an expression like `vec![].push(x)`.
|
||||
// Here, the `vec![]` would wind up MIR stored into a
|
||||
// temporary variable `t` which we can borrow to invoke
|
||||
// `<Vec<_>>::push(&mut t, x)`.
|
||||
//
|
||||
// Note that an expression can have many adjustments, and we
|
||||
// are just ignoring those intermediate types. This is because
|
||||
// those intermediate values are always linearly "consumed" by
|
||||
// the other adjustments, and hence would never be directly
|
||||
// captured in the MIR.
|
||||
//
|
||||
// (Note that this partly relies on the fact that the `Deref`
|
||||
// traits always return references, which means their content
|
||||
// can be reborrowed without needing to spill to a temporary.
|
||||
// If this were not the case, then we could conceivably have
|
||||
// to create intermediate temporaries.)
|
||||
//
|
||||
// The type table might not have information for this expression
|
||||
// if it is in a malformed scope. (#66387)
|
||||
if let Some(ty) = self.fcx.typeck_results.borrow().expr_ty_opt(expr) {
|
||||
self.record(ty, expr.hir_id, scope, Some(expr), expr.span);
|
||||
} else {
|
||||
self.fcx.tcx.sess.delay_span_bug(expr.span, "no type for node");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SuspendCheckData<'a, 'tcx> {
|
||||
expr: Option<&'tcx Expr<'tcx>>,
|
||||
source_span: Span,
|
||||
yield_span: Span,
|
||||
descr_pre: &'a str,
|
||||
descr_post: &'a str,
|
||||
plural_len: usize,
|
||||
}
|
||||
|
||||
// Returns whether it emitted a diagnostic or not
|
||||
// Note that this fn and the proceeding one are based on the code
|
||||
// for creating must_use diagnostics
|
||||
//
|
||||
// Note that this technique was chosen over things like a `Suspend` marker trait
|
||||
// as it is simpler and has precedent in the compiler
|
||||
fn check_must_not_suspend_ty<'tcx>(
|
||||
fcx: &FnCtxt<'_, 'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
hir_id: HirId,
|
||||
data: SuspendCheckData<'_, 'tcx>,
|
||||
) -> bool {
|
||||
if ty.is_unit()
|
||||
// FIXME: should this check `is_ty_uninhabited_from`. This query is not available in this stage
|
||||
// of typeck (before ReVar and RePlaceholder are removed), but may remove noise, like in
|
||||
// `must_use`
|
||||
// || fcx.tcx.is_ty_uninhabited_from(fcx.tcx.parent_module(hir_id).to_def_id(), ty, fcx.param_env)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let plural_suffix = pluralize!(data.plural_len);
|
||||
|
||||
debug!("Checking must_not_suspend for {}", ty);
|
||||
|
||||
match *ty.kind() {
|
||||
ty::Adt(..) if ty.is_box() => {
|
||||
let boxed_ty = ty.boxed_ty();
|
||||
let descr_pre = &format!("{}boxed ", data.descr_pre);
|
||||
check_must_not_suspend_ty(fcx, boxed_ty, hir_id, SuspendCheckData { descr_pre, ..data })
|
||||
}
|
||||
ty::Adt(def, _) => check_must_not_suspend_def(fcx.tcx, def.did(), hir_id, data),
|
||||
// FIXME: support adding the attribute to TAITs
|
||||
ty::Opaque(def, _) => {
|
||||
let mut has_emitted = false;
|
||||
for &(predicate, _) in fcx.tcx.explicit_item_bounds(def) {
|
||||
// We only look at the `DefId`, so it is safe to skip the binder here.
|
||||
if let ty::PredicateKind::Trait(ref poly_trait_predicate) =
|
||||
predicate.kind().skip_binder()
|
||||
{
|
||||
let def_id = poly_trait_predicate.trait_ref.def_id;
|
||||
let descr_pre = &format!("{}implementer{} of ", data.descr_pre, plural_suffix);
|
||||
if check_must_not_suspend_def(
|
||||
fcx.tcx,
|
||||
def_id,
|
||||
hir_id,
|
||||
SuspendCheckData { descr_pre, ..data },
|
||||
) {
|
||||
has_emitted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
has_emitted
|
||||
}
|
||||
ty::Dynamic(binder, _, _) => {
|
||||
let mut has_emitted = false;
|
||||
for predicate in binder.iter() {
|
||||
if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
|
||||
let def_id = trait_ref.def_id;
|
||||
let descr_post = &format!(" trait object{}{}", plural_suffix, data.descr_post);
|
||||
if check_must_not_suspend_def(
|
||||
fcx.tcx,
|
||||
def_id,
|
||||
hir_id,
|
||||
SuspendCheckData { descr_post, ..data },
|
||||
) {
|
||||
has_emitted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
has_emitted
|
||||
}
|
||||
ty::Tuple(fields) => {
|
||||
let mut has_emitted = false;
|
||||
let comps = match data.expr.map(|e| &e.kind) {
|
||||
Some(hir::ExprKind::Tup(comps)) => {
|
||||
debug_assert_eq!(comps.len(), fields.len());
|
||||
Some(comps)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
for (i, ty) in fields.iter().enumerate() {
|
||||
let descr_post = &format!(" in tuple element {i}");
|
||||
let span = comps.and_then(|c| c.get(i)).map(|e| e.span).unwrap_or(data.source_span);
|
||||
if check_must_not_suspend_ty(
|
||||
fcx,
|
||||
ty,
|
||||
hir_id,
|
||||
SuspendCheckData {
|
||||
descr_post,
|
||||
expr: comps.and_then(|comps| comps.get(i)),
|
||||
source_span: span,
|
||||
..data
|
||||
},
|
||||
) {
|
||||
has_emitted = true;
|
||||
}
|
||||
}
|
||||
has_emitted
|
||||
}
|
||||
ty::Array(ty, len) => {
|
||||
let descr_pre = &format!("{}array{} of ", data.descr_pre, plural_suffix);
|
||||
check_must_not_suspend_ty(
|
||||
fcx,
|
||||
ty,
|
||||
hir_id,
|
||||
SuspendCheckData {
|
||||
descr_pre,
|
||||
plural_len: len.try_eval_usize(fcx.tcx, fcx.param_env).unwrap_or(0) as usize
|
||||
+ 1,
|
||||
..data
|
||||
},
|
||||
)
|
||||
}
|
||||
// If drop tracking is enabled, we want to look through references, since the referrent
|
||||
// may not be considered live across the await point.
|
||||
ty::Ref(_region, ty, _mutability) if fcx.sess().opts.unstable_opts.drop_tracking => {
|
||||
let descr_pre = &format!("{}reference{} to ", data.descr_pre, plural_suffix);
|
||||
check_must_not_suspend_ty(fcx, ty, hir_id, SuspendCheckData { descr_pre, ..data })
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_must_not_suspend_def(
|
||||
tcx: TyCtxt<'_>,
|
||||
def_id: DefId,
|
||||
hir_id: HirId,
|
||||
data: SuspendCheckData<'_, '_>,
|
||||
) -> bool {
|
||||
if let Some(attr) = tcx.get_attr(def_id, sym::must_not_suspend) {
|
||||
tcx.struct_span_lint_hir(
|
||||
rustc_session::lint::builtin::MUST_NOT_SUSPEND,
|
||||
hir_id,
|
||||
data.source_span,
|
||||
DelayDm(|| {
|
||||
format!(
|
||||
"{}`{}`{} held across a suspend point, but should not be",
|
||||
data.descr_pre,
|
||||
tcx.def_path_str(def_id),
|
||||
data.descr_post,
|
||||
)
|
||||
}),
|
||||
|lint| {
|
||||
// add span pointing to the offending yield/await
|
||||
lint.span_label(data.yield_span, "the value is held across this suspend point");
|
||||
|
||||
// Add optional reason note
|
||||
if let Some(note) = attr.value_str() {
|
||||
// FIXME(guswynn): consider formatting this better
|
||||
lint.span_note(data.source_span, note.as_str());
|
||||
}
|
||||
|
||||
// Add some quick suggestions on what to do
|
||||
// FIXME: can `drop` work as a suggestion here as well?
|
||||
lint.span_help(
|
||||
data.source_span,
|
||||
"consider using a block (`{ ... }`) \
|
||||
to shrink the value's scope, ending before the suspend point",
|
||||
);
|
||||
|
||||
lint
|
||||
},
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
//! Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped
|
||||
//! (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the
|
||||
//! generator type. See `InteriorVisitor::record` for where the results of this analysis are used.
|
||||
//!
|
||||
//! There are three phases to this analysis:
|
||||
//! 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed.
|
||||
//! 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized,
|
||||
//! and also build a control flow graph.
|
||||
//! 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through
|
||||
//! the CFG and find the exact points where we know a value is definitely dropped.
|
||||
//!
|
||||
//! The end result is a data structure that maps the post-order index of each node in the HIR tree
|
||||
//! to a set of values that are known to be dropped at that location.
|
||||
|
||||
use self::cfg_build::build_control_flow_graph;
|
||||
use self::record_consumed_borrow::find_consumed_and_borrowed;
|
||||
use crate::check::FnCtxt;
|
||||
use hir::def_id::DefId;
|
||||
use hir::{Body, HirId, HirIdMap, Node};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_hir as hir;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId};
|
||||
use rustc_middle::ty;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
mod cfg_build;
|
||||
mod cfg_propagate;
|
||||
mod cfg_visualize;
|
||||
mod record_consumed_borrow;
|
||||
|
||||
pub fn compute_drop_ranges<'a, 'tcx>(
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
def_id: DefId,
|
||||
body: &'tcx Body<'tcx>,
|
||||
) -> DropRanges {
|
||||
if fcx.sess().opts.unstable_opts.drop_tracking {
|
||||
let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body);
|
||||
|
||||
let typeck_results = &fcx.typeck_results.borrow();
|
||||
let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0);
|
||||
let (mut drop_ranges, borrowed_temporaries) = build_control_flow_graph(
|
||||
fcx.tcx.hir(),
|
||||
fcx.tcx,
|
||||
typeck_results,
|
||||
consumed_borrowed_places,
|
||||
body,
|
||||
num_exprs,
|
||||
);
|
||||
|
||||
drop_ranges.propagate_to_fixpoint();
|
||||
|
||||
debug!("borrowed_temporaries = {borrowed_temporaries:?}");
|
||||
DropRanges {
|
||||
tracked_value_map: drop_ranges.tracked_value_map,
|
||||
nodes: drop_ranges.nodes,
|
||||
borrowed_temporaries: Some(borrowed_temporaries),
|
||||
}
|
||||
} else {
|
||||
// If drop range tracking is not enabled, skip all the analysis and produce an
|
||||
// empty set of DropRanges.
|
||||
DropRanges {
|
||||
tracked_value_map: FxHashMap::default(),
|
||||
nodes: IndexVec::new(),
|
||||
borrowed_temporaries: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies `f` to consumable node in the HIR subtree pointed to by `place`.
|
||||
///
|
||||
/// This includes the place itself, and if the place is a reference to a local
|
||||
/// variable then `f` is also called on the HIR node for that variable as well.
|
||||
///
|
||||
/// For example, if `place` points to `foo()`, then `f` is called once for the
|
||||
/// result of `foo`. On the other hand, if `place` points to `x` then `f` will
|
||||
/// be called both on the `ExprKind::Path` node that represents the expression
|
||||
/// as well as the HirId of the local `x` itself.
|
||||
fn for_each_consumable<'tcx>(hir: Map<'tcx>, place: TrackedValue, mut f: impl FnMut(TrackedValue)) {
|
||||
f(place);
|
||||
let node = hir.find(place.hir_id());
|
||||
if let Some(Node::Expr(expr)) = node {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Path(hir::QPath::Resolved(
|
||||
_,
|
||||
hir::Path { res: hir::def::Res::Local(hir_id), .. },
|
||||
)) => {
|
||||
f(TrackedValue::Variable(*hir_id));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
pub struct PostOrderId {
|
||||
DEBUG_FORMAT = "id({})",
|
||||
}
|
||||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
pub struct TrackedValueIndex {
|
||||
DEBUG_FORMAT = "hidx({})",
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies a value whose drop state we need to track.
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
enum TrackedValue {
|
||||
/// Represents a named variable, such as a let binding, parameter, or upvar.
|
||||
///
|
||||
/// The HirId points to the variable's definition site.
|
||||
Variable(HirId),
|
||||
/// A value produced as a result of an expression.
|
||||
///
|
||||
/// The HirId points to the expression that returns this value.
|
||||
Temporary(HirId),
|
||||
}
|
||||
|
||||
impl Debug for TrackedValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
ty::tls::with_opt(|opt_tcx| {
|
||||
if let Some(tcx) = opt_tcx {
|
||||
write!(f, "{}", tcx.hir().node_to_string(self.hir_id()))
|
||||
} else {
|
||||
match self {
|
||||
Self::Variable(hir_id) => write!(f, "Variable({:?})", hir_id),
|
||||
Self::Temporary(hir_id) => write!(f, "Temporary({:?})", hir_id),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackedValue {
|
||||
fn hir_id(&self) -> HirId {
|
||||
match self {
|
||||
TrackedValue::Variable(hir_id) | TrackedValue::Temporary(hir_id) => *hir_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_place_with_projections_allowed(place_with_id: &PlaceWithHirId<'_>) -> Self {
|
||||
match place_with_id.place.base {
|
||||
PlaceBase::Rvalue | PlaceBase::StaticItem => {
|
||||
TrackedValue::Temporary(place_with_id.hir_id)
|
||||
}
|
||||
PlaceBase::Local(hir_id)
|
||||
| PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => {
|
||||
TrackedValue::Variable(hir_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a reason why we might not be able to convert a HirId or Place
|
||||
/// into a tracked value.
|
||||
#[derive(Debug)]
|
||||
enum TrackedValueConversionError {
|
||||
/// Place projects are not currently supported.
|
||||
///
|
||||
/// The reasoning around these is kind of subtle, so we choose to be more
|
||||
/// conservative around these for now. There is no reason in theory we
|
||||
/// cannot support these, we just have not implemented it yet.
|
||||
PlaceProjectionsNotSupported,
|
||||
}
|
||||
|
||||
impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue {
|
||||
type Error = TrackedValueConversionError;
|
||||
|
||||
fn try_from(place_with_id: &PlaceWithHirId<'_>) -> Result<Self, Self::Error> {
|
||||
if !place_with_id.place.projections.is_empty() {
|
||||
debug!(
|
||||
"TrackedValue from PlaceWithHirId: {:?} has projections, which are not supported.",
|
||||
place_with_id
|
||||
);
|
||||
return Err(TrackedValueConversionError::PlaceProjectionsNotSupported);
|
||||
}
|
||||
|
||||
Ok(TrackedValue::from_place_with_projections_allowed(place_with_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DropRanges {
|
||||
tracked_value_map: FxHashMap<TrackedValue, TrackedValueIndex>,
|
||||
nodes: IndexVec<PostOrderId, NodeInfo>,
|
||||
borrowed_temporaries: Option<FxHashSet<HirId>>,
|
||||
}
|
||||
|
||||
impl DropRanges {
|
||||
pub fn is_dropped_at(&self, hir_id: HirId, location: usize) -> bool {
|
||||
self.tracked_value_map
|
||||
.get(&TrackedValue::Temporary(hir_id))
|
||||
.or(self.tracked_value_map.get(&TrackedValue::Variable(hir_id)))
|
||||
.cloned()
|
||||
.map_or(false, |tracked_value_id| {
|
||||
self.expect_node(location.into()).drop_state.contains(tracked_value_id)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_borrowed_temporary(&self, expr: &hir::Expr<'_>) -> bool {
|
||||
if let Some(b) = &self.borrowed_temporaries { b.contains(&expr.hir_id) } else { true }
|
||||
}
|
||||
|
||||
/// Returns a reference to the NodeInfo for a node, panicking if it does not exist
|
||||
fn expect_node(&self, id: PostOrderId) -> &NodeInfo {
|
||||
&self.nodes[id]
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks information needed to compute drop ranges.
|
||||
struct DropRangesBuilder {
|
||||
/// The core of DropRangesBuilder is a set of nodes, which each represent
|
||||
/// one expression. We primarily refer to them by their index in a
|
||||
/// post-order traversal of the HIR tree, since this is what
|
||||
/// generator_interior uses to talk about yield positions.
|
||||
///
|
||||
/// This IndexVec keeps the relevant details for each node. See the
|
||||
/// NodeInfo struct for more details, but this information includes things
|
||||
/// such as the set of control-flow successors, which variables are dropped
|
||||
/// or reinitialized, and whether each variable has been inferred to be
|
||||
/// known-dropped or potentially reinitialized at each point.
|
||||
nodes: IndexVec<PostOrderId, NodeInfo>,
|
||||
/// We refer to values whose drop state we are tracking by the HirId of
|
||||
/// where they are defined. Within a NodeInfo, however, we store the
|
||||
/// drop-state in a bit vector indexed by a HirIdIndex
|
||||
/// (see NodeInfo::drop_state). The hir_id_map field stores the mapping
|
||||
/// from HirIds to the HirIdIndex that is used to represent that value in
|
||||
/// bitvector.
|
||||
tracked_value_map: FxHashMap<TrackedValue, TrackedValueIndex>,
|
||||
|
||||
/// When building the control flow graph, we don't always know the
|
||||
/// post-order index of the target node at the point we encounter it.
|
||||
/// For example, this happens with break and continue. In those cases,
|
||||
/// we store a pair of the PostOrderId of the source and the HirId
|
||||
/// of the target. Once we have gathered all of these edges, we make a
|
||||
/// pass over the set of deferred edges (see process_deferred_edges in
|
||||
/// cfg_build.rs), look up the PostOrderId for the target (since now the
|
||||
/// post-order index for all nodes is known), and add missing control flow
|
||||
/// edges.
|
||||
deferred_edges: Vec<(PostOrderId, HirId)>,
|
||||
/// This maps HirIds of expressions to their post-order index. It is
|
||||
/// used in process_deferred_edges to correctly add back-edges.
|
||||
post_order_map: HirIdMap<PostOrderId>,
|
||||
}
|
||||
|
||||
impl Debug for DropRangesBuilder {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("DropRanges")
|
||||
.field("hir_id_map", &self.tracked_value_map)
|
||||
.field("post_order_maps", &self.post_order_map)
|
||||
.field("nodes", &self.nodes.iter_enumerated().collect::<BTreeMap<_, _>>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// DropRanges keeps track of what values are definitely dropped at each point in the code.
|
||||
///
|
||||
/// Values of interest are defined by the hir_id of their place. Locations in code are identified
|
||||
/// by their index in the post-order traversal. At its core, DropRanges maps
|
||||
/// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely
|
||||
/// dropped at the point of the node identified by post_order_id.
|
||||
impl DropRangesBuilder {
|
||||
/// Returns the number of values (hir_ids) that are tracked
|
||||
fn num_values(&self) -> usize {
|
||||
self.tracked_value_map.len()
|
||||
}
|
||||
|
||||
fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo {
|
||||
let size = self.num_values();
|
||||
self.nodes.ensure_contains_elem(id, || NodeInfo::new(size));
|
||||
&mut self.nodes[id]
|
||||
}
|
||||
|
||||
fn add_control_edge(&mut self, from: PostOrderId, to: PostOrderId) {
|
||||
trace!("adding control edge from {:?} to {:?}", from, to);
|
||||
self.node_mut(from).successors.push(to);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NodeInfo {
|
||||
/// IDs of nodes that can follow this one in the control flow
|
||||
///
|
||||
/// If the vec is empty, then control proceeds to the next node.
|
||||
successors: Vec<PostOrderId>,
|
||||
|
||||
/// List of hir_ids that are dropped by this node.
|
||||
drops: Vec<TrackedValueIndex>,
|
||||
|
||||
/// List of hir_ids that are reinitialized by this node.
|
||||
reinits: Vec<TrackedValueIndex>,
|
||||
|
||||
/// Set of values that are definitely dropped at this point.
|
||||
drop_state: BitSet<TrackedValueIndex>,
|
||||
}
|
||||
|
||||
impl NodeInfo {
|
||||
fn new(num_values: usize) -> Self {
|
||||
Self {
|
||||
successors: vec![],
|
||||
drops: vec![],
|
||||
reinits: vec![],
|
||||
drop_state: BitSet::new_filled(num_values),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,563 +0,0 @@
|
|||
use super::{
|
||||
for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRangesBuilder,
|
||||
NodeInfo, PostOrderId, TrackedValue, TrackedValueIndex,
|
||||
};
|
||||
use hir::{
|
||||
intravisit::{self, Visitor},
|
||||
Body, Expr, ExprKind, Guard, HirId, LoopIdError,
|
||||
};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_hir as hir;
|
||||
use rustc_index::vec::IndexVec;
|
||||
use rustc_middle::{
|
||||
hir::map::Map,
|
||||
ty::{TyCtxt, TypeckResults},
|
||||
};
|
||||
use std::mem::swap;
|
||||
|
||||
/// Traverses the body to find the control flow graph and locations for the
|
||||
/// relevant places are dropped or reinitialized.
|
||||
///
|
||||
/// The resulting structure still needs to be iterated to a fixed point, which
|
||||
/// can be done with propagate_to_fixpoint in cfg_propagate.
|
||||
pub(super) fn build_control_flow_graph<'tcx>(
|
||||
hir: Map<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
typeck_results: &TypeckResults<'tcx>,
|
||||
consumed_borrowed_places: ConsumedAndBorrowedPlaces,
|
||||
body: &'tcx Body<'tcx>,
|
||||
num_exprs: usize,
|
||||
) -> (DropRangesBuilder, FxHashSet<HirId>) {
|
||||
let mut drop_range_visitor =
|
||||
DropRangeVisitor::new(hir, tcx, typeck_results, consumed_borrowed_places, num_exprs);
|
||||
intravisit::walk_body(&mut drop_range_visitor, body);
|
||||
|
||||
drop_range_visitor.drop_ranges.process_deferred_edges();
|
||||
if let Some(filename) = &tcx.sess.opts.unstable_opts.dump_drop_tracking_cfg {
|
||||
super::cfg_visualize::write_graph_to_file(&drop_range_visitor.drop_ranges, filename, tcx);
|
||||
}
|
||||
|
||||
(drop_range_visitor.drop_ranges, drop_range_visitor.places.borrowed_temporaries)
|
||||
}
|
||||
|
||||
/// This struct is used to gather the information for `DropRanges` to determine the regions of the
|
||||
/// HIR tree for which a value is dropped.
|
||||
///
|
||||
/// We are interested in points where a variables is dropped or initialized, and the control flow
|
||||
/// of the code. We identify locations in code by their post-order traversal index, so it is
|
||||
/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`.
|
||||
///
|
||||
/// We make several simplifying assumptions, with the goal of being more conservative than
|
||||
/// necessary rather than less conservative (since being less conservative is unsound, but more
|
||||
/// conservative is still safe). These assumptions are:
|
||||
///
|
||||
/// 1. Moving a variable `a` counts as a move of the whole variable.
|
||||
/// 2. Moving a partial path like `a.b.c` is ignored.
|
||||
/// 3. Reinitializing through a field (e.g. `a.b.c = 5`) counts as a reinitialization of all of
|
||||
/// `a`.
|
||||
///
|
||||
/// Some examples:
|
||||
///
|
||||
/// Rule 1:
|
||||
/// ```rust
|
||||
/// let mut a = (vec![0], vec![0]);
|
||||
/// drop(a);
|
||||
/// // `a` is not considered initialized.
|
||||
/// ```
|
||||
///
|
||||
/// Rule 2:
|
||||
/// ```rust
|
||||
/// let mut a = (vec![0], vec![0]);
|
||||
/// drop(a.0);
|
||||
/// drop(a.1);
|
||||
/// // `a` is still considered initialized.
|
||||
/// ```
|
||||
///
|
||||
/// Rule 3:
|
||||
/// ```compile_fail,E0382
|
||||
/// let mut a = (vec![0], vec![0]);
|
||||
/// drop(a);
|
||||
/// a.1 = vec![1];
|
||||
/// // all of `a` is considered initialized
|
||||
/// ```
|
||||
|
||||
struct DropRangeVisitor<'a, 'tcx> {
|
||||
hir: Map<'tcx>,
|
||||
places: ConsumedAndBorrowedPlaces,
|
||||
drop_ranges: DropRangesBuilder,
|
||||
expr_index: PostOrderId,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
typeck_results: &'a TypeckResults<'tcx>,
|
||||
label_stack: Vec<(Option<rustc_ast::Label>, PostOrderId)>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
|
||||
fn new(
|
||||
hir: Map<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
typeck_results: &'a TypeckResults<'tcx>,
|
||||
places: ConsumedAndBorrowedPlaces,
|
||||
num_exprs: usize,
|
||||
) -> Self {
|
||||
debug!("consumed_places: {:?}", places.consumed);
|
||||
let drop_ranges = DropRangesBuilder::new(
|
||||
places.consumed.iter().flat_map(|(_, places)| places.iter().cloned()),
|
||||
hir,
|
||||
num_exprs,
|
||||
);
|
||||
Self {
|
||||
hir,
|
||||
places,
|
||||
drop_ranges,
|
||||
expr_index: PostOrderId::from_u32(0),
|
||||
typeck_results,
|
||||
tcx,
|
||||
label_stack: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn record_drop(&mut self, value: TrackedValue) {
|
||||
if self.places.borrowed.contains(&value) {
|
||||
debug!("not marking {:?} as dropped because it is borrowed at some point", value);
|
||||
} else {
|
||||
debug!("marking {:?} as dropped at {:?}", value, self.expr_index);
|
||||
let count = self.expr_index;
|
||||
self.drop_ranges.drop_at(value, count);
|
||||
}
|
||||
}
|
||||
|
||||
/// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all
|
||||
/// expressions. This method consumes a little deeper into the expression when needed.
|
||||
fn consume_expr(&mut self, expr: &hir::Expr<'_>) {
|
||||
debug!("consuming expr {:?}, count={:?}", expr.kind, self.expr_index);
|
||||
let places = self
|
||||
.places
|
||||
.consumed
|
||||
.get(&expr.hir_id)
|
||||
.map_or(vec![], |places| places.iter().cloned().collect());
|
||||
for place in places {
|
||||
trace!(?place, "consuming place");
|
||||
for_each_consumable(self.hir, place, |value| self.record_drop(value));
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks an expression as being reinitialized.
|
||||
///
|
||||
/// Note that we always approximated on the side of things being more
|
||||
/// initialized than they actually are, as opposed to less. In cases such
|
||||
/// as `x.y = ...`, we would consider all of `x` as being initialized
|
||||
/// instead of just the `y` field.
|
||||
///
|
||||
/// This is because it is always safe to consider something initialized
|
||||
/// even when it is not, but the other way around will cause problems.
|
||||
///
|
||||
/// In the future, we will hopefully tighten up these rules to be more
|
||||
/// precise.
|
||||
fn reinit_expr(&mut self, expr: &hir::Expr<'_>) {
|
||||
// Walk the expression to find the base. For example, in an expression
|
||||
// like `*a[i].x`, we want to find the `a` and mark that as
|
||||
// reinitialized.
|
||||
match expr.kind {
|
||||
ExprKind::Path(hir::QPath::Resolved(
|
||||
_,
|
||||
hir::Path { res: hir::def::Res::Local(hir_id), .. },
|
||||
)) => {
|
||||
// This is the base case, where we have found an actual named variable.
|
||||
|
||||
let location = self.expr_index;
|
||||
debug!("reinitializing {:?} at {:?}", hir_id, location);
|
||||
self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location);
|
||||
}
|
||||
|
||||
ExprKind::Field(base, _) => self.reinit_expr(base),
|
||||
|
||||
// Most expressions do not refer to something where we need to track
|
||||
// reinitializations.
|
||||
//
|
||||
// Some of these may be interesting in the future
|
||||
ExprKind::Path(..)
|
||||
| ExprKind::Box(..)
|
||||
| ExprKind::ConstBlock(..)
|
||||
| ExprKind::Array(..)
|
||||
| ExprKind::Call(..)
|
||||
| ExprKind::MethodCall(..)
|
||||
| ExprKind::Tup(..)
|
||||
| ExprKind::Binary(..)
|
||||
| ExprKind::Unary(..)
|
||||
| ExprKind::Lit(..)
|
||||
| ExprKind::Cast(..)
|
||||
| ExprKind::Type(..)
|
||||
| ExprKind::DropTemps(..)
|
||||
| ExprKind::Let(..)
|
||||
| ExprKind::If(..)
|
||||
| ExprKind::Loop(..)
|
||||
| ExprKind::Match(..)
|
||||
| ExprKind::Closure { .. }
|
||||
| ExprKind::Block(..)
|
||||
| ExprKind::Assign(..)
|
||||
| ExprKind::AssignOp(..)
|
||||
| ExprKind::Index(..)
|
||||
| ExprKind::AddrOf(..)
|
||||
| ExprKind::Break(..)
|
||||
| ExprKind::Continue(..)
|
||||
| ExprKind::Ret(..)
|
||||
| ExprKind::InlineAsm(..)
|
||||
| ExprKind::Struct(..)
|
||||
| ExprKind::Repeat(..)
|
||||
| ExprKind::Yield(..)
|
||||
| ExprKind::Err => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// For an expression with an uninhabited return type (e.g. a function that returns !),
|
||||
/// this adds a self edge to the CFG to model the fact that the function does not
|
||||
/// return.
|
||||
fn handle_uninhabited_return(&mut self, expr: &Expr<'tcx>) {
|
||||
let ty = self.typeck_results.expr_ty(expr);
|
||||
let ty = self.tcx.erase_regions(ty);
|
||||
let m = self.tcx.parent_module(expr.hir_id).to_def_id();
|
||||
let param_env = self.tcx.param_env(m.expect_local());
|
||||
if self.tcx.is_ty_uninhabited_from(m, ty, param_env) {
|
||||
// This function will not return. We model this fact as an infinite loop.
|
||||
self.drop_ranges.add_control_edge(self.expr_index + 1, self.expr_index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a Destination to an equivalent expression node
|
||||
///
|
||||
/// The destination field of a Break or Continue expression can target either an
|
||||
/// expression or a block. The drop range analysis, however, only deals in
|
||||
/// expression nodes, so blocks that might be the destination of a Break or Continue
|
||||
/// will not have a PostOrderId.
|
||||
///
|
||||
/// If the destination is an expression, this function will simply return that expression's
|
||||
/// hir_id. If the destination is a block, this function will return the hir_id of last
|
||||
/// expression in the block.
|
||||
fn find_target_expression_from_destination(
|
||||
&self,
|
||||
destination: hir::Destination,
|
||||
) -> Result<HirId, LoopIdError> {
|
||||
destination.target_id.map(|target| {
|
||||
let node = self.hir.get(target);
|
||||
match node {
|
||||
hir::Node::Expr(_) => target,
|
||||
hir::Node::Block(b) => find_last_block_expression(b),
|
||||
hir::Node::Param(..)
|
||||
| hir::Node::Item(..)
|
||||
| hir::Node::ForeignItem(..)
|
||||
| hir::Node::TraitItem(..)
|
||||
| hir::Node::ImplItem(..)
|
||||
| hir::Node::Variant(..)
|
||||
| hir::Node::Field(..)
|
||||
| hir::Node::AnonConst(..)
|
||||
| hir::Node::Stmt(..)
|
||||
| hir::Node::PathSegment(..)
|
||||
| hir::Node::Ty(..)
|
||||
| hir::Node::TypeBinding(..)
|
||||
| hir::Node::TraitRef(..)
|
||||
| hir::Node::Pat(..)
|
||||
| hir::Node::PatField(..)
|
||||
| hir::Node::ExprField(..)
|
||||
| hir::Node::Arm(..)
|
||||
| hir::Node::Local(..)
|
||||
| hir::Node::Ctor(..)
|
||||
| hir::Node::Lifetime(..)
|
||||
| hir::Node::GenericParam(..)
|
||||
| hir::Node::Crate(..)
|
||||
| hir::Node::Infer(..) => bug!("Unsupported branch target: {:?}", node),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn find_last_block_expression(block: &hir::Block<'_>) -> HirId {
|
||||
block.expr.map_or_else(
|
||||
// If there is no tail expression, there will be at least one statement in the
|
||||
// block because the block contains a break or continue statement.
|
||||
|| block.stmts.last().unwrap().hir_id,
|
||||
|expr| expr.hir_id,
|
||||
)
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
|
||||
let mut reinit = None;
|
||||
match expr.kind {
|
||||
ExprKind::Assign(lhs, rhs, _) => {
|
||||
self.visit_expr(lhs);
|
||||
self.visit_expr(rhs);
|
||||
|
||||
reinit = Some(lhs);
|
||||
}
|
||||
|
||||
ExprKind::If(test, if_true, if_false) => {
|
||||
self.visit_expr(test);
|
||||
|
||||
let fork = self.expr_index;
|
||||
|
||||
self.drop_ranges.add_control_edge(fork, self.expr_index + 1);
|
||||
self.visit_expr(if_true);
|
||||
let true_end = self.expr_index;
|
||||
|
||||
self.drop_ranges.add_control_edge(fork, self.expr_index + 1);
|
||||
if let Some(if_false) = if_false {
|
||||
self.visit_expr(if_false);
|
||||
}
|
||||
|
||||
self.drop_ranges.add_control_edge(true_end, self.expr_index + 1);
|
||||
}
|
||||
ExprKind::Match(scrutinee, arms, ..) => {
|
||||
// We walk through the match expression almost like a chain of if expressions.
|
||||
// Here's a diagram to follow along with:
|
||||
//
|
||||
// ┌─┐
|
||||
// match │A│ {
|
||||
// ┌───┴─┘
|
||||
// │
|
||||
// ┌▼┌───►┌─┐ ┌─┐
|
||||
// │B│ if │C│ =>│D│,
|
||||
// └─┘ ├─┴──►└─┴──────┐
|
||||
// ┌──┘ │
|
||||
// ┌──┘ │
|
||||
// │ │
|
||||
// ┌▼┌───►┌─┐ ┌─┐ │
|
||||
// │E│ if │F│ =>│G│, │
|
||||
// └─┘ ├─┴──►└─┴┐ │
|
||||
// │ │ │
|
||||
// } ▼ ▼ │
|
||||
// ┌─┐◄───────────────────┘
|
||||
// │H│
|
||||
// └─┘
|
||||
//
|
||||
// The order we want is that the scrutinee (A) flows into the first pattern (B),
|
||||
// which flows into the guard (C). Then the guard either flows into the arm body
|
||||
// (D) or into the start of the next arm (E). Finally, the body flows to the end
|
||||
// of the match block (H).
|
||||
//
|
||||
// The subsequent arms follow the same ordering. First we go to the pattern, then
|
||||
// the guard (if present, otherwise it flows straight into the body), then into
|
||||
// the body and then to the end of the match expression.
|
||||
//
|
||||
// The comments below show which edge is being added.
|
||||
self.visit_expr(scrutinee);
|
||||
|
||||
let (guard_exit, arm_end_ids) = arms.iter().fold(
|
||||
(self.expr_index, vec![]),
|
||||
|(incoming_edge, mut arm_end_ids), hir::Arm { pat, body, guard, .. }| {
|
||||
// A -> B, or C -> E
|
||||
self.drop_ranges.add_control_edge(incoming_edge, self.expr_index + 1);
|
||||
self.visit_pat(pat);
|
||||
// B -> C and E -> F are added implicitly due to the traversal order.
|
||||
match guard {
|
||||
Some(Guard::If(expr)) => self.visit_expr(expr),
|
||||
Some(Guard::IfLet(let_expr)) => {
|
||||
self.visit_let_expr(let_expr);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
// Likewise, C -> D and F -> G are added implicitly.
|
||||
|
||||
// Save C, F, so we can add the other outgoing edge.
|
||||
let to_next_arm = self.expr_index;
|
||||
|
||||
// The default edge does not get added since we also have an explicit edge,
|
||||
// so we also need to add an edge to the next node as well.
|
||||
//
|
||||
// This adds C -> D, F -> G
|
||||
self.drop_ranges.add_control_edge(self.expr_index, self.expr_index + 1);
|
||||
self.visit_expr(body);
|
||||
|
||||
// Save the end of the body so we can add the exit edge once we know where
|
||||
// the exit is.
|
||||
arm_end_ids.push(self.expr_index);
|
||||
|
||||
// Pass C to the next iteration, as well as vec![D]
|
||||
//
|
||||
// On the last round through, we pass F and vec![D, G] so that we can
|
||||
// add all the exit edges.
|
||||
(to_next_arm, arm_end_ids)
|
||||
},
|
||||
);
|
||||
// F -> H
|
||||
self.drop_ranges.add_control_edge(guard_exit, self.expr_index + 1);
|
||||
|
||||
arm_end_ids.into_iter().for_each(|arm_end| {
|
||||
// D -> H, G -> H
|
||||
self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1)
|
||||
});
|
||||
}
|
||||
|
||||
ExprKind::Loop(body, label, ..) => {
|
||||
let loop_begin = self.expr_index + 1;
|
||||
self.label_stack.push((label, loop_begin));
|
||||
if body.stmts.is_empty() && body.expr.is_none() {
|
||||
// For empty loops we won't have updated self.expr_index after visiting the
|
||||
// body, meaning we'd get an edge from expr_index to expr_index + 1, but
|
||||
// instead we want an edge from expr_index + 1 to expr_index + 1.
|
||||
self.drop_ranges.add_control_edge(loop_begin, loop_begin);
|
||||
} else {
|
||||
self.visit_block(body);
|
||||
self.drop_ranges.add_control_edge(self.expr_index, loop_begin);
|
||||
}
|
||||
self.label_stack.pop();
|
||||
}
|
||||
// Find the loop entry by searching through the label stack for either the last entry
|
||||
// (if label is none), or the first entry where the label matches this one. The Loop
|
||||
// case maintains this stack mapping labels to the PostOrderId for the loop entry.
|
||||
ExprKind::Continue(hir::Destination { label, .. }, ..) => self
|
||||
.label_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(loop_label, _)| label.is_none() || *loop_label == label)
|
||||
.map_or((), |(_, target)| {
|
||||
self.drop_ranges.add_control_edge(self.expr_index, *target)
|
||||
}),
|
||||
|
||||
ExprKind::Break(destination, ..) => {
|
||||
// destination either points to an expression or to a block. We use
|
||||
// find_target_expression_from_destination to use the last expression of the block
|
||||
// if destination points to a block.
|
||||
//
|
||||
// We add an edge to the hir_id of the expression/block we are breaking out of, and
|
||||
// then in process_deferred_edges we will map this hir_id to its PostOrderId, which
|
||||
// will refer to the end of the block due to the post order traversal.
|
||||
self.find_target_expression_from_destination(destination).map_or((), |target| {
|
||||
self.drop_ranges.add_control_edge_hir_id(self.expr_index, target)
|
||||
})
|
||||
}
|
||||
|
||||
ExprKind::Call(f, args) => {
|
||||
self.visit_expr(f);
|
||||
for arg in args {
|
||||
self.visit_expr(arg);
|
||||
}
|
||||
|
||||
self.handle_uninhabited_return(expr);
|
||||
}
|
||||
ExprKind::MethodCall(_, receiver, exprs, _) => {
|
||||
self.visit_expr(receiver);
|
||||
for expr in exprs {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
|
||||
self.handle_uninhabited_return(expr);
|
||||
}
|
||||
|
||||
ExprKind::AddrOf(..)
|
||||
| ExprKind::Array(..)
|
||||
| ExprKind::AssignOp(..)
|
||||
| ExprKind::Binary(..)
|
||||
| ExprKind::Block(..)
|
||||
| ExprKind::Box(..)
|
||||
| ExprKind::Cast(..)
|
||||
| ExprKind::Closure { .. }
|
||||
| ExprKind::ConstBlock(..)
|
||||
| ExprKind::DropTemps(..)
|
||||
| ExprKind::Err
|
||||
| ExprKind::Field(..)
|
||||
| ExprKind::Index(..)
|
||||
| ExprKind::InlineAsm(..)
|
||||
| ExprKind::Let(..)
|
||||
| ExprKind::Lit(..)
|
||||
| ExprKind::Path(..)
|
||||
| ExprKind::Repeat(..)
|
||||
| ExprKind::Ret(..)
|
||||
| ExprKind::Struct(..)
|
||||
| ExprKind::Tup(..)
|
||||
| ExprKind::Type(..)
|
||||
| ExprKind::Unary(..)
|
||||
| ExprKind::Yield(..) => intravisit::walk_expr(self, expr),
|
||||
}
|
||||
|
||||
self.expr_index = self.expr_index + 1;
|
||||
self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_index);
|
||||
self.consume_expr(expr);
|
||||
if let Some(expr) = reinit {
|
||||
self.reinit_expr(expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
|
||||
intravisit::walk_pat(self, pat);
|
||||
|
||||
// Increment expr_count here to match what InteriorVisitor expects.
|
||||
self.expr_index = self.expr_index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl DropRangesBuilder {
|
||||
fn new(
|
||||
tracked_values: impl Iterator<Item = TrackedValue>,
|
||||
hir: Map<'_>,
|
||||
num_exprs: usize,
|
||||
) -> Self {
|
||||
let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default();
|
||||
let mut next = <_>::from(0u32);
|
||||
for value in tracked_values {
|
||||
for_each_consumable(hir, value, |value| {
|
||||
if !tracked_value_map.contains_key(&value) {
|
||||
tracked_value_map.insert(value, next);
|
||||
next = next + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
debug!("hir_id_map: {:?}", tracked_value_map);
|
||||
let num_values = tracked_value_map.len();
|
||||
Self {
|
||||
tracked_value_map,
|
||||
nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1),
|
||||
deferred_edges: <_>::default(),
|
||||
post_order_map: <_>::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tracked_value_index(&self, tracked_value: TrackedValue) -> TrackedValueIndex {
|
||||
*self.tracked_value_map.get(&tracked_value).unwrap()
|
||||
}
|
||||
|
||||
/// Adds an entry in the mapping from HirIds to PostOrderIds
|
||||
///
|
||||
/// Needed so that `add_control_edge_hir_id` can work.
|
||||
fn add_node_mapping(&mut self, node_hir_id: HirId, post_order_id: PostOrderId) {
|
||||
self.post_order_map.insert(node_hir_id, post_order_id);
|
||||
}
|
||||
|
||||
/// Like add_control_edge, but uses a hir_id as the target.
|
||||
///
|
||||
/// This can be used for branches where we do not know the PostOrderId of the target yet,
|
||||
/// such as when handling `break` or `continue`.
|
||||
fn add_control_edge_hir_id(&mut self, from: PostOrderId, to: HirId) {
|
||||
self.deferred_edges.push((from, to));
|
||||
}
|
||||
|
||||
fn drop_at(&mut self, value: TrackedValue, location: PostOrderId) {
|
||||
let value = self.tracked_value_index(value);
|
||||
self.node_mut(location).drops.push(value);
|
||||
}
|
||||
|
||||
fn reinit_at(&mut self, value: TrackedValue, location: PostOrderId) {
|
||||
let value = match self.tracked_value_map.get(&value) {
|
||||
Some(value) => *value,
|
||||
// If there's no value, this is never consumed and therefore is never dropped. We can
|
||||
// ignore this.
|
||||
None => return,
|
||||
};
|
||||
self.node_mut(location).reinits.push(value);
|
||||
}
|
||||
|
||||
/// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them.
|
||||
///
|
||||
/// Should be called after visiting the HIR but before solving the control flow, otherwise some
|
||||
/// edges will be missed.
|
||||
fn process_deferred_edges(&mut self) {
|
||||
trace!("processing deferred edges. post_order_map={:#?}", self.post_order_map);
|
||||
let mut edges = vec![];
|
||||
swap(&mut edges, &mut self.deferred_edges);
|
||||
edges.into_iter().for_each(|(from, to)| {
|
||||
trace!("Adding deferred edge from {:?} to {:?}", from, to);
|
||||
let to = *self.post_order_map.get(&to).expect("Expression ID not found");
|
||||
trace!("target edge PostOrderId={:?}", to);
|
||||
self.add_control_edge(from, to)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
use super::{DropRangesBuilder, PostOrderId};
|
||||
use rustc_index::{bit_set::BitSet, vec::IndexVec};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl DropRangesBuilder {
|
||||
pub fn propagate_to_fixpoint(&mut self) {
|
||||
trace!("before fixpoint: {:#?}", self);
|
||||
let preds = self.compute_predecessors();
|
||||
|
||||
trace!("predecessors: {:#?}", preds.iter_enumerated().collect::<BTreeMap<_, _>>());
|
||||
|
||||
let mut new_state = BitSet::new_empty(self.num_values());
|
||||
let mut changed_nodes = BitSet::new_empty(self.nodes.len());
|
||||
let mut unchanged_mask = BitSet::new_filled(self.nodes.len());
|
||||
changed_nodes.insert(0u32.into());
|
||||
|
||||
let mut propagate = || {
|
||||
let mut changed = false;
|
||||
unchanged_mask.insert_all();
|
||||
for id in self.nodes.indices() {
|
||||
trace!("processing {:?}, changed_nodes: {:?}", id, changed_nodes);
|
||||
// Check if any predecessor has changed, and if not then short-circuit.
|
||||
//
|
||||
// We handle the start node specially, since it doesn't have any predecessors,
|
||||
// but we need to start somewhere.
|
||||
if match id.index() {
|
||||
0 => !changed_nodes.contains(id),
|
||||
_ => !preds[id].iter().any(|pred| changed_nodes.contains(*pred)),
|
||||
} {
|
||||
trace!("short-circuiting because none of {:?} have changed", preds[id]);
|
||||
unchanged_mask.remove(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if id.index() == 0 {
|
||||
new_state.clear();
|
||||
} else {
|
||||
// If we are not the start node and we have no predecessors, treat
|
||||
// everything as dropped because there's no way to get here anyway.
|
||||
new_state.insert_all();
|
||||
};
|
||||
|
||||
for pred in &preds[id] {
|
||||
new_state.intersect(&self.nodes[*pred].drop_state);
|
||||
}
|
||||
|
||||
for drop in &self.nodes[id].drops {
|
||||
new_state.insert(*drop);
|
||||
}
|
||||
|
||||
for reinit in &self.nodes[id].reinits {
|
||||
new_state.remove(*reinit);
|
||||
}
|
||||
|
||||
if self.nodes[id].drop_state.intersect(&new_state) {
|
||||
changed_nodes.insert(id);
|
||||
changed = true;
|
||||
} else {
|
||||
unchanged_mask.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
changed_nodes.intersect(&unchanged_mask);
|
||||
changed
|
||||
};
|
||||
|
||||
while propagate() {
|
||||
trace!("drop_state changed, re-running propagation");
|
||||
}
|
||||
|
||||
trace!("after fixpoint: {:#?}", self);
|
||||
}
|
||||
|
||||
fn compute_predecessors(&self) -> IndexVec<PostOrderId, Vec<PostOrderId>> {
|
||||
let mut preds = IndexVec::from_fn_n(|_| vec![], self.nodes.len());
|
||||
for (id, node) in self.nodes.iter_enumerated() {
|
||||
// If the node has no explicit successors, we assume that control
|
||||
// will from this node into the next one.
|
||||
//
|
||||
// If there are successors listed, then we assume that all
|
||||
// possible successors are given and we do not include the default.
|
||||
if node.successors.len() == 0 && id.index() != self.nodes.len() - 1 {
|
||||
preds[id + 1].push(id);
|
||||
} else {
|
||||
for succ in &node.successors {
|
||||
preds[*succ].push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
preds
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
//! Implementation of GraphWalk for DropRanges so we can visualize the control
|
||||
//! flow graph when needed for debugging.
|
||||
|
||||
use rustc_graphviz as dot;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use super::{DropRangesBuilder, PostOrderId};
|
||||
|
||||
/// Writes the CFG for DropRangesBuilder to a .dot file for visualization.
|
||||
///
|
||||
/// It is not normally called, but is kept around to easily add debugging
|
||||
/// code when needed.
|
||||
pub(super) fn write_graph_to_file(
|
||||
drop_ranges: &DropRangesBuilder,
|
||||
filename: &str,
|
||||
tcx: TyCtxt<'_>,
|
||||
) {
|
||||
dot::render(
|
||||
&DropRangesGraph { drop_ranges, tcx },
|
||||
&mut std::fs::File::create(filename).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
struct DropRangesGraph<'a, 'tcx> {
|
||||
drop_ranges: &'a DropRangesBuilder,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a> dot::GraphWalk<'a> for DropRangesGraph<'_, '_> {
|
||||
type Node = PostOrderId;
|
||||
|
||||
type Edge = (PostOrderId, PostOrderId);
|
||||
|
||||
fn nodes(&'a self) -> dot::Nodes<'a, Self::Node> {
|
||||
self.drop_ranges.nodes.iter_enumerated().map(|(i, _)| i).collect()
|
||||
}
|
||||
|
||||
fn edges(&'a self) -> dot::Edges<'a, Self::Edge> {
|
||||
self.drop_ranges
|
||||
.nodes
|
||||
.iter_enumerated()
|
||||
.flat_map(|(i, node)| {
|
||||
if node.successors.len() == 0 {
|
||||
vec![(i, i + 1)]
|
||||
} else {
|
||||
node.successors.iter().map(move |&s| (i, s)).collect()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn source(&'a self, edge: &Self::Edge) -> Self::Node {
|
||||
edge.0
|
||||
}
|
||||
|
||||
fn target(&'a self, edge: &Self::Edge) -> Self::Node {
|
||||
edge.1
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> dot::Labeller<'a> for DropRangesGraph<'_, '_> {
|
||||
type Node = PostOrderId;
|
||||
|
||||
type Edge = (PostOrderId, PostOrderId);
|
||||
|
||||
fn graph_id(&'a self) -> dot::Id<'a> {
|
||||
dot::Id::new("drop_ranges").unwrap()
|
||||
}
|
||||
|
||||
fn node_id(&'a self, n: &Self::Node) -> dot::Id<'a> {
|
||||
dot::Id::new(format!("id{}", n.index())).unwrap()
|
||||
}
|
||||
|
||||
fn node_label(&'a self, n: &Self::Node) -> dot::LabelText<'a> {
|
||||
dot::LabelText::LabelStr(
|
||||
format!(
|
||||
"{n:?}: {}",
|
||||
self.drop_ranges
|
||||
.post_order_map
|
||||
.iter()
|
||||
.find(|(_hir_id, &post_order_id)| post_order_id == *n)
|
||||
.map_or("<unknown>".into(), |(hir_id, _)| self
|
||||
.tcx
|
||||
.hir()
|
||||
.node_to_string(*hir_id))
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
use super::TrackedValue;
|
||||
use crate::{
|
||||
check::FnCtxt,
|
||||
expr_use_visitor::{self, ExprUseVisitor},
|
||||
};
|
||||
use hir::{def_id::DefId, Body, HirId, HirIdMap};
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::hir::place::{PlaceBase, Projection, ProjectionKind};
|
||||
use rustc_middle::ty::{ParamEnv, TyCtxt};
|
||||
|
||||
pub(super) fn find_consumed_and_borrowed<'a, 'tcx>(
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
def_id: DefId,
|
||||
body: &'tcx Body<'tcx>,
|
||||
) -> ConsumedAndBorrowedPlaces {
|
||||
let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx, fcx.param_env);
|
||||
expr_use_visitor.consume_body(fcx, def_id, body);
|
||||
expr_use_visitor.places
|
||||
}
|
||||
|
||||
pub(super) struct ConsumedAndBorrowedPlaces {
|
||||
/// Records the variables/expressions that are dropped by a given expression.
|
||||
///
|
||||
/// The key is the hir-id of the expression, and the value is a set or hir-ids for variables
|
||||
/// or values that are consumed by that expression.
|
||||
///
|
||||
/// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is
|
||||
/// not considered a drop of `x`, although it would be a drop of `x.y`.
|
||||
pub(super) consumed: HirIdMap<FxHashSet<TrackedValue>>,
|
||||
|
||||
/// A set of hir-ids of values or variables that are borrowed at some point within the body.
|
||||
pub(super) borrowed: FxHashSet<TrackedValue>,
|
||||
|
||||
/// A set of hir-ids of values or variables that are borrowed at some point within the body.
|
||||
pub(super) borrowed_temporaries: FxHashSet<HirId>,
|
||||
}
|
||||
|
||||
/// Works with ExprUseVisitor to find interesting values for the drop range analysis.
|
||||
///
|
||||
/// Interesting values are those that are either dropped or borrowed. For dropped values, we also
|
||||
/// record the parent expression, which is the point where the drop actually takes place.
|
||||
struct ExprUseDelegate<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
places: ConsumedAndBorrowedPlaces,
|
||||
}
|
||||
|
||||
impl<'tcx> ExprUseDelegate<'tcx> {
|
||||
fn new(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Self {
|
||||
Self {
|
||||
tcx,
|
||||
param_env,
|
||||
places: ConsumedAndBorrowedPlaces {
|
||||
consumed: <_>::default(),
|
||||
borrowed: <_>::default(),
|
||||
borrowed_temporaries: <_>::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn consume_body(&mut self, fcx: &'_ FnCtxt<'_, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>) {
|
||||
// Run ExprUseVisitor to find where values are consumed.
|
||||
ExprUseVisitor::new(
|
||||
self,
|
||||
&fcx.infcx,
|
||||
def_id.expect_local(),
|
||||
fcx.param_env,
|
||||
&fcx.typeck_results.borrow(),
|
||||
)
|
||||
.consume_body(body);
|
||||
}
|
||||
|
||||
fn mark_consumed(&mut self, consumer: HirId, target: TrackedValue) {
|
||||
self.places.consumed.entry(consumer).or_insert_with(|| <_>::default());
|
||||
|
||||
debug!(?consumer, ?target, "mark_consumed");
|
||||
self.places.consumed.get_mut(&consumer).map(|places| places.insert(target));
|
||||
}
|
||||
|
||||
fn borrow_place(&mut self, place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>) {
|
||||
self.places
|
||||
.borrowed
|
||||
.insert(TrackedValue::from_place_with_projections_allowed(place_with_id));
|
||||
|
||||
// Ordinarily a value is consumed by it's parent, but in the special case of a
|
||||
// borrowed RValue, we create a reference that lives as long as the temporary scope
|
||||
// for that expression (typically, the innermost statement, but sometimes the enclosing
|
||||
// block). We record this fact here so that later in generator_interior
|
||||
// we can use the correct scope.
|
||||
//
|
||||
// We special case borrows through a dereference (`&*x`, `&mut *x` where `x` is
|
||||
// some rvalue expression), since these are essentially a copy of a pointer.
|
||||
// In other words, this borrow does not refer to the
|
||||
// temporary (`*x`), but to the referent (whatever `x` is a borrow of).
|
||||
//
|
||||
// We were considering that we might encounter problems down the line if somehow,
|
||||
// some part of the compiler were to look at this result and try to use it to
|
||||
// drive a borrowck-like analysis (this does not currently happen, as of this writing).
|
||||
// But even this should be fine, because the lifetime of the dereferenced reference
|
||||
// found in the rvalue is only significant as an intermediate 'link' to the value we
|
||||
// are producing, and we separately track whether that value is live over a yield.
|
||||
// Example:
|
||||
//
|
||||
// ```notrust
|
||||
// fn identity<T>(x: &mut T) -> &mut T { x }
|
||||
// let a: A = ...;
|
||||
// let y: &'y mut A = &mut *identity(&'a mut a);
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^ the borrow we are talking about
|
||||
// ```
|
||||
//
|
||||
// The expression `*identity(...)` is a deref of an rvalue,
|
||||
// where the `identity(...)` (the rvalue) produces a return type
|
||||
// of `&'rv mut A`, where `'a: 'rv`. We then assign this result to
|
||||
// `'y`, resulting in (transitively) `'a: 'y` (i.e., while `y` is in use,
|
||||
// `a` will be considered borrowed). Other parts of the code will ensure
|
||||
// that if `y` is live over a yield, `&'y mut A` appears in the generator
|
||||
// state. If `'y` is live, then any sound region analysis must conclude
|
||||
// that `'a` is also live. So if this causes a bug, blame some other
|
||||
// part of the code!
|
||||
let is_deref = place_with_id
|
||||
.place
|
||||
.projections
|
||||
.iter()
|
||||
.any(|Projection { kind, .. }| *kind == ProjectionKind::Deref);
|
||||
|
||||
if let (false, PlaceBase::Rvalue) = (is_deref, place_with_id.place.base) {
|
||||
self.places.borrowed_temporaries.insert(place_with_id.hir_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> {
|
||||
fn consume(
|
||||
&mut self,
|
||||
place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
|
||||
diag_expr_id: HirId,
|
||||
) {
|
||||
let hir = self.tcx.hir();
|
||||
let parent = match hir.find_parent_node(place_with_id.hir_id) {
|
||||
Some(parent) => parent,
|
||||
None => place_with_id.hir_id,
|
||||
};
|
||||
debug!(
|
||||
"consume {:?}; diag_expr_id={}, using parent {}",
|
||||
place_with_id,
|
||||
hir.node_to_string(diag_expr_id),
|
||||
hir.node_to_string(parent)
|
||||
);
|
||||
place_with_id
|
||||
.try_into()
|
||||
.map_or((), |tracked_value| self.mark_consumed(parent, tracked_value));
|
||||
}
|
||||
|
||||
fn borrow(
|
||||
&mut self,
|
||||
place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
|
||||
diag_expr_id: HirId,
|
||||
bk: rustc_middle::ty::BorrowKind,
|
||||
) {
|
||||
debug!(
|
||||
"borrow: place_with_id = {place_with_id:#?}, diag_expr_id={diag_expr_id:#?}, \
|
||||
borrow_kind={bk:#?}"
|
||||
);
|
||||
|
||||
self.borrow_place(place_with_id);
|
||||
}
|
||||
|
||||
fn copy(
|
||||
&mut self,
|
||||
place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
|
||||
_diag_expr_id: HirId,
|
||||
) {
|
||||
debug!("copy: place_with_id = {place_with_id:?}");
|
||||
|
||||
self.places
|
||||
.borrowed
|
||||
.insert(TrackedValue::from_place_with_projections_allowed(place_with_id));
|
||||
|
||||
// For copied we treat this mostly like a borrow except that we don't add the place
|
||||
// to borrowed_temporaries because the copy is consumed.
|
||||
}
|
||||
|
||||
fn mutate(
|
||||
&mut self,
|
||||
assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>,
|
||||
diag_expr_id: HirId,
|
||||
) {
|
||||
debug!("mutate {assignee_place:?}; diag_expr_id={diag_expr_id:?}");
|
||||
|
||||
if assignee_place.place.base == PlaceBase::Rvalue
|
||||
&& assignee_place.place.projections.is_empty()
|
||||
{
|
||||
// Assigning to an Rvalue is illegal unless done through a dereference. We would have
|
||||
// already gotten a type error, so we will just return here.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the type being assigned needs dropped, then the mutation counts as a borrow
|
||||
// since it is essentially doing `Drop::drop(&mut x); x = new_value;`.
|
||||
//
|
||||
// FIXME(drop-tracking): We need to be more responsible about inference
|
||||
// variables here, since `needs_drop` is a "raw" type query, i.e. it
|
||||
// basically requires types to have been fully resolved.
|
||||
if assignee_place.place.base_ty.needs_drop(self.tcx, self.param_env) {
|
||||
self.places
|
||||
.borrowed
|
||||
.insert(TrackedValue::from_place_with_projections_allowed(assignee_place));
|
||||
}
|
||||
}
|
||||
|
||||
fn bind(
|
||||
&mut self,
|
||||
binding_place: &expr_use_visitor::PlaceWithHirId<'tcx>,
|
||||
diag_expr_id: HirId,
|
||||
) {
|
||||
debug!("bind {binding_place:?}; diag_expr_id={diag_expr_id:?}");
|
||||
}
|
||||
|
||||
fn fake_read(
|
||||
&mut self,
|
||||
place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
|
||||
cause: rustc_middle::mir::FakeReadCause,
|
||||
diag_expr_id: HirId,
|
||||
) {
|
||||
debug!(
|
||||
"fake_read place_with_id={place_with_id:?}; cause={cause:?}; diag_expr_id={diag_expr_id:?}"
|
||||
);
|
||||
|
||||
// fake reads happen in places like the scrutinee of a match expression.
|
||||
// we treat those as a borrow, much like a copy: the idea is that we are
|
||||
// transiently creating a `&T` ref that we can read from to observe the current
|
||||
// value (this `&T` is immediately dropped afterwards).
|
||||
self.borrow_place(place_with_id);
|
||||
}
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
use super::callee::DeferredCallResolution;
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::HirIdMap;
|
||||
use rustc_infer::infer;
|
||||
use rustc_infer::infer::{DefiningAnchor, InferCtxt, InferOk, TyCtxtInferExt};
|
||||
use rustc_middle::ty::fold::TypeFoldable;
|
||||
use rustc_middle::ty::visit::TypeVisitable;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_span::def_id::LocalDefIdMap;
|
||||
use rustc_span::{self, Span};
|
||||
use rustc_trait_selection::infer::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::{
|
||||
self, ObligationCause, ObligationCtxt, TraitEngine, TraitEngineExt as _,
|
||||
};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Closures defined within the function. For example:
|
||||
/// ```ignore (illustrative)
|
||||
/// fn foo() {
|
||||
/// bar(move|| { ... })
|
||||
/// }
|
||||
/// ```
|
||||
/// Here, the function `foo()` and the closure passed to
|
||||
/// `bar()` will each have their own `FnCtxt`, but they will
|
||||
/// share the inherited fields.
|
||||
pub struct Inherited<'tcx> {
|
||||
pub(super) infcx: InferCtxt<'tcx>,
|
||||
|
||||
pub(super) typeck_results: RefCell<ty::TypeckResults<'tcx>>,
|
||||
|
||||
pub(super) locals: RefCell<HirIdMap<super::LocalTy<'tcx>>>,
|
||||
|
||||
pub(super) fulfillment_cx: RefCell<Box<dyn TraitEngine<'tcx>>>,
|
||||
|
||||
// Some additional `Sized` obligations badly affect type inference.
|
||||
// These obligations are added in a later stage of typeck.
|
||||
// Removing these may also cause additional complications, see #101066.
|
||||
pub(super) deferred_sized_obligations:
|
||||
RefCell<Vec<(Ty<'tcx>, Span, traits::ObligationCauseCode<'tcx>)>>,
|
||||
|
||||
// When we process a call like `c()` where `c` is a closure type,
|
||||
// we may not have decided yet whether `c` is a `Fn`, `FnMut`, or
|
||||
// `FnOnce` closure. In that case, we defer full resolution of the
|
||||
// call until upvar inference can kick in and make the
|
||||
// decision. We keep these deferred resolutions grouped by the
|
||||
// def-id of the closure, so that once we decide, we can easily go
|
||||
// back and process them.
|
||||
pub(super) deferred_call_resolutions: RefCell<LocalDefIdMap<Vec<DeferredCallResolution<'tcx>>>>,
|
||||
|
||||
pub(super) deferred_cast_checks: RefCell<Vec<super::cast::CastCheck<'tcx>>>,
|
||||
|
||||
pub(super) deferred_transmute_checks: RefCell<Vec<(Ty<'tcx>, Ty<'tcx>, hir::HirId)>>,
|
||||
|
||||
pub(super) deferred_asm_checks: RefCell<Vec<(&'tcx hir::InlineAsm<'tcx>, hir::HirId)>>,
|
||||
|
||||
pub(super) deferred_generator_interiors:
|
||||
RefCell<Vec<(hir::BodyId, Ty<'tcx>, hir::GeneratorKind)>>,
|
||||
|
||||
pub(super) body_id: Option<hir::BodyId>,
|
||||
|
||||
/// Whenever we introduce an adjustment from `!` into a type variable,
|
||||
/// we record that type variable here. This is later used to inform
|
||||
/// fallback. See the `fallback` module for details.
|
||||
pub(super) diverging_type_vars: RefCell<FxHashSet<Ty<'tcx>>>,
|
||||
}
|
||||
|
||||
impl<'tcx> Deref for Inherited<'tcx> {
|
||||
type Target = InferCtxt<'tcx>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.infcx
|
||||
}
|
||||
}
|
||||
|
||||
/// A temporary returned by `Inherited::build(...)`. This is necessary
|
||||
/// for multiple `InferCtxt` to share the same `typeck_results`
|
||||
/// without using `Rc` or something similar.
|
||||
pub struct InheritedBuilder<'tcx> {
|
||||
infcx: infer::InferCtxtBuilder<'tcx>,
|
||||
def_id: LocalDefId,
|
||||
typeck_results: RefCell<ty::TypeckResults<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'tcx> Inherited<'tcx> {
|
||||
pub fn build(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> InheritedBuilder<'tcx> {
|
||||
let hir_owner = tcx.hir().local_def_id_to_hir_id(def_id).owner;
|
||||
|
||||
InheritedBuilder {
|
||||
infcx: tcx
|
||||
.infer_ctxt()
|
||||
.ignoring_regions()
|
||||
.with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id))
|
||||
.with_normalize_fn_sig_for_diagnostic(Lrc::new(move |infcx, fn_sig| {
|
||||
if fn_sig.has_escaping_bound_vars() {
|
||||
return fn_sig;
|
||||
}
|
||||
infcx.probe(|_| {
|
||||
let ocx = ObligationCtxt::new_in_snapshot(infcx);
|
||||
let normalized_fn_sig = ocx.normalize(
|
||||
ObligationCause::dummy(),
|
||||
// FIXME(compiler-errors): This is probably not the right param-env...
|
||||
infcx.tcx.param_env(def_id),
|
||||
fn_sig,
|
||||
);
|
||||
if ocx.select_all_or_error().is_empty() {
|
||||
let normalized_fn_sig =
|
||||
infcx.resolve_vars_if_possible(normalized_fn_sig);
|
||||
if !normalized_fn_sig.needs_infer() {
|
||||
return normalized_fn_sig;
|
||||
}
|
||||
}
|
||||
fn_sig
|
||||
})
|
||||
})),
|
||||
def_id,
|
||||
typeck_results: RefCell::new(ty::TypeckResults::new(hir_owner)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> InheritedBuilder<'tcx> {
|
||||
pub fn enter<F, R>(mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&Inherited<'tcx>) -> R,
|
||||
{
|
||||
let def_id = self.def_id;
|
||||
f(&Inherited::new(self.infcx.build(), def_id, self.typeck_results))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Inherited<'tcx> {
|
||||
fn new(
|
||||
infcx: InferCtxt<'tcx>,
|
||||
def_id: LocalDefId,
|
||||
typeck_results: RefCell<ty::TypeckResults<'tcx>>,
|
||||
) -> Self {
|
||||
let tcx = infcx.tcx;
|
||||
let body_id = tcx.hir().maybe_body_owned_by(def_id);
|
||||
|
||||
Inherited {
|
||||
typeck_results,
|
||||
infcx,
|
||||
fulfillment_cx: RefCell::new(<dyn TraitEngine<'_>>::new(tcx)),
|
||||
locals: RefCell::new(Default::default()),
|
||||
deferred_sized_obligations: RefCell::new(Vec::new()),
|
||||
deferred_call_resolutions: RefCell::new(Default::default()),
|
||||
deferred_cast_checks: RefCell::new(Vec::new()),
|
||||
deferred_transmute_checks: RefCell::new(Vec::new()),
|
||||
deferred_asm_checks: RefCell::new(Vec::new()),
|
||||
deferred_generator_interiors: RefCell::new(Vec::new()),
|
||||
diverging_type_vars: RefCell::new(Default::default()),
|
||||
body_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub(super) fn register_predicate(&self, obligation: traits::PredicateObligation<'tcx>) {
|
||||
if obligation.has_escaping_bound_vars() {
|
||||
span_bug!(obligation.cause.span, "escaping bound vars in predicate {:?}", obligation);
|
||||
}
|
||||
self.fulfillment_cx.borrow_mut().register_predicate_obligation(self, obligation);
|
||||
}
|
||||
|
||||
pub(super) fn register_predicates<I>(&self, obligations: I)
|
||||
where
|
||||
I: IntoIterator<Item = traits::PredicateObligation<'tcx>>,
|
||||
{
|
||||
for obligation in obligations {
|
||||
self.register_predicate(obligation);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn register_infer_ok_obligations<T>(&self, infer_ok: InferOk<'tcx, T>) -> T {
|
||||
self.register_predicates(infer_ok.obligations);
|
||||
infer_ok.value
|
||||
}
|
||||
|
||||
pub(super) fn normalize_associated_types_in<T>(
|
||||
&self,
|
||||
span: Span,
|
||||
body_id: hir::HirId,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
value: T,
|
||||
) -> T
|
||||
where
|
||||
T: TypeFoldable<'tcx>,
|
||||
{
|
||||
self.normalize_associated_types_in_with_cause(
|
||||
ObligationCause::misc(span, body_id),
|
||||
param_env,
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn normalize_associated_types_in_with_cause<T>(
|
||||
&self,
|
||||
cause: ObligationCause<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
value: T,
|
||||
) -> T
|
||||
where
|
||||
T: TypeFoldable<'tcx>,
|
||||
{
|
||||
let ok = self.partially_normalize_associated_types_in(cause, param_env, value);
|
||||
debug!(?ok);
|
||||
self.register_infer_ok_obligations(ok)
|
||||
}
|
||||
}
|
|
@ -1,594 +0,0 @@
|
|||
use super::{probe, MethodCallee};
|
||||
|
||||
use crate::astconv::{AstConv, CreateSubstsForGenericArgsCtxt, IsMethodCall};
|
||||
use crate::check::{callee, FnCtxt};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::GenericArg;
|
||||
use rustc_infer::infer::{self, InferOk};
|
||||
use rustc_middle::traits::{ObligationCauseCode, UnifyReceiverContext};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast};
|
||||
use rustc_middle::ty::adjustment::{AllowTwoPhase, AutoBorrow, AutoBorrowMutability};
|
||||
use rustc_middle::ty::fold::TypeFoldable;
|
||||
use rustc_middle::ty::subst::{self, SubstsRef};
|
||||
use rustc_middle::ty::{self, GenericParamDefKind, Ty};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::traits;
|
||||
|
||||
use std::iter;
|
||||
use std::ops::Deref;
|
||||
|
||||
struct ConfirmContext<'a, 'tcx> {
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
span: Span,
|
||||
self_expr: &'tcx hir::Expr<'tcx>,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Deref for ConfirmContext<'a, 'tcx> {
|
||||
type Target = FnCtxt<'a, 'tcx>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.fcx
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfirmResult<'tcx> {
|
||||
pub callee: MethodCallee<'tcx>,
|
||||
pub illegal_sized_bound: Option<Span>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
pub fn confirm_method(
|
||||
&self,
|
||||
span: Span,
|
||||
self_expr: &'tcx hir::Expr<'tcx>,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
unadjusted_self_ty: Ty<'tcx>,
|
||||
pick: probe::Pick<'tcx>,
|
||||
segment: &hir::PathSegment<'_>,
|
||||
) -> ConfirmResult<'tcx> {
|
||||
debug!(
|
||||
"confirm(unadjusted_self_ty={:?}, pick={:?}, generic_args={:?})",
|
||||
unadjusted_self_ty, pick, segment.args,
|
||||
);
|
||||
|
||||
let mut confirm_cx = ConfirmContext::new(self, span, self_expr, call_expr);
|
||||
confirm_cx.confirm(unadjusted_self_ty, pick, segment)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ConfirmContext<'a, 'tcx> {
|
||||
fn new(
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
span: Span,
|
||||
self_expr: &'tcx hir::Expr<'tcx>,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
) -> ConfirmContext<'a, 'tcx> {
|
||||
ConfirmContext { fcx, span, self_expr, call_expr }
|
||||
}
|
||||
|
||||
fn confirm(
|
||||
&mut self,
|
||||
unadjusted_self_ty: Ty<'tcx>,
|
||||
pick: probe::Pick<'tcx>,
|
||||
segment: &hir::PathSegment<'_>,
|
||||
) -> ConfirmResult<'tcx> {
|
||||
// Adjust the self expression the user provided and obtain the adjusted type.
|
||||
let self_ty = self.adjust_self_ty(unadjusted_self_ty, &pick);
|
||||
|
||||
// Create substitutions for the method's type parameters.
|
||||
let rcvr_substs = self.fresh_receiver_substs(self_ty, &pick);
|
||||
let all_substs = self.instantiate_method_substs(&pick, segment, rcvr_substs);
|
||||
|
||||
debug!("rcvr_substs={rcvr_substs:?}, all_substs={all_substs:?}");
|
||||
|
||||
// Create the final signature for the method, replacing late-bound regions.
|
||||
let (method_sig, method_predicates) = self.instantiate_method_sig(&pick, all_substs);
|
||||
|
||||
// If there is a `Self: Sized` bound and `Self` is a trait object, it is possible that
|
||||
// something which derefs to `Self` actually implements the trait and the caller
|
||||
// wanted to make a static dispatch on it but forgot to import the trait.
|
||||
// See test `src/test/ui/issue-35976.rs`.
|
||||
//
|
||||
// In that case, we'll error anyway, but we'll also re-run the search with all traits
|
||||
// in scope, and if we find another method which can be used, we'll output an
|
||||
// appropriate hint suggesting to import the trait.
|
||||
let filler_substs = rcvr_substs
|
||||
.extend_to(self.tcx, pick.item.def_id, |def, _| self.tcx.mk_param_from_def(def));
|
||||
let illegal_sized_bound = self.predicates_require_illegal_sized_bound(
|
||||
&self.tcx.predicates_of(pick.item.def_id).instantiate(self.tcx, filler_substs),
|
||||
);
|
||||
|
||||
// Unify the (adjusted) self type with what the method expects.
|
||||
//
|
||||
// SUBTLE: if we want good error messages, because of "guessing" while matching
|
||||
// traits, no trait system method can be called before this point because they
|
||||
// could alter our Self-type, except for normalizing the receiver from the
|
||||
// signature (which is also done during probing).
|
||||
let method_sig_rcvr = self.normalize_associated_types_in(self.span, method_sig.inputs()[0]);
|
||||
debug!(
|
||||
"confirm: self_ty={:?} method_sig_rcvr={:?} method_sig={:?} method_predicates={:?}",
|
||||
self_ty, method_sig_rcvr, method_sig, method_predicates
|
||||
);
|
||||
self.unify_receivers(self_ty, method_sig_rcvr, &pick, all_substs);
|
||||
|
||||
let (method_sig, method_predicates) =
|
||||
self.normalize_associated_types_in(self.span, (method_sig, method_predicates));
|
||||
let method_sig = ty::Binder::dummy(method_sig);
|
||||
|
||||
// Make sure nobody calls `drop()` explicitly.
|
||||
self.enforce_illegal_method_limitations(&pick);
|
||||
|
||||
// Add any trait/regions obligations specified on the method's type parameters.
|
||||
// We won't add these if we encountered an illegal sized bound, so that we can use
|
||||
// a custom error in that case.
|
||||
if illegal_sized_bound.is_none() {
|
||||
self.add_obligations(
|
||||
self.tcx.mk_fn_ptr(method_sig),
|
||||
all_substs,
|
||||
method_predicates,
|
||||
pick.item.def_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Create the final `MethodCallee`.
|
||||
let callee = MethodCallee {
|
||||
def_id: pick.item.def_id,
|
||||
substs: all_substs,
|
||||
sig: method_sig.skip_binder(),
|
||||
};
|
||||
ConfirmResult { callee, illegal_sized_bound }
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// ADJUSTMENTS
|
||||
|
||||
fn adjust_self_ty(
|
||||
&mut self,
|
||||
unadjusted_self_ty: Ty<'tcx>,
|
||||
pick: &probe::Pick<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
// Commit the autoderefs by calling `autoderef` again, but this
|
||||
// time writing the results into the various typeck results.
|
||||
let mut autoderef =
|
||||
self.autoderef_overloaded_span(self.span, unadjusted_self_ty, self.call_expr.span);
|
||||
let Some((ty, n)) = autoderef.nth(pick.autoderefs) else {
|
||||
return self.tcx.ty_error_with_message(
|
||||
rustc_span::DUMMY_SP,
|
||||
&format!("failed autoderef {}", pick.autoderefs),
|
||||
);
|
||||
};
|
||||
assert_eq!(n, pick.autoderefs);
|
||||
|
||||
let mut adjustments = self.adjust_steps(&autoderef);
|
||||
let mut target = self.structurally_resolved_type(autoderef.span(), ty);
|
||||
|
||||
match pick.autoref_or_ptr_adjustment {
|
||||
Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl, unsize }) => {
|
||||
let region = self.next_region_var(infer::Autoref(self.span));
|
||||
// Type we're wrapping in a reference, used later for unsizing
|
||||
let base_ty = target;
|
||||
|
||||
target = self.tcx.mk_ref(region, ty::TypeAndMut { mutbl, ty: target });
|
||||
let mutbl = match mutbl {
|
||||
hir::Mutability::Not => AutoBorrowMutability::Not,
|
||||
hir::Mutability::Mut => AutoBorrowMutability::Mut {
|
||||
// Method call receivers are the primary use case
|
||||
// for two-phase borrows.
|
||||
allow_two_phase_borrow: AllowTwoPhase::Yes,
|
||||
},
|
||||
};
|
||||
adjustments.push(Adjustment {
|
||||
kind: Adjust::Borrow(AutoBorrow::Ref(region, mutbl)),
|
||||
target,
|
||||
});
|
||||
|
||||
if unsize {
|
||||
let unsized_ty = if let ty::Array(elem_ty, _) = base_ty.kind() {
|
||||
self.tcx.mk_slice(*elem_ty)
|
||||
} else {
|
||||
bug!(
|
||||
"AutorefOrPtrAdjustment's unsize flag should only be set for array ty, found {}",
|
||||
base_ty
|
||||
)
|
||||
};
|
||||
target = self
|
||||
.tcx
|
||||
.mk_ref(region, ty::TypeAndMut { mutbl: mutbl.into(), ty: unsized_ty });
|
||||
adjustments
|
||||
.push(Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target });
|
||||
}
|
||||
}
|
||||
Some(probe::AutorefOrPtrAdjustment::ToConstPtr) => {
|
||||
target = match target.kind() {
|
||||
&ty::RawPtr(ty::TypeAndMut { ty, mutbl }) => {
|
||||
assert_eq!(mutbl, hir::Mutability::Mut);
|
||||
self.tcx.mk_ptr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ty })
|
||||
}
|
||||
other => panic!("Cannot adjust receiver type {:?} to const ptr", other),
|
||||
};
|
||||
|
||||
adjustments.push(Adjustment {
|
||||
kind: Adjust::Pointer(PointerCast::MutToConstPointer),
|
||||
target,
|
||||
});
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
self.register_predicates(autoderef.into_obligations());
|
||||
|
||||
// Write out the final adjustments.
|
||||
self.apply_adjustments(self.self_expr, adjustments);
|
||||
|
||||
target
|
||||
}
|
||||
|
||||
/// Returns a set of substitutions for the method *receiver* where all type and region
|
||||
/// parameters are instantiated with fresh variables. This substitution does not include any
|
||||
/// parameters declared on the method itself.
|
||||
///
|
||||
/// Note that this substitution may include late-bound regions from the impl level. If so,
|
||||
/// these are instantiated later in the `instantiate_method_sig` routine.
|
||||
fn fresh_receiver_substs(
|
||||
&mut self,
|
||||
self_ty: Ty<'tcx>,
|
||||
pick: &probe::Pick<'tcx>,
|
||||
) -> SubstsRef<'tcx> {
|
||||
match pick.kind {
|
||||
probe::InherentImplPick => {
|
||||
let impl_def_id = pick.item.container_id(self.tcx);
|
||||
assert!(
|
||||
self.tcx.impl_trait_ref(impl_def_id).is_none(),
|
||||
"impl {:?} is not an inherent impl",
|
||||
impl_def_id
|
||||
);
|
||||
self.fresh_substs_for_item(self.span, impl_def_id)
|
||||
}
|
||||
|
||||
probe::ObjectPick => {
|
||||
let trait_def_id = pick.item.container_id(self.tcx);
|
||||
self.extract_existential_trait_ref(self_ty, |this, object_ty, principal| {
|
||||
// The object data has no entry for the Self
|
||||
// Type. For the purposes of this method call, we
|
||||
// substitute the object type itself. This
|
||||
// wouldn't be a sound substitution in all cases,
|
||||
// since each instance of the object type is a
|
||||
// different existential and hence could match
|
||||
// distinct types (e.g., if `Self` appeared as an
|
||||
// argument type), but those cases have already
|
||||
// been ruled out when we deemed the trait to be
|
||||
// "object safe".
|
||||
let original_poly_trait_ref = principal.with_self_ty(this.tcx, object_ty);
|
||||
let upcast_poly_trait_ref = this.upcast(original_poly_trait_ref, trait_def_id);
|
||||
let upcast_trait_ref =
|
||||
this.replace_bound_vars_with_fresh_vars(upcast_poly_trait_ref);
|
||||
debug!(
|
||||
"original_poly_trait_ref={:?} upcast_trait_ref={:?} target_trait={:?}",
|
||||
original_poly_trait_ref, upcast_trait_ref, trait_def_id
|
||||
);
|
||||
upcast_trait_ref.substs
|
||||
})
|
||||
}
|
||||
|
||||
probe::TraitPick => {
|
||||
let trait_def_id = pick.item.container_id(self.tcx);
|
||||
|
||||
// Make a trait reference `$0 : Trait<$1...$n>`
|
||||
// consisting entirely of type variables. Later on in
|
||||
// the process we will unify the transformed-self-type
|
||||
// of the method with the actual type in order to
|
||||
// unify some of these variables.
|
||||
self.fresh_substs_for_item(self.span, trait_def_id)
|
||||
}
|
||||
|
||||
probe::WhereClausePick(poly_trait_ref) => {
|
||||
// Where clauses can have bound regions in them. We need to instantiate
|
||||
// those to convert from a poly-trait-ref to a trait-ref.
|
||||
self.replace_bound_vars_with_fresh_vars(poly_trait_ref).substs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_existential_trait_ref<R, F>(&mut self, self_ty: Ty<'tcx>, mut closure: F) -> R
|
||||
where
|
||||
F: FnMut(&mut ConfirmContext<'a, 'tcx>, Ty<'tcx>, ty::PolyExistentialTraitRef<'tcx>) -> R,
|
||||
{
|
||||
// If we specified that this is an object method, then the
|
||||
// self-type ought to be something that can be dereferenced to
|
||||
// yield an object-type (e.g., `&Object` or `Box<Object>`
|
||||
// etc).
|
||||
|
||||
// FIXME: this feels, like, super dubious
|
||||
self.fcx
|
||||
.autoderef(self.span, self_ty)
|
||||
.include_raw_pointers()
|
||||
.find_map(|(ty, _)| match ty.kind() {
|
||||
ty::Dynamic(data, ..) => Some(closure(
|
||||
self,
|
||||
ty,
|
||||
data.principal().unwrap_or_else(|| {
|
||||
span_bug!(self.span, "calling trait method on empty object?")
|
||||
}),
|
||||
)),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
span_bug!(
|
||||
self.span,
|
||||
"self-type `{}` for ObjectPick never dereferenced to an object",
|
||||
self_ty
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn instantiate_method_substs(
|
||||
&mut self,
|
||||
pick: &probe::Pick<'tcx>,
|
||||
seg: &hir::PathSegment<'_>,
|
||||
parent_substs: SubstsRef<'tcx>,
|
||||
) -> SubstsRef<'tcx> {
|
||||
// Determine the values for the generic parameters of the method.
|
||||
// If they were not explicitly supplied, just construct fresh
|
||||
// variables.
|
||||
let generics = self.tcx.generics_of(pick.item.def_id);
|
||||
|
||||
let arg_count_correct = <dyn AstConv<'_>>::check_generic_arg_count_for_call(
|
||||
self.tcx,
|
||||
self.span,
|
||||
pick.item.def_id,
|
||||
generics,
|
||||
seg,
|
||||
IsMethodCall::Yes,
|
||||
);
|
||||
|
||||
// Create subst for early-bound lifetime parameters, combining
|
||||
// parameters from the type and those from the method.
|
||||
assert_eq!(generics.parent_count, parent_substs.len());
|
||||
|
||||
struct MethodSubstsCtxt<'a, 'tcx> {
|
||||
cfcx: &'a ConfirmContext<'a, 'tcx>,
|
||||
pick: &'a probe::Pick<'tcx>,
|
||||
seg: &'a hir::PathSegment<'a>,
|
||||
}
|
||||
impl<'a, 'tcx> CreateSubstsForGenericArgsCtxt<'a, 'tcx> for MethodSubstsCtxt<'a, 'tcx> {
|
||||
fn args_for_def_id(
|
||||
&mut self,
|
||||
def_id: DefId,
|
||||
) -> (Option<&'a hir::GenericArgs<'a>>, bool) {
|
||||
if def_id == self.pick.item.def_id {
|
||||
if let Some(data) = self.seg.args {
|
||||
return (Some(data), false);
|
||||
}
|
||||
}
|
||||
(None, false)
|
||||
}
|
||||
|
||||
fn provided_kind(
|
||||
&mut self,
|
||||
param: &ty::GenericParamDef,
|
||||
arg: &GenericArg<'_>,
|
||||
) -> subst::GenericArg<'tcx> {
|
||||
match (¶m.kind, arg) {
|
||||
(GenericParamDefKind::Lifetime, GenericArg::Lifetime(lt)) => {
|
||||
<dyn AstConv<'_>>::ast_region_to_region(self.cfcx.fcx, lt, Some(param))
|
||||
.into()
|
||||
}
|
||||
(GenericParamDefKind::Type { .. }, GenericArg::Type(ty)) => {
|
||||
self.cfcx.to_ty(ty).into()
|
||||
}
|
||||
(GenericParamDefKind::Const { .. }, GenericArg::Const(ct)) => {
|
||||
self.cfcx.const_arg_to_const(&ct.value, param.def_id).into()
|
||||
}
|
||||
(GenericParamDefKind::Type { .. }, GenericArg::Infer(inf)) => {
|
||||
self.cfcx.ty_infer(Some(param), inf.span).into()
|
||||
}
|
||||
(GenericParamDefKind::Const { .. }, GenericArg::Infer(inf)) => {
|
||||
let tcx = self.cfcx.tcx();
|
||||
self.cfcx.ct_infer(tcx.type_of(param.def_id), Some(param), inf.span).into()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn inferred_kind(
|
||||
&mut self,
|
||||
_substs: Option<&[subst::GenericArg<'tcx>]>,
|
||||
param: &ty::GenericParamDef,
|
||||
_infer_args: bool,
|
||||
) -> subst::GenericArg<'tcx> {
|
||||
self.cfcx.var_for_def(self.cfcx.span, param)
|
||||
}
|
||||
}
|
||||
<dyn AstConv<'_>>::create_substs_for_generic_args(
|
||||
self.tcx,
|
||||
pick.item.def_id,
|
||||
parent_substs,
|
||||
false,
|
||||
None,
|
||||
&arg_count_correct,
|
||||
&mut MethodSubstsCtxt { cfcx: self, pick, seg },
|
||||
)
|
||||
}
|
||||
|
||||
fn unify_receivers(
|
||||
&mut self,
|
||||
self_ty: Ty<'tcx>,
|
||||
method_self_ty: Ty<'tcx>,
|
||||
pick: &probe::Pick<'tcx>,
|
||||
substs: SubstsRef<'tcx>,
|
||||
) {
|
||||
debug!(
|
||||
"unify_receivers: self_ty={:?} method_self_ty={:?} span={:?} pick={:?}",
|
||||
self_ty, method_self_ty, self.span, pick
|
||||
);
|
||||
let cause = self.cause(
|
||||
self.span,
|
||||
ObligationCauseCode::UnifyReceiver(Box::new(UnifyReceiverContext {
|
||||
assoc_item: pick.item,
|
||||
param_env: self.param_env,
|
||||
substs,
|
||||
})),
|
||||
);
|
||||
match self.at(&cause, self.param_env).sup(method_self_ty, self_ty) {
|
||||
Ok(InferOk { obligations, value: () }) => {
|
||||
self.register_predicates(obligations);
|
||||
}
|
||||
Err(_) => {
|
||||
span_bug!(
|
||||
self.span,
|
||||
"{} was a subtype of {} but now is not?",
|
||||
self_ty,
|
||||
method_self_ty
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this returns the *unnormalized* predicates and method sig. Because of
|
||||
// inference guessing, the predicates and method signature can't be normalized
|
||||
// until we unify the `Self` type.
|
||||
fn instantiate_method_sig(
|
||||
&mut self,
|
||||
pick: &probe::Pick<'tcx>,
|
||||
all_substs: SubstsRef<'tcx>,
|
||||
) -> (ty::FnSig<'tcx>, ty::InstantiatedPredicates<'tcx>) {
|
||||
debug!("instantiate_method_sig(pick={:?}, all_substs={:?})", pick, all_substs);
|
||||
|
||||
// Instantiate the bounds on the method with the
|
||||
// type/early-bound-regions substitutions performed. There can
|
||||
// be no late-bound regions appearing here.
|
||||
let def_id = pick.item.def_id;
|
||||
let method_predicates = self.tcx.predicates_of(def_id).instantiate(self.tcx, all_substs);
|
||||
|
||||
debug!("method_predicates after subst = {:?}", method_predicates);
|
||||
|
||||
let sig = self.tcx.bound_fn_sig(def_id);
|
||||
|
||||
let sig = sig.subst(self.tcx, all_substs);
|
||||
debug!("type scheme substituted, sig={:?}", sig);
|
||||
|
||||
let sig = self.replace_bound_vars_with_fresh_vars(sig);
|
||||
debug!("late-bound lifetimes from method instantiated, sig={:?}", sig);
|
||||
|
||||
(sig, method_predicates)
|
||||
}
|
||||
|
||||
fn add_obligations(
|
||||
&mut self,
|
||||
fty: Ty<'tcx>,
|
||||
all_substs: SubstsRef<'tcx>,
|
||||
method_predicates: ty::InstantiatedPredicates<'tcx>,
|
||||
def_id: DefId,
|
||||
) {
|
||||
debug!(
|
||||
"add_obligations: fty={:?} all_substs={:?} method_predicates={:?} def_id={:?}",
|
||||
fty, all_substs, method_predicates, def_id
|
||||
);
|
||||
|
||||
// FIXME: could replace with the following, but we already calculated `method_predicates`,
|
||||
// so we just call `predicates_for_generics` directly to avoid redoing work.
|
||||
// `self.add_required_obligations(self.span, def_id, &all_substs);`
|
||||
for obligation in traits::predicates_for_generics(
|
||||
|idx, span| {
|
||||
let code = if span.is_dummy() {
|
||||
ObligationCauseCode::ExprItemObligation(def_id, self.call_expr.hir_id, idx)
|
||||
} else {
|
||||
ObligationCauseCode::ExprBindingObligation(
|
||||
def_id,
|
||||
span,
|
||||
self.call_expr.hir_id,
|
||||
idx,
|
||||
)
|
||||
};
|
||||
traits::ObligationCause::new(self.span, self.body_id, code)
|
||||
},
|
||||
self.param_env,
|
||||
method_predicates,
|
||||
) {
|
||||
self.register_predicate(obligation);
|
||||
}
|
||||
|
||||
// this is a projection from a trait reference, so we have to
|
||||
// make sure that the trait reference inputs are well-formed.
|
||||
self.add_wf_bounds(all_substs, self.call_expr);
|
||||
|
||||
// the function type must also be well-formed (this is not
|
||||
// implied by the substs being well-formed because of inherent
|
||||
// impls and late-bound regions - see issue #28609).
|
||||
self.register_wf_obligation(fty.into(), self.span, traits::WellFormed(None));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// MISCELLANY
|
||||
|
||||
fn predicates_require_illegal_sized_bound(
|
||||
&self,
|
||||
predicates: &ty::InstantiatedPredicates<'tcx>,
|
||||
) -> Option<Span> {
|
||||
let sized_def_id = self.tcx.lang_items().sized_trait()?;
|
||||
|
||||
traits::elaborate_predicates(self.tcx, predicates.predicates.iter().copied())
|
||||
// We don't care about regions here.
|
||||
.filter_map(|obligation| match obligation.predicate.kind().skip_binder() {
|
||||
ty::PredicateKind::Trait(trait_pred) if trait_pred.def_id() == sized_def_id => {
|
||||
let span = iter::zip(&predicates.predicates, &predicates.spans)
|
||||
.find_map(
|
||||
|(p, span)| {
|
||||
if *p == obligation.predicate { Some(*span) } else { None }
|
||||
},
|
||||
)
|
||||
.unwrap_or(rustc_span::DUMMY_SP);
|
||||
Some((trait_pred, span))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.find_map(|(trait_pred, span)| match trait_pred.self_ty().kind() {
|
||||
ty::Dynamic(..) => Some(span),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn enforce_illegal_method_limitations(&self, pick: &probe::Pick<'_>) {
|
||||
// Disallow calls to the method `drop` defined in the `Drop` trait.
|
||||
if let Some(trait_def_id) = pick.item.trait_container(self.tcx) {
|
||||
callee::check_legal_trait_for_method_call(
|
||||
self.tcx,
|
||||
self.span,
|
||||
Some(self.self_expr.span),
|
||||
self.call_expr.span,
|
||||
trait_def_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn upcast(
|
||||
&mut self,
|
||||
source_trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
target_trait_def_id: DefId,
|
||||
) -> ty::PolyTraitRef<'tcx> {
|
||||
let upcast_trait_refs =
|
||||
traits::upcast_choices(self.tcx, source_trait_ref, target_trait_def_id);
|
||||
|
||||
// must be exactly one trait ref or we'd get an ambig error etc
|
||||
if upcast_trait_refs.len() != 1 {
|
||||
span_bug!(
|
||||
self.span,
|
||||
"cannot uniquely upcast `{:?}` to `{:?}`: `{:?}`",
|
||||
source_trait_ref,
|
||||
target_trait_def_id,
|
||||
upcast_trait_refs
|
||||
);
|
||||
}
|
||||
|
||||
upcast_trait_refs.into_iter().next().unwrap()
|
||||
}
|
||||
|
||||
fn replace_bound_vars_with_fresh_vars<T>(&self, value: ty::Binder<'tcx, T>) -> T
|
||||
where
|
||||
T: TypeFoldable<'tcx> + Copy,
|
||||
{
|
||||
self.fcx.replace_bound_vars_with_fresh_vars(self.span, infer::FnCall, value)
|
||||
}
|
||||
}
|
|
@ -1,625 +0,0 @@
|
|||
//! Method lookup: the secret sauce of Rust. See the [rustc dev guide] for more information.
|
||||
//!
|
||||
//! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/method-lookup.html
|
||||
|
||||
mod confirm;
|
||||
mod prelude2021;
|
||||
pub mod probe;
|
||||
mod suggest;
|
||||
|
||||
pub use self::suggest::SelfSource;
|
||||
pub use self::MethodError::*;
|
||||
|
||||
use crate::check::{Expectation, FnCtxt};
|
||||
use crate::ObligationCause;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_errors::{Applicability, Diagnostic};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::{CtorOf, DefKind, Namespace};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_infer::infer::{self, InferOk};
|
||||
use rustc_middle::ty::subst::{InternalSubsts, SubstsRef};
|
||||
use rustc_middle::ty::{self, DefIdTree, GenericParamDefKind, ToPredicate, Ty, TypeVisitable};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::traits;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
|
||||
use self::probe::{IsSuggestion, ProbeScope};
|
||||
|
||||
pub fn provide(providers: &mut ty::query::Providers) {
|
||||
probe::provide(providers);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MethodCallee<'tcx> {
|
||||
/// Impl method ID, for inherent methods, or trait method ID, otherwise.
|
||||
pub def_id: DefId,
|
||||
pub substs: SubstsRef<'tcx>,
|
||||
|
||||
/// Instantiated method signature, i.e., it has been
|
||||
/// substituted, normalized, and has had late-bound
|
||||
/// lifetimes replaced with inference variables.
|
||||
pub sig: ty::FnSig<'tcx>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MethodError<'tcx> {
|
||||
// Did not find an applicable method, but we did find various near-misses that may work.
|
||||
NoMatch(NoMatchData<'tcx>),
|
||||
|
||||
// Multiple methods might apply.
|
||||
Ambiguity(Vec<CandidateSource>),
|
||||
|
||||
// Found an applicable method, but it is not visible. The third argument contains a list of
|
||||
// not-in-scope traits which may work.
|
||||
PrivateMatch(DefKind, DefId, Vec<DefId>),
|
||||
|
||||
// Found a `Self: Sized` bound where `Self` is a trait object, also the caller may have
|
||||
// forgotten to import a trait.
|
||||
IllegalSizedBound(Vec<DefId>, bool, Span),
|
||||
|
||||
// Found a match, but the return type is wrong
|
||||
BadReturnType,
|
||||
}
|
||||
|
||||
// Contains a list of static methods that may apply, a list of unsatisfied trait predicates which
|
||||
// could lead to matches if satisfied, and a list of not-in-scope traits which may work.
|
||||
#[derive(Debug)]
|
||||
pub struct NoMatchData<'tcx> {
|
||||
pub static_candidates: Vec<CandidateSource>,
|
||||
pub unsatisfied_predicates:
|
||||
Vec<(ty::Predicate<'tcx>, Option<ty::Predicate<'tcx>>, Option<ObligationCause<'tcx>>)>,
|
||||
pub out_of_scope_traits: Vec<DefId>,
|
||||
pub lev_candidate: Option<ty::AssocItem>,
|
||||
pub mode: probe::Mode,
|
||||
}
|
||||
|
||||
// A pared down enum describing just the places from which a method
|
||||
// candidate can arise. Used for error reporting only.
|
||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub enum CandidateSource {
|
||||
Impl(DefId),
|
||||
Trait(DefId /* trait id */),
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
/// Determines whether the type `self_ty` supports a method name `method_name` or not.
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub fn method_exists(
|
||||
&self,
|
||||
method_name: Ident,
|
||||
self_ty: Ty<'tcx>,
|
||||
call_expr_id: hir::HirId,
|
||||
allow_private: bool,
|
||||
) -> bool {
|
||||
let mode = probe::Mode::MethodCall;
|
||||
match self.probe_for_name(
|
||||
method_name.span,
|
||||
mode,
|
||||
method_name,
|
||||
IsSuggestion(false),
|
||||
self_ty,
|
||||
call_expr_id,
|
||||
ProbeScope::TraitsInScope,
|
||||
) {
|
||||
Ok(..) => true,
|
||||
Err(NoMatch(..)) => false,
|
||||
Err(Ambiguity(..)) => true,
|
||||
Err(PrivateMatch(..)) => allow_private,
|
||||
Err(IllegalSizedBound(..)) => true,
|
||||
Err(BadReturnType) => bug!("no return type expectations but got BadReturnType"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a suggestion to call the given method to the provided diagnostic.
|
||||
#[instrument(level = "debug", skip(self, err, call_expr))]
|
||||
pub(crate) fn suggest_method_call(
|
||||
&self,
|
||||
err: &mut Diagnostic,
|
||||
msg: &str,
|
||||
method_name: Ident,
|
||||
self_ty: Ty<'tcx>,
|
||||
call_expr: &hir::Expr<'_>,
|
||||
span: Option<Span>,
|
||||
) {
|
||||
let params = self
|
||||
.probe_for_name(
|
||||
method_name.span,
|
||||
probe::Mode::MethodCall,
|
||||
method_name,
|
||||
IsSuggestion(false),
|
||||
self_ty,
|
||||
call_expr.hir_id,
|
||||
ProbeScope::TraitsInScope,
|
||||
)
|
||||
.map(|pick| {
|
||||
let sig = self.tcx.fn_sig(pick.item.def_id);
|
||||
sig.inputs().skip_binder().len().saturating_sub(1)
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
// Account for `foo.bar<T>`;
|
||||
let sugg_span = span.unwrap_or(call_expr.span).shrink_to_hi();
|
||||
let (suggestion, applicability) = (
|
||||
format!("({})", (0..params).map(|_| "_").collect::<Vec<_>>().join(", ")),
|
||||
if params > 0 { Applicability::HasPlaceholders } else { Applicability::MaybeIncorrect },
|
||||
);
|
||||
|
||||
err.span_suggestion_verbose(sugg_span, msg, suggestion, applicability);
|
||||
}
|
||||
|
||||
/// Performs method lookup. If lookup is successful, it will return the callee
|
||||
/// and store an appropriate adjustment for the self-expr. In some cases it may
|
||||
/// report an error (e.g., invoking the `drop` method).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// Given a method call like `foo.bar::<T1,...Tn>(a, b + 1, ...)`:
|
||||
///
|
||||
/// * `self`: the surrounding `FnCtxt` (!)
|
||||
/// * `self_ty`: the (unadjusted) type of the self expression (`foo`)
|
||||
/// * `segment`: the name and generic arguments of the method (`bar::<T1, ...Tn>`)
|
||||
/// * `span`: the span for the method call
|
||||
/// * `call_expr`: the complete method call: (`foo.bar::<T1,...Tn>(...)`)
|
||||
/// * `self_expr`: the self expression (`foo`)
|
||||
/// * `args`: the expressions of the arguments (`a, b + 1, ...`)
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
pub fn lookup_method(
|
||||
&self,
|
||||
self_ty: Ty<'tcx>,
|
||||
segment: &hir::PathSegment<'_>,
|
||||
span: Span,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
self_expr: &'tcx hir::Expr<'tcx>,
|
||||
args: &'tcx [hir::Expr<'tcx>],
|
||||
) -> Result<MethodCallee<'tcx>, MethodError<'tcx>> {
|
||||
let pick =
|
||||
self.lookup_probe(span, segment.ident, self_ty, call_expr, ProbeScope::TraitsInScope)?;
|
||||
|
||||
self.lint_dot_call_from_2018(self_ty, segment, span, call_expr, self_expr, &pick, args);
|
||||
|
||||
for import_id in &pick.import_ids {
|
||||
debug!("used_trait_import: {:?}", import_id);
|
||||
Lrc::get_mut(&mut self.typeck_results.borrow_mut().used_trait_imports)
|
||||
.unwrap()
|
||||
.insert(*import_id);
|
||||
}
|
||||
|
||||
self.tcx.check_stability(pick.item.def_id, Some(call_expr.hir_id), span, None);
|
||||
|
||||
let result =
|
||||
self.confirm_method(span, self_expr, call_expr, self_ty, pick.clone(), segment);
|
||||
debug!("result = {:?}", result);
|
||||
|
||||
if let Some(span) = result.illegal_sized_bound {
|
||||
let mut needs_mut = false;
|
||||
if let ty::Ref(region, t_type, mutability) = self_ty.kind() {
|
||||
let trait_type = self
|
||||
.tcx
|
||||
.mk_ref(*region, ty::TypeAndMut { ty: *t_type, mutbl: mutability.invert() });
|
||||
// We probe again to see if there might be a borrow mutability discrepancy.
|
||||
match self.lookup_probe(
|
||||
span,
|
||||
segment.ident,
|
||||
trait_type,
|
||||
call_expr,
|
||||
ProbeScope::TraitsInScope,
|
||||
) {
|
||||
Ok(ref new_pick) if *new_pick != pick => {
|
||||
needs_mut = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// We probe again, taking all traits into account (not only those in scope).
|
||||
let mut candidates = match self.lookup_probe(
|
||||
span,
|
||||
segment.ident,
|
||||
self_ty,
|
||||
call_expr,
|
||||
ProbeScope::AllTraits,
|
||||
) {
|
||||
// If we find a different result the caller probably forgot to import a trait.
|
||||
Ok(ref new_pick) if *new_pick != pick => vec![new_pick.item.container_id(self.tcx)],
|
||||
Err(Ambiguity(ref sources)) => sources
|
||||
.iter()
|
||||
.filter_map(|source| {
|
||||
match *source {
|
||||
// Note: this cannot come from an inherent impl,
|
||||
// because the first probing succeeded.
|
||||
CandidateSource::Impl(def) => self.tcx.trait_id_of_impl(def),
|
||||
CandidateSource::Trait(_) => None,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
candidates.retain(|candidate| *candidate != self.tcx.parent(result.callee.def_id));
|
||||
|
||||
return Err(IllegalSizedBound(candidates, needs_mut, span));
|
||||
}
|
||||
|
||||
Ok(result.callee)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, call_expr))]
|
||||
pub fn lookup_probe(
|
||||
&self,
|
||||
span: Span,
|
||||
method_name: Ident,
|
||||
self_ty: Ty<'tcx>,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
scope: ProbeScope,
|
||||
) -> probe::PickResult<'tcx> {
|
||||
let mode = probe::Mode::MethodCall;
|
||||
let self_ty = self.resolve_vars_if_possible(self_ty);
|
||||
self.probe_for_name(
|
||||
span,
|
||||
mode,
|
||||
method_name,
|
||||
IsSuggestion(false),
|
||||
self_ty,
|
||||
call_expr.hir_id,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn obligation_for_method(
|
||||
&self,
|
||||
span: Span,
|
||||
trait_def_id: DefId,
|
||||
self_ty: Ty<'tcx>,
|
||||
opt_input_types: Option<&[Ty<'tcx>]>,
|
||||
) -> (traits::Obligation<'tcx, ty::Predicate<'tcx>>, &'tcx ty::List<ty::subst::GenericArg<'tcx>>)
|
||||
{
|
||||
// Construct a trait-reference `self_ty : Trait<input_tys>`
|
||||
let substs = InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| {
|
||||
match param.kind {
|
||||
GenericParamDefKind::Lifetime | GenericParamDefKind::Const { .. } => {}
|
||||
GenericParamDefKind::Type { .. } => {
|
||||
if param.index == 0 {
|
||||
return self_ty.into();
|
||||
} else if let Some(input_types) = opt_input_types {
|
||||
return input_types[param.index as usize - 1].into();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.var_for_def(span, param)
|
||||
});
|
||||
|
||||
let trait_ref = ty::TraitRef::new(trait_def_id, substs);
|
||||
|
||||
// Construct an obligation
|
||||
let poly_trait_ref = ty::Binder::dummy(trait_ref);
|
||||
(
|
||||
traits::Obligation::misc(
|
||||
span,
|
||||
self.body_id,
|
||||
self.param_env,
|
||||
poly_trait_ref.without_const().to_predicate(self.tcx),
|
||||
),
|
||||
substs,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn obligation_for_op_method(
|
||||
&self,
|
||||
span: Span,
|
||||
trait_def_id: DefId,
|
||||
self_ty: Ty<'tcx>,
|
||||
opt_input_type: Option<Ty<'tcx>>,
|
||||
opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> (traits::Obligation<'tcx, ty::Predicate<'tcx>>, &'tcx ty::List<ty::subst::GenericArg<'tcx>>)
|
||||
{
|
||||
// Construct a trait-reference `self_ty : Trait<input_tys>`
|
||||
let substs = InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| {
|
||||
match param.kind {
|
||||
GenericParamDefKind::Lifetime | GenericParamDefKind::Const { .. } => {}
|
||||
GenericParamDefKind::Type { .. } => {
|
||||
if param.index == 0 {
|
||||
return self_ty.into();
|
||||
} else if let Some(input_type) = opt_input_type {
|
||||
return input_type.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.var_for_def(span, param)
|
||||
});
|
||||
|
||||
let trait_ref = ty::TraitRef::new(trait_def_id, substs);
|
||||
|
||||
// Construct an obligation
|
||||
let poly_trait_ref = ty::Binder::dummy(trait_ref);
|
||||
let output_ty = expected.only_has_type(self).and_then(|ty| (!ty.needs_infer()).then(|| ty));
|
||||
|
||||
(
|
||||
traits::Obligation::new(
|
||||
traits::ObligationCause::new(
|
||||
span,
|
||||
self.body_id,
|
||||
traits::BinOp {
|
||||
rhs_span: opt_input_expr.map(|expr| expr.span),
|
||||
is_lit: opt_input_expr
|
||||
.map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
|
||||
output_ty,
|
||||
},
|
||||
),
|
||||
self.param_env,
|
||||
poly_trait_ref.without_const().to_predicate(self.tcx),
|
||||
),
|
||||
substs,
|
||||
)
|
||||
}
|
||||
|
||||
/// `lookup_method_in_trait` is used for overloaded operators.
|
||||
/// It does a very narrow slice of what the normal probe/confirm path does.
|
||||
/// In particular, it doesn't really do any probing: it simply constructs
|
||||
/// an obligation for a particular trait with the given self type and checks
|
||||
/// whether that trait is implemented.
|
||||
#[instrument(level = "debug", skip(self, span))]
|
||||
pub(super) fn lookup_method_in_trait(
|
||||
&self,
|
||||
span: Span,
|
||||
m_name: Ident,
|
||||
trait_def_id: DefId,
|
||||
self_ty: Ty<'tcx>,
|
||||
opt_input_types: Option<&[Ty<'tcx>]>,
|
||||
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
|
||||
let (obligation, substs) =
|
||||
self.obligation_for_method(span, trait_def_id, self_ty, opt_input_types);
|
||||
self.construct_obligation_for_trait(
|
||||
span,
|
||||
m_name,
|
||||
trait_def_id,
|
||||
obligation,
|
||||
substs,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn lookup_op_method_in_trait(
|
||||
&self,
|
||||
span: Span,
|
||||
m_name: Ident,
|
||||
trait_def_id: DefId,
|
||||
self_ty: Ty<'tcx>,
|
||||
opt_input_type: Option<Ty<'tcx>>,
|
||||
opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
|
||||
let (obligation, substs) = self.obligation_for_op_method(
|
||||
span,
|
||||
trait_def_id,
|
||||
self_ty,
|
||||
opt_input_type,
|
||||
opt_input_expr,
|
||||
expected,
|
||||
);
|
||||
self.construct_obligation_for_trait(
|
||||
span,
|
||||
m_name,
|
||||
trait_def_id,
|
||||
obligation,
|
||||
substs,
|
||||
opt_input_expr,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
// FIXME(#18741): it seems likely that we can consolidate some of this
|
||||
// code with the other method-lookup code. In particular, the second half
|
||||
// of this method is basically the same as confirmation.
|
||||
fn construct_obligation_for_trait(
|
||||
&self,
|
||||
span: Span,
|
||||
m_name: Ident,
|
||||
trait_def_id: DefId,
|
||||
obligation: traits::PredicateObligation<'tcx>,
|
||||
substs: &'tcx ty::List<ty::subst::GenericArg<'tcx>>,
|
||||
opt_input_expr: Option<&'tcx hir::Expr<'tcx>>,
|
||||
is_op: bool,
|
||||
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
|
||||
debug!(?obligation);
|
||||
|
||||
// Now we want to know if this can be matched
|
||||
if !self.predicate_may_hold(&obligation) {
|
||||
debug!("--> Cannot match obligation");
|
||||
// Cannot be matched, no such method resolution is possible.
|
||||
return None;
|
||||
}
|
||||
|
||||
// Trait must have a method named `m_name` and it should not have
|
||||
// type parameters or early-bound regions.
|
||||
let tcx = self.tcx;
|
||||
let Some(method_item) = self.associated_value(trait_def_id, m_name) else {
|
||||
tcx.sess.delay_span_bug(
|
||||
span,
|
||||
"operator trait does not have corresponding operator method",
|
||||
);
|
||||
return None;
|
||||
};
|
||||
let def_id = method_item.def_id;
|
||||
let generics = tcx.generics_of(def_id);
|
||||
assert_eq!(generics.params.len(), 0);
|
||||
|
||||
debug!("lookup_in_trait_adjusted: method_item={:?}", method_item);
|
||||
let mut obligations = vec![];
|
||||
|
||||
// Instantiate late-bound regions and substitute the trait
|
||||
// parameters into the method type to get the actual method type.
|
||||
//
|
||||
// N.B., instantiate late-bound regions first so that
|
||||
// `instantiate_type_scheme` can normalize associated types that
|
||||
// may reference those regions.
|
||||
let fn_sig = tcx.bound_fn_sig(def_id);
|
||||
let fn_sig = fn_sig.subst(self.tcx, substs);
|
||||
let fn_sig = self.replace_bound_vars_with_fresh_vars(span, infer::FnCall, fn_sig);
|
||||
|
||||
let InferOk { value, obligations: o } = if is_op {
|
||||
self.normalize_op_associated_types_in_as_infer_ok(span, fn_sig, opt_input_expr)
|
||||
} else {
|
||||
self.normalize_associated_types_in_as_infer_ok(span, fn_sig)
|
||||
};
|
||||
let fn_sig = {
|
||||
obligations.extend(o);
|
||||
value
|
||||
};
|
||||
|
||||
// Register obligations for the parameters. This will include the
|
||||
// `Self` parameter, which in turn has a bound of the main trait,
|
||||
// so this also effectively registers `obligation` as well. (We
|
||||
// used to register `obligation` explicitly, but that resulted in
|
||||
// double error messages being reported.)
|
||||
//
|
||||
// Note that as the method comes from a trait, it should not have
|
||||
// any late-bound regions appearing in its bounds.
|
||||
let bounds = self.tcx.predicates_of(def_id).instantiate(self.tcx, substs);
|
||||
|
||||
let InferOk { value, obligations: o } = if is_op {
|
||||
self.normalize_op_associated_types_in_as_infer_ok(span, bounds, opt_input_expr)
|
||||
} else {
|
||||
self.normalize_associated_types_in_as_infer_ok(span, bounds)
|
||||
};
|
||||
let bounds = {
|
||||
obligations.extend(o);
|
||||
value
|
||||
};
|
||||
|
||||
assert!(!bounds.has_escaping_bound_vars());
|
||||
|
||||
let cause = if is_op {
|
||||
ObligationCause::new(
|
||||
span,
|
||||
self.body_id,
|
||||
traits::BinOp {
|
||||
rhs_span: opt_input_expr.map(|expr| expr.span),
|
||||
is_lit: opt_input_expr
|
||||
.map_or(false, |expr| matches!(expr.kind, hir::ExprKind::Lit(_))),
|
||||
output_ty: None,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
traits::ObligationCause::misc(span, self.body_id)
|
||||
};
|
||||
let predicates_cause = cause.clone();
|
||||
obligations.extend(traits::predicates_for_generics(
|
||||
move |_, _| predicates_cause.clone(),
|
||||
self.param_env,
|
||||
bounds,
|
||||
));
|
||||
|
||||
// Also add an obligation for the method type being well-formed.
|
||||
let method_ty = tcx.mk_fn_ptr(ty::Binder::dummy(fn_sig));
|
||||
debug!(
|
||||
"lookup_in_trait_adjusted: matched method method_ty={:?} obligation={:?}",
|
||||
method_ty, obligation
|
||||
);
|
||||
obligations.push(traits::Obligation::new(
|
||||
cause,
|
||||
self.param_env,
|
||||
ty::Binder::dummy(ty::PredicateKind::WellFormed(method_ty.into())).to_predicate(tcx),
|
||||
));
|
||||
|
||||
let callee = MethodCallee { def_id, substs, sig: fn_sig };
|
||||
|
||||
debug!("callee = {:?}", callee);
|
||||
|
||||
Some(InferOk { obligations, value: callee })
|
||||
}
|
||||
|
||||
/// Performs a [full-qualified function call] (formerly "universal function call") lookup. If
|
||||
/// lookup is successful, it will return the type of definition and the [`DefId`] of the found
|
||||
/// function definition.
|
||||
///
|
||||
/// [full-qualified function call]: https://doc.rust-lang.org/reference/expressions/call-expr.html#disambiguating-function-calls
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// Given a function call like `Foo::bar::<T1,...Tn>(...)`:
|
||||
///
|
||||
/// * `self`: the surrounding `FnCtxt` (!)
|
||||
/// * `span`: the span of the call, excluding arguments (`Foo::bar::<T1, ...Tn>`)
|
||||
/// * `method_name`: the identifier of the function within the container type (`bar`)
|
||||
/// * `self_ty`: the type to search within (`Foo`)
|
||||
/// * `self_ty_span` the span for the type being searched within (span of `Foo`)
|
||||
/// * `expr_id`: the [`hir::HirId`] of the expression composing the entire call
|
||||
#[instrument(level = "debug", skip(self), ret)]
|
||||
pub fn resolve_fully_qualified_call(
|
||||
&self,
|
||||
span: Span,
|
||||
method_name: Ident,
|
||||
self_ty: Ty<'tcx>,
|
||||
self_ty_span: Span,
|
||||
expr_id: hir::HirId,
|
||||
) -> Result<(DefKind, DefId), MethodError<'tcx>> {
|
||||
let tcx = self.tcx;
|
||||
|
||||
// Check if we have an enum variant.
|
||||
if let ty::Adt(adt_def, _) = self_ty.kind() {
|
||||
if adt_def.is_enum() {
|
||||
let variant_def = adt_def
|
||||
.variants()
|
||||
.iter()
|
||||
.find(|vd| tcx.hygienic_eq(method_name, vd.ident(tcx), adt_def.did()));
|
||||
if let Some(variant_def) = variant_def {
|
||||
// Braced variants generate unusable names in value namespace (reserved for
|
||||
// possible future use), so variants resolved as associated items may refer to
|
||||
// them as well. It's ok to use the variant's id as a ctor id since an
|
||||
// error will be reported on any use of such resolution anyway.
|
||||
let ctor_def_id = variant_def.ctor_def_id.unwrap_or(variant_def.def_id);
|
||||
tcx.check_stability(ctor_def_id, Some(expr_id), span, Some(method_name.span));
|
||||
return Ok((
|
||||
DefKind::Ctor(CtorOf::Variant, variant_def.ctor_kind),
|
||||
ctor_def_id,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pick = self.probe_for_name(
|
||||
span,
|
||||
probe::Mode::Path,
|
||||
method_name,
|
||||
IsSuggestion(false),
|
||||
self_ty,
|
||||
expr_id,
|
||||
ProbeScope::TraitsInScope,
|
||||
)?;
|
||||
|
||||
self.lint_fully_qualified_call_from_2018(
|
||||
span,
|
||||
method_name,
|
||||
self_ty,
|
||||
self_ty_span,
|
||||
expr_id,
|
||||
&pick,
|
||||
);
|
||||
|
||||
debug!(?pick);
|
||||
{
|
||||
let mut typeck_results = self.typeck_results.borrow_mut();
|
||||
let used_trait_imports = Lrc::get_mut(&mut typeck_results.used_trait_imports).unwrap();
|
||||
for import_id in pick.import_ids {
|
||||
debug!(used_trait_import=?import_id);
|
||||
used_trait_imports.insert(import_id);
|
||||
}
|
||||
}
|
||||
|
||||
let def_kind = pick.item.kind.as_def_kind();
|
||||
tcx.check_stability(pick.item.def_id, Some(expr_id), span, Some(method_name.span));
|
||||
Ok((def_kind, pick.item.def_id))
|
||||
}
|
||||
|
||||
/// Finds item with name `item_name` defined in impl/trait `def_id`
|
||||
/// and return it, or `None`, if no such item was defined there.
|
||||
pub fn associated_value(&self, def_id: DefId, item_name: Ident) -> Option<ty::AssocItem> {
|
||||
self.tcx
|
||||
.associated_items(def_id)
|
||||
.find_by_name_and_namespace(self.tcx, item_name, Namespace::ValueNS, def_id)
|
||||
.copied()
|
||||
}
|
||||
}
|
|
@ -1,416 +0,0 @@
|
|||
use hir::def_id::DefId;
|
||||
use hir::HirId;
|
||||
use hir::ItemKind;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::ty::subst::InternalSubsts;
|
||||
use rustc_middle::ty::{Adt, Array, Ref, Ty};
|
||||
use rustc_session::lint::builtin::RUST_2021_PRELUDE_COLLISIONS;
|
||||
use rustc_span::symbol::kw::{Empty, Underscore};
|
||||
use rustc_span::symbol::{sym, Ident};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::infer::InferCtxtExt;
|
||||
|
||||
use crate::check::{
|
||||
method::probe::{self, Pick},
|
||||
FnCtxt,
|
||||
};
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
pub(super) fn lint_dot_call_from_2018(
|
||||
&self,
|
||||
self_ty: Ty<'tcx>,
|
||||
segment: &hir::PathSegment<'_>,
|
||||
span: Span,
|
||||
call_expr: &'tcx hir::Expr<'tcx>,
|
||||
self_expr: &'tcx hir::Expr<'tcx>,
|
||||
pick: &Pick<'tcx>,
|
||||
args: &'tcx [hir::Expr<'tcx>],
|
||||
) {
|
||||
debug!(
|
||||
"lookup(method_name={}, self_ty={:?}, call_expr={:?}, self_expr={:?})",
|
||||
segment.ident, self_ty, call_expr, self_expr
|
||||
);
|
||||
|
||||
// Rust 2021 and later is already using the new prelude
|
||||
if span.rust_2021() {
|
||||
return;
|
||||
}
|
||||
|
||||
let prelude_or_array_lint = match segment.ident.name {
|
||||
// `try_into` was added to the prelude in Rust 2021.
|
||||
sym::try_into => RUST_2021_PRELUDE_COLLISIONS,
|
||||
// `into_iter` wasn't added to the prelude,
|
||||
// but `[T; N].into_iter()` doesn't resolve to IntoIterator::into_iter
|
||||
// before Rust 2021, which results in the same problem.
|
||||
// It is only a problem for arrays.
|
||||
sym::into_iter if let Array(..) = self_ty.kind() => {
|
||||
// In this case, it wasn't really a prelude addition that was the problem.
|
||||
// Instead, the problem is that the array-into_iter hack will no longer apply in Rust 2021.
|
||||
rustc_lint::ARRAY_INTO_ITER
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// No need to lint if method came from std/core, as that will now be in the prelude
|
||||
if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(pick.kind, probe::PickKind::InherentImplPick | probe::PickKind::ObjectPick) {
|
||||
// avoid repeatedly adding unneeded `&*`s
|
||||
if pick.autoderefs == 1
|
||||
&& matches!(
|
||||
pick.autoref_or_ptr_adjustment,
|
||||
Some(probe::AutorefOrPtrAdjustment::Autoref { .. })
|
||||
)
|
||||
&& matches!(self_ty.kind(), Ref(..))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if it's an inherent `self` method (not `&self` or `&mut self`), it will take
|
||||
// precedence over the `TryInto` impl, and thus won't break in 2021 edition
|
||||
if pick.autoderefs == 0 && pick.autoref_or_ptr_adjustment.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inherent impls only require not relying on autoref and autoderef in order to
|
||||
// ensure that the trait implementation won't be used
|
||||
self.tcx.struct_span_lint_hir(
|
||||
prelude_or_array_lint,
|
||||
self_expr.hir_id,
|
||||
self_expr.span,
|
||||
format!("trait method `{}` will become ambiguous in Rust 2021", segment.ident.name),
|
||||
|lint| {
|
||||
let sp = self_expr.span;
|
||||
|
||||
let derefs = "*".repeat(pick.autoderefs);
|
||||
|
||||
let autoref = match pick.autoref_or_ptr_adjustment {
|
||||
Some(probe::AutorefOrPtrAdjustment::Autoref {
|
||||
mutbl: Mutability::Mut,
|
||||
..
|
||||
}) => "&mut ",
|
||||
Some(probe::AutorefOrPtrAdjustment::Autoref {
|
||||
mutbl: Mutability::Not,
|
||||
..
|
||||
}) => "&",
|
||||
Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
|
||||
};
|
||||
if let Ok(self_expr) = self.sess().source_map().span_to_snippet(self_expr.span)
|
||||
{
|
||||
let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
|
||||
pick.autoref_or_ptr_adjustment
|
||||
{
|
||||
format!("{}{} as *const _", derefs, self_expr)
|
||||
} else {
|
||||
format!("{}{}{}", autoref, derefs, self_expr)
|
||||
};
|
||||
|
||||
lint.span_suggestion(
|
||||
sp,
|
||||
"disambiguate the method call",
|
||||
format!("({})", self_adjusted),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
|
||||
pick.autoref_or_ptr_adjustment
|
||||
{
|
||||
format!("{}(...) as *const _", derefs)
|
||||
} else {
|
||||
format!("{}{}...", autoref, derefs)
|
||||
};
|
||||
lint.span_help(
|
||||
sp,
|
||||
&format!("disambiguate the method call with `({})`", self_adjusted,),
|
||||
);
|
||||
}
|
||||
|
||||
lint
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// trait implementations require full disambiguation to not clash with the new prelude
|
||||
// additions (i.e. convert from dot-call to fully-qualified call)
|
||||
self.tcx.struct_span_lint_hir(
|
||||
prelude_or_array_lint,
|
||||
call_expr.hir_id,
|
||||
call_expr.span,
|
||||
format!("trait method `{}` will become ambiguous in Rust 2021", segment.ident.name),
|
||||
|lint| {
|
||||
let sp = call_expr.span;
|
||||
let trait_name = self.trait_path_or_bare_name(
|
||||
span,
|
||||
call_expr.hir_id,
|
||||
pick.item.container_id(self.tcx),
|
||||
);
|
||||
|
||||
let (self_adjusted, precise) = self.adjust_expr(pick, self_expr, sp);
|
||||
if precise {
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let span = arg.span.find_ancestor_inside(sp).unwrap_or_default();
|
||||
format!(
|
||||
", {}",
|
||||
self.sess().source_map().span_to_snippet(span).unwrap()
|
||||
)
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
lint.span_suggestion(
|
||||
sp,
|
||||
"disambiguate the associated function",
|
||||
format!(
|
||||
"{}::{}{}({}{})",
|
||||
trait_name,
|
||||
segment.ident.name,
|
||||
if let Some(args) = segment.args.as_ref().and_then(|args| self
|
||||
.sess()
|
||||
.source_map()
|
||||
.span_to_snippet(args.span_ext)
|
||||
.ok())
|
||||
{
|
||||
// Keep turbofish.
|
||||
format!("::{}", args)
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
self_adjusted,
|
||||
args,
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
lint.span_help(
|
||||
sp,
|
||||
&format!(
|
||||
"disambiguate the associated function with `{}::{}(...)`",
|
||||
trait_name, segment.ident,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
lint
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lint_fully_qualified_call_from_2018(
|
||||
&self,
|
||||
span: Span,
|
||||
method_name: Ident,
|
||||
self_ty: Ty<'tcx>,
|
||||
self_ty_span: Span,
|
||||
expr_id: hir::HirId,
|
||||
pick: &Pick<'tcx>,
|
||||
) {
|
||||
// Rust 2021 and later is already using the new prelude
|
||||
if span.rust_2021() {
|
||||
return;
|
||||
}
|
||||
|
||||
// These are the fully qualified methods added to prelude in Rust 2021
|
||||
if !matches!(method_name.name, sym::try_into | sym::try_from | sym::from_iter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to lint if method came from std/core, as that will now be in the prelude
|
||||
if matches!(self.tcx.crate_name(pick.item.def_id.krate), sym::std | sym::core) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For from_iter, check if the type actually implements FromIterator.
|
||||
// If we know it does not, we don't need to warn.
|
||||
if method_name.name == sym::from_iter {
|
||||
if let Some(trait_def_id) = self.tcx.get_diagnostic_item(sym::FromIterator) {
|
||||
if !self
|
||||
.infcx
|
||||
.type_implements_trait(
|
||||
trait_def_id,
|
||||
self_ty,
|
||||
InternalSubsts::empty(),
|
||||
self.param_env,
|
||||
)
|
||||
.may_apply()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No need to lint if this is an inherent method called on a specific type, like `Vec::foo(...)`,
|
||||
// since such methods take precedence over trait methods.
|
||||
if matches!(pick.kind, probe::PickKind::InherentImplPick) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.tcx.struct_span_lint_hir(
|
||||
RUST_2021_PRELUDE_COLLISIONS,
|
||||
expr_id,
|
||||
span,
|
||||
format!(
|
||||
"trait-associated function `{}` will become ambiguous in Rust 2021",
|
||||
method_name.name
|
||||
),
|
||||
|lint| {
|
||||
// "type" refers to either a type or, more likely, a trait from which
|
||||
// the associated function or method is from.
|
||||
let container_id = pick.item.container_id(self.tcx);
|
||||
let trait_path = self.trait_path_or_bare_name(span, expr_id, container_id);
|
||||
let trait_generics = self.tcx.generics_of(container_id);
|
||||
|
||||
let trait_name = if trait_generics.params.len() <= trait_generics.has_self as usize
|
||||
{
|
||||
trait_path
|
||||
} else {
|
||||
let counts = trait_generics.own_counts();
|
||||
format!(
|
||||
"{}<{}>",
|
||||
trait_path,
|
||||
std::iter::repeat("'_")
|
||||
.take(counts.lifetimes)
|
||||
.chain(std::iter::repeat("_").take(
|
||||
counts.types + counts.consts - trait_generics.has_self as usize
|
||||
))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
};
|
||||
|
||||
let mut self_ty_name = self_ty_span
|
||||
.find_ancestor_inside(span)
|
||||
.and_then(|span| self.sess().source_map().span_to_snippet(span).ok())
|
||||
.unwrap_or_else(|| self_ty.to_string());
|
||||
|
||||
// Get the number of generics the self type has (if an Adt) unless we can determine that
|
||||
// the user has written the self type with generics already which we (naively) do by looking
|
||||
// for a "<" in `self_ty_name`.
|
||||
if !self_ty_name.contains('<') {
|
||||
if let Adt(def, _) = self_ty.kind() {
|
||||
let generics = self.tcx.generics_of(def.did());
|
||||
if !generics.params.is_empty() {
|
||||
let counts = generics.own_counts();
|
||||
self_ty_name += &format!(
|
||||
"<{}>",
|
||||
std::iter::repeat("'_")
|
||||
.take(counts.lifetimes)
|
||||
.chain(
|
||||
std::iter::repeat("_").take(counts.types + counts.consts)
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
lint.span_suggestion(
|
||||
span,
|
||||
"disambiguate the associated function",
|
||||
format!("<{} as {}>::{}", self_ty_name, trait_name, method_name.name,),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
lint
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn trait_path_or_bare_name(
|
||||
&self,
|
||||
span: Span,
|
||||
expr_hir_id: HirId,
|
||||
trait_def_id: DefId,
|
||||
) -> String {
|
||||
self.trait_path(span, expr_hir_id, trait_def_id).unwrap_or_else(|| {
|
||||
let key = self.tcx.def_key(trait_def_id);
|
||||
format!("{}", key.disambiguated_data.data)
|
||||
})
|
||||
}
|
||||
|
||||
fn trait_path(&self, span: Span, expr_hir_id: HirId, trait_def_id: DefId) -> Option<String> {
|
||||
let applicable_traits = self.tcx.in_scope_traits(expr_hir_id)?;
|
||||
let applicable_trait = applicable_traits.iter().find(|t| t.def_id == trait_def_id)?;
|
||||
if applicable_trait.import_ids.is_empty() {
|
||||
// The trait was declared within the module, we only need to use its name.
|
||||
return None;
|
||||
}
|
||||
|
||||
let import_items: Vec<_> = applicable_trait
|
||||
.import_ids
|
||||
.iter()
|
||||
.map(|&import_id| self.tcx.hir().expect_item(import_id))
|
||||
.collect();
|
||||
|
||||
// Find an identifier with which this trait was imported (note that `_` doesn't count).
|
||||
let any_id = import_items
|
||||
.iter()
|
||||
.filter_map(|item| if item.ident.name != Underscore { Some(item.ident) } else { None })
|
||||
.next();
|
||||
if let Some(any_id) = any_id {
|
||||
if any_id.name == Empty {
|
||||
// Glob import, so just use its name.
|
||||
return None;
|
||||
} else {
|
||||
return Some(format!("{}", any_id));
|
||||
}
|
||||
}
|
||||
|
||||
// All that is left is `_`! We need to use the full path. It doesn't matter which one we pick,
|
||||
// so just take the first one.
|
||||
match import_items[0].kind {
|
||||
ItemKind::Use(path, _) => Some(
|
||||
path.segments
|
||||
.iter()
|
||||
.map(|segment| segment.ident.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("::"),
|
||||
),
|
||||
_ => {
|
||||
span_bug!(span, "unexpected item kind, expected a use: {:?}", import_items[0].kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a string version of the `expr` that includes explicit adjustments.
|
||||
/// Returns the string and also a bool indicating whether this is a *precise*
|
||||
/// suggestion.
|
||||
fn adjust_expr(
|
||||
&self,
|
||||
pick: &Pick<'tcx>,
|
||||
expr: &hir::Expr<'tcx>,
|
||||
outer: Span,
|
||||
) -> (String, bool) {
|
||||
let derefs = "*".repeat(pick.autoderefs);
|
||||
|
||||
let autoref = match pick.autoref_or_ptr_adjustment {
|
||||
Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Mut, .. }) => "&mut ",
|
||||
Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl: Mutability::Not, .. }) => "&",
|
||||
Some(probe::AutorefOrPtrAdjustment::ToConstPtr) | None => "",
|
||||
};
|
||||
|
||||
let (expr_text, precise) = if let Some(expr_text) = expr
|
||||
.span
|
||||
.find_ancestor_inside(outer)
|
||||
.and_then(|span| self.sess().source_map().span_to_snippet(span).ok())
|
||||
{
|
||||
(expr_text, true)
|
||||
} else {
|
||||
("(..)".to_string(), false)
|
||||
};
|
||||
|
||||
let adjusted_text = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) =
|
||||
pick.autoref_or_ptr_adjustment
|
||||
{
|
||||
format!("{}{} as *const _", derefs, expr_text)
|
||||
} else {
|
||||
format!("{}{}{}", autoref, derefs, expr_text)
|
||||
};
|
||||
|
||||
(adjusted_text, precise)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,994 +0,0 @@
|
|||
//! Code related to processing overloaded binary and unary operators.
|
||||
|
||||
use super::method::MethodCallee;
|
||||
use super::{has_expected_num_generic_args, FnCtxt};
|
||||
use crate::check::Expectation;
|
||||
use rustc_ast as ast;
|
||||
use rustc_errors::{self, struct_span_err, Applicability, Diagnostic};
|
||||
use rustc_hir as hir;
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_infer::traits::ObligationCauseCode;
|
||||
use rustc_middle::ty::adjustment::{
|
||||
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
|
||||
};
|
||||
use rustc_middle::ty::print::with_no_trimmed_paths;
|
||||
use rustc_middle::ty::{self, DefIdTree, Ty, TyCtxt, TypeFolder, TypeSuperFoldable, TypeVisitable};
|
||||
use rustc_session::errors::ExprParenthesesNeeded;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::{sym, Ident};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::infer::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _;
|
||||
use rustc_trait_selection::traits::{FulfillmentError, TraitEngine, TraitEngineExt};
|
||||
use rustc_type_ir::sty::TyKind::*;
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
/// Checks a `a <op>= b`
|
||||
pub fn check_binop_assign(
|
||||
&self,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
op: hir::BinOp,
|
||||
lhs: &'tcx hir::Expr<'tcx>,
|
||||
rhs: &'tcx hir::Expr<'tcx>,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let (lhs_ty, rhs_ty, return_ty) =
|
||||
self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected);
|
||||
|
||||
let ty =
|
||||
if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) {
|
||||
self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, op);
|
||||
self.tcx.mk_unit()
|
||||
} else {
|
||||
return_ty
|
||||
};
|
||||
|
||||
self.check_lhs_assignable(lhs, "E0067", op.span, |err| {
|
||||
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
|
||||
if self
|
||||
.lookup_op_method(
|
||||
lhs_deref_ty,
|
||||
Some(rhs_ty),
|
||||
Some(rhs),
|
||||
Op::Binary(op, IsAssign::Yes),
|
||||
expected,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
// If LHS += RHS is an error, but *LHS += RHS is successful, then we will have
|
||||
// emitted a better suggestion during error handling in check_overloaded_binop.
|
||||
if self
|
||||
.lookup_op_method(
|
||||
lhs_ty,
|
||||
Some(rhs_ty),
|
||||
Some(rhs),
|
||||
Op::Binary(op, IsAssign::Yes),
|
||||
expected,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
err.downgrade_to_delayed_bug();
|
||||
} else {
|
||||
// Otherwise, it's valid to suggest dereferencing the LHS here.
|
||||
err.span_suggestion_verbose(
|
||||
lhs.span.shrink_to_lo(),
|
||||
"consider dereferencing the left-hand side of this operation",
|
||||
"*",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ty
|
||||
}
|
||||
|
||||
/// Checks a potentially overloaded binary operator.
|
||||
pub fn check_binop(
|
||||
&self,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
op: hir::BinOp,
|
||||
lhs_expr: &'tcx hir::Expr<'tcx>,
|
||||
rhs_expr: &'tcx hir::Expr<'tcx>,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
let tcx = self.tcx;
|
||||
|
||||
debug!(
|
||||
"check_binop(expr.hir_id={}, expr={:?}, op={:?}, lhs_expr={:?}, rhs_expr={:?})",
|
||||
expr.hir_id, expr, op, lhs_expr, rhs_expr
|
||||
);
|
||||
|
||||
match BinOpCategory::from(op) {
|
||||
BinOpCategory::Shortcircuit => {
|
||||
// && and || are a simple case.
|
||||
self.check_expr_coercable_to_type(lhs_expr, tcx.types.bool, None);
|
||||
let lhs_diverges = self.diverges.get();
|
||||
self.check_expr_coercable_to_type(rhs_expr, tcx.types.bool, None);
|
||||
|
||||
// Depending on the LHS' value, the RHS can never execute.
|
||||
self.diverges.set(lhs_diverges);
|
||||
|
||||
tcx.types.bool
|
||||
}
|
||||
_ => {
|
||||
// Otherwise, we always treat operators as if they are
|
||||
// overloaded. This is the way to be most flexible w/r/t
|
||||
// types that get inferred.
|
||||
let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop(
|
||||
expr,
|
||||
lhs_expr,
|
||||
rhs_expr,
|
||||
op,
|
||||
IsAssign::No,
|
||||
expected,
|
||||
);
|
||||
|
||||
// Supply type inference hints if relevant. Probably these
|
||||
// hints should be enforced during select as part of the
|
||||
// `consider_unification_despite_ambiguity` routine, but this
|
||||
// more convenient for now.
|
||||
//
|
||||
// The basic idea is to help type inference by taking
|
||||
// advantage of things we know about how the impls for
|
||||
// scalar types are arranged. This is important in a
|
||||
// scenario like `1_u32 << 2`, because it lets us quickly
|
||||
// deduce that the result type should be `u32`, even
|
||||
// though we don't know yet what type 2 has and hence
|
||||
// can't pin this down to a specific impl.
|
||||
if !lhs_ty.is_ty_var()
|
||||
&& !rhs_ty.is_ty_var()
|
||||
&& is_builtin_binop(lhs_ty, rhs_ty, op)
|
||||
{
|
||||
let builtin_return_ty = self.enforce_builtin_binop_types(
|
||||
lhs_expr.span,
|
||||
lhs_ty,
|
||||
rhs_expr.span,
|
||||
rhs_ty,
|
||||
op,
|
||||
);
|
||||
self.demand_suptype(expr.span, builtin_return_ty, return_ty);
|
||||
}
|
||||
|
||||
return_ty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enforce_builtin_binop_types(
|
||||
&self,
|
||||
lhs_span: Span,
|
||||
lhs_ty: Ty<'tcx>,
|
||||
rhs_span: Span,
|
||||
rhs_ty: Ty<'tcx>,
|
||||
op: hir::BinOp,
|
||||
) -> Ty<'tcx> {
|
||||
debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op));
|
||||
|
||||
// Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
|
||||
// (See https://github.com/rust-lang/rust/issues/57447.)
|
||||
let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty));
|
||||
|
||||
let tcx = self.tcx;
|
||||
match BinOpCategory::from(op) {
|
||||
BinOpCategory::Shortcircuit => {
|
||||
self.demand_suptype(lhs_span, tcx.types.bool, lhs_ty);
|
||||
self.demand_suptype(rhs_span, tcx.types.bool, rhs_ty);
|
||||
tcx.types.bool
|
||||
}
|
||||
|
||||
BinOpCategory::Shift => {
|
||||
// result type is same as LHS always
|
||||
lhs_ty
|
||||
}
|
||||
|
||||
BinOpCategory::Math | BinOpCategory::Bitwise => {
|
||||
// both LHS and RHS and result will have the same type
|
||||
self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
|
||||
lhs_ty
|
||||
}
|
||||
|
||||
BinOpCategory::Comparison => {
|
||||
// both LHS and RHS and result will have the same type
|
||||
self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
|
||||
tcx.types.bool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_overloaded_binop(
|
||||
&self,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
lhs_expr: &'tcx hir::Expr<'tcx>,
|
||||
rhs_expr: &'tcx hir::Expr<'tcx>,
|
||||
op: hir::BinOp,
|
||||
is_assign: IsAssign,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
|
||||
debug!(
|
||||
"check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})",
|
||||
expr.hir_id, op, is_assign
|
||||
);
|
||||
|
||||
let lhs_ty = match is_assign {
|
||||
IsAssign::No => {
|
||||
// Find a suitable supertype of the LHS expression's type, by coercing to
|
||||
// a type variable, to pass as the `Self` to the trait, avoiding invariant
|
||||
// trait matching creating lifetime constraints that are too strict.
|
||||
// e.g., adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
|
||||
// in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
|
||||
let lhs_ty = self.check_expr(lhs_expr);
|
||||
let fresh_var = self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: lhs_expr.span,
|
||||
});
|
||||
self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No)
|
||||
}
|
||||
IsAssign::Yes => {
|
||||
// rust-lang/rust#52126: We have to use strict
|
||||
// equivalence on the LHS of an assign-op like `+=`;
|
||||
// overwritten or mutably-borrowed places cannot be
|
||||
// coerced to a supertype.
|
||||
self.check_expr(lhs_expr)
|
||||
}
|
||||
};
|
||||
let lhs_ty = self.resolve_vars_with_obligations(lhs_ty);
|
||||
|
||||
// N.B., as we have not yet type-checked the RHS, we don't have the
|
||||
// type at hand. Make a variable to represent it. The whole reason
|
||||
// for this indirection is so that, below, we can check the expr
|
||||
// using this variable as the expected type, which sometimes lets
|
||||
// us do better coercions than we would be able to do otherwise,
|
||||
// particularly for things like `String + &String`.
|
||||
let rhs_ty_var = self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: rhs_expr.span,
|
||||
});
|
||||
|
||||
let result = self.lookup_op_method(
|
||||
lhs_ty,
|
||||
Some(rhs_ty_var),
|
||||
Some(rhs_expr),
|
||||
Op::Binary(op, is_assign),
|
||||
expected,
|
||||
);
|
||||
|
||||
// see `NB` above
|
||||
let rhs_ty = self.check_expr_coercable_to_type(rhs_expr, rhs_ty_var, Some(lhs_expr));
|
||||
let rhs_ty = self.resolve_vars_with_obligations(rhs_ty);
|
||||
|
||||
let return_ty = match result {
|
||||
Ok(method) => {
|
||||
let by_ref_binop = !op.node.is_by_value();
|
||||
if is_assign == IsAssign::Yes || by_ref_binop {
|
||||
if let ty::Ref(region, _, mutbl) = method.sig.inputs()[0].kind() {
|
||||
let mutbl = match mutbl {
|
||||
hir::Mutability::Not => AutoBorrowMutability::Not,
|
||||
hir::Mutability::Mut => AutoBorrowMutability::Mut {
|
||||
// Allow two-phase borrows for binops in initial deployment
|
||||
// since they desugar to methods
|
||||
allow_two_phase_borrow: AllowTwoPhase::Yes,
|
||||
},
|
||||
};
|
||||
let autoref = Adjustment {
|
||||
kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)),
|
||||
target: method.sig.inputs()[0],
|
||||
};
|
||||
self.apply_adjustments(lhs_expr, vec![autoref]);
|
||||
}
|
||||
}
|
||||
if by_ref_binop {
|
||||
if let ty::Ref(region, _, mutbl) = method.sig.inputs()[1].kind() {
|
||||
let mutbl = match mutbl {
|
||||
hir::Mutability::Not => AutoBorrowMutability::Not,
|
||||
hir::Mutability::Mut => AutoBorrowMutability::Mut {
|
||||
// Allow two-phase borrows for binops in initial deployment
|
||||
// since they desugar to methods
|
||||
allow_two_phase_borrow: AllowTwoPhase::Yes,
|
||||
},
|
||||
};
|
||||
let autoref = Adjustment {
|
||||
kind: Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)),
|
||||
target: method.sig.inputs()[1],
|
||||
};
|
||||
// HACK(eddyb) Bypass checks due to reborrows being in
|
||||
// some cases applied on the RHS, on top of which we need
|
||||
// to autoref, which is not allowed by apply_adjustments.
|
||||
// self.apply_adjustments(rhs_expr, vec![autoref]);
|
||||
self.typeck_results
|
||||
.borrow_mut()
|
||||
.adjustments_mut()
|
||||
.entry(rhs_expr.hir_id)
|
||||
.or_default()
|
||||
.push(autoref);
|
||||
}
|
||||
}
|
||||
self.write_method_call(expr.hir_id, method);
|
||||
|
||||
method.sig.output()
|
||||
}
|
||||
// error types are considered "builtin"
|
||||
Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(),
|
||||
Err(errors) => {
|
||||
let (_, trait_def_id) =
|
||||
lang_item_for_op(self.tcx, Op::Binary(op, is_assign), op.span);
|
||||
let missing_trait = trait_def_id
|
||||
.map(|def_id| with_no_trimmed_paths!(self.tcx.def_path_str(def_id)));
|
||||
let (mut err, output_def_id) = match is_assign {
|
||||
IsAssign::Yes => {
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
expr.span,
|
||||
E0368,
|
||||
"binary assignment operation `{}=` cannot be applied to type `{}`",
|
||||
op.node.as_str(),
|
||||
lhs_ty,
|
||||
);
|
||||
err.span_label(
|
||||
lhs_expr.span,
|
||||
format!("cannot use `{}=` on type `{}`", op.node.as_str(), lhs_ty),
|
||||
);
|
||||
self.note_unmet_impls_on_type(&mut err, errors);
|
||||
(err, None)
|
||||
}
|
||||
IsAssign::No => {
|
||||
let message = match op.node {
|
||||
hir::BinOpKind::Add => {
|
||||
format!("cannot add `{rhs_ty}` to `{lhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::Sub => {
|
||||
format!("cannot subtract `{rhs_ty}` from `{lhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::Mul => {
|
||||
format!("cannot multiply `{lhs_ty}` by `{rhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::Div => {
|
||||
format!("cannot divide `{lhs_ty}` by `{rhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::Rem => {
|
||||
format!("cannot mod `{lhs_ty}` by `{rhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::BitAnd => {
|
||||
format!("no implementation for `{lhs_ty} & {rhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::BitXor => {
|
||||
format!("no implementation for `{lhs_ty} ^ {rhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::BitOr => {
|
||||
format!("no implementation for `{lhs_ty} | {rhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::Shl => {
|
||||
format!("no implementation for `{lhs_ty} << {rhs_ty}`")
|
||||
}
|
||||
hir::BinOpKind::Shr => {
|
||||
format!("no implementation for `{lhs_ty} >> {rhs_ty}`")
|
||||
}
|
||||
_ => format!(
|
||||
"binary operation `{}` cannot be applied to type `{}`",
|
||||
op.node.as_str(),
|
||||
lhs_ty
|
||||
),
|
||||
};
|
||||
let output_def_id = trait_def_id.and_then(|def_id| {
|
||||
self.tcx
|
||||
.associated_item_def_ids(def_id)
|
||||
.iter()
|
||||
.find(|item_def_id| {
|
||||
self.tcx.associated_item(*item_def_id).name == sym::Output
|
||||
})
|
||||
.cloned()
|
||||
});
|
||||
let mut err = struct_span_err!(self.tcx.sess, op.span, E0369, "{message}");
|
||||
if !lhs_expr.span.eq(&rhs_expr.span) {
|
||||
err.span_label(lhs_expr.span, lhs_ty.to_string());
|
||||
err.span_label(rhs_expr.span, rhs_ty.to_string());
|
||||
}
|
||||
self.note_unmet_impls_on_type(&mut err, errors);
|
||||
(err, output_def_id)
|
||||
}
|
||||
};
|
||||
|
||||
let mut suggest_deref_binop = |lhs_deref_ty: Ty<'tcx>| {
|
||||
if self
|
||||
.lookup_op_method(
|
||||
lhs_deref_ty,
|
||||
Some(rhs_ty),
|
||||
Some(rhs_expr),
|
||||
Op::Binary(op, is_assign),
|
||||
expected,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
let msg = &format!(
|
||||
"`{}{}` can be used on `{}` if you dereference the left-hand side",
|
||||
op.node.as_str(),
|
||||
match is_assign {
|
||||
IsAssign::Yes => "=",
|
||||
IsAssign::No => "",
|
||||
},
|
||||
lhs_deref_ty,
|
||||
);
|
||||
err.span_suggestion_verbose(
|
||||
lhs_expr.span.shrink_to_lo(),
|
||||
msg,
|
||||
"*",
|
||||
rustc_errors::Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let is_compatible = |lhs_ty, rhs_ty| {
|
||||
self.lookup_op_method(
|
||||
lhs_ty,
|
||||
Some(rhs_ty),
|
||||
Some(rhs_expr),
|
||||
Op::Binary(op, is_assign),
|
||||
expected,
|
||||
)
|
||||
.is_ok()
|
||||
};
|
||||
|
||||
// We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
|
||||
// `a += b` => `*a += b` if a is a mut ref.
|
||||
if !op.span.can_be_used_for_suggestions() {
|
||||
// Suppress suggestions when lhs and rhs are not in the same span as the error
|
||||
} else if is_assign == IsAssign::Yes
|
||||
&& let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty)
|
||||
{
|
||||
suggest_deref_binop(lhs_deref_ty);
|
||||
} else if is_assign == IsAssign::No
|
||||
&& let Ref(_, lhs_deref_ty, _) = lhs_ty.kind()
|
||||
{
|
||||
if self.type_is_copy_modulo_regions(
|
||||
self.param_env,
|
||||
*lhs_deref_ty,
|
||||
lhs_expr.span,
|
||||
) {
|
||||
suggest_deref_binop(*lhs_deref_ty);
|
||||
}
|
||||
} else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| {
|
||||
is_compatible(lhs_ty, rhs_ty)
|
||||
}) || self.suggest_fn_call(&mut err, rhs_expr, rhs_ty, |rhs_ty| {
|
||||
is_compatible(lhs_ty, rhs_ty)
|
||||
}) || self.suggest_two_fn_call(
|
||||
&mut err,
|
||||
rhs_expr,
|
||||
rhs_ty,
|
||||
lhs_expr,
|
||||
lhs_ty,
|
||||
|lhs_ty, rhs_ty| is_compatible(lhs_ty, rhs_ty),
|
||||
) {
|
||||
// Cool
|
||||
}
|
||||
|
||||
if let Some(missing_trait) = missing_trait {
|
||||
if op.node == hir::BinOpKind::Add
|
||||
&& self.check_str_addition(
|
||||
lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, is_assign, op,
|
||||
)
|
||||
{
|
||||
// This has nothing here because it means we did string
|
||||
// concatenation (e.g., "Hello " + "World!"). This means
|
||||
// we don't want the note in the else clause to be emitted
|
||||
} else if lhs_ty.has_non_region_param() {
|
||||
// Look for a TraitPredicate in the Fulfillment errors,
|
||||
// and use it to generate a suggestion.
|
||||
//
|
||||
// Note that lookup_op_method must be called again but
|
||||
// with a specific rhs_ty instead of a placeholder so
|
||||
// the resulting predicate generates a more specific
|
||||
// suggestion for the user.
|
||||
let errors = self
|
||||
.lookup_op_method(
|
||||
lhs_ty,
|
||||
Some(rhs_ty),
|
||||
Some(rhs_expr),
|
||||
Op::Binary(op, is_assign),
|
||||
expected,
|
||||
)
|
||||
.unwrap_err();
|
||||
if !errors.is_empty() {
|
||||
for error in errors {
|
||||
if let Some(trait_pred) =
|
||||
error.obligation.predicate.to_opt_poly_trait_pred()
|
||||
{
|
||||
let output_associated_item = match error.obligation.cause.code()
|
||||
{
|
||||
ObligationCauseCode::BinOp {
|
||||
output_ty: Some(output_ty),
|
||||
..
|
||||
} => {
|
||||
// Make sure that we're attaching `Output = ..` to the right trait predicate
|
||||
if let Some(output_def_id) = output_def_id
|
||||
&& let Some(trait_def_id) = trait_def_id
|
||||
&& self.tcx.parent(output_def_id) == trait_def_id
|
||||
{
|
||||
Some(("Output", *output_ty))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
self.err_ctxt().suggest_restricting_param_bound(
|
||||
&mut err,
|
||||
trait_pred,
|
||||
output_associated_item,
|
||||
self.body_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When we know that a missing bound is responsible, we don't show
|
||||
// this note as it is redundant.
|
||||
err.note(&format!(
|
||||
"the trait `{missing_trait}` is not implemented for `{lhs_ty}`"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
err.emit();
|
||||
self.tcx.ty_error()
|
||||
}
|
||||
};
|
||||
|
||||
(lhs_ty, rhs_ty, return_ty)
|
||||
}
|
||||
|
||||
/// Provide actionable suggestions when trying to add two strings with incorrect types,
|
||||
/// like `&str + &str`, `String + String` and `&str + &String`.
|
||||
///
|
||||
/// If this function returns `true` it means a note was printed, so we don't need
|
||||
/// to print the normal "implementation of `std::ops::Add` might be missing" note
|
||||
fn check_str_addition(
|
||||
&self,
|
||||
lhs_expr: &'tcx hir::Expr<'tcx>,
|
||||
rhs_expr: &'tcx hir::Expr<'tcx>,
|
||||
lhs_ty: Ty<'tcx>,
|
||||
rhs_ty: Ty<'tcx>,
|
||||
err: &mut Diagnostic,
|
||||
is_assign: IsAssign,
|
||||
op: hir::BinOp,
|
||||
) -> bool {
|
||||
let str_concat_note = "string concatenation requires an owned `String` on the left";
|
||||
let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
|
||||
let to_owned_msg = "create an owned `String` from a string reference";
|
||||
|
||||
let is_std_string = |ty: Ty<'tcx>| {
|
||||
ty.ty_adt_def()
|
||||
.map_or(false, |ty_def| self.tcx.is_diagnostic_item(sym::String, ty_def.did()))
|
||||
};
|
||||
|
||||
match (lhs_ty.kind(), rhs_ty.kind()) {
|
||||
(&Ref(_, l_ty, _), &Ref(_, r_ty, _)) // &str or &String + &str, &String or &&str
|
||||
if (*l_ty.kind() == Str || is_std_string(l_ty))
|
||||
&& (*r_ty.kind() == Str
|
||||
|| is_std_string(r_ty)
|
||||
|| matches!(
|
||||
r_ty.kind(), Ref(_, inner_ty, _) if *inner_ty.kind() == Str
|
||||
)) =>
|
||||
{
|
||||
if let IsAssign::No = is_assign { // Do not supply this message if `&str += &str`
|
||||
err.span_label(op.span, "`+` cannot be used to concatenate two `&str` strings");
|
||||
err.note(str_concat_note);
|
||||
if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
|
||||
err.span_suggestion_verbose(
|
||||
lhs_expr.span.until(lhs_inner_expr.span),
|
||||
rm_borrow_msg,
|
||||
"",
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
} else {
|
||||
err.span_suggestion_verbose(
|
||||
lhs_expr.span.shrink_to_hi(),
|
||||
to_owned_msg,
|
||||
".to_owned()",
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
(&Ref(_, l_ty, _), &Adt(..)) // Handle `&str` & `&String` + `String`
|
||||
if (*l_ty.kind() == Str || is_std_string(l_ty)) && is_std_string(rhs_ty) =>
|
||||
{
|
||||
err.span_label(
|
||||
op.span,
|
||||
"`+` cannot be used to concatenate a `&str` with a `String`",
|
||||
);
|
||||
match is_assign {
|
||||
IsAssign::No => {
|
||||
let sugg_msg;
|
||||
let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
|
||||
sugg_msg = "remove the borrow on the left and add one on the right";
|
||||
(lhs_expr.span.until(lhs_inner_expr.span), "".to_owned())
|
||||
} else {
|
||||
sugg_msg = "create an owned `String` on the left and add a borrow on the right";
|
||||
(lhs_expr.span.shrink_to_hi(), ".to_owned()".to_owned())
|
||||
};
|
||||
let suggestions = vec![
|
||||
lhs_sugg,
|
||||
(rhs_expr.span.shrink_to_lo(), "&".to_owned()),
|
||||
];
|
||||
err.multipart_suggestion_verbose(
|
||||
sugg_msg,
|
||||
suggestions,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
IsAssign::Yes => {
|
||||
err.note(str_concat_note);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_user_unop(
|
||||
&self,
|
||||
ex: &'tcx hir::Expr<'tcx>,
|
||||
operand_ty: Ty<'tcx>,
|
||||
op: hir::UnOp,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Ty<'tcx> {
|
||||
assert!(op.is_by_value());
|
||||
match self.lookup_op_method(operand_ty, None, None, Op::Unary(op, ex.span), expected) {
|
||||
Ok(method) => {
|
||||
self.write_method_call(ex.hir_id, method);
|
||||
method.sig.output()
|
||||
}
|
||||
Err(errors) => {
|
||||
let actual = self.resolve_vars_if_possible(operand_ty);
|
||||
if !actual.references_error() {
|
||||
let mut err = struct_span_err!(
|
||||
self.tcx.sess,
|
||||
ex.span,
|
||||
E0600,
|
||||
"cannot apply unary operator `{}` to type `{}`",
|
||||
op.as_str(),
|
||||
actual
|
||||
);
|
||||
err.span_label(
|
||||
ex.span,
|
||||
format!("cannot apply unary operator `{}`", op.as_str()),
|
||||
);
|
||||
|
||||
if operand_ty.has_non_region_param() {
|
||||
let predicates = errors.iter().filter_map(|error| {
|
||||
error.obligation.predicate.to_opt_poly_trait_pred()
|
||||
});
|
||||
for pred in predicates {
|
||||
self.err_ctxt().suggest_restricting_param_bound(
|
||||
&mut err,
|
||||
pred,
|
||||
None,
|
||||
self.body_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let sp = self.tcx.sess.source_map().start_point(ex.span);
|
||||
if let Some(sp) =
|
||||
self.tcx.sess.parse_sess.ambiguous_block_expr_parse.borrow().get(&sp)
|
||||
{
|
||||
// If the previous expression was a block expression, suggest parentheses
|
||||
// (turning this into a binary subtraction operation instead.)
|
||||
// for example, `{2} - 2` -> `({2}) - 2` (see src\test\ui\parser\expr-as-stmt.rs)
|
||||
err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp));
|
||||
} else {
|
||||
match actual.kind() {
|
||||
Uint(_) if op == hir::UnOp::Neg => {
|
||||
err.note("unsigned values cannot be negated");
|
||||
|
||||
if let hir::ExprKind::Unary(
|
||||
_,
|
||||
hir::Expr {
|
||||
kind:
|
||||
hir::ExprKind::Lit(Spanned {
|
||||
node: ast::LitKind::Int(1, _),
|
||||
..
|
||||
}),
|
||||
..
|
||||
},
|
||||
) = ex.kind
|
||||
{
|
||||
err.span_suggestion(
|
||||
ex.span,
|
||||
&format!(
|
||||
"you may have meant the maximum value of `{actual}`",
|
||||
),
|
||||
format!("{actual}::MAX"),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
Str | Never | Char | Tuple(_) | Array(_, _) => {}
|
||||
Ref(_, lty, _) if *lty.kind() == Str => {}
|
||||
_ => {
|
||||
self.note_unmet_impls_on_type(&mut err, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
err.emit();
|
||||
}
|
||||
self.tcx.ty_error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_op_method(
|
||||
&self,
|
||||
lhs_ty: Ty<'tcx>,
|
||||
other_ty: Option<Ty<'tcx>>,
|
||||
other_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
|
||||
op: Op,
|
||||
expected: Expectation<'tcx>,
|
||||
) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
|
||||
let span = match op {
|
||||
Op::Binary(op, _) => op.span,
|
||||
Op::Unary(_, span) => span,
|
||||
};
|
||||
let (opname, trait_did) = lang_item_for_op(self.tcx, op, span);
|
||||
|
||||
debug!(
|
||||
"lookup_op_method(lhs_ty={:?}, op={:?}, opname={:?}, trait_did={:?})",
|
||||
lhs_ty, op, opname, trait_did
|
||||
);
|
||||
|
||||
// Catches cases like #83893, where a lang item is declared with the
|
||||
// wrong number of generic arguments. Should have yielded an error
|
||||
// elsewhere by now, but we have to catch it here so that we do not
|
||||
// index `other_tys` out of bounds (if the lang item has too many
|
||||
// generic arguments, `other_tys` is too short).
|
||||
if !has_expected_num_generic_args(
|
||||
self.tcx,
|
||||
trait_did,
|
||||
match op {
|
||||
// Binary ops have a generic right-hand side, unary ops don't
|
||||
Op::Binary(..) => 1,
|
||||
Op::Unary(..) => 0,
|
||||
},
|
||||
) {
|
||||
return Err(vec![]);
|
||||
}
|
||||
|
||||
let opname = Ident::with_dummy_span(opname);
|
||||
let method = trait_did.and_then(|trait_did| {
|
||||
self.lookup_op_method_in_trait(
|
||||
span,
|
||||
opname,
|
||||
trait_did,
|
||||
lhs_ty,
|
||||
other_ty,
|
||||
other_ty_expr,
|
||||
expected,
|
||||
)
|
||||
});
|
||||
|
||||
match (method, trait_did) {
|
||||
(Some(ok), _) => {
|
||||
let method = self.register_infer_ok_obligations(ok);
|
||||
self.select_obligations_where_possible(false, |_| {});
|
||||
Ok(method)
|
||||
}
|
||||
(None, None) => Err(vec![]),
|
||||
(None, Some(trait_did)) => {
|
||||
let (obligation, _) = self.obligation_for_op_method(
|
||||
span,
|
||||
trait_did,
|
||||
lhs_ty,
|
||||
other_ty,
|
||||
other_ty_expr,
|
||||
expected,
|
||||
);
|
||||
let mut fulfill = <dyn TraitEngine<'_>>::new(self.tcx);
|
||||
fulfill.register_predicate_obligation(self, obligation);
|
||||
Err(fulfill.select_where_possible(&self.infcx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lang_item_for_op(
|
||||
tcx: TyCtxt<'_>,
|
||||
op: Op,
|
||||
span: Span,
|
||||
) -> (rustc_span::Symbol, Option<hir::def_id::DefId>) {
|
||||
let lang = tcx.lang_items();
|
||||
if let Op::Binary(op, IsAssign::Yes) = op {
|
||||
match op.node {
|
||||
hir::BinOpKind::Add => (sym::add_assign, lang.add_assign_trait()),
|
||||
hir::BinOpKind::Sub => (sym::sub_assign, lang.sub_assign_trait()),
|
||||
hir::BinOpKind::Mul => (sym::mul_assign, lang.mul_assign_trait()),
|
||||
hir::BinOpKind::Div => (sym::div_assign, lang.div_assign_trait()),
|
||||
hir::BinOpKind::Rem => (sym::rem_assign, lang.rem_assign_trait()),
|
||||
hir::BinOpKind::BitXor => (sym::bitxor_assign, lang.bitxor_assign_trait()),
|
||||
hir::BinOpKind::BitAnd => (sym::bitand_assign, lang.bitand_assign_trait()),
|
||||
hir::BinOpKind::BitOr => (sym::bitor_assign, lang.bitor_assign_trait()),
|
||||
hir::BinOpKind::Shl => (sym::shl_assign, lang.shl_assign_trait()),
|
||||
hir::BinOpKind::Shr => (sym::shr_assign, lang.shr_assign_trait()),
|
||||
hir::BinOpKind::Lt
|
||||
| hir::BinOpKind::Le
|
||||
| hir::BinOpKind::Ge
|
||||
| hir::BinOpKind::Gt
|
||||
| hir::BinOpKind::Eq
|
||||
| hir::BinOpKind::Ne
|
||||
| hir::BinOpKind::And
|
||||
| hir::BinOpKind::Or => {
|
||||
span_bug!(span, "impossible assignment operation: {}=", op.node.as_str())
|
||||
}
|
||||
}
|
||||
} else if let Op::Binary(op, IsAssign::No) = op {
|
||||
match op.node {
|
||||
hir::BinOpKind::Add => (sym::add, lang.add_trait()),
|
||||
hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()),
|
||||
hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()),
|
||||
hir::BinOpKind::Div => (sym::div, lang.div_trait()),
|
||||
hir::BinOpKind::Rem => (sym::rem, lang.rem_trait()),
|
||||
hir::BinOpKind::BitXor => (sym::bitxor, lang.bitxor_trait()),
|
||||
hir::BinOpKind::BitAnd => (sym::bitand, lang.bitand_trait()),
|
||||
hir::BinOpKind::BitOr => (sym::bitor, lang.bitor_trait()),
|
||||
hir::BinOpKind::Shl => (sym::shl, lang.shl_trait()),
|
||||
hir::BinOpKind::Shr => (sym::shr, lang.shr_trait()),
|
||||
hir::BinOpKind::Lt => (sym::lt, lang.partial_ord_trait()),
|
||||
hir::BinOpKind::Le => (sym::le, lang.partial_ord_trait()),
|
||||
hir::BinOpKind::Ge => (sym::ge, lang.partial_ord_trait()),
|
||||
hir::BinOpKind::Gt => (sym::gt, lang.partial_ord_trait()),
|
||||
hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()),
|
||||
hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()),
|
||||
hir::BinOpKind::And | hir::BinOpKind::Or => {
|
||||
span_bug!(span, "&& and || are not overloadable")
|
||||
}
|
||||
}
|
||||
} else if let Op::Unary(hir::UnOp::Not, _) = op {
|
||||
(sym::not, lang.not_trait())
|
||||
} else if let Op::Unary(hir::UnOp::Neg, _) = op {
|
||||
(sym::neg, lang.neg_trait())
|
||||
} else {
|
||||
bug!("lookup_op_method: op not supported: {:?}", op)
|
||||
}
|
||||
}
|
||||
|
||||
// Binary operator categories. These categories summarize the behavior
|
||||
// with respect to the builtin operations supported.
|
||||
enum BinOpCategory {
|
||||
/// &&, || -- cannot be overridden
|
||||
Shortcircuit,
|
||||
|
||||
/// <<, >> -- when shifting a single integer, rhs can be any
|
||||
/// integer type. For simd, types must match.
|
||||
Shift,
|
||||
|
||||
/// +, -, etc -- takes equal types, produces same type as input,
|
||||
/// applicable to ints/floats/simd
|
||||
Math,
|
||||
|
||||
/// &, |, ^ -- takes equal types, produces same type as input,
|
||||
/// applicable to ints/floats/simd/bool
|
||||
Bitwise,
|
||||
|
||||
/// ==, !=, etc -- takes equal types, produces bools, except for simd,
|
||||
/// which produce the input type
|
||||
Comparison,
|
||||
}
|
||||
|
||||
impl BinOpCategory {
|
||||
fn from(op: hir::BinOp) -> BinOpCategory {
|
||||
match op.node {
|
||||
hir::BinOpKind::Shl | hir::BinOpKind::Shr => BinOpCategory::Shift,
|
||||
|
||||
hir::BinOpKind::Add
|
||||
| hir::BinOpKind::Sub
|
||||
| hir::BinOpKind::Mul
|
||||
| hir::BinOpKind::Div
|
||||
| hir::BinOpKind::Rem => BinOpCategory::Math,
|
||||
|
||||
hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => {
|
||||
BinOpCategory::Bitwise
|
||||
}
|
||||
|
||||
hir::BinOpKind::Eq
|
||||
| hir::BinOpKind::Ne
|
||||
| hir::BinOpKind::Lt
|
||||
| hir::BinOpKind::Le
|
||||
| hir::BinOpKind::Ge
|
||||
| hir::BinOpKind::Gt => BinOpCategory::Comparison,
|
||||
|
||||
hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`)
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum IsAssign {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Op {
|
||||
Binary(hir::BinOp, IsAssign),
|
||||
Unary(hir::UnOp, Span),
|
||||
}
|
||||
|
||||
/// Dereferences a single level of immutable referencing.
|
||||
fn deref_ty_if_possible<'tcx>(ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
match ty.kind() {
|
||||
ty::Ref(_, ty, hir::Mutability::Not) => *ty,
|
||||
_ => ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a built-in arithmetic operation (e.g., u32
|
||||
/// + u32, i16x4 == i16x4) and false if these types would have to be
|
||||
/// overloaded to be legal. There are two reasons that we distinguish
|
||||
/// builtin operations from overloaded ones (vs trying to drive
|
||||
/// everything uniformly through the trait system and intrinsics or
|
||||
/// something like that):
|
||||
///
|
||||
/// 1. Builtin operations can trivially be evaluated in constants.
|
||||
/// 2. For comparison operators applied to SIMD types the result is
|
||||
/// not of type `bool`. For example, `i16x4 == i16x4` yields a
|
||||
/// type like `i16x4`. This means that the overloaded trait
|
||||
/// `PartialEq` is not applicable.
|
||||
///
|
||||
/// Reason #2 is the killer. I tried for a while to always use
|
||||
/// overloaded logic and just check the types in constants/codegen after
|
||||
/// the fact, and it worked fine, except for SIMD types. -nmatsakis
|
||||
fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool {
|
||||
// Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
|
||||
// (See https://github.com/rust-lang/rust/issues/57447.)
|
||||
let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
|
||||
|
||||
match BinOpCategory::from(op) {
|
||||
BinOpCategory::Shortcircuit => true,
|
||||
|
||||
BinOpCategory::Shift => {
|
||||
lhs.references_error()
|
||||
|| rhs.references_error()
|
||||
|| lhs.is_integral() && rhs.is_integral()
|
||||
}
|
||||
|
||||
BinOpCategory::Math => {
|
||||
lhs.references_error()
|
||||
|| rhs.references_error()
|
||||
|| lhs.is_integral() && rhs.is_integral()
|
||||
|| lhs.is_floating_point() && rhs.is_floating_point()
|
||||
}
|
||||
|
||||
BinOpCategory::Bitwise => {
|
||||
lhs.references_error()
|
||||
|| rhs.references_error()
|
||||
|| lhs.is_integral() && rhs.is_integral()
|
||||
|| lhs.is_floating_point() && rhs.is_floating_point()
|
||||
|| lhs.is_bool() && rhs.is_bool()
|
||||
}
|
||||
|
||||
BinOpCategory::Comparison => {
|
||||
lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TypeParamEraser<'a, 'tcx>(&'a FnCtxt<'a, 'tcx>, Span);
|
||||
|
||||
impl<'tcx> TypeFolder<'tcx> for TypeParamEraser<'_, 'tcx> {
|
||||
fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
self.0.tcx
|
||||
}
|
||||
|
||||
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
match ty.kind() {
|
||||
ty::Param(_) => self.0.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: self.1,
|
||||
}),
|
||||
_ => ty.super_fold_with(self),
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,451 +0,0 @@
|
|||
use crate::check::method::MethodCallee;
|
||||
use crate::check::{has_expected_num_generic_args, FnCtxt, PlaceOp};
|
||||
use rustc_ast as ast;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use rustc_infer::infer::InferOk;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref, PointerCast};
|
||||
use rustc_middle::ty::adjustment::{AllowTwoPhase, AutoBorrow, AutoBorrowMutability};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::symbol::{sym, Ident};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::autoderef::Autoderef;
|
||||
use std::slice;
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
/// Type-check `*oprnd_expr` with `oprnd_expr` type-checked already.
|
||||
pub(super) fn lookup_derefing(
|
||||
&self,
|
||||
expr: &hir::Expr<'_>,
|
||||
oprnd_expr: &'tcx hir::Expr<'tcx>,
|
||||
oprnd_ty: Ty<'tcx>,
|
||||
) -> Option<Ty<'tcx>> {
|
||||
if let Some(mt) = oprnd_ty.builtin_deref(true) {
|
||||
return Some(mt.ty);
|
||||
}
|
||||
|
||||
let ok = self.try_overloaded_deref(expr.span, oprnd_ty)?;
|
||||
let method = self.register_infer_ok_obligations(ok);
|
||||
if let ty::Ref(region, _, hir::Mutability::Not) = method.sig.inputs()[0].kind() {
|
||||
self.apply_adjustments(
|
||||
oprnd_expr,
|
||||
vec![Adjustment {
|
||||
kind: Adjust::Borrow(AutoBorrow::Ref(*region, AutoBorrowMutability::Not)),
|
||||
target: method.sig.inputs()[0],
|
||||
}],
|
||||
);
|
||||
} else {
|
||||
span_bug!(expr.span, "input to deref is not a ref?");
|
||||
}
|
||||
let ty = self.make_overloaded_place_return_type(method).ty;
|
||||
self.write_method_call(expr.hir_id, method);
|
||||
Some(ty)
|
||||
}
|
||||
|
||||
/// Type-check `*base_expr[index_expr]` with `base_expr` and `index_expr` type-checked already.
|
||||
pub(super) fn lookup_indexing(
|
||||
&self,
|
||||
expr: &hir::Expr<'_>,
|
||||
base_expr: &'tcx hir::Expr<'tcx>,
|
||||
base_ty: Ty<'tcx>,
|
||||
index_expr: &'tcx hir::Expr<'tcx>,
|
||||
idx_ty: Ty<'tcx>,
|
||||
) -> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)> {
|
||||
// FIXME(#18741) -- this is almost but not quite the same as the
|
||||
// autoderef that normal method probing does. They could likely be
|
||||
// consolidated.
|
||||
|
||||
let mut autoderef = self.autoderef(base_expr.span, base_ty);
|
||||
let mut result = None;
|
||||
while result.is_none() && autoderef.next().is_some() {
|
||||
result = self.try_index_step(expr, base_expr, &autoderef, idx_ty, index_expr);
|
||||
}
|
||||
self.register_predicates(autoderef.into_obligations());
|
||||
result
|
||||
}
|
||||
|
||||
fn negative_index(
|
||||
&self,
|
||||
ty: Ty<'tcx>,
|
||||
span: Span,
|
||||
base_expr: &hir::Expr<'_>,
|
||||
) -> Option<(Ty<'tcx>, Ty<'tcx>)> {
|
||||
let ty = self.resolve_vars_if_possible(ty);
|
||||
let mut err = self.tcx.sess.struct_span_err(
|
||||
span,
|
||||
&format!("negative integers cannot be used to index on a `{ty}`"),
|
||||
);
|
||||
err.span_label(span, &format!("cannot use a negative integer for indexing on `{ty}`"));
|
||||
if let (hir::ExprKind::Path(..), Ok(snippet)) =
|
||||
(&base_expr.kind, self.tcx.sess.source_map().span_to_snippet(base_expr.span))
|
||||
{
|
||||
// `foo[-1]` to `foo[foo.len() - 1]`
|
||||
err.span_suggestion_verbose(
|
||||
span.shrink_to_lo(),
|
||||
&format!(
|
||||
"to access an element starting from the end of the `{ty}`, compute the index",
|
||||
),
|
||||
format!("{snippet}.len() "),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
Some((self.tcx.ty_error(), self.tcx.ty_error()))
|
||||
}
|
||||
|
||||
/// To type-check `base_expr[index_expr]`, we progressively autoderef
|
||||
/// (and otherwise adjust) `base_expr`, looking for a type which either
|
||||
/// supports builtin indexing or overloaded indexing.
|
||||
/// This loop implements one step in that search; the autoderef loop
|
||||
/// is implemented by `lookup_indexing`.
|
||||
fn try_index_step(
|
||||
&self,
|
||||
expr: &hir::Expr<'_>,
|
||||
base_expr: &hir::Expr<'_>,
|
||||
autoderef: &Autoderef<'a, 'tcx>,
|
||||
index_ty: Ty<'tcx>,
|
||||
index_expr: &hir::Expr<'_>,
|
||||
) -> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)> {
|
||||
let adjusted_ty =
|
||||
self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false));
|
||||
debug!(
|
||||
"try_index_step(expr={:?}, base_expr={:?}, adjusted_ty={:?}, \
|
||||
index_ty={:?})",
|
||||
expr, base_expr, adjusted_ty, index_ty
|
||||
);
|
||||
|
||||
if let hir::ExprKind::Unary(
|
||||
hir::UnOp::Neg,
|
||||
hir::Expr {
|
||||
kind: hir::ExprKind::Lit(hir::Lit { node: ast::LitKind::Int(..), .. }),
|
||||
..
|
||||
},
|
||||
) = index_expr.kind
|
||||
{
|
||||
match adjusted_ty.kind() {
|
||||
ty::Adt(def, _) if self.tcx.is_diagnostic_item(sym::Vec, def.did()) => {
|
||||
return self.negative_index(adjusted_ty, index_expr.span, base_expr);
|
||||
}
|
||||
ty::Slice(_) | ty::Array(_, _) => {
|
||||
return self.negative_index(adjusted_ty, index_expr.span, base_expr);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for unsize in [false, true] {
|
||||
let mut self_ty = adjusted_ty;
|
||||
if unsize {
|
||||
// We only unsize arrays here.
|
||||
if let ty::Array(element_ty, _) = adjusted_ty.kind() {
|
||||
self_ty = self.tcx.mk_slice(*element_ty);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If some lookup succeeds, write callee into table and extract index/element
|
||||
// type from the method signature.
|
||||
// If some lookup succeeded, install method in table
|
||||
let input_ty = self.next_ty_var(TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::AutoDeref,
|
||||
span: base_expr.span,
|
||||
});
|
||||
let method =
|
||||
self.try_overloaded_place_op(expr.span, self_ty, &[input_ty], PlaceOp::Index);
|
||||
|
||||
if let Some(result) = method {
|
||||
debug!("try_index_step: success, using overloaded indexing");
|
||||
let method = self.register_infer_ok_obligations(result);
|
||||
|
||||
let mut adjustments = self.adjust_steps(autoderef);
|
||||
if let ty::Ref(region, _, hir::Mutability::Not) = method.sig.inputs()[0].kind() {
|
||||
adjustments.push(Adjustment {
|
||||
kind: Adjust::Borrow(AutoBorrow::Ref(*region, AutoBorrowMutability::Not)),
|
||||
target: self.tcx.mk_ref(
|
||||
*region,
|
||||
ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: adjusted_ty },
|
||||
),
|
||||
});
|
||||
} else {
|
||||
span_bug!(expr.span, "input to index is not a ref?");
|
||||
}
|
||||
if unsize {
|
||||
adjustments.push(Adjustment {
|
||||
kind: Adjust::Pointer(PointerCast::Unsize),
|
||||
target: method.sig.inputs()[0],
|
||||
});
|
||||
}
|
||||
self.apply_adjustments(base_expr, adjustments);
|
||||
|
||||
self.write_method_call(expr.hir_id, method);
|
||||
|
||||
return Some((input_ty, self.make_overloaded_place_return_type(method).ty));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Try to resolve an overloaded place op. We only deal with the immutable
|
||||
/// variant here (Deref/Index). In some contexts we would need the mutable
|
||||
/// variant (DerefMut/IndexMut); those would be later converted by
|
||||
/// `convert_place_derefs_to_mutable`.
|
||||
pub(super) fn try_overloaded_place_op(
|
||||
&self,
|
||||
span: Span,
|
||||
base_ty: Ty<'tcx>,
|
||||
arg_tys: &[Ty<'tcx>],
|
||||
op: PlaceOp,
|
||||
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
|
||||
debug!("try_overloaded_place_op({:?},{:?},{:?})", span, base_ty, op);
|
||||
|
||||
let (imm_tr, imm_op) = match op {
|
||||
PlaceOp::Deref => (self.tcx.lang_items().deref_trait(), sym::deref),
|
||||
PlaceOp::Index => (self.tcx.lang_items().index_trait(), sym::index),
|
||||
};
|
||||
|
||||
// If the lang item was declared incorrectly, stop here so that we don't
|
||||
// run into an ICE (#83893). The error is reported where the lang item is
|
||||
// declared.
|
||||
if !has_expected_num_generic_args(
|
||||
self.tcx,
|
||||
imm_tr,
|
||||
match op {
|
||||
PlaceOp::Deref => 0,
|
||||
PlaceOp::Index => 1,
|
||||
},
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
imm_tr.and_then(|trait_did| {
|
||||
self.lookup_method_in_trait(
|
||||
span,
|
||||
Ident::with_dummy_span(imm_op),
|
||||
trait_did,
|
||||
base_ty,
|
||||
Some(arg_tys),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn try_mutable_overloaded_place_op(
|
||||
&self,
|
||||
span: Span,
|
||||
base_ty: Ty<'tcx>,
|
||||
arg_tys: &[Ty<'tcx>],
|
||||
op: PlaceOp,
|
||||
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
|
||||
debug!("try_mutable_overloaded_place_op({:?},{:?},{:?})", span, base_ty, op);
|
||||
|
||||
let (mut_tr, mut_op) = match op {
|
||||
PlaceOp::Deref => (self.tcx.lang_items().deref_mut_trait(), sym::deref_mut),
|
||||
PlaceOp::Index => (self.tcx.lang_items().index_mut_trait(), sym::index_mut),
|
||||
};
|
||||
|
||||
// If the lang item was declared incorrectly, stop here so that we don't
|
||||
// run into an ICE (#83893). The error is reported where the lang item is
|
||||
// declared.
|
||||
if !has_expected_num_generic_args(
|
||||
self.tcx,
|
||||
mut_tr,
|
||||
match op {
|
||||
PlaceOp::Deref => 0,
|
||||
PlaceOp::Index => 1,
|
||||
},
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
mut_tr.and_then(|trait_did| {
|
||||
self.lookup_method_in_trait(
|
||||
span,
|
||||
Ident::with_dummy_span(mut_op),
|
||||
trait_did,
|
||||
base_ty,
|
||||
Some(arg_tys),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert auto-derefs, indices, etc of an expression from `Deref` and `Index`
|
||||
/// into `DerefMut` and `IndexMut` respectively.
|
||||
///
|
||||
/// This is a second pass of typechecking derefs/indices. We need this because we do not
|
||||
/// always know whether a place needs to be mutable or not in the first pass.
|
||||
/// This happens whether there is an implicit mutable reborrow, e.g. when the type
|
||||
/// is used as the receiver of a method call.
|
||||
pub fn convert_place_derefs_to_mutable(&self, expr: &hir::Expr<'_>) {
|
||||
// Gather up expressions we want to munge.
|
||||
let mut exprs = vec![expr];
|
||||
|
||||
while let hir::ExprKind::Field(ref expr, _)
|
||||
| hir::ExprKind::Index(ref expr, _)
|
||||
| hir::ExprKind::Unary(hir::UnOp::Deref, ref expr) = exprs.last().unwrap().kind
|
||||
{
|
||||
exprs.push(expr);
|
||||
}
|
||||
|
||||
debug!("convert_place_derefs_to_mutable: exprs={:?}", exprs);
|
||||
|
||||
// Fix up autoderefs and derefs.
|
||||
let mut inside_union = false;
|
||||
for (i, &expr) in exprs.iter().rev().enumerate() {
|
||||
debug!("convert_place_derefs_to_mutable: i={} expr={:?}", i, expr);
|
||||
|
||||
let mut source = self.node_ty(expr.hir_id);
|
||||
if matches!(expr.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _)) {
|
||||
// Clear previous flag; after a pointer indirection it does not apply any more.
|
||||
inside_union = false;
|
||||
}
|
||||
if source.is_union() {
|
||||
inside_union = true;
|
||||
}
|
||||
// Fix up the autoderefs. Autorefs can only occur immediately preceding
|
||||
// overloaded place ops, and will be fixed by them in order to get
|
||||
// the correct region.
|
||||
// Do not mutate adjustments in place, but rather take them,
|
||||
// and replace them after mutating them, to avoid having the
|
||||
// typeck results borrowed during (`deref_mut`) method resolution.
|
||||
let previous_adjustments =
|
||||
self.typeck_results.borrow_mut().adjustments_mut().remove(expr.hir_id);
|
||||
if let Some(mut adjustments) = previous_adjustments {
|
||||
for adjustment in &mut adjustments {
|
||||
if let Adjust::Deref(Some(ref mut deref)) = adjustment.kind
|
||||
&& let Some(ok) = self.try_mutable_overloaded_place_op(
|
||||
expr.span,
|
||||
source,
|
||||
&[],
|
||||
PlaceOp::Deref,
|
||||
)
|
||||
{
|
||||
let method = self.register_infer_ok_obligations(ok);
|
||||
if let ty::Ref(region, _, mutbl) = *method.sig.output().kind() {
|
||||
*deref = OverloadedDeref { region, mutbl, span: deref.span };
|
||||
}
|
||||
// If this is a union field, also throw an error for `DerefMut` of `ManuallyDrop` (see RFC 2514).
|
||||
// This helps avoid accidental drops.
|
||||
if inside_union
|
||||
&& source.ty_adt_def().map_or(false, |adt| adt.is_manually_drop())
|
||||
{
|
||||
let mut err = self.tcx.sess.struct_span_err(
|
||||
expr.span,
|
||||
"not automatically applying `DerefMut` on `ManuallyDrop` union field",
|
||||
);
|
||||
err.help(
|
||||
"writing to this reference calls the destructor for the old value",
|
||||
);
|
||||
err.help("add an explicit `*` if that is desired, or call `ptr::write` to not run the destructor");
|
||||
err.emit();
|
||||
}
|
||||
}
|
||||
source = adjustment.target;
|
||||
}
|
||||
self.typeck_results.borrow_mut().adjustments_mut().insert(expr.hir_id, adjustments);
|
||||
}
|
||||
|
||||
match expr.kind {
|
||||
hir::ExprKind::Index(base_expr, ..) => {
|
||||
self.convert_place_op_to_mutable(PlaceOp::Index, expr, base_expr);
|
||||
}
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, base_expr) => {
|
||||
self.convert_place_op_to_mutable(PlaceOp::Deref, expr, base_expr);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_place_op_to_mutable(
|
||||
&self,
|
||||
op: PlaceOp,
|
||||
expr: &hir::Expr<'_>,
|
||||
base_expr: &hir::Expr<'_>,
|
||||
) {
|
||||
debug!("convert_place_op_to_mutable({:?}, {:?}, {:?})", op, expr, base_expr);
|
||||
if !self.typeck_results.borrow().is_method_call(expr) {
|
||||
debug!("convert_place_op_to_mutable - builtin, nothing to do");
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to deref because overloaded place ops take self by-reference.
|
||||
let base_ty = self
|
||||
.typeck_results
|
||||
.borrow()
|
||||
.expr_ty_adjusted(base_expr)
|
||||
.builtin_deref(false)
|
||||
.expect("place op takes something that is not a ref")
|
||||
.ty;
|
||||
|
||||
let arg_ty = match op {
|
||||
PlaceOp::Deref => None,
|
||||
PlaceOp::Index => {
|
||||
// We would need to recover the `T` used when we resolve `<_ as Index<T>>::index`
|
||||
// in try_index_step. This is the subst at index 1.
|
||||
//
|
||||
// Note: we should *not* use `expr_ty` of index_expr here because autoderef
|
||||
// during coercions can cause type of index_expr to differ from `T` (#72002).
|
||||
// We also could not use `expr_ty_adjusted` of index_expr because reborrowing
|
||||
// during coercions can also cause type of index_expr to differ from `T`,
|
||||
// which can potentially cause regionck failure (#74933).
|
||||
Some(self.typeck_results.borrow().node_substs(expr.hir_id).type_at(1))
|
||||
}
|
||||
};
|
||||
let arg_tys = match arg_ty {
|
||||
None => &[],
|
||||
Some(ref ty) => slice::from_ref(ty),
|
||||
};
|
||||
|
||||
let method = self.try_mutable_overloaded_place_op(expr.span, base_ty, arg_tys, op);
|
||||
let method = match method {
|
||||
Some(ok) => self.register_infer_ok_obligations(ok),
|
||||
// Couldn't find the mutable variant of the place op, keep the
|
||||
// current, immutable version.
|
||||
None => return,
|
||||
};
|
||||
debug!("convert_place_op_to_mutable: method={:?}", method);
|
||||
self.write_method_call(expr.hir_id, method);
|
||||
|
||||
let ty::Ref(region, _, hir::Mutability::Mut) = method.sig.inputs()[0].kind() else {
|
||||
span_bug!(expr.span, "input to mutable place op is not a mut ref?");
|
||||
};
|
||||
|
||||
// Convert the autoref in the base expr to mutable with the correct
|
||||
// region and mutability.
|
||||
let base_expr_ty = self.node_ty(base_expr.hir_id);
|
||||
if let Some(adjustments) =
|
||||
self.typeck_results.borrow_mut().adjustments_mut().get_mut(base_expr.hir_id)
|
||||
{
|
||||
let mut source = base_expr_ty;
|
||||
for adjustment in &mut adjustments[..] {
|
||||
if let Adjust::Borrow(AutoBorrow::Ref(..)) = adjustment.kind {
|
||||
debug!("convert_place_op_to_mutable: converting autoref {:?}", adjustment);
|
||||
let mutbl = AutoBorrowMutability::Mut {
|
||||
// Deref/indexing can be desugared to a method call,
|
||||
// so maybe we could use two-phase here.
|
||||
// See the documentation of AllowTwoPhase for why that's
|
||||
// not the case today.
|
||||
allow_two_phase_borrow: AllowTwoPhase::No,
|
||||
};
|
||||
adjustment.kind = Adjust::Borrow(AutoBorrow::Ref(*region, mutbl));
|
||||
adjustment.target = self
|
||||
.tcx
|
||||
.mk_ref(*region, ty::TypeAndMut { ty: source, mutbl: mutbl.into() });
|
||||
}
|
||||
source = adjustment.target;
|
||||
}
|
||||
|
||||
// If we have an autoref followed by unsizing at the end, fix the unsize target.
|
||||
if let [
|
||||
..,
|
||||
Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(..)), .. },
|
||||
Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), ref mut target },
|
||||
] = adjustments[..]
|
||||
{
|
||||
*target = method.sig.inputs()[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
use super::FnCtxt;
|
||||
use hir::def_id::DefId;
|
||||
use hir::Node;
|
||||
use rustc_hir as hir;
|
||||
use rustc_middle::middle::region::{RvalueCandidateType, Scope, ScopeTree};
|
||||
use rustc_middle::ty::RvalueScopes;
|
||||
|
||||
/// Applied to an expression `expr` if `expr` -- or something owned or partially owned by
|
||||
/// `expr` -- is going to be indirectly referenced by a variable in a let statement. In that
|
||||
/// case, the "temporary lifetime" or `expr` is extended to be the block enclosing the `let`
|
||||
/// statement.
|
||||
///
|
||||
/// More formally, if `expr` matches the grammar `ET`, record the rvalue scope of the matching
|
||||
/// `<rvalue>` as `blk_id`:
|
||||
///
|
||||
/// ```text
|
||||
/// ET = *ET
|
||||
/// | ET[...]
|
||||
/// | ET.f
|
||||
/// | (ET)
|
||||
/// | <rvalue>
|
||||
/// ```
|
||||
///
|
||||
/// Note: ET is intended to match "rvalues or places based on rvalues".
|
||||
fn record_rvalue_scope_rec(
|
||||
rvalue_scopes: &mut RvalueScopes,
|
||||
mut expr: &hir::Expr<'_>,
|
||||
lifetime: Option<Scope>,
|
||||
) {
|
||||
loop {
|
||||
// Note: give all the expressions matching `ET` with the
|
||||
// extended temporary lifetime, not just the innermost rvalue,
|
||||
// because in codegen if we must compile e.g., `*rvalue()`
|
||||
// into a temporary, we request the temporary scope of the
|
||||
// outer expression.
|
||||
|
||||
rvalue_scopes.record_rvalue_scope(expr.hir_id.local_id, lifetime);
|
||||
|
||||
match expr.kind {
|
||||
hir::ExprKind::AddrOf(_, _, subexpr)
|
||||
| hir::ExprKind::Unary(hir::UnOp::Deref, subexpr)
|
||||
| hir::ExprKind::Field(subexpr, _)
|
||||
| hir::ExprKind::Index(subexpr, _) => {
|
||||
expr = subexpr;
|
||||
}
|
||||
_ => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn record_rvalue_scope(
|
||||
rvalue_scopes: &mut RvalueScopes,
|
||||
expr: &hir::Expr<'_>,
|
||||
candidate: &RvalueCandidateType,
|
||||
) {
|
||||
debug!("resolve_rvalue_scope(expr={expr:?}, candidate={candidate:?})");
|
||||
match candidate {
|
||||
RvalueCandidateType::Borrow { lifetime, .. }
|
||||
| RvalueCandidateType::Pattern { lifetime, .. } => {
|
||||
record_rvalue_scope_rec(rvalue_scopes, expr, *lifetime)
|
||||
} // FIXME(@dingxiangfei2009): handle the candidates in the function call arguments
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_rvalue_scopes<'a, 'tcx>(
|
||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||
scope_tree: &'a ScopeTree,
|
||||
def_id: DefId,
|
||||
) -> RvalueScopes {
|
||||
let tcx = &fcx.tcx;
|
||||
let hir_map = tcx.hir();
|
||||
let mut rvalue_scopes = RvalueScopes::new();
|
||||
debug!("start resolving rvalue scopes, def_id={def_id:?}");
|
||||
debug!("rvalue_scope: rvalue_candidates={:?}", scope_tree.rvalue_candidates);
|
||||
for (&hir_id, candidate) in &scope_tree.rvalue_candidates {
|
||||
let Some(Node::Expr(expr)) = hir_map.find(hir_id) else {
|
||||
bug!("hir node does not exist")
|
||||
};
|
||||
record_rvalue_scope(&mut rvalue_scopes, expr, candidate);
|
||||
}
|
||||
rvalue_scopes
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,807 +0,0 @@
|
|||
// Type resolution: the phase that finds all the types in the AST with
|
||||
// unresolved type variables and replaces "ty_var" types with their
|
||||
// substitutions.
|
||||
|
||||
use crate::check::FnCtxt;
|
||||
use hir::def_id::LocalDefId;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_infer::infer::error_reporting::TypeAnnotationNeeded::E0282;
|
||||
use rustc_infer::infer::InferCtxt;
|
||||
use rustc_middle::hir::place::Place as HirPlace;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast};
|
||||
use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
|
||||
use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable};
|
||||
use rustc_middle::ty::TypeckResults;
|
||||
use rustc_middle::ty::{self, ClosureSizeProfileData, Ty, TyCtxt};
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::Span;
|
||||
|
||||
use std::mem;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Entry point
|
||||
|
||||
// During type inference, partially inferred types are
|
||||
// represented using Type variables (ty::Infer). These don't appear in
|
||||
// the final TypeckResults since all of the types should have been
|
||||
// inferred once typeck is done.
|
||||
// When type inference is running however, having to update the typeck
|
||||
// typeck results every time a new type is inferred would be unreasonably slow,
|
||||
// so instead all of the replacement happens at the end in
|
||||
// resolve_type_vars_in_body, which creates a new TypeTables which
|
||||
// doesn't contain any inference types.
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
pub fn resolve_type_vars_in_body(
|
||||
&self,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
) -> &'tcx ty::TypeckResults<'tcx> {
|
||||
let item_id = self.tcx.hir().body_owner(body.id());
|
||||
let item_def_id = self.tcx.hir().local_def_id(item_id);
|
||||
|
||||
// This attribute causes us to dump some writeback information
|
||||
// in the form of errors, which is used for unit tests.
|
||||
let rustc_dump_user_substs =
|
||||
self.tcx.has_attr(item_def_id.to_def_id(), sym::rustc_dump_user_substs);
|
||||
|
||||
let mut wbcx = WritebackCx::new(self, body, rustc_dump_user_substs);
|
||||
for param in body.params {
|
||||
wbcx.visit_node_id(param.pat.span, param.hir_id);
|
||||
}
|
||||
// Type only exists for constants and statics, not functions.
|
||||
match self.tcx.hir().body_owner_kind(item_def_id) {
|
||||
hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => {
|
||||
wbcx.visit_node_id(body.value.span, item_id);
|
||||
}
|
||||
hir::BodyOwnerKind::Closure | hir::BodyOwnerKind::Fn => (),
|
||||
}
|
||||
wbcx.visit_body(body);
|
||||
wbcx.visit_min_capture_map();
|
||||
wbcx.eval_closure_size();
|
||||
wbcx.visit_fake_reads_map();
|
||||
wbcx.visit_closures();
|
||||
wbcx.visit_liberated_fn_sigs();
|
||||
wbcx.visit_fru_field_types();
|
||||
wbcx.visit_opaque_types();
|
||||
wbcx.visit_coercion_casts();
|
||||
wbcx.visit_user_provided_tys();
|
||||
wbcx.visit_user_provided_sigs();
|
||||
wbcx.visit_generator_interior_types();
|
||||
|
||||
wbcx.typeck_results.rvalue_scopes =
|
||||
mem::take(&mut self.typeck_results.borrow_mut().rvalue_scopes);
|
||||
|
||||
let used_trait_imports =
|
||||
mem::take(&mut self.typeck_results.borrow_mut().used_trait_imports);
|
||||
debug!("used_trait_imports({:?}) = {:?}", item_def_id, used_trait_imports);
|
||||
wbcx.typeck_results.used_trait_imports = used_trait_imports;
|
||||
|
||||
wbcx.typeck_results.treat_byte_string_as_slice =
|
||||
mem::take(&mut self.typeck_results.borrow_mut().treat_byte_string_as_slice);
|
||||
|
||||
if self.is_tainted_by_errors() {
|
||||
// FIXME(eddyb) keep track of `ErrorGuaranteed` from where the error was emitted.
|
||||
wbcx.typeck_results.tainted_by_errors =
|
||||
Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
|
||||
}
|
||||
|
||||
debug!("writeback: typeck results for {:?} are {:#?}", item_def_id, wbcx.typeck_results);
|
||||
|
||||
self.tcx.arena.alloc(wbcx.typeck_results)
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// The Writeback context. This visitor walks the HIR, checking the
|
||||
// fn-specific typeck results to find references to types or regions. It
|
||||
// resolves those regions to remove inference variables and writes the
|
||||
// final result back into the master typeck results in the tcx. Here and
|
||||
// there, it applies a few ad-hoc checks that were not convenient to
|
||||
// do elsewhere.
|
||||
|
||||
struct WritebackCx<'cx, 'tcx> {
|
||||
fcx: &'cx FnCtxt<'cx, 'tcx>,
|
||||
|
||||
typeck_results: ty::TypeckResults<'tcx>,
|
||||
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
|
||||
rustc_dump_user_substs: bool,
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
|
||||
fn new(
|
||||
fcx: &'cx FnCtxt<'cx, 'tcx>,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
rustc_dump_user_substs: bool,
|
||||
) -> WritebackCx<'cx, 'tcx> {
|
||||
let owner = body.id().hir_id.owner;
|
||||
|
||||
WritebackCx {
|
||||
fcx,
|
||||
typeck_results: ty::TypeckResults::new(owner),
|
||||
body,
|
||||
rustc_dump_user_substs,
|
||||
}
|
||||
}
|
||||
|
||||
fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
self.fcx.tcx
|
||||
}
|
||||
|
||||
fn write_ty_to_typeck_results(&mut self, hir_id: hir::HirId, ty: Ty<'tcx>) {
|
||||
debug!("write_ty_to_typeck_results({:?}, {:?})", hir_id, ty);
|
||||
assert!(!ty.needs_infer() && !ty.has_placeholders() && !ty.has_free_regions());
|
||||
self.typeck_results.node_types_mut().insert(hir_id, ty);
|
||||
}
|
||||
|
||||
// Hacky hack: During type-checking, we treat *all* operators
|
||||
// as potentially overloaded. But then, during writeback, if
|
||||
// we observe that something like `a+b` is (known to be)
|
||||
// operating on scalars, we clear the overload.
|
||||
fn fix_scalar_builtin_expr(&mut self, e: &hir::Expr<'_>) {
|
||||
match e.kind {
|
||||
hir::ExprKind::Unary(hir::UnOp::Neg | hir::UnOp::Not, inner) => {
|
||||
let inner_ty = self.fcx.node_ty(inner.hir_id);
|
||||
let inner_ty = self.fcx.resolve_vars_if_possible(inner_ty);
|
||||
|
||||
if inner_ty.is_scalar() {
|
||||
let mut typeck_results = self.fcx.typeck_results.borrow_mut();
|
||||
typeck_results.type_dependent_defs_mut().remove(e.hir_id);
|
||||
typeck_results.node_substs_mut().remove(e.hir_id);
|
||||
}
|
||||
}
|
||||
hir::ExprKind::Binary(ref op, lhs, rhs) | hir::ExprKind::AssignOp(ref op, lhs, rhs) => {
|
||||
let lhs_ty = self.fcx.node_ty(lhs.hir_id);
|
||||
let lhs_ty = self.fcx.resolve_vars_if_possible(lhs_ty);
|
||||
|
||||
let rhs_ty = self.fcx.node_ty(rhs.hir_id);
|
||||
let rhs_ty = self.fcx.resolve_vars_if_possible(rhs_ty);
|
||||
|
||||
if lhs_ty.is_scalar() && rhs_ty.is_scalar() {
|
||||
let mut typeck_results = self.fcx.typeck_results.borrow_mut();
|
||||
typeck_results.type_dependent_defs_mut().remove(e.hir_id);
|
||||
typeck_results.node_substs_mut().remove(e.hir_id);
|
||||
|
||||
match e.kind {
|
||||
hir::ExprKind::Binary(..) => {
|
||||
if !op.node.is_by_value() {
|
||||
let mut adjustments = typeck_results.adjustments_mut();
|
||||
if let Some(a) = adjustments.get_mut(lhs.hir_id) {
|
||||
a.pop();
|
||||
}
|
||||
if let Some(a) = adjustments.get_mut(rhs.hir_id) {
|
||||
a.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
hir::ExprKind::AssignOp(..)
|
||||
if let Some(a) = typeck_results.adjustments_mut().get_mut(lhs.hir_id) =>
|
||||
{
|
||||
a.pop();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// (ouz-a 1005988): Normally `[T] : std::ops::Index<usize>` should be normalized
|
||||
// into [T] but currently `Where` clause stops the normalization process for it,
|
||||
// here we compare types of expr and base in a code without `Where` clause they would be equal
|
||||
// if they are not we don't modify the expr, hence we bypass the ICE
|
||||
fn is_builtin_index(
|
||||
&mut self,
|
||||
typeck_results: &TypeckResults<'tcx>,
|
||||
e: &hir::Expr<'_>,
|
||||
base_ty: Ty<'tcx>,
|
||||
index_ty: Ty<'tcx>,
|
||||
) -> bool {
|
||||
if let Some(elem_ty) = base_ty.builtin_index() {
|
||||
let Some(exp_ty) = typeck_results.expr_ty_opt(e) else {return false;};
|
||||
let resolved_exp_ty = self.resolve(exp_ty, &e.span);
|
||||
|
||||
elem_ty == resolved_exp_ty && index_ty == self.fcx.tcx.types.usize
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Similar to operators, indexing is always assumed to be overloaded
|
||||
// Here, correct cases where an indexing expression can be simplified
|
||||
// to use builtin indexing because the index type is known to be
|
||||
// usize-ish
|
||||
fn fix_index_builtin_expr(&mut self, e: &hir::Expr<'_>) {
|
||||
if let hir::ExprKind::Index(ref base, ref index) = e.kind {
|
||||
let mut typeck_results = self.fcx.typeck_results.borrow_mut();
|
||||
|
||||
// All valid indexing looks like this; might encounter non-valid indexes at this point.
|
||||
let base_ty = typeck_results
|
||||
.expr_ty_adjusted_opt(base)
|
||||
.map(|t| self.fcx.resolve_vars_if_possible(t).kind());
|
||||
if base_ty.is_none() {
|
||||
// When encountering `return [0][0]` outside of a `fn` body we can encounter a base
|
||||
// that isn't in the type table. We assume more relevant errors have already been
|
||||
// emitted, so we delay an ICE if none have. (#64638)
|
||||
self.tcx().sess.delay_span_bug(e.span, &format!("bad base: `{:?}`", base));
|
||||
}
|
||||
if let Some(ty::Ref(_, base_ty, _)) = base_ty {
|
||||
let index_ty = typeck_results.expr_ty_adjusted_opt(index).unwrap_or_else(|| {
|
||||
// When encountering `return [0][0]` outside of a `fn` body we would attempt
|
||||
// to access an nonexistent index. We assume that more relevant errors will
|
||||
// already have been emitted, so we only gate on this with an ICE if no
|
||||
// error has been emitted. (#64638)
|
||||
self.fcx.tcx.ty_error_with_message(
|
||||
e.span,
|
||||
&format!("bad index {:?} for base: `{:?}`", index, base),
|
||||
)
|
||||
});
|
||||
let index_ty = self.fcx.resolve_vars_if_possible(index_ty);
|
||||
let resolved_base_ty = self.resolve(*base_ty, &base.span);
|
||||
|
||||
if self.is_builtin_index(&typeck_results, e, resolved_base_ty, index_ty) {
|
||||
// Remove the method call record
|
||||
typeck_results.type_dependent_defs_mut().remove(e.hir_id);
|
||||
typeck_results.node_substs_mut().remove(e.hir_id);
|
||||
|
||||
if let Some(a) = typeck_results.adjustments_mut().get_mut(base.hir_id) {
|
||||
// Discard the need for a mutable borrow
|
||||
|
||||
// Extra adjustment made when indexing causes a drop
|
||||
// of size information - we need to get rid of it
|
||||
// Since this is "after" the other adjustment to be
|
||||
// discarded, we do an extra `pop()`
|
||||
if let Some(Adjustment {
|
||||
kind: Adjust::Pointer(PointerCast::Unsize), ..
|
||||
}) = a.pop()
|
||||
{
|
||||
// So the borrow discard actually happens here
|
||||
a.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Impl of Visitor for Resolver
|
||||
//
|
||||
// This is the master code which walks the AST. It delegates most of
|
||||
// the heavy lifting to the generic visit and resolve functions
|
||||
// below. In general, a function is made into a `visitor` if it must
|
||||
// traffic in node-ids or update typeck results in the type context etc.
|
||||
|
||||
impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> {
|
||||
fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
|
||||
self.fix_scalar_builtin_expr(e);
|
||||
self.fix_index_builtin_expr(e);
|
||||
|
||||
match e.kind {
|
||||
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
|
||||
let body = self.fcx.tcx.hir().body(body);
|
||||
for param in body.params {
|
||||
self.visit_node_id(e.span, param.hir_id);
|
||||
}
|
||||
|
||||
self.visit_body(body);
|
||||
}
|
||||
hir::ExprKind::Struct(_, fields, _) => {
|
||||
for field in fields {
|
||||
self.visit_field_id(field.hir_id);
|
||||
}
|
||||
}
|
||||
hir::ExprKind::Field(..) => {
|
||||
self.visit_field_id(e.hir_id);
|
||||
}
|
||||
hir::ExprKind::ConstBlock(anon_const) => {
|
||||
self.visit_node_id(e.span, anon_const.hir_id);
|
||||
|
||||
let body = self.tcx().hir().body(anon_const.body);
|
||||
self.visit_body(body);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.visit_node_id(e.span, e.hir_id);
|
||||
intravisit::walk_expr(self, e);
|
||||
}
|
||||
|
||||
fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
|
||||
match &p.kind {
|
||||
hir::GenericParamKind::Lifetime { .. } => {
|
||||
// Nothing to write back here
|
||||
}
|
||||
hir::GenericParamKind::Type { .. } | hir::GenericParamKind::Const { .. } => {
|
||||
self.tcx().sess.delay_span_bug(p.span, format!("unexpected generic param: {p:?}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) {
|
||||
self.visit_node_id(b.span, b.hir_id);
|
||||
intravisit::walk_block(self, b);
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
|
||||
match p.kind {
|
||||
hir::PatKind::Binding(..) => {
|
||||
let typeck_results = self.fcx.typeck_results.borrow();
|
||||
if let Some(bm) =
|
||||
typeck_results.extract_binding_mode(self.tcx().sess, p.hir_id, p.span)
|
||||
{
|
||||
self.typeck_results.pat_binding_modes_mut().insert(p.hir_id, bm);
|
||||
}
|
||||
}
|
||||
hir::PatKind::Struct(_, fields, _) => {
|
||||
for field in fields {
|
||||
self.visit_field_id(field.hir_id);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
self.visit_pat_adjustments(p.span, p.hir_id);
|
||||
|
||||
self.visit_node_id(p.span, p.hir_id);
|
||||
intravisit::walk_pat(self, p);
|
||||
}
|
||||
|
||||
fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
|
||||
intravisit::walk_local(self, l);
|
||||
let var_ty = self.fcx.local_ty(l.span, l.hir_id).decl_ty;
|
||||
let var_ty = self.resolve(var_ty, &l.span);
|
||||
self.write_ty_to_typeck_results(l.hir_id, var_ty);
|
||||
}
|
||||
|
||||
fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'tcx>) {
|
||||
intravisit::walk_ty(self, hir_ty);
|
||||
let ty = self.fcx.node_ty(hir_ty.hir_id);
|
||||
let ty = self.resolve(ty, &hir_ty.span);
|
||||
self.write_ty_to_typeck_results(hir_ty.hir_id, ty);
|
||||
}
|
||||
|
||||
fn visit_infer(&mut self, inf: &'tcx hir::InferArg) {
|
||||
intravisit::walk_inf(self, inf);
|
||||
// Ignore cases where the inference is a const.
|
||||
if let Some(ty) = self.fcx.node_ty_opt(inf.hir_id) {
|
||||
let ty = self.resolve(ty, &inf.span);
|
||||
self.write_ty_to_typeck_results(inf.hir_id, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
|
||||
fn eval_closure_size(&mut self) {
|
||||
let mut res: FxHashMap<LocalDefId, ClosureSizeProfileData<'tcx>> = Default::default();
|
||||
for (&closure_def_id, data) in self.fcx.typeck_results.borrow().closure_size_eval.iter() {
|
||||
let closure_hir_id = self.tcx().hir().local_def_id_to_hir_id(closure_def_id);
|
||||
|
||||
let data = self.resolve(*data, &closure_hir_id);
|
||||
|
||||
res.insert(closure_def_id, data);
|
||||
}
|
||||
|
||||
self.typeck_results.closure_size_eval = res;
|
||||
}
|
||||
fn visit_min_capture_map(&mut self) {
|
||||
let mut min_captures_wb = ty::MinCaptureInformationMap::with_capacity_and_hasher(
|
||||
self.fcx.typeck_results.borrow().closure_min_captures.len(),
|
||||
Default::default(),
|
||||
);
|
||||
for (&closure_def_id, root_min_captures) in
|
||||
self.fcx.typeck_results.borrow().closure_min_captures.iter()
|
||||
{
|
||||
let mut root_var_map_wb = ty::RootVariableMinCaptureList::with_capacity_and_hasher(
|
||||
root_min_captures.len(),
|
||||
Default::default(),
|
||||
);
|
||||
for (var_hir_id, min_list) in root_min_captures.iter() {
|
||||
let min_list_wb = min_list
|
||||
.iter()
|
||||
.map(|captured_place| {
|
||||
let locatable = captured_place.info.path_expr_id.unwrap_or_else(|| {
|
||||
self.tcx().hir().local_def_id_to_hir_id(closure_def_id)
|
||||
});
|
||||
|
||||
self.resolve(captured_place.clone(), &locatable)
|
||||
})
|
||||
.collect();
|
||||
root_var_map_wb.insert(*var_hir_id, min_list_wb);
|
||||
}
|
||||
min_captures_wb.insert(closure_def_id, root_var_map_wb);
|
||||
}
|
||||
|
||||
self.typeck_results.closure_min_captures = min_captures_wb;
|
||||
}
|
||||
|
||||
fn visit_fake_reads_map(&mut self) {
|
||||
let mut resolved_closure_fake_reads: FxHashMap<
|
||||
LocalDefId,
|
||||
Vec<(HirPlace<'tcx>, FakeReadCause, hir::HirId)>,
|
||||
> = Default::default();
|
||||
for (&closure_def_id, fake_reads) in
|
||||
self.fcx.typeck_results.borrow().closure_fake_reads.iter()
|
||||
{
|
||||
let mut resolved_fake_reads = Vec::<(HirPlace<'tcx>, FakeReadCause, hir::HirId)>::new();
|
||||
for (place, cause, hir_id) in fake_reads.iter() {
|
||||
let locatable = self.tcx().hir().local_def_id_to_hir_id(closure_def_id);
|
||||
|
||||
let resolved_fake_read = self.resolve(place.clone(), &locatable);
|
||||
resolved_fake_reads.push((resolved_fake_read, *cause, *hir_id));
|
||||
}
|
||||
resolved_closure_fake_reads.insert(closure_def_id, resolved_fake_reads);
|
||||
}
|
||||
self.typeck_results.closure_fake_reads = resolved_closure_fake_reads;
|
||||
}
|
||||
|
||||
fn visit_closures(&mut self) {
|
||||
let fcx_typeck_results = self.fcx.typeck_results.borrow();
|
||||
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
|
||||
let common_hir_owner = fcx_typeck_results.hir_owner;
|
||||
|
||||
for (id, origin) in fcx_typeck_results.closure_kind_origins().iter() {
|
||||
let hir_id = hir::HirId { owner: common_hir_owner, local_id: *id };
|
||||
let place_span = origin.0;
|
||||
let place = self.resolve(origin.1.clone(), &place_span);
|
||||
self.typeck_results.closure_kind_origins_mut().insert(hir_id, (place_span, place));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_coercion_casts(&mut self) {
|
||||
let fcx_typeck_results = self.fcx.typeck_results.borrow();
|
||||
let fcx_coercion_casts = fcx_typeck_results.coercion_casts();
|
||||
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
|
||||
|
||||
for local_id in fcx_coercion_casts {
|
||||
self.typeck_results.set_coercion_cast(*local_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_user_provided_tys(&mut self) {
|
||||
let fcx_typeck_results = self.fcx.typeck_results.borrow();
|
||||
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
|
||||
let common_hir_owner = fcx_typeck_results.hir_owner;
|
||||
|
||||
let mut errors_buffer = Vec::new();
|
||||
for (&local_id, c_ty) in fcx_typeck_results.user_provided_types().iter() {
|
||||
let hir_id = hir::HirId { owner: common_hir_owner, local_id };
|
||||
|
||||
if cfg!(debug_assertions) && c_ty.needs_infer() {
|
||||
span_bug!(
|
||||
hir_id.to_span(self.fcx.tcx),
|
||||
"writeback: `{:?}` has inference variables",
|
||||
c_ty
|
||||
);
|
||||
};
|
||||
|
||||
self.typeck_results.user_provided_types_mut().insert(hir_id, *c_ty);
|
||||
|
||||
if let ty::UserType::TypeOf(_, user_substs) = c_ty.value {
|
||||
if self.rustc_dump_user_substs {
|
||||
// This is a unit-testing mechanism.
|
||||
let span = self.tcx().hir().span(hir_id);
|
||||
// We need to buffer the errors in order to guarantee a consistent
|
||||
// order when emitting them.
|
||||
let err = self
|
||||
.tcx()
|
||||
.sess
|
||||
.struct_span_err(span, &format!("user substs: {:?}", user_substs));
|
||||
err.buffer(&mut errors_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !errors_buffer.is_empty() {
|
||||
errors_buffer.sort_by_key(|diag| diag.span.primary_span());
|
||||
for mut diag in errors_buffer {
|
||||
self.tcx().sess.diagnostic().emit_diagnostic(&mut diag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_user_provided_sigs(&mut self) {
|
||||
let fcx_typeck_results = self.fcx.typeck_results.borrow();
|
||||
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
|
||||
|
||||
for (&def_id, c_sig) in fcx_typeck_results.user_provided_sigs.iter() {
|
||||
if cfg!(debug_assertions) && c_sig.needs_infer() {
|
||||
span_bug!(
|
||||
self.fcx.tcx.hir().span_if_local(def_id).unwrap(),
|
||||
"writeback: `{:?}` has inference variables",
|
||||
c_sig
|
||||
);
|
||||
};
|
||||
|
||||
self.typeck_results.user_provided_sigs.insert(def_id, *c_sig);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_generator_interior_types(&mut self) {
|
||||
let fcx_typeck_results = self.fcx.typeck_results.borrow();
|
||||
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
|
||||
self.typeck_results.generator_interior_types =
|
||||
fcx_typeck_results.generator_interior_types.clone();
|
||||
}
|
||||
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
fn visit_opaque_types(&mut self) {
|
||||
let opaque_types =
|
||||
self.fcx.infcx.inner.borrow_mut().opaque_type_storage.take_opaque_types();
|
||||
for (opaque_type_key, decl) in opaque_types {
|
||||
let hidden_type = self.resolve(decl.hidden_type, &decl.hidden_type.span);
|
||||
let opaque_type_key = self.resolve(opaque_type_key, &decl.hidden_type.span);
|
||||
|
||||
struct RecursionChecker {
|
||||
def_id: LocalDefId,
|
||||
}
|
||||
impl<'tcx> ty::TypeVisitor<'tcx> for RecursionChecker {
|
||||
type BreakTy = ();
|
||||
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
if let ty::Opaque(def_id, _) = *t.kind() {
|
||||
if def_id == self.def_id.to_def_id() {
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
t.super_visit_with(self)
|
||||
}
|
||||
}
|
||||
if hidden_type
|
||||
.visit_with(&mut RecursionChecker { def_id: opaque_type_key.def_id })
|
||||
.is_break()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let hidden_type = hidden_type.remap_generic_params_to_declaration_params(
|
||||
opaque_type_key,
|
||||
self.fcx.infcx.tcx,
|
||||
true,
|
||||
decl.origin,
|
||||
);
|
||||
|
||||
self.typeck_results.concrete_opaque_types.insert(opaque_type_key.def_id, hidden_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_field_id(&mut self, hir_id: hir::HirId) {
|
||||
if let Some(index) = self.fcx.typeck_results.borrow_mut().field_indices_mut().remove(hir_id)
|
||||
{
|
||||
self.typeck_results.field_indices_mut().insert(hir_id, index);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, span), level = "debug")]
|
||||
fn visit_node_id(&mut self, span: Span, hir_id: hir::HirId) {
|
||||
// Export associated path extensions and method resolutions.
|
||||
if let Some(def) =
|
||||
self.fcx.typeck_results.borrow_mut().type_dependent_defs_mut().remove(hir_id)
|
||||
{
|
||||
self.typeck_results.type_dependent_defs_mut().insert(hir_id, def);
|
||||
}
|
||||
|
||||
// Resolve any borrowings for the node with id `node_id`
|
||||
self.visit_adjustments(span, hir_id);
|
||||
|
||||
// Resolve the type of the node with id `node_id`
|
||||
let n_ty = self.fcx.node_ty(hir_id);
|
||||
let n_ty = self.resolve(n_ty, &span);
|
||||
self.write_ty_to_typeck_results(hir_id, n_ty);
|
||||
debug!(?n_ty);
|
||||
|
||||
// Resolve any substitutions
|
||||
if let Some(substs) = self.fcx.typeck_results.borrow().node_substs_opt(hir_id) {
|
||||
let substs = self.resolve(substs, &span);
|
||||
debug!("write_substs_to_tcx({:?}, {:?})", hir_id, substs);
|
||||
assert!(!substs.needs_infer() && !substs.has_placeholders());
|
||||
self.typeck_results.node_substs_mut().insert(hir_id, substs);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, span), level = "debug")]
|
||||
fn visit_adjustments(&mut self, span: Span, hir_id: hir::HirId) {
|
||||
let adjustment = self.fcx.typeck_results.borrow_mut().adjustments_mut().remove(hir_id);
|
||||
match adjustment {
|
||||
None => {
|
||||
debug!("no adjustments for node");
|
||||
}
|
||||
|
||||
Some(adjustment) => {
|
||||
let resolved_adjustment = self.resolve(adjustment, &span);
|
||||
debug!(?resolved_adjustment);
|
||||
self.typeck_results.adjustments_mut().insert(hir_id, resolved_adjustment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, span), level = "debug")]
|
||||
fn visit_pat_adjustments(&mut self, span: Span, hir_id: hir::HirId) {
|
||||
let adjustment = self.fcx.typeck_results.borrow_mut().pat_adjustments_mut().remove(hir_id);
|
||||
match adjustment {
|
||||
None => {
|
||||
debug!("no pat_adjustments for node");
|
||||
}
|
||||
|
||||
Some(adjustment) => {
|
||||
let resolved_adjustment = self.resolve(adjustment, &span);
|
||||
debug!(?resolved_adjustment);
|
||||
self.typeck_results.pat_adjustments_mut().insert(hir_id, resolved_adjustment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_liberated_fn_sigs(&mut self) {
|
||||
let fcx_typeck_results = self.fcx.typeck_results.borrow();
|
||||
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
|
||||
let common_hir_owner = fcx_typeck_results.hir_owner;
|
||||
|
||||
for (&local_id, &fn_sig) in fcx_typeck_results.liberated_fn_sigs().iter() {
|
||||
let hir_id = hir::HirId { owner: common_hir_owner, local_id };
|
||||
let fn_sig = self.resolve(fn_sig, &hir_id);
|
||||
self.typeck_results.liberated_fn_sigs_mut().insert(hir_id, fn_sig);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_fru_field_types(&mut self) {
|
||||
let fcx_typeck_results = self.fcx.typeck_results.borrow();
|
||||
assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
|
||||
let common_hir_owner = fcx_typeck_results.hir_owner;
|
||||
|
||||
for (&local_id, ftys) in fcx_typeck_results.fru_field_types().iter() {
|
||||
let hir_id = hir::HirId { owner: common_hir_owner, local_id };
|
||||
let ftys = self.resolve(ftys.clone(), &hir_id);
|
||||
self.typeck_results.fru_field_types_mut().insert(hir_id, ftys);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve<T>(&mut self, x: T, span: &dyn Locatable) -> T
|
||||
where
|
||||
T: TypeFoldable<'tcx>,
|
||||
{
|
||||
let mut resolver = Resolver::new(self.fcx, span, self.body);
|
||||
let x = x.fold_with(&mut resolver);
|
||||
if cfg!(debug_assertions) && x.needs_infer() {
|
||||
span_bug!(span.to_span(self.fcx.tcx), "writeback: `{:?}` has inference variables", x);
|
||||
}
|
||||
|
||||
// We may have introduced e.g. `ty::Error`, if inference failed, make sure
|
||||
// to mark the `TypeckResults` as tainted in that case, so that downstream
|
||||
// users of the typeck results don't produce extra errors, or worse, ICEs.
|
||||
if resolver.replaced_with_error {
|
||||
// FIXME(eddyb) keep track of `ErrorGuaranteed` from where the error was emitted.
|
||||
self.typeck_results.tainted_by_errors =
|
||||
Some(ErrorGuaranteed::unchecked_claim_error_was_emitted());
|
||||
}
|
||||
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Locatable {
|
||||
fn to_span(&self, tcx: TyCtxt<'_>) -> Span;
|
||||
}
|
||||
|
||||
impl Locatable for Span {
|
||||
fn to_span(&self, _: TyCtxt<'_>) -> Span {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl Locatable for hir::HirId {
|
||||
fn to_span(&self, tcx: TyCtxt<'_>) -> Span {
|
||||
tcx.hir().span(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The Resolver. This is the type folding engine that detects
|
||||
/// unresolved types and so forth.
|
||||
struct Resolver<'cx, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
infcx: &'cx InferCtxt<'tcx>,
|
||||
span: &'cx dyn Locatable,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
|
||||
/// Set to `true` if any `Ty` or `ty::Const` had to be replaced with an `Error`.
|
||||
replaced_with_error: bool,
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
|
||||
fn new(
|
||||
fcx: &'cx FnCtxt<'cx, 'tcx>,
|
||||
span: &'cx dyn Locatable,
|
||||
body: &'tcx hir::Body<'tcx>,
|
||||
) -> Resolver<'cx, 'tcx> {
|
||||
Resolver { tcx: fcx.tcx, infcx: fcx, span, body, replaced_with_error: false }
|
||||
}
|
||||
|
||||
fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) {
|
||||
if !self.tcx.sess.has_errors().is_some() {
|
||||
self.infcx
|
||||
.err_ctxt()
|
||||
.emit_inference_failure_err(
|
||||
Some(self.body.id()),
|
||||
self.span.to_span(self.tcx),
|
||||
p.into(),
|
||||
E0282,
|
||||
false,
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EraseEarlyRegions<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> TypeFolder<'tcx> for EraseEarlyRegions<'tcx> {
|
||||
fn tcx<'b>(&'b self) -> TyCtxt<'tcx> {
|
||||
self.tcx
|
||||
}
|
||||
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
if ty.has_type_flags(ty::TypeFlags::HAS_FREE_REGIONS) {
|
||||
ty.super_fold_with(self)
|
||||
} else {
|
||||
ty
|
||||
}
|
||||
}
|
||||
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
|
||||
if r.is_late_bound() { r } else { self.tcx.lifetimes.re_erased }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> TypeFolder<'tcx> for Resolver<'cx, 'tcx> {
|
||||
fn tcx<'a>(&'a self) -> TyCtxt<'tcx> {
|
||||
self.tcx
|
||||
}
|
||||
|
||||
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
|
||||
match self.infcx.fully_resolve(t) {
|
||||
Ok(t) => {
|
||||
// Do not anonymize late-bound regions
|
||||
// (e.g. keep `for<'a>` named `for<'a>`).
|
||||
// This allows NLL to generate error messages that
|
||||
// refer to the higher-ranked lifetime names written by the user.
|
||||
EraseEarlyRegions { tcx: self.tcx }.fold_ty(t)
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("Resolver::fold_ty: input type `{:?}` not fully resolvable", t);
|
||||
self.report_error(t);
|
||||
self.replaced_with_error = true;
|
||||
self.tcx().ty_error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
|
||||
debug_assert!(!r.is_late_bound(), "Should not be resolving bound region.");
|
||||
self.tcx.lifetimes.re_erased
|
||||
}
|
||||
|
||||
fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
|
||||
match self.infcx.fully_resolve(ct) {
|
||||
Ok(ct) => self.tcx.erase_regions(ct),
|
||||
Err(_) => {
|
||||
debug!("Resolver::fold_const: input const `{:?}` not fully resolvable", ct);
|
||||
self.report_error(ct);
|
||||
self.replaced_with_error = true;
|
||||
self.tcx().const_error(ct.ty())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// During type check, we store promises with the result of trait
|
||||
// lookup rather than the actual results (because the results are not
|
||||
// necessarily available immediately). These routines unwind the
|
||||
// promises. It is expected that we will have already reported any
|
||||
// errors that may be encountered, so if the promises store an error,
|
||||
// a dummy result is returned.
|
Loading…
Add table
Add a link
Reference in a new issue