Skip to content

Commit ad24fd6

Browse files
author
Jorge Aparicio
committed
Don't rely on the type of lhs when type checking lhs OP rhs
Closes rust-lang#8280 Closes rust-lang#19035
1 parent 24937ff commit ad24fd6

File tree

4 files changed

+213
-55
lines changed

4 files changed

+213
-55
lines changed

src/librustc/middle/ty.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4999,14 +4999,19 @@ pub fn unboxed_closure_upvars<'tcx>(tcx: &ctxt<'tcx>, closure_id: ast::DefId, su
49994999
}
50005000
}
50015001

5002-
pub fn is_binopable<'tcx>(cx: &ctxt<'tcx>, ty: Ty<'tcx>, op: ast::BinOp) -> bool {
5002+
/// Returns `true` if `lhs OP rhs` is a built-in operation
5003+
pub fn is_builtin_binop<'tcx>(cx: &ctxt<'tcx>,
5004+
lhs: Ty<'tcx>,
5005+
rhs: Ty<'tcx>,
5006+
op: ast::BinOp)
5007+
-> bool {
50035008
#![allow(non_upper_case_globals)]
50045009
static tycat_other: int = 0;
50055010
static tycat_bool: int = 1;
50065011
static tycat_char: int = 2;
50075012
static tycat_int: int = 3;
50085013
static tycat_float: int = 4;
5009-
static tycat_raw_ptr: int = 6;
5014+
static tycat_raw_ptr: int = 5;
50105015

50115016
static opcat_add: int = 0;
50125017
static opcat_sub: int = 1;
@@ -5065,10 +5070,16 @@ pub fn is_binopable<'tcx>(cx: &ctxt<'tcx>, ty: Ty<'tcx>, op: ast::BinOp) -> bool
50655070
/*char*/ [f, f, f, f, t, t, f, f, f],
50665071
/*int*/ [t, t, t, t, t, t, t, f, t],
50675072
/*float*/ [t, t, t, f, t, t, f, f, f],
5068-
/*bot*/ [t, t, t, t, t, t, t, t, t],
50695073
/*raw ptr*/ [f, f, f, f, t, t, f, f, f]];
50705074

5071-
return tbl[tycat(cx, ty) as uint ][opcat(op) as uint];
5075+
let lhs_cat = tycat(cx, lhs);
5076+
let rhs_cat = tycat(cx, rhs);
5077+
5078+
if lhs_cat == rhs_cat {
5079+
tbl[lhs_cat as uint ][opcat(op) as uint]
5080+
} else {
5081+
false
5082+
}
50725083
}
50735084

50745085
/// Returns an equivalent type with all the typedefs and self regions removed.

src/librustc/middle/typeck/check/mod.rs

Lines changed: 151 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3215,6 +3215,89 @@ fn check_expr_with_unifier<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
32153215
fcx.write_ty(id, if_ty);
32163216
}
32173217

