diff --git a/src/librustc/infer/error_reporting/mod.rs b/src/librustc/infer/error_reporting/mod.rs index f71cce8273c46..d61b37ef7c8f1 100644 --- a/src/librustc/infer/error_reporting/mod.rs +++ b/src/librustc/infer/error_reporting/mod.rs @@ -511,6 +511,10 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> { } } }, + ObligationCauseCode::IfExpression { then, outer } => { + err.span_label(then, "expected because of this"); + outer.map(|sp| err.span_label(sp, "if and else have incompatible types")); + } _ => (), } } @@ -1460,7 +1464,7 @@ impl<'tcx> ObligationCause<'tcx> { } _ => "match arms have incompatible types", }), - IfExpression => Error0308("if and else have incompatible types"), + IfExpression { .. } => Error0308("if and else have incompatible types"), IfExpressionWithNoElse => Error0317("if may be missing an else clause"), MainFunctionType => Error0580("main function has wrong type"), StartFunctionType => Error0308("start function has wrong type"), @@ -1488,7 +1492,7 @@ impl<'tcx> ObligationCause<'tcx> { hir::MatchSource::IfLetDesugar { .. } => "`if let` arms have compatible types", _ => "match arms have compatible types", }, - IfExpression => "if and else have compatible types", + IfExpression { .. } => "if and else have compatible types", IfExpressionWithNoElse => "if missing an else returns ()", MainFunctionType => "`main` function has the correct type", StartFunctionType => "`start` function has the correct type", diff --git a/src/librustc/traits/error_reporting.rs b/src/librustc/traits/error_reporting.rs index 7d9e80fd60f5c..367a7eacdfcaf 100644 --- a/src/librustc/traits/error_reporting.rs +++ b/src/librustc/traits/error_reporting.rs @@ -1445,7 +1445,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> { ObligationCauseCode::ExprAssignable | ObligationCauseCode::MatchExpressionArm { .. } | ObligationCauseCode::MatchExpressionArmPattern { .. } | - ObligationCauseCode::IfExpression | + ObligationCauseCode::IfExpression { .. } | ObligationCauseCode::IfExpressionWithNoElse | ObligationCauseCode::MainFunctionType | ObligationCauseCode::StartFunctionType | diff --git a/src/librustc/traits/mod.rs b/src/librustc/traits/mod.rs index 14c25b77a1b82..3f3f489013cff 100644 --- a/src/librustc/traits/mod.rs +++ b/src/librustc/traits/mod.rs @@ -229,7 +229,10 @@ pub enum ObligationCauseCode<'tcx> { MatchExpressionArmPattern { span: Span, ty: Ty<'tcx> }, /// Computing common supertype in an if expression - IfExpression, + IfExpression { + then: Span, + outer: Option, + }, /// Computing common supertype of an if expression with no else counter-part IfExpressionWithNoElse, diff --git a/src/librustc/traits/structural_impls.rs b/src/librustc/traits/structural_impls.rs index 277e2ed0e87d2..a1dd99457d1e6 100644 --- a/src/librustc/traits/structural_impls.rs +++ b/src/librustc/traits/structural_impls.rs @@ -520,7 +520,7 @@ impl<'a, 'tcx> Lift<'tcx> for traits::ObligationCauseCode<'a> { super::MatchExpressionArmPattern { span, ty } => { tcx.lift(&ty).map(|ty| super::MatchExpressionArmPattern { span, ty }) } - super::IfExpression => Some(super::IfExpression), + super::IfExpression { then, outer } => Some(super::IfExpression { then, outer }), super::IfExpressionWithNoElse => Some(super::IfExpressionWithNoElse), super::MainFunctionType => Some(super::MainFunctionType), super::StartFunctionType => Some(super::StartFunctionType), diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index dbbb7f42feb24..9939c2a12b860 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -3366,7 +3366,93 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { let coerce_to_ty = expected.coercion_target_type(self, sp); let mut coerce: DynamicCoerceMany = CoerceMany::new(coerce_to_ty); - let if_cause = self.cause(sp, ObligationCauseCode::IfExpression); + let mut outer_sp = if self.tcx.sess.source_map().is_multiline(sp) { + // 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(sp) + } 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 = opt_else_expr.map(|expr| { + if let ExprKind::Block(block, _) = &expr.node { + if let Some(expr) = &block.expr { + expr.span + } else if let Some(stmt) = block.stmts.last() { + // possibly incorrect trailing `;` in the else arm + stmt.span + } else { // empty block, point at its entirety + // 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 outer_sp.is_some() { + outer_sp = Some(self.tcx.sess.source_map().def_span(sp)); + } + expr.span + } + } else { // shouldn't happen unless the parser has done something weird + expr.span + } + }).unwrap_or(sp); // shouldn't be needed + let then_sp = if let ExprKind::Block(block, _) = &then_expr.node { + if let Some(expr) = &block.expr { + expr.span + } else if let Some(stmt) = block.stmts.last() { + // possibly incorrect trailing `;` in the else arm + stmt.span + } else { // empty block, point at its entirety + outer_sp = None; // same as in `error_sp`, cleanup output + then_expr.span + } + } else { // shouldn't happen unless the parser has done something weird + then_expr.span + }; + + let if_cause = self.cause(error_sp, ObligationCauseCode::IfExpression { + then: then_sp, + outer: outer_sp, + }); coerce.coerce(self, &if_cause, then_expr, then_ty); if let Some(else_expr) = opt_else_expr { diff --git a/src/test/ui/if-else-type-mismatch.rs b/src/test/ui/if-else-type-mismatch.rs new file mode 100644 index 0000000000000..37955a29d2421 --- /dev/null +++ b/src/test/ui/if-else-type-mismatch.rs @@ -0,0 +1,34 @@ +fn main() { + let _ = if true { + 42i32 + } else { + 42u32 + }; + //~^^ ERROR if and else have incompatible types + let _ = if true { 42i32 } else { 42u32 }; + //~^ ERROR if and else have incompatible types + let _ = if true { + 42i32; + } else { + 42u32 + }; + //~^^ ERROR if and else have incompatible types + let _ = if true { + 42i32 + } else { + 42u32; + }; + //~^^ ERROR if and else have incompatible types + let _ = if true { + + } else { + 42u32 + }; + //~^^ ERROR if and else have incompatible types + let _ = if true { + 42i32 + } else { + + }; + //~^^^ ERROR if and else have incompatible types +} diff --git a/src/test/ui/if-else-type-mismatch.stderr b/src/test/ui/if-else-type-mismatch.stderr new file mode 100644 index 0000000000000..d20edc57cbda1 --- /dev/null +++ b/src/test/ui/if-else-type-mismatch.stderr @@ -0,0 +1,92 @@ +error[E0308]: if and else have incompatible types + --> $DIR/if-else-type-mismatch.rs:5:9 + | +LL | let _ = if true { + | _____________- +LL | | 42i32 + | | ----- expected because of this +LL | | } else { +LL | | 42u32 + | | ^^^^^ expected i32, found u32 +LL | | }; + | |_____- if and else have incompatible types + | + = note: expected type `i32` + found type `u32` + +error[E0308]: if and else have incompatible types + --> $DIR/if-else-type-mismatch.rs:8:38 + | +LL | let _ = if true { 42i32 } else { 42u32 }; + | ----- ^^^^^ expected i32, found u32 + | | + | expected because of this + | + = note: expected type `i32` + found type `u32` + +error[E0308]: if and else have incompatible types + --> $DIR/if-else-type-mismatch.rs:13:9 + | +LL | let _ = if true { + | _____________- +LL | | 42i32; + | | ------ expected because of this +LL | | } else { +LL | | 42u32 + | | ^^^^^ expected (), found u32 +LL | | }; + | |_____- if and else have incompatible types + | + = note: expected type `()` + found type `u32` + +error[E0308]: if and else have incompatible types + --> $DIR/if-else-type-mismatch.rs:19:9 + | +LL | let _ = if true { + | _____________- +LL | | 42i32 + | | ----- expected because of this +LL | | } else { +LL | | 42u32; + | | ^^^^^^ expected i32, found () +LL | | }; + | |_____- if and else have incompatible types + | + = note: expected type `i32` + found type `()` + +error[E0308]: if and else have incompatible types + --> $DIR/if-else-type-mismatch.rs:25:9 + | +LL | let _ = if true { + | _____________________- +LL | | +LL | | } else { + | |_____- expected because of this +LL | 42u32 + | ^^^^^ expected (), found u32 + | + = note: expected type `()` + found type `u32` + +error[E0308]: if and else have incompatible types + --> $DIR/if-else-type-mismatch.rs:30:12 + | +LL | let _ = if true { + | ------- if and else have incompatible types +LL | 42i32 + | ----- expected because of this +LL | } else { + | ____________^ +LL | | +LL | | }; + | |_____^ expected i32, found () + | + = note: expected type `i32` + found type `()` + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/if/if-branch-types.stderr b/src/test/ui/if/if-branch-types.stderr index 44e172da78394..74b925f72fff0 100644 --- a/src/test/ui/if/if-branch-types.stderr +++ b/src/test/ui/if/if-branch-types.stderr @@ -1,8 +1,10 @@ error[E0308]: if and else have incompatible types - --> $DIR/if-branch-types.rs:2:13 + --> $DIR/if-branch-types.rs:2:38 | LL | let x = if true { 10i32 } else { 10u32 }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected i32, found u32 + | ----- ^^^^^ expected i32, found u32 + | | + | expected because of this | = note: expected type `i32` found type `u32` diff --git a/src/test/ui/regions/region-invariant-static-error-reporting.stderr b/src/test/ui/regions/region-invariant-static-error-reporting.stderr index 40a3e7e96486b..60e70ddcd9701 100644 --- a/src/test/ui/regions/region-invariant-static-error-reporting.stderr +++ b/src/test/ui/regions/region-invariant-static-error-reporting.stderr @@ -1,13 +1,15 @@ error[E0308]: if and else have incompatible types - --> $DIR/region-invariant-static-error-reporting.rs:14:15 + --> $DIR/region-invariant-static-error-reporting.rs:17:9 | LL | let bad = if x.is_some() { - | _______________^ + | _______________- LL | | x.unwrap() + | | ---------- expected because of this LL | | } else { LL | | mk_static() + | | ^^^^^^^^^^^ lifetime mismatch LL | | }; - | |_____^ lifetime mismatch + | |_____- if and else have incompatible types | = note: expected type `Invariant<'a>` found type `Invariant<'static>` diff --git a/src/test/ui/str/str-array-assignment.stderr b/src/test/ui/str/str-array-assignment.stderr index 24d8db481b468..87809d212d79d 100644 --- a/src/test/ui/str/str-array-assignment.stderr +++ b/src/test/ui/str/str-array-assignment.stderr @@ -1,8 +1,10 @@ error[E0308]: if and else have incompatible types - --> $DIR/str-array-assignment.rs:3:11 + --> $DIR/str-array-assignment.rs:3:37 | LL | let t = if true { s[..2] } else { s }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected str, found &str + | ------ ^ expected str, found &str + | | + | expected because of this | = note: expected type `str` found type `&str`