about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc_typeck/check/coercion.rs227
1 files changed, 227 insertions, 0 deletions
diff --git a/src/librustc_typeck/check/coercion.rs b/src/librustc_typeck/check/coercion.rs
index c43291557f7..40ae169a94e 100644
--- a/src/librustc_typeck/check/coercion.rs
+++ b/src/librustc_typeck/check/coercion.rs
@@ -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
+        }
+    }
+}