Skip to content

Commit 1e9e80f

Browse files
authored
Rollup merge of rust-lang#59439 - Centril:generalize-assign-to-bool-diagnostic, r=oli-obk
Generalize diagnostic for `x = y` where `bool` is the expected type Extracted out of rust-lang#59288. Currently we special case a diagnostic for `if x = y { ...` since the expected type is `bool` in this case and we instead suggest `if x == y`. This PR generalizes this such that given an expression of form `x = y` (`ExprKind::Assign(..)`) where the expected type is `bool`, we emit a suggestion `x == y`. r? @oli-obk Let's do a perf run to make sure this was not the source of regressions in rust-lang#59288.
2 parents 99f6de7 + ce1c5e0 commit 1e9e80f

File tree

7 files changed

+322
-91
lines changed

7 files changed

+322
-91
lines changed

src/librustc_typeck/check/coercion.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1233,7 +1233,12 @@ impl<'gcx, 'tcx, 'exprs, E> CoerceMany<'gcx, 'tcx, 'exprs, E>
12331233
augment_error(&mut db);
12341234
}
12351235

1236-
db.emit();
1236+
if expression.filter(|e| fcx.is_assign_to_bool(e, expected)).is_some() {
1237+
// Error reported in `check_assign` so avoid emitting error again.
1238+
db.delay_as_bug();
1239+
} else {
1240+
db.emit();
1241+
}
12371242

12381243
self.final_ty = Some(fcx.tcx.types.err);
12391244
}

src/librustc_typeck/check/demand.rs

+52-31
Original file line numberDiff line numberDiff line change
@@ -119,44 +119,65 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
119119
let expr_ty = self.resolve_type_vars_with_obligations(checked_ty);
120120
let mut err = self.report_mismatched_types(&cause, expected, expr_ty, e);
121121

122-
// If the expected type is an enum (Issue #55250) with any variants whose
123-
// sole field is of the found type, suggest such variants. (Issue #42764)
124-
if let ty::Adt(expected_adt, substs) = expected.sty {
125-
if expected_adt.is_enum() {
126-
let mut compatible_variants = expected_adt.variants
127-
.iter()
128-
.filter(|variant| variant.fields.len() == 1)
129-
.filter_map(|variant| {
130-
let sole_field = &variant.fields[0];
131-
let sole_field_ty = sole_field.ty(self.tcx, substs);
132-
if self.can_coerce(expr_ty, sole_field_ty) {
133-
let variant_path = self.tcx.def_path_str(variant.def_id);
134-
// FIXME #56861: DRYer prelude filtering
135-
Some(variant_path.trim_start_matches("std::prelude::v1::").to_string())
136-
} else {
137-
None
138-
}
139-
}).peekable();
140-
141-
if compatible_variants.peek().is_some() {
142-
let expr_text = print::to_string(print::NO_ANN, |s| s.print_expr(expr));
143-
let suggestions = compatible_variants
144-
.map(|v| format!("{}({})", v, expr_text));
145-
err.span_suggestions(
146-
expr.span,
147-
"try using a variant of the expected type",
148-
suggestions,
149-
Applicability::MaybeIncorrect,
150-
);
151-
}
152-
}
122+
if self.is_assign_to_bool(expr, expected) {
123+
// Error reported in `check_assign` so avoid emitting error again.
124+
err.delay_as_bug();
125+
return (expected, None)
153126
}
154127

128+
self.suggest_compatible_variants(&mut err, expr, expected, expr_ty);
155129
self.suggest_ref_or_into(&mut err, expr, expected, expr_ty);
156130

157131
(expected, Some(err))
158132
}
159133

