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:
parent
eeb6447bbf
commit
dad3140407
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user