Skip to content

Commit 2f581cf

Browse files
committed
Auto merge of #45435 - eddyb:binop-subtype-lhs, r=nikomatsakis
rustc_typeck: use subtyping on the LHS of binops. Fixes #45425. r? @nikomatsakis
2 parents 7402866 + 1a7fb7d commit 2f581cf

File tree

8 files changed

+156
-74
lines changed

8 files changed

+156
-74
lines changed

src/librustc_typeck/check/demand.rs

+57-48
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,16 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
7474
}
7575
}
7676

77-
pub fn demand_coerce(&self, expr: &hir::Expr, checked_ty: Ty<'tcx>, expected: Ty<'tcx>) {
78-
if let Some(mut err) = self.demand_coerce_diag(expr, checked_ty, expected) {
77+
pub fn demand_coerce(&self,
78+
expr: &hir::Expr,
79+
checked_ty: Ty<'tcx>,
80+
expected: Ty<'tcx>)
81+
-> Ty<'tcx> {
82+
let (ty, err) = self.demand_coerce_diag(expr, checked_ty, expected);
83+
if let Some(mut err) = err {
7984
err.emit();
8085
}
86+
ty
8187
}
8288

8389
// Checks that the type of `expr` can be coerced to `expected`.
@@ -88,61 +94,64 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
8894
pub fn demand_coerce_diag(&self,
8995
expr: &hir::Expr,
9096
checked_ty: Ty<'tcx>,
91-
expected: Ty<'tcx>) -> Option<DiagnosticBuilder<'tcx>> {
97+
expected: Ty<'tcx>)
98+
-> (Ty<'tcx>, Option<DiagnosticBuilder<'tcx>>) {
9299
let expected = self.resolve_type_vars_with_obligations(expected);
93100

94-
if let Err(e) = self.try_coerce(expr, checked_ty, self.diverges.get(), expected) {
95-
let cause = self.misc(expr.span);
96-
let expr_ty = self.resolve_type_vars_with_obligations(checked_ty);
97-
let mut err = self.report_mismatched_types(&cause, expected, expr_ty, e);
101+
let e = match self.try_coerce(expr, checked_ty, self.diverges.get(), expected) {
102+
Ok(ty) => return (ty, None),
103+
Err(e) => e
104+
};
98105

99-
// If the expected type is an enum with any variants whose sole
100-
// field is of the found type, suggest such variants. See Issue
101-
// #42764.
102-
if let ty::TyAdt(expected_adt, substs) = expected.sty {
103-
let mut compatible_variants = vec![];
104-
for variant in &expected_adt.variants {
105-
if variant.fields.len() == 1 {
106-
let sole_field = &variant.fields[0];
107-
let sole_field_ty = sole_field.ty(self.tcx, substs);
108-
if self.can_coerce(expr_ty, sole_field_ty) {
109-
let mut variant_path = self.tcx.item_path_str(variant.did);
110-
variant_path = variant_path.trim_left_matches("std::prelude::v1::")
111-
.to_string();
112-
compatible_variants.push(variant_path);
113-
}
106+
let cause = self.misc(expr.span);
107+
let expr_ty = self.resolve_type_vars_with_obligations(checked_ty);
108+
let mut err = self.report_mismatched_types(&cause, expected, expr_ty, e);
109+
110+
// If the expected type is an enum with any variants whose sole
111+
// field is of the found type, suggest such variants. See Issue
112+
// #42764.
113+
if let ty::TyAdt(expected_adt, substs) = expected.sty {
114+
let mut compatible_variants = vec![];
115+
for variant in &expected_adt.variants {
116+
if variant.fields.len() == 1 {
117+
let sole_field = &variant.fields[0];
118+
let sole_field_ty = sole_field.ty(self.tcx, substs);
119+
if self.can_coerce(expr_ty, sole_field_ty) {
120+
let mut variant_path = self.tcx.item_path_str(variant.did);
121+
variant_path = variant_path.trim_left_matches("std::prelude::v1::")
122+
.to_string();
123+
compatible_variants.push(variant_path);
114124
}
115125
}
116-
if !compatible_variants.is_empty() {
117-
let expr_text = print::to_string(print::NO_ANN, |s| s.print_expr(expr));
118-
let suggestions = compatible_variants.iter()
119-
.map(|v| format!("{}({})", v, expr_text)).collect::<Vec<_>>();
120-
err.span_suggestions(expr.span,
121-
"try using a variant of the expected type",
122-
suggestions);
123-
}
124126
}
127+
if !compatible_variants.is_empty() {
128+
let expr_text = print::to_string(print::NO_ANN, |s| s.print_expr(expr));
129+
let suggestions = compatible_variants.iter()
130+
.map(|v| format!("{}({})", v, expr_text)).collect::<Vec<_>>();
131+
err.span_suggestions(expr.span,
132+
"try using a variant of the expected type",
133+
suggestions);
134+
}
135+
}
125136

126-
if let Some(suggestion) = self.check_ref(expr,
127-
checked_ty,
128-
expected) {
129-
err.help(&suggestion);
130-
} else {
131-
let mode = probe::Mode::MethodCall;
132-
let suggestions = self.probe_for_return_type(syntax_pos::DUMMY_SP,
133-
mode,
134-
expected,
135-
checked_ty,
136-
ast::DUMMY_NODE_ID);
137-
if suggestions.len() > 0 {
138-
err.help(&format!("here are some functions which \
139-
might fulfill your needs:\n{}",
140-
self.get_best_match(&suggestions).join("\n")));
141-
}
137+
if let Some(suggestion) = self.check_ref(expr,
138+
checked_ty,
139+
expected) {
140+
err.help(&suggestion);
141+
} else {
142+
let mode = probe::Mode::MethodCall;
143+
let suggestions = self.probe_for_return_type(syntax_pos::DUMMY_SP,
144+
mode,
145+
expected,
146+
checked_ty,
147+
ast::DUMMY_NODE_ID);
148+
if suggestions.len() > 0 {
149+
err.help(&format!("here are some functions which \
150+
might fulfill your needs:\n{}",
151+
self.get_best_match(&suggestions).join("\n")));
142152
}
143-
return Some(err);
144153
}
145-
None
154+
(expected, Some(err))
146155
}
147156

148157
fn format_method_suggestion(&self, method: &AssociatedItem) -> String {

src/librustc_typeck/check/mod.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -2755,9 +2755,19 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
27552755
fn check_expr_coercable_to_type(&self,
27562756
expr: &'gcx hir::Expr,
27572757
expected: Ty<'tcx>) -> Ty<'tcx> {
2758-
let ty = self.check_expr_with_hint(expr, expected);
2759-
self.demand_coerce(expr, ty, expected);
2760-
ty
2758+
self.check_expr_coercable_to_type_with_lvalue_pref(expr, expected, NoPreference)
2759+
}
2760+
2761+
fn check_expr_coercable_to_type_with_lvalue_pref(&self,
2762+
expr: &'gcx hir::Expr,
2763+
expected: Ty<'tcx>,
2764+
lvalue_pref: LvaluePreference)
2765+
-> Ty<'tcx> {
2766+
let ty = self.check_expr_with_expectation_and_lvalue_pref(
2767+
expr,
2768+
ExpectHasType(expected),
2769+
lvalue_pref);
2770+
self.demand_coerce(expr, ty, expected)
27612771
}
27622772

27632773
fn check_expr_with_hint(&self, expr: &'gcx hir::Expr,

src/librustc_typeck/check/op.rs

+27-21
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
1313
use super::FnCtxt;
1414
use super::method::MethodCallee;
15-
use rustc::ty::{self, Ty, TypeFoldable, PreferMutLvalue, TypeVariants};
15+
use rustc::ty::{self, Ty, TypeFoldable, NoPreference, PreferMutLvalue, TypeVariants};
1616
use rustc::ty::TypeVariants::{TyStr, TyRef};
1717
use rustc::ty::adjustment::{Adjustment, Adjust, AutoBorrow};
1818
use rustc::infer::type_variable::TypeVariableOrigin;
@@ -29,12 +29,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
2929
lhs_expr: &'gcx hir::Expr,
3030
rhs_expr: &'gcx hir::Expr) -> Ty<'tcx>
3131
{
32-
let lhs_ty = self.check_expr_with_lvalue_pref(lhs_expr, PreferMutLvalue);
33-
34-
let lhs_ty = self.resolve_type_vars_with_obligations(lhs_ty);
35-
let (rhs_ty, return_ty) =
36-
self.check_overloaded_binop(expr, lhs_expr, lhs_ty, rhs_expr, op, IsAssign::Yes);
37-
let rhs_ty = self.resolve_type_vars_with_obligations(rhs_ty);
32+
let (lhs_ty, rhs_ty, return_ty) =
33+
self.check_overloaded_binop(expr, lhs_expr, rhs_expr, op, IsAssign::Yes);
3834

3935
let ty = if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var()
4036
&& is_builtin_binop(lhs_ty, rhs_ty, op) {
@@ -73,27 +69,24 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
7369
lhs_expr,
7470
rhs_expr);
7571

76-
let lhs_ty = self.check_expr(lhs_expr);
77-
let lhs_ty = self.resolve_type_vars_with_obligations(lhs_ty);
78-
7972
match BinOpCategory::from(op) {
8073
BinOpCategory::Shortcircuit => {
8174
// && and || are a simple case.
75+
self.check_expr_coercable_to_type(lhs_expr, tcx.types.bool);
8276
let lhs_diverges = self.diverges.get();
83-
self.demand_suptype(lhs_expr.span, tcx.mk_bool(), lhs_ty);
84-
self.check_expr_coercable_to_type(rhs_expr, tcx.mk_bool());
77+
self.check_expr_coercable_to_type(rhs_expr, tcx.types.bool);
8578

8679
// Depending on the LHS' value, the RHS can never execute.
8780
self.diverges.set(lhs_diverges);
8881

89-
tcx.mk_bool()
82+
tcx.types.bool
9083
}
9184
_ => {
9285
// Otherwise, we always treat operators as if they are
9386
// overloaded. This is the way to be most flexible w/r/t
9487
// types that get inferred.
95-
let (rhs_ty, return_ty) =
96-
self.check_overloaded_binop(expr, lhs_expr, lhs_ty,
88+
let (lhs_ty, rhs_ty, return_ty) =
89+
self.check_overloaded_binop(expr, lhs_expr,
9790
rhs_expr, op, IsAssign::No);
9891

9992
// Supply type inference hints if relevant. Probably these
@@ -108,7 +101,6 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
108101
// deduce that the result type should be `u32`, even
109102
// though we don't know yet what type 2 has and hence
110103
// can't pin this down to a specific impl.
111-
let rhs_ty = self.resolve_type_vars_with_obligations(rhs_ty);
112104
if
113105
!lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() &&
114106
is_builtin_binop(lhs_ty, rhs_ty, op)
@@ -164,17 +156,30 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
164156
fn check_overloaded_binop(&self,
165157
expr: &'gcx hir::Expr,
166158
lhs_expr: &'gcx hir::Expr,
167-
lhs_ty: Ty<'tcx>,
168159
rhs_expr: &'gcx hir::Expr,
169160
op: hir::BinOp,
170161
is_assign: IsAssign)
171-
-> (Ty<'tcx>, Ty<'tcx>)
162+
-> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)
172163
{
173-
debug!("check_overloaded_binop(expr.id={}, lhs_ty={:?}, is_assign={:?})",
164+
debug!("check_overloaded_binop(expr.id={}, op={:?}, is_assign={:?})",
174165
expr.id,
175-
lhs_ty,
166+
op,
176167
is_assign);
177168

169+
let lhs_pref = match is_assign {
170+
IsAssign::Yes => PreferMutLvalue,
171+
IsAssign::No => NoPreference
172+
};
173+
// Find a suitable supertype of the LHS expression's type, by coercing to
174+
// a type variable, to pass as the `Self` to the trait, avoiding invariant
175+
// trait matching creating lifetime constraints that are too strict.
176+
// E.g. adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result
177+
// in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`.
178+
let lhs_ty = self.check_expr_coercable_to_type_with_lvalue_pref(lhs_expr,
179+
self.next_ty_var(TypeVariableOrigin::MiscVariable(lhs_expr.span)),
180+
lhs_pref);
181+
let lhs_ty = self.resolve_type_vars_with_obligations(lhs_ty);
182+
178183
// NB: As we have not yet type-checked the RHS, we don't have the
179184
// type at hand. Make a variable to represent it. The whole reason
180185
// for this indirection is so that, below, we can check the expr
@@ -187,6 +192,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
187192

188193
// see `NB` above
189194
let rhs_ty = self.check_expr_coercable_to_type(rhs_expr, rhs_ty_var);
195+
let rhs_ty = self.resolve_type_vars_with_obligations(rhs_ty);
190196

191197
let return_ty = match result {
192198
Ok(method) => {
@@ -296,7 +302,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
296302
}
297303
};
298304

299-
(rhs_ty_var, return_ty)
305+
(lhs_ty, rhs_ty, return_ty)
300306
}
301307

302308
fn check_str_addition(&self,

src/test/compile-fail/issue-41394.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
enum Foo {
1212
A = "" + 1
13-
//~^ ERROR binary operation `+` cannot be applied to type `&'static str`
13+
//~^ ERROR binary operation `+` cannot be applied to type `&str`
1414
}
1515

1616
enum Bar {

src/test/run-fail/binop-fail-3.rs

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
fn foo() -> ! {
1313
panic!("quux");
1414
}
15+
16+
#[allow(resolve_trait_on_defaulted_unit)]
1517
fn main() {
1618
foo() == foo(); // these types wind up being defaulted to ()
1719
}

src/test/run-pass/issue-32008.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Tests that binary operators allow subtyping on both the LHS and RHS,
12+
// and as such do not introduce unnecesarily strict lifetime constraints.
13+
14+
use std::ops::Add;
15+
16+
struct Foo;
17+
18+
impl<'a> Add<&'a Foo> for &'a Foo {
19+
type Output = ();
20+
fn add(self, rhs: &'a Foo) {}
21+
}
22+
23+
fn try_to_add(input: &Foo) {
24+
let local = Foo;
25+
26+
// Manual reborrow worked even with invariant trait search.
27+
&*input + &local;
28+
29+
// Direct use of the reference on the LHS requires additional
30+
// subtyping before searching (invariantly) for `LHS: Add<RHS>`.
31+
input + &local;
32+
}
33+
34+
fn main() {
35+
}

src/test/run-pass/issue-45425.rs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::ops::Add;
12+
13+
fn ref_add<T>(a: &T, b: &T) -> T
14+
where
15+
for<'x> &'x T: Add<&'x T, Output = T>,
16+
{
17+
a + b
18+
}
19+
20+
fn main() {}

src/test/ui/span/issue-39018.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error[E0369]: binary operation `+` cannot be applied to type `&'static str`
1+
error[E0369]: binary operation `+` cannot be applied to type `&str`
22
--> $DIR/issue-39018.rs:12:13
33
|
44
12 | let x = "Hello " + "World!";

0 commit comments

Comments
 (0)