introduce (but do not yet use) a CoerceMany API

The existing pattern for coercions is fairly complex. `CoerceMany`
enapsulates it better into a helper.
This commit is contained in:
Niko Matsakis 2017-03-17 09:39:13 -04:00
parent eeb6447bbf
commit dad3140407

View file

@ -837,3 +837,230 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
}
}
}
/// CoerceMany encapsulates the pattern you should use when you have
/// many expressions that are all getting coerced to a common
/// type. This arises, for example, when you have a match (the result
/// of each arm is coerced to a common type). It also arises in less
/// obvious places, such as when you have many `break foo` expressions
/// that target the same loop, or the various `return` expressions in
/// a function.
///
/// The basic protocol is as follows:
///
/// - Instantiate the `CoerceMany` with an initial `expected_ty`.
/// This will also serve as the "starting LUB". The expectation is
/// that this type is something which all of the expressions *must*
/// be coercible to. Use a fresh type variable if needed.
/// - For each expression whose result is to be coerced, invoke `coerce()` with.
/// - In some cases we wish to coerce "non-expressions" whose types are implicitly
/// unit. This happens for example if you have a `break` with no expression,
/// or an `if` with no `else`. In that case, invoke `coerce_forced_unit()`.
/// - `coerce()` and `coerce_forced_unit()` may report errors. They hide this
/// from you so that you don't have to worry your pretty head about it.
/// But if an error is reported, the final type will be `err`.
/// - Invoking `coerce()` may cause us to go and adjust the "adjustments" on
/// previously coerced expressions.
/// - When all done, invoke `complete()`. This will return the LUB of
/// all your expressions.
/// - WARNING: I don't believe this final type is guaranteed to be
/// related to your initial `expected_ty` in any particular way,
/// although it will typically be a subtype, so you should check it.
/// - Invoking `complete()` may cause us to go and adjust the "adjustments" on
/// previously coerced expressions.
///
/// Example:
///
/// ```
/// let mut coerce = CoerceMany::new(expected_ty);
/// for expr in exprs {
/// let expr_ty = fcx.check_expr_with_expectation(expr, expected);
/// coerce.coerce(fcx, &cause, expr, expr_ty);
/// }
/// let final_ty = coerce.complete(fcx);
/// ```
#[derive(Clone)] // (*)
pub struct CoerceMany<'gcx: 'tcx, 'tcx> {
expected_ty: Ty<'tcx>,
final_ty: Option<Ty<'tcx>>,
expressions: Vec<&'gcx hir::Expr>,
}
// (*) this is clone because `FnCtxt` is clone, but it seems dubious -- nmatsakis
impl<'gcx, 'tcx> CoerceMany<'gcx, 'tcx> {
pub fn new(expected_ty: Ty<'tcx>) -> Self {
CoerceMany {
expected_ty,
final_ty: None,
expressions: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.expressions.is_empty()
}
/// Return the "expected type" with which this coercion was
/// constructed. This represents the "downward propagated" type
/// that was given to us at the start of typing whatever construct
/// we are typing (e.g., the match expression).
///
/// Typically, this is used as the expected type when
/// type-checking each of the alternative expressions whose types
/// we are trying to merge.
pub fn expected_ty(&self) -> Ty<'tcx> {
self.expected_ty
}
/// Returns the current "merged type", representing our best-guess
/// at the LUB of the expressions we've seen so far (if any). This
/// isn't *final* until you call `self.final()`, which will return
/// the merged type.
pub fn merged_ty(&self) -> Ty<'tcx> {
self.final_ty.unwrap_or(self.expected_ty)
}
/// Indicates that the value generated by `expression`, which is
/// of type `expression_ty`, is one of the possibility that we
/// could coerce from. This will record `expression` and later
/// calls to `coerce` may come back and add adjustments and things
/// if necessary.
pub fn coerce<'a>(&mut self,
fcx: &FnCtxt<'a, 'gcx, 'tcx>,
cause: &ObligationCause<'tcx>,
expression: &'gcx hir::Expr,
expression_ty: Ty<'tcx>)
{
self.coerce_inner(fcx, cause, Some(expression), expression_ty)
}
/// Indicates that one of the inputs is a "forced unit". This
/// occurs in a case like `if foo { ... };`, where the issing else
/// generates a "forced unit". Another example is a `loop { break;
/// }`, where the `break` has no argument expression. We treat
/// these cases slightly differently for error-reporting
/// purposes. Note that these tend to correspond to cases where
/// the `()` expression is implicit in the source, and hence we do
/// not take an expression argument.
pub fn coerce_forced_unit<'a>(&mut self,
fcx: &FnCtxt<'a, 'gcx, 'tcx>,
cause: &ObligationCause<'tcx>)
{
self.coerce_inner(fcx,
cause,
None,
fcx.tcx.mk_nil())
}
/// The inner coercion "engine". If `expression` is `None`, this
/// is a forced-unit case, and hence `expression_ty` must be
/// `Nil`.
fn coerce_inner<'a>(&mut self,
fcx: &FnCtxt<'a, 'gcx, 'tcx>,
cause: &ObligationCause<'tcx>,
expression: Option<&'gcx hir::Expr>,
mut expression_ty: Ty<'tcx>)
{
// Incorporate whatever type inference information we have
// until now; in principle we might also want to process
// pending obligations, but doing so should only improve
// compatibility (hopefully that is true) by helping us
// uncover never types better.
if expression_ty.is_ty_var() {
expression_ty = fcx.infcx.shallow_resolve(expression_ty);
}
// If we see any error types, just propagate that error
// upwards.
if expression_ty.references_error() || self.merged_ty().references_error() {
self.final_ty = Some(fcx.tcx.types.err);
return;
}
// Handle the actual type unification etc.
let result = if let Some(expression) = expression {
if self.expressions.is_empty() {
// Special-case the first expression we are coercing.
// To be honest, I'm not entirely sure why we do this.
fcx.try_coerce(expression, expression_ty, self.expected_ty)
} else {
fcx.try_find_coercion_lub(cause,
|| self.expressions.iter().cloned(),
self.merged_ty(),
expression,
expression_ty)
}
} else {
// this is a hack for cases where we default to `()` because
// the expression etc has been omitted from the source. An
// example is an `if let` without an else:
//
// if let Some(x) = ... { }
//
// we wind up with a second match arm that is like `_ =>
// ()`. That is the case we are considering here. We take
// a different path to get the right "expected, found"
// message and so forth (and because we know that
// `expression_ty` will be unit).
//
// Another example is `break` with no argument expression.
assert!(expression_ty.is_nil());
assert!(expression_ty.is_nil(), "if let hack without unit type");
fcx.eq_types(true, cause, expression_ty, self.merged_ty())
.map(|infer_ok| {
fcx.register_infer_ok_obligations(infer_ok);
expression_ty
})
};
match result {
Ok(v) => {
self.final_ty = Some(v);
self.expressions.extend(expression);
}
Err(err) => {
let (expected, found) = if expression.is_none() {
// In the case where this is a "forced unit", like
// `break`, we want to call the `()` "expected"
// since it is implied by the syntax.
assert!(expression_ty.is_nil());
(expression_ty, self.final_ty.unwrap_or(self.expected_ty))
} else {
// Otherwise, the "expected" type for error
// reporting is the current unification type,
// which is basically the LUB of the expressions
// we've seen so far (combined with the expected
// type)
(self.final_ty.unwrap_or(self.expected_ty), expression_ty)
};
match cause.code {
ObligationCauseCode::ReturnNoExpression => {
struct_span_err!(fcx.tcx.sess, cause.span, E0069,
"`return;` in a function whose return type is not `()`")
.span_label(cause.span, &format!("return type is not ()"))
.emit();
}
_ => {
fcx.report_mismatched_types(cause, expected, found, err)
.emit();
}
}
self.final_ty = Some(fcx.tcx.types.err);
}
}
}
pub fn complete<'a>(self, fcx: &FnCtxt<'a, 'gcx, 'tcx>) -> Ty<'tcx> {
if let Some(final_ty) = self.final_ty {
final_ty
} else {
// If we only had inputs that were of type `!` (or no
// inputs at all), then the final type is `!`.
assert!(self.expressions.is_empty());
fcx.tcx.types.never
}
}
}