1
Fork 0

rustc_hir_typeck: move whole files

This commit is contained in:
lcnr 2022-10-20 15:52:05 +02:00
parent 53728ff751
commit f468a90bad
37 changed files with 0 additions and 0 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 (&param.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)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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