134+
/// Returns whether the expected type is `bool` and the expression is `x = y`.
135+
pub fn is_assign_to_bool(&self, expr: &hir::Expr, expected: Ty<'tcx>) -> bool {
136+
if let hir::ExprKind::Assign(..) = expr.node {
137+
return expected == self.tcx.types.bool;
138+
}
139+
false
140+
}
141+
142+
/// If the expected type is an enum (Issue #55250) with any variants whose
143+
/// sole field is of the found type, suggest such variants. (Issue #42764)
144+
fn suggest_compatible_variants(
145+
&self,
146+
err: &mut DiagnosticBuilder<'_>,
147+
expr: &hir::Expr,
148+
expected: Ty<'tcx>,
149+
expr_ty: Ty<'tcx>,
150+
) {
151+
if let ty::Adt(expected_adt, substs) = expected.sty {
152+
if !expected_adt.is_enum() {
153+
return;
154+
}
155+
156+
let mut compatible_variants = expected_adt.variants
157+
.iter()
158+
.filter(|variant| variant.fields.len() == 1)
159+
.filter_map(|variant| {
160+
let sole_field = &variant.fields[0];
161+
let sole_field_ty = sole_field.ty(self.tcx, substs);
162+
if self.can_coerce(expr_ty, sole_field_ty) {
163+
let variant_path = self.tcx.def_path_str(variant.def_id);
164+
// FIXME #56861: DRYer prelude filtering
165+
Some(variant_path.trim_start_matches("std::prelude::v1::").to_string())
166+
} else {
167+
None
168+
}
169+
}).peekable();
170+
171+
if compatible_variants.peek().is_some() {
172+
let expr_text = print::to_string(print::NO_ANN, |s| s.print_expr(expr));
173+
let suggestions = compatible_variants
174+
.map(|v| format!("{}({})", v, expr_text));
175+
let msg = "try using a variant of the expected type";
176+
err.span_suggestions(expr.span, msg, suggestions, Applicability::MaybeIncorrect);
177+
}
178+
}
179+
}
180+
160181
pub fn get_conversion_methods(&self, span: Span, expected: Ty<'tcx>, checked_ty: Ty<'tcx>)
161182
-> Vec<AssociatedItem> {
162183
let mut methods = self.probe_for_return_type(span,

src/librustc_typeck/check/mod.rs

+53-53
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,6 @@ pub enum Expectation<'tcx> {
246246
/// We know nothing about what type this expression should have.
247247
NoExpectation,
248248

249-
/// This expression is an `if` condition, it must resolve to `bool`.
250-
ExpectIfCondition,
251-
252249
/// This expression should have the type given (or some subtype).
253250
ExpectHasType(Ty<'tcx>),
254251

@@ -328,7 +325,6 @@ impl<'a, 'gcx, 'tcx> Expectation<'tcx> {
328325
fn resolve(self, fcx: &FnCtxt<'a, 'gcx, 'tcx>) -> Expectation<'tcx> {
329326
match self {
330327
NoExpectation => NoExpectation,
331-
ExpectIfCondition => ExpectIfCondition,
332328
ExpectCastableToType(t) => {
333329
ExpectCastableToType(fcx.resolve_type_vars_if_possible(&t))
334330
}
@@ -344,7 +340,6 @@ impl<'a, 'gcx, 'tcx> Expectation<'tcx> {
344340
fn to_option(self, fcx: &FnCtxt<'a, 'gcx, 'tcx>) -> Option<Ty<'tcx>> {
345341
match self.resolve(fcx) {
346342
NoExpectation => None,
347-
ExpectIfCondition => Some(fcx.tcx.types.bool),
348343
ExpectCastableToType(ty) |
349344
ExpectHasType(ty) |
350345
ExpectRvalueLikeUnsized(ty) => Some(ty),
@@ -358,7 +353,6 @@ impl<'a, 'gcx, 'tcx> Expectation<'tcx> {
358353
fn only_has_type(self, fcx: &FnCtxt<'a, 'gcx, 'tcx>) -> Option<Ty<'tcx>> {
359354
match self.resolve(fcx) {
360355
ExpectHasType(ty) => Some(ty),
361-
ExpectIfCondition => Some(fcx.tcx.types.bool),
362356
NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) => None,
363357
}
364358
}
@@ -3148,25 +3142,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
31483142
}
31493143

31503144
if let Some(mut err) = self.demand_suptype_diag(expr.span, expected_ty, ty) {
3151-
// Add help to type error if this is an `if` condition with an assignment.
3152-
if let (ExpectIfCondition, &ExprKind::Assign(ref lhs, ref rhs))
3153-
= (expected, &expr.node)
3154-
{
3155-
let msg = "try comparing for equality";
3156-
if let (Ok(left), Ok(right)) = (
3157-
self.tcx.sess.source_map().span_to_snippet(lhs.span),
3158-
self.tcx.sess.source_map().span_to_snippet(rhs.span))
3159-
{
3160-
err.span_suggestion(
3161-
expr.span,
3162-
msg,
3163-
format!("{} == {}", left, right),
3164-
Applicability::MaybeIncorrect);
3165-
} else {
3166-
err.help(msg);
3167-
}
3145+
if self.is_assign_to_bool(expr, expected_ty) {
3146+
// Error reported in `check_assign` so avoid emitting error again.
3147+
// FIXME(centril): Consider removing if/when `if` desugars to `match`.
3148+
err.delay_as_bug();
3149+
} else {
3150+
err.emit();
31683151
}
3169-
err.emit();
31703152
}
31713153
ty
31723154
}
@@ -3337,7 +3319,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
33373319
opt_else_expr: Option<&'gcx hir::Expr>,
33383320
sp: Span,
33393321
expected: Expectation<'tcx>) -> Ty<'tcx> {
3340-
let cond_ty = self.check_expr_meets_expectation_or_error(cond_expr, ExpectIfCondition);
3322+
let cond_ty = self.check_expr_has_type_or_error(cond_expr, self.tcx.types.bool);
33413323
let cond_diverges = self.diverges.get();
33423324
self.diverges.set(Diverges::Maybe);
33433325

@@ -4422,34 +4404,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
44224404
tcx.types.never
44234405
}
44244406
ExprKind::Assign(ref lhs, ref rhs) => {
4425-
let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace);
4426-
4427-
let rhs_ty = self.check_expr_coercable_to_type(&rhs, lhs_ty);
4428-
4429-
match expected {
4430-
ExpectIfCondition => {
4431-
self.tcx.sess.delay_span_bug(lhs.span, "invalid lhs expression in if;\
4432-
expected error elsehwere");
4433-
}
4434-
_ => {
4435-
// Only check this if not in an `if` condition, as the
4436-
// mistyped comparison help is more appropriate.
4437-
if !lhs.is_place_expr() {
4438-
struct_span_err!(self.tcx.sess, expr.span, E0070,
4439-
"invalid left-hand side expression")
4440-
.span_label(expr.span, "left-hand of expression not valid")
4441-
.emit();
4442-
}
4443-
}
4444-
}
4445-
4446-
self.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized);
4447-
4448-
if lhs_ty.references_error() || rhs_ty.references_error() {
4449-
tcx.types.err
4450-
} else {
4451-
tcx.mk_unit()
4452-
}
4407+
self.check_assign(expr, expected, lhs, rhs)
44534408
}
44544409
ExprKind::If(ref cond, ref then_expr, ref opt_else_expr) => {
44554410
self.check_then_else(&cond, then_expr, opt_else_expr.as_ref().map(|e| &**e),
@@ -4750,6 +4705,51 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
47504705
}
47514706
}
47524707

4708+
/// Type check assignment expression `expr` of form `lhs = rhs`.
4709+
/// The expected type is `()` and is passsed to the function for the purposes of diagnostics.
4710+
fn check_assign(
4711+
&self,
4712+
expr: &'gcx hir::Expr,
4713+
expected: Expectation<'tcx>,
4714+
lhs: &'gcx hir::Expr,
4715+
rhs: &'gcx hir::Expr,
4716+
) -> Ty<'tcx> {
4717+
let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace);
4718+
let rhs_ty = self.check_expr_coercable_to_type(&rhs, lhs_ty);
4719+
4720+
let expected_ty = expected.coercion_target_type(self, expr.span);
4721+
if expected_ty == self.tcx.types.bool {
4722+
// The expected type is `bool` but this will result in `()` so we can reasonably
4723+
// say that the user intended to write `lhs == rhs` instead of `lhs = rhs`.
4724+
// The likely cause of this is `if foo = bar { .. }`.
4725+
let actual_ty = self.tcx.mk_unit();
4726+
let mut err = self.demand_suptype_diag(expr.span, expected_ty, actual_ty).unwrap();
4727+
let msg = "try comparing for equality";
4728+
let left = self.tcx.sess.source_map().span_to_snippet(lhs.span);
4729+
let right = self.tcx.sess.source_map().span_to_snippet(rhs.span);
4730+
if let (Ok(left), Ok(right)) = (left, right) {
4731+
let help = format!("{} == {}", left, right);
4732+
err.span_suggestion(expr.span, msg, help, Applicability::MaybeIncorrect);
4733+
} else {
4734+
err.help(msg);
4735+
}
4736+
err.emit();
4737+
} else if !lhs.is_place_expr() {
4738+
struct_span_err!(self.tcx.sess, expr.span, E0070,
4739+
"invalid left-hand side expression")
4740+
.span_label(expr.span, "left-hand of expression not valid")
4741+
.emit();
4742+
}
4743+
4744+
self.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized);
4745+
4746+
if lhs_ty.references_error() || rhs_ty.references_error() {
4747+
self.tcx.types.err
4748+
} else {
4749+
self.tcx.mk_unit()
4750+
}
4751+
}
4752+
47534753
// Finish resolving a path in a struct expression or pattern `S::A { .. }` if necessary.
47544754
// The newly resolved definition is written into `type_dependent_defs`.
47554755
fn finish_resolving_struct_path(&self,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// The purpose of this text is to ensure that we get good
2+
// diagnostics when a `bool` is expected but that due to
3+
// an assignment expression `x = y` the type is `()`.
4+
5+
fn main() {
6+
let _: bool = 0 = 0; //~ ERROR mismatched types [E0308]
7+
8+
let _: bool = match 0 {
9+
0 => 0 = 0, //~ ERROR mismatched types [E0308]
10+
_ => 0 = 0, //~ ERROR mismatched types [E0308]
11+
};
12+
13+
let _: bool = match true {
14+
true => 0 = 0, //~ ERROR mismatched types [E0308]
15+
_ => (),
16+
};
17+
18+
if 0 = 0 {} //~ ERROR mismatched types [E0308]
19+
20+
let _: bool = if { 0 = 0 } { //~ ERROR mismatched types [E0308]
21+
0 = 0 //~ ERROR mismatched types [E0308]
22+
} else {
23+
0 = 0 //~ ERROR mismatched types [E0308]
24+
};
25+
26+
let _ = (0 = 0) //~ ERROR mismatched types [E0308]
27+
&& { 0 = 0 } //~ ERROR mismatched types [E0308]
28+
|| (0 = 0); //~ ERROR mismatched types [E0308]
29+
30+
// A test to check that not expecting `bool` behaves well:
31+
let _: usize = 0 = 0;
32+
//~^ ERROR mismatched types [E0308]
33+
//~| ERROR invalid left-hand side expression [E0070]
34+
}

0 commit comments

Comments
 (0)