Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perform type inference in range pattern #88090

Merged
merged 3 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 43 additions & 16 deletions compiler/rustc_typeck/src/check/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,16 +448,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ti: TopInfo<'tcx>,
) -> Ty<'tcx> {
let calc_side = |opt_expr: Option<&'tcx hir::Expr<'tcx>>| match opt_expr {
None => (None, None),
None => None,
Some(expr) => {
let ty = self.check_expr(expr);
// Check that the end-point is of numeric or char type.
let fail = !(ty.is_numeric() || ty.is_char() || ty.references_error());
(Some(ty), Some((fail, ty, expr.span)))
// Check that the end-point is possibly of numeric or char type.
// The early check here is not for correctness, but rather better
// diagnostics (e.g. when `&str` is being matched, `expected` will
// be peeled to `str` while ty here is still `&str`, if we don't
// err ealy here, a rather confusing unification error will be
// emitted instead).
let fail =
!(ty.is_numeric() || ty.is_char() || ty.is_ty_var() || ty.references_error());
Some((fail, ty, expr.span))
}
};
let (lhs_ty, lhs) = calc_side(lhs);
let (rhs_ty, rhs) = calc_side(rhs);
let mut lhs = calc_side(lhs);
let mut rhs = calc_side(rhs);

if let (Some((true, ..)), _) | (_, Some((true, ..))) = (lhs, rhs) {
// There exists a side that didn't meet our criteria that the end-point
Expand All @@ -466,25 +472,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return self.tcx.ty_error();
}

// Now that we know the types can be unified we find the unified type
// and use it to type the entire expression.
let common_type = self.resolve_vars_if_possible(lhs_ty.or(rhs_ty).unwrap_or(expected));

// Unify each side with `expected`.
// Subtyping doesn't matter here, as the value is some kind of scalar.
let demand_eqtype = |x, y| {
if let Some((_, x_ty, x_span)) = x {
let demand_eqtype = |x: &mut _, y| {
if let Some((ref mut fail, x_ty, x_span)) = *x {
if let Some(mut err) = self.demand_eqtype_pat_diag(x_span, expected, x_ty, ti) {
if let Some((_, y_ty, y_span)) = y {
self.endpoint_has_type(&mut err, y_span, y_ty);
}
err.emit();
*fail = true;
};
}
};
demand_eqtype(lhs, rhs);
demand_eqtype(rhs, lhs);
demand_eqtype(&mut lhs, rhs);
demand_eqtype(&mut rhs, lhs);

if let (Some((true, ..)), _) | (_, Some((true, ..))) = (lhs, rhs) {
return self.tcx.ty_error();
}

common_type
// Find the unified type and check if it's of numeric or char type again.
// This check is needed if both sides are inference variables.
// We require types to be resolved here so that we emit inference failure
// rather than "_ is not a char or numeric".
let ty = self.structurally_resolved_type(span, expected);
if !(ty.is_numeric() || ty.is_char() || ty.references_error()) {
if let Some((ref mut fail, _, _)) = lhs {
*fail = true;
}
if let Some((ref mut fail, _, _)) = rhs {
*fail = true;
}
self.emit_err_pat_range(span, lhs, rhs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is why. We pass in lhs and rhs. Inside of emit_err_pat_range we don't resolve_vars_if_possible on lhs.1 or rhs.1. I think that doing that should make the labels talk about String instead of _.

return self.tcx.ty_error();
}
ty
}

fn endpoint_has_type(&self, err: &mut DiagnosticBuilder<'_>, span: Span, ty: Ty<'_>) {
Expand All @@ -511,10 +534,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
E0029,
"only `char` and numeric types are allowed in range patterns"
);
let msg = |ty| format!("this is of type `{}` but it should be `char` or numeric", ty);
let msg = |ty| {
let ty = self.resolve_vars_if_possible(ty);
format!("this is of type `{}` but it should be `char` or numeric", ty)
};
let mut one_side_err = |first_span, first_ty, second: Option<(bool, Ty<'tcx>, Span)>| {
err.span_label(first_span, &msg(first_ty));
if let Some((_, ty, sp)) = second {
let ty = self.resolve_vars_if_possible(ty);
self.endpoint_has_type(&mut err, sp, ty);
}
};
Expand Down
28 changes: 28 additions & 0 deletions src/test/ui/pattern/issue-88074-pat-range-type-inference-err.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
trait Zero {
const ZERO: Self;
}

impl Zero for String {
const ZERO: Self = String::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When associated consts are involved in this error, we should likely point at them.

}

fn foo() {
match String::new() {
Zero::ZERO ..= Zero::ZERO => {},
//~^ ERROR only `char` and numeric types are allowed in range patterns
_ => {},
}
}

fn bar() {
match Zero::ZERO {
Zero::ZERO ..= Zero::ZERO => {},
//~^ ERROR type annotations needed [E0282]
_ => {},
}
}

fn main() {
foo();
bar();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0029]: only `char` and numeric types are allowed in range patterns
--> $DIR/issue-88074-pat-range-type-inference-err.rs:11:9
|
LL | Zero::ZERO ..= Zero::ZERO => {},
| ----------^^^^^----------
| | |
| | this is of type `String` but it should be `char` or numeric
| this is of type `String` but it should be `char` or numeric

error[E0282]: type annotations needed
--> $DIR/issue-88074-pat-range-type-inference-err.rs:19:9
|
LL | Zero::ZERO ..= Zero::ZERO => {},
| ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
|
= note: type must be known at this point

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0029, E0282.
For more information about an error, try `rustc --explain E0029`.
16 changes: 16 additions & 0 deletions src/test/ui/pattern/issue-88074-pat-range-type-inference.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// check-pass

trait Zero {
const ZERO: Self;
}

impl Zero for i32 {
const ZERO: Self = 0;
}

fn main() {
match 1 {
Zero::ZERO ..= 1 => {},
_ => {},
}
}
1 change: 0 additions & 1 deletion src/test/ui/pattern/patkind-litrange-no-expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ enum_number!(Change {
Neg = -1,
Arith = 1 + 1, //~ ERROR arbitrary expressions aren't allowed in patterns
//~| ERROR arbitrary expressions aren't allowed in patterns
//~| ERROR only `char` and numeric types are allowed in range patterns
});

fn main() {}
12 changes: 1 addition & 11 deletions src/test/ui/pattern/patkind-litrange-no-expr.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,5 @@ error: arbitrary expressions aren't allowed in patterns
LL | Arith = 1 + 1,
| ^^^^^

error[E0029]: only `char` and numeric types are allowed in range patterns
--> $DIR/patkind-litrange-no-expr.rs:20:13
|
LL | $( $value ..= 42 => Some($name::$variant), )* // PatKind::Range
| -- this is of type `{integer}`
...
LL | Arith = 1 + 1,
| ^^^^^ this is of type `_` but it should be `char` or numeric

error: aborting due to 3 previous errors
error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0029`.