3218+
// FIXME This is very similar to `look_op_method`. We should try to merge them.
3219+
fn lookup_binop_method<'a, 'tcx>(fcx: &'a FnCtxt<'a, 'tcx>,
3220+
op_ex: &ast::Expr,
3221+
lhs_ty: Ty<'tcx>,
3222+
opname: ast::Name,
3223+
trait_did: Option<ast::DefId>,
3224+
lhs: &'a ast::Expr,
3225+
rhs: &P<ast::Expr>,
3226+
unbound_method: ||) -> Ty<'tcx> {
3227+
let method = match trait_did {
3228+
Some(trait_did) => {
3229+
// We do eager coercions to make using operators
3230+
// more ergonomic:
3231+
//
3232+
// - If the input is of type &'a T (resp. &'a mut T),
3233+
// then reborrow it to &'b T (resp. &'b mut T) where
3234+
// 'b <= 'a. This makes things like `x == y`, where
3235+
// `x` and `y` are both region pointers, work. We
3236+
// could also solve this with variance or different
3237+
// traits that don't force left and right to have same
3238+
// type.
3239+
let (adj_ty, adjustment) = match lhs_ty.sty {
3240+
ty::ty_rptr(r_in, mt) => {
3241+
let r_adj = fcx.infcx().next_region_var(infer::Autoref(lhs.span));
3242+
fcx.mk_subr(infer::Reborrow(lhs.span), r_adj, r_in);
3243+
let adjusted_ty = ty::mk_rptr(fcx.tcx(), r_adj, mt);
3244+
let autoptr = ty::AutoPtr(r_adj, mt.mutbl, None);
3245+
let adjustment = ty::AutoDerefRef { autoderefs: 1, autoref: Some(autoptr) };
3246+
(adjusted_ty, adjustment)
3247+
}
3248+
_ => {
3249+
(lhs_ty, ty::AutoDerefRef { autoderefs: 0, autoref: None })
3250+
}
3251+
};
3252+
3253+
debug!("adjusted_ty={} adjustment={}",
3254+
adj_ty.repr(fcx.tcx()),
3255+
adjustment);
3256+
3257+
method::lookup_in_trait_adjusted(fcx, op_ex.span, Some(lhs), opname,
3258+
trait_did, adjustment, adj_ty, None)
3259+
}
3260+
None => None
3261+
};
3262+
3263+
match method {
3264+
Some(method) => {
3265+
let result_ty = {
3266+
let fty = match method.ty.sty {
3267+
ty::ty_bare_fn(ref fty) => fty,
3268+
_ => unreachable!(),
3269+
};
3270+
3271+
// NB We don't call `check_method_argument_types` here to avoid type checking
3272+
// the `lhs` and `rhs` expressions again
3273+
match fty.sig.inputs[1].sty {
3274+
ty::ty_rptr(_, mt) => demand::coerce(fcx, rhs.span, mt.ty, &**rhs),
3275+
// So we hit this case when one implements the
3276+
// operator traits but leaves an argument as
3277+
// just T instead of &T. We'll catch it in the
3278+
// mismatch impl/trait method phase no need to
3279+
// ICE here.
3280+
// See: #11450
3281+
_ => {},
3282+
}
3283+
3284+
match fty.sig.output {
3285+
ty::FnConverging(result_ty) => result_ty,
3286+
ty::FnDiverging => ty::mk_err(),
3287+
}
3288+
};
3289+
// HACK(eddyb) Fully qualified path to work around a resolve bug.
3290+
let method_call = ::middle::typeck::MethodCall::expr(op_ex.id);
3291+
fcx.inh.method_map.borrow_mut().insert(method_call, method);
3292+
result_ty
3293+
}
3294+
None => {
3295+
unbound_method();
3296+
ty::mk_err()
3297+
}
3298+
}
3299+
}
3300+
32183301
fn lookup_op_method<'a, 'tcx>(fcx: &'a FnCtxt<'a, 'tcx>,
32193302
op_ex: &ast::Expr,
32203303
lhs_ty: Ty<'tcx>,
@@ -3310,55 +3393,59 @@ fn check_expr_with_unifier<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
33103393
SimpleBinop => NoPreference
33113394
};
33123395
check_expr_with_lvalue_pref(fcx, &*lhs, lvalue_pref);
3396+
check_expr(fcx, &**rhs);
33133397

3314-
// Callee does bot / err checking
3315-
let lhs_t = structurally_resolved_type(fcx, lhs.span,
3316-
fcx.expr_ty(&*lhs));
3398+
let lhs_t = try_structurally_resolved_type(fcx, fcx.expr_ty(&*lhs));
3399+
let rhs_t = try_structurally_resolved_type(fcx, fcx.expr_ty(&**rhs));
33173400

3318-
if ty::type_is_integral(lhs_t) && ast_util::is_shift_binop(op) {
3319-
// Shift is a special case: rhs must be uint, no matter what lhs is
3320-
check_expr_has_type(fcx, &**rhs, ty::mk_uint());
3321-
fcx.write_ty(expr.id, lhs_t);
3322-
return;
3323-
}
3401+
if let (Some(lhs_t), Some(rhs_t)) = (lhs_t, rhs_t) {
3402+
if ty::type_is_integral(lhs_t) && ty::type_is_integral(rhs_t) &&
3403+
ast_util::is_shift_binop(op) {
3404+
// Integer shift is a special case: rhs must be uint, no matter what lhs is
3405+
check_expr_has_type(fcx, &**rhs, ty::mk_uint());
3406+
fcx.write_ty(expr.id, lhs_t);
3407+
return;
3408+
}
33243409

3325-
if ty::is_binopable(tcx, lhs_t, op) {
3326-
let tvar = fcx.infcx().next_ty_var();
3327-
demand::suptype(fcx, expr.span, tvar, lhs_t);
3328-
check_expr_has_type(fcx, &**rhs, tvar);
3329-
3330-
let result_t = match op {
3331-
ast::BiEq | ast::BiNe | ast::BiLt | ast::BiLe | ast::BiGe |
3332-
ast::BiGt => {
3333-
if ty::type_is_simd(tcx, lhs_t) {
3334-
if ty::type_is_fp(ty::simd_type(tcx, lhs_t)) {
3335-
fcx.type_error_message(expr.span,
3336-
|actual| {
3337-
format!("binary comparison \
3338-
operation `{}` not \
3339-
supported for floating \
3340-
point SIMD vector `{}`",
3341-
ast_util::binop_to_string(op),
3342-
actual)
3343-
},
3344-
lhs_t,
3345-
None
3346-
);
3347-
ty::mk_err()
3410+
if ty::is_builtin_binop(tcx, lhs_t, rhs_t, op) {
3411+
demand::suptype(fcx, expr.span, lhs_t, rhs_t);
3412+
3413+
let result_t = match op {
3414+
ast::BiEq | ast::BiNe | ast::BiLt | ast::BiLe | ast::BiGe |
3415+
ast::BiGt => {
3416+
if ty::type_is_simd(tcx, lhs_t) {
3417+
if ty::type_is_fp(ty::simd_type(tcx, lhs_t)) {
3418+
fcx.type_error_message(expr.span,
3419+
|actual| {
3420+
format!("binary comparison \
3421+
operation `{}` not \
3422+
supported for floating \
3423+
point SIMD vector `{}`",
3424+
ast_util::binop_to_string(op),
3425+
actual)
3426+
},
3427+
lhs_t,
3428+
None
3429+
);
3430+
ty::mk_err()
3431+
} else {
3432+
lhs_t
3433+
}
33483434
} else {
3349-
lhs_t
3435+
ty::mk_bool()
33503436
}
3351-
} else {
3352-
ty::mk_bool()
3353-
}
3354-
},
3355-
_ => lhs_t,
3356-
};
3437+
},
3438+
_ => lhs_t,
3439+
};
33573440

3358-
fcx.write_ty(expr.id, result_t);
3359-
return;
3441+
fcx.write_ty(expr.id, result_t);
3442+
return;
3443+
}
33603444
}
33613445

3446+
// NB Use the type of `lhs` for error messages, even if it's not fully resolved
3447+
let lhs_t = fcx.expr_ty(lhs);
3448+
33623449
if op == ast::BiOr || op == ast::BiAnd {
33633450
// This is an error; one of the operands must have the wrong
33643451
// type
@@ -3430,8 +3517,8 @@ fn check_expr_with_unifier<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
34303517
return ty::mk_err();
34313518
}
34323519
};
3433-
lookup_op_method(fcx, ex, lhs_resolved_t, token::intern(name),
3434-
trait_did, lhs_expr, Some(rhs), || {
3520+
lookup_binop_method(fcx, ex, lhs_resolved_t, token::intern(name),
3521+
trait_did, lhs_expr, rhs, || {
34353522
fcx.type_error_message(ex.span, |actual| {
34363523
format!("binary operation `{}` cannot be applied to type `{}`",
34373524
ast_util::binop_to_string(op),
@@ -5458,6 +5545,24 @@ pub fn instantiate_path<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
54585545
// resolution is possible, then an error is reported.
54595546
pub fn structurally_resolved_type<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, sp: Span,
54605547
mut ty: Ty<'tcx>) -> Ty<'tcx> {
5548+
match try_structurally_resolved_type(fcx, ty) {
5549+
Some(ty) => ty,
5550+
None => {
5551+
fcx.type_error_message(sp, |_actual| {
5552+
"the type of this value must be known in this \
5553+
context".to_string()
5554+
}, ty, None);
5555+
demand::suptype(fcx, sp, ty::mk_err(), ty);
5556+
ty = ty::mk_err();
5557+
ty
5558+
}
5559+
}
5560+
}
5561+
5562+
// Like `structurally_resolved_type`, but if resolving fails returns `None` instead of raising a
5563+
// compile error
5564+
fn try_structurally_resolved_type<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
5565+
mut ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
54615566
// If `ty` is a type variable, see whether we already know what it is.
54625567
ty = fcx.infcx().shallow_resolve(ty);
54635568

@@ -5474,15 +5579,10 @@ pub fn structurally_resolved_type<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, sp: Span,
54745579

54755580
// If not, error.
54765581
if ty::type_is_ty_var(ty) {
5477-
fcx.type_error_message(sp, |_actual| {
5478-
"the type of this value must be known in this \
5479-
context".to_string()
5480-
}, ty, None);
5481-
demand::suptype(fcx, sp, ty::mk_err(), ty);
5482-
ty = ty::mk_err();
5582+
None
5583+
} else {
5584+
Some(ty)
54835585
}
5484-
5485-
ty
54865586
}
54875587

54885588
// Returns the one-level-deep structure of the given type.

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2014 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+
pub struct Foo;
12+
13+
impl Add<int, Foo> for Foo {
14+
fn add(&self, _: &int) -> Foo { Foo }
15+
}
16+
17+
impl Add<Foo, Foo> for int {
18+
fn add(&self, _: &Foo) -> Foo { Foo }
19+
}
20+
21+
fn main() {
22+
1 + Foo;
23+
Foo + 1;
24+
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2014 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+
fn make<T>() -> T {
12+
unimplemented!();
13+
}
14+
15+
fn lhs_unknown<T: Add<T, T>>(rhs: T) -> T {
16+
make() + rhs
17+
}
18+
19+
fn rhs_unknown<T: Add<T, T>>(lhs: T) -> T {
20+
lhs + make()
21+
}
22+
23+
fn main() {}

0 commit comments

Comments
 (0)