Auto merge of #23673 - nikomatsakis:issue-23319-binops-ng-5, r=pnkfelix

The current binary operator code assumed that if the LHS was a scalar (`i32` etc), then the RHS had to match. This is not true with multidispatch. This PR generalizes the existing code to (primarily) use the traits -- this also allows us to defer the precise type-checking when the types aren't fully known. The one caveat is the unstable SIMD types, which don't fit in with the current traits -- in that case, the LHS type must be known to be SIMD ahead of time.

There is one semi-hacky bit in that during writeback, for builtin operators, if the types resolve to scalars (i32 etc) then we clear the method override. This is because we know what the semantics are and it is more efficient to generate the code directly. It also ensures that we can use these overloaded operators in constants and so forth.

cc @japaric
cc @aturon 

Fixes #23319 (and others).
This commit is contained in:
bors 2015-03-30 14:25:48 +00:00
commit 9de34a84bb
39 changed files with 685 additions and 563 deletions

View file

@ -22,13 +22,13 @@ macro_rules! map_insert_rand_bench {
let mut rng = rand::weak_rng();
for _ in 0..n {
let i = rng.gen() % n;
let i = rng.gen::<usize>() % n;
map.insert(i, i);
}
// measure
b.iter(|| {
let k = rng.gen() % n;
let k = rng.gen::<usize>() % n;
map.insert(k, k);
map.remove(&k);
});
@ -77,7 +77,7 @@ macro_rules! map_find_rand_bench {
// setup
let mut rng = rand::weak_rng();
let mut keys: Vec<_> = (0..n).map(|_| rng.gen() % n).collect();
let mut keys: Vec<_> = (0..n).map(|_| rng.gen::<usize>() % n).collect();
for &k in &keys {
map.insert(k, k);

View file

@ -485,6 +485,7 @@ pub trait Neg {
macro_rules! neg_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
#[allow(unsigned_negation)]
impl Neg for $t {
#[stable(feature = "rust1", since = "1.0.0")]
type Output = $t;
@ -498,28 +499,7 @@ macro_rules! neg_impl {
)*)
}
macro_rules! neg_uint_impl {
($t:ty, $t_signed:ty) => {
#[stable(feature = "rust1", since = "1.0.0")]
impl Neg for $t {
type Output = $t;
#[inline]
fn neg(self) -> $t { -(self as $t_signed) as $t }
}
forward_ref_unop! { impl Neg, neg for $t }
}
}
neg_impl! { isize i8 i16 i32 i64 f32 f64 }
neg_uint_impl! { usize, isize }
neg_uint_impl! { u8, i8 }
neg_uint_impl! { u16, i16 }
neg_uint_impl! { u32, i32 }
neg_uint_impl! { u64, i64 }
neg_impl! { usize u8 u16 u32 u64 isize i8 i16 i32 i64 f32 f64 }
/// The `Not` trait is used to specify the functionality of unary `!`.
///

View file

@ -256,7 +256,7 @@ fn ziggurat<R: Rng, P, Z>(
return zero_case(rng, u);
}
// algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1
if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen() < pdf(x) {
if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen::<f64>() < pdf(x) {
return x;
}
}

View file

@ -154,7 +154,7 @@ macro_rules! float_impl {
}
}
fn sample_range<R: Rng>(r: &Range<$ty>, rng: &mut R) -> $ty {
r.low + r.range * rng.gen()
r.low + r.range * rng.gen::<$ty>()
}
}
}

View file

@ -3039,6 +3039,10 @@ pub fn mk_nil<'tcx>(cx: &ctxt<'tcx>) -> Ty<'tcx> {
mk_tup(cx, Vec::new())
}
pub fn mk_bool<'tcx>(cx: &ctxt<'tcx>) -> Ty<'tcx> {
mk_t(cx, ty_bool)
}
pub fn mk_bare_fn<'tcx>(cx: &ctxt<'tcx>,
opt_def_id: Option<ast::DefId>,
fty: &'tcx BareFnTy<'tcx>) -> Ty<'tcx> {
@ -3406,8 +3410,12 @@ pub fn type_is_scalar(ty: Ty) -> bool {
/// Returns true if this type is a floating point type and false otherwise.
pub fn type_is_floating_point(ty: Ty) -> bool {
match ty.sty {
ty_float(_) => true,
_ => false,
ty_float(_) |
ty_infer(FloatVar(_)) =>
true,
_ =>
false,
}
}
@ -5805,78 +5813,6 @@ pub fn closure_upvars<'tcx>(typer: &mc::Typer<'tcx>,
}
}
pub fn is_binopable<'tcx>(cx: &ctxt<'tcx>, ty: Ty<'tcx>, op: ast::BinOp) -> bool {
#![allow(non_upper_case_globals)]
const tycat_other: isize = 0;
const tycat_bool: isize = 1;
const tycat_char: isize = 2;
const tycat_int: isize = 3;
const tycat_float: isize = 4;
const tycat_raw_ptr: isize = 6;
const opcat_add: isize = 0;
const opcat_sub: isize = 1;
const opcat_mult: isize = 2;
const opcat_shift: isize = 3;
const opcat_rel: isize = 4;
const opcat_eq: isize = 5;
const opcat_bit: isize = 6;
const opcat_logic: isize = 7;
const opcat_mod: isize = 8;
fn opcat(op: ast::BinOp) -> isize {
match op.node {
ast::BiAdd => opcat_add,
ast::BiSub => opcat_sub,
ast::BiMul => opcat_mult,
ast::BiDiv => opcat_mult,
ast::BiRem => opcat_mod,
ast::BiAnd => opcat_logic,
ast::BiOr => opcat_logic,
ast::BiBitXor => opcat_bit,
ast::BiBitAnd => opcat_bit,
ast::BiBitOr => opcat_bit,
ast::BiShl => opcat_shift,
ast::BiShr => opcat_shift,
ast::BiEq => opcat_eq,
ast::BiNe => opcat_eq,
ast::BiLt => opcat_rel,
ast::BiLe => opcat_rel,
ast::BiGe => opcat_rel,
ast::BiGt => opcat_rel
}
}
fn tycat<'tcx>(cx: &ctxt<'tcx>, ty: Ty<'tcx>) -> isize {
if type_is_simd(cx, ty) {
return tycat(cx, simd_type(cx, ty))
}
match ty.sty {
ty_char => tycat_char,
ty_bool => tycat_bool,
ty_int(_) | ty_uint(_) | ty_infer(IntVar(_)) => tycat_int,
ty_float(_) | ty_infer(FloatVar(_)) => tycat_float,
ty_ptr(_) => tycat_raw_ptr,
_ => tycat_other
}
}
const t: bool = true;
const f: bool = false;
let tbl = [
// +, -, *, shift, rel, ==, bit, logic, mod
/*other*/ [f, f, f, f, f, f, f, f, f],
/*bool*/ [f, f, f, f, t, t, t, t, f],
/*char*/ [f, f, f, f, t, t, f, f, f],
/*isize*/ [t, t, t, t, t, t, t, f, t],
/*float*/ [t, t, t, f, t, t, f, f, f],
/*bot*/ [t, t, t, t, t, t, t, t, t],
/*raw ptr*/ [f, f, f, f, t, t, f, f, f]];
return tbl[tycat(cx, ty) as usize ][opcat(op) as usize];
}
// Returns the repeat count for a repeating vector expression.
pub fn eval_repeat_count(tcx: &ctxt, count_expr: &ast::Expr) -> usize {
match const_eval::eval_const_expr_partial(tcx, count_expr, Some(tcx.types.usize)) {

View file

@ -560,7 +560,7 @@ pub fn compare_scalar_types<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
_ => bcx.sess().bug("compare_scalar_types: must be a comparison operator")
}
}
ty::ty_bool | ty::ty_uint(_) | ty::ty_char => {
ty::ty_bare_fn(..) | ty::ty_bool | ty::ty_uint(_) | ty::ty_char => {
ICmp(bcx, bin_op_to_icmp_predicate(bcx.ccx(), op, false), lhs, rhs, debug_loc)
}
ty::ty_ptr(mt) if common::type_is_sized(bcx.tcx(), mt.ty) => {

View file

@ -351,7 +351,14 @@ pub fn const_expr<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
fn const_expr_unadjusted<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
e: &ast::Expr,
ety: Ty<'tcx>,
param_substs: &'tcx Substs<'tcx>) -> ValueRef {
param_substs: &'tcx Substs<'tcx>)
-> ValueRef
{
debug!("const_expr_unadjusted(e={}, ety={}, param_substs={})",
e.repr(cx.tcx()),
ety.repr(cx.tcx()),
param_substs.repr(cx.tcx()));
let map_list = |exprs: &[P<ast::Expr>]| {
exprs.iter().map(|e| const_expr(cx, &**e, param_substs).0)
.fold(Vec::new(), |mut l, val| { l.push(val); l })
@ -366,6 +373,9 @@ fn const_expr_unadjusted<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
/* Neither type is bottom, and we expect them to be unified
* already, so the following is safe. */
let (te1, ty) = const_expr(cx, &**e1, param_substs);
debug!("const_expr_unadjusted: te1={}, ty={}",
cx.tn().val_to_string(te1),
ty.repr(cx.tcx()));
let is_simd = ty::type_is_simd(cx.tcx(), ty);
let intype = if is_simd {
ty::simd_type(cx.tcx(), ty)

View file

@ -2384,6 +2384,7 @@ fn deref_once<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
}
#[derive(Debug)]
enum OverflowOp {
Add,
Sub,
@ -2413,6 +2414,7 @@ enum OverflowCodegen {
enum OverflowOpViaInputCheck { Shl, Shr, }
#[derive(Debug)]
enum OverflowOpViaIntrinsic { Add, Sub, Mul, }
impl OverflowOpViaIntrinsic {
@ -2437,7 +2439,8 @@ impl OverflowOpViaIntrinsic {
_ => panic!("unsupported target word size")
},
ref t @ ty_uint(_) | ref t @ ty_int(_) => t.clone(),
_ => panic!("tried to get overflow intrinsic for non-int type")
_ => panic!("tried to get overflow intrinsic for {:?} applied to non-int type",
*self)
};
match *self {

View file

@ -9,7 +9,6 @@
// except according to those terms.
use super::autoderef;
use super::AutorefArgs;
use super::check_argument_types;
use super::check_expr;
use super::check_method_argument_types;
@ -258,7 +257,6 @@ fn confirm_builtin_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
&fn_sig.inputs,
&expected_arg_tys[..],
arg_exprs,
AutorefArgs::No,
fn_sig.variadic,
TupleArgumentsFlag::DontTupleArguments);
@ -288,7 +286,6 @@ fn confirm_deferred_closure_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
&*fn_sig.inputs,
&*expected_arg_tys,
arg_exprs,
AutorefArgs::No,
fn_sig.variadic,
TupleArgumentsFlag::TupleArguments);
@ -308,7 +305,6 @@ fn confirm_overloaded_call<'a,'tcx>(fcx: &FnCtxt<'a, 'tcx>,
method_callee.ty,
callee_expr,
arg_exprs,
AutorefArgs::No,
TupleArgumentsFlag::TupleArguments,
expected);
write_call(fcx, call_expr, output_type);

View file

@ -79,7 +79,6 @@ type parameter).
pub use self::LvaluePreference::*;
pub use self::Expectation::*;
pub use self::compare_method::compare_impl_method;
use self::IsBinopAssignment::*;
use self::TupleArgumentsFlag::*;
use astconv::{self, ast_region_to_region, ast_ty_to_ty, AstConv, PathParamMode};
@ -142,6 +141,7 @@ pub mod wf;
mod closure;
mod callee;
mod compare_method;
mod op;
/// closures defined within the function. For example:
///
@ -288,15 +288,6 @@ impl UnsafetyState {
}
}
/// Whether `check_binop` is part of an assignment or not.
/// Used to know whether we allow user overloads and to print
/// better messages on error.
#[derive(PartialEq)]
enum IsBinopAssignment{
SimpleBinop,
BinopAssignment,
}
#[derive(Clone)]
pub struct FnCtxt<'a, 'tcx: 'a> {
body_id: ast::NodeId,
@ -1325,14 +1316,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// version, this version will also select obligations if it seems
/// useful, in an effort to get more type information.
fn resolve_type_vars_if_possible(&self, mut ty: Ty<'tcx>) -> Ty<'tcx> {
debug!("resolve_type_vars_if_possible(ty={})", ty.repr(self.tcx()));
// No ty::infer()? Nothing needs doing.
if !ty::type_has_ty_infer(ty) {
debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
return ty;
}
// If `ty` is a type variable, see whether we already know what it is.
ty = self.infcx().resolve_type_vars_if_possible(&ty);
if !ty::type_has_ty_infer(ty) {
debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
return ty;
}
@ -1340,6 +1335,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
vtable::select_new_fcx_obligations(self);
ty = self.infcx().resolve_type_vars_if_possible(&ty);
if !ty::type_has_ty_infer(ty) {
debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
return ty;
}
@ -1348,7 +1344,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// indirect dependencies that don't seem worth tracking
// precisely.
vtable::select_fcx_obligations_where_possible(self);
self.infcx().resolve_type_vars_if_possible(&ty)
ty = self.infcx().resolve_type_vars_if_possible(&ty);
debug!("resolve_type_vars_if_possible: ty={}", ty.repr(self.tcx()));
ty
}
/// Resolves all type variables in `t` and then, if any were left
@ -2092,24 +2091,17 @@ fn make_overloaded_lvalue_return_type<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
{
match method {
Some(method) => {
let ref_ty = // invoked methods have all LB regions instantiated
ty::no_late_bound_regions(
fcx.tcx(), &ty::ty_fn_ret(method.ty)).unwrap();
match method_call {
Some(method_call) => {
fcx.inh.method_map.borrow_mut().insert(method_call,
method);
}
None => {}
}
match ref_ty {
ty::FnConverging(ref_ty) => {
ty::deref(ref_ty, true)
}
ty::FnDiverging => {
fcx.tcx().sess.bug("index/deref traits do not define a `!` return")
}
// extract method method return type, which will be &T;
// all LB regions should have been instantiated during method lookup
let ret_ty = ty::ty_fn_ret(method.ty);
let ret_ty = ty::no_late_bound_regions(fcx.tcx(), &ret_ty).unwrap().unwrap();
if let Some(method_call) = method_call {
fcx.inh.method_map.borrow_mut().insert(method_call, method);
}
// method returns &T, but the type as visible to user is T, so deref
ty::deref(ret_ty, true)
}
None => None,
}
@ -2238,7 +2230,6 @@ fn check_method_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
method_fn_ty: Ty<'tcx>,
callee_expr: &'tcx ast::Expr,
args_no_rcvr: &'tcx [P<ast::Expr>],
autoref_args: AutorefArgs,
tuple_arguments: TupleArgumentsFlag,
expected: Expectation<'tcx>)
-> ty::FnOutput<'tcx> {
@ -2255,7 +2246,6 @@ fn check_method_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
&err_inputs[..],
&[],
args_no_rcvr,
autoref_args,
false,
tuple_arguments);
ty::FnConverging(fcx.tcx().types.err)
@ -2273,7 +2263,6 @@ fn check_method_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
&fty.sig.0.inputs[1..],
&expected_arg_tys[..],
args_no_rcvr,
autoref_args,
fty.sig.0.variadic,
tuple_arguments);
fty.sig.0.output
@ -2293,7 +2282,6 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
fn_inputs: &[Ty<'tcx>],
expected_arg_tys: &[Ty<'tcx>],
args: &'tcx [P<ast::Expr>],
autoref_args: AutorefArgs,
variadic: bool,
tuple_arguments: TupleArgumentsFlag) {
let tcx = fcx.ccx.tcx;
@ -2406,26 +2394,7 @@ fn check_argument_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
if is_block == check_blocks {
debug!("checking the argument");
let mut formal_ty = formal_tys[i];
match autoref_args {
AutorefArgs::Yes => {
match formal_ty.sty {
ty::ty_rptr(_, mt) => formal_ty = mt.ty,
ty::ty_err => (),
_ => {
// So we hit this case when one implements the
// operator traits but leaves an argument as
// just T instead of &T. We'll catch it in the
// mismatch impl/trait method phase no need to
// ICE here.
// See: #11450
formal_ty = tcx.types.err;
}
}
}
AutorefArgs::No => {}
}
let formal_ty = formal_tys[i];
// The special-cased logic below has three functions:
// 1. Provide as good of an expected type as possible.
@ -2622,14 +2591,6 @@ pub fn impl_self_ty<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
TypeAndSubsts { substs: substs, ty: substd_ty }
}
// Controls whether the arguments are automatically referenced. This is useful
// for overloaded binary and unary operators.
#[derive(Copy, PartialEq)]
pub enum AutorefArgs {
Yes,
No,
}
/// Controls whether the arguments are tupled. This is used for the call
/// operator.
///
@ -2755,7 +2716,6 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
fn_ty,
expr,
&args[1..],
AutorefArgs::No,
DontTupleArguments,
expected);
@ -2806,277 +2766,6 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
fcx.write_ty(id, if_ty);
}
fn lookup_op_method<'a, 'tcx, F>(fcx: &'a FnCtxt<'a, 'tcx>,
op_ex: &'tcx ast::Expr,
lhs_ty: Ty<'tcx>,
opname: ast::Name,
trait_did: Option<ast::DefId>,
lhs: &'a ast::Expr,
rhs: Option<&'tcx P<ast::Expr>>,
unbound_method: F,
autoref_args: AutorefArgs) -> Ty<'tcx> where
F: FnOnce(),
{
let method = match trait_did {
Some(trait_did) => {
// We do eager coercions to make using operators
// more ergonomic:
//
// - If the input is of type &'a T (resp. &'a mut T),
// then reborrow it to &'b T (resp. &'b mut T) where
// 'b <= 'a. This makes things like `x == y`, where
// `x` and `y` are both region pointers, work. We
// could also solve this with variance or different
// traits that don't force left and right to have same
// type.
let (adj_ty, adjustment) = match lhs_ty.sty {
ty::ty_rptr(r_in, mt) => {
let r_adj = fcx.infcx().next_region_var(infer::Autoref(lhs.span));
fcx.mk_subr(infer::Reborrow(lhs.span), r_adj, *r_in);
let adjusted_ty = ty::mk_rptr(fcx.tcx(), fcx.tcx().mk_region(r_adj), mt);
let autoptr = ty::AutoPtr(r_adj, mt.mutbl, None);
let adjustment = ty::AutoDerefRef { autoderefs: 1, autoref: Some(autoptr) };
(adjusted_ty, adjustment)
}
_ => {
(lhs_ty, ty::AutoDerefRef { autoderefs: 0, autoref: None })
}
};
debug!("adjusted_ty={} adjustment={:?}",
adj_ty.repr(fcx.tcx()),
adjustment);
method::lookup_in_trait_adjusted(fcx, op_ex.span, Some(lhs), opname,
trait_did, adjustment, adj_ty, None)
}
None => None
};
let args = match rhs {
Some(rhs) => slice::ref_slice(rhs),
None => &[][..]
};
match method {
Some(method) => {
let method_ty = method.ty;
// HACK(eddyb) Fully qualified path to work around a resolve bug.
let method_call = ::middle::ty::MethodCall::expr(op_ex.id);
fcx.inh.method_map.borrow_mut().insert(method_call, method);
match check_method_argument_types(fcx,
op_ex.span,
method_ty,
op_ex,
args,
autoref_args,
DontTupleArguments,
NoExpectation) {
ty::FnConverging(result_type) => result_type,
ty::FnDiverging => fcx.tcx().types.err
}
}
None => {
unbound_method();
// Check the args anyway
// so we get all the error messages
let expected_ty = fcx.tcx().types.err;
check_method_argument_types(fcx,
op_ex.span,
expected_ty,
op_ex,
args,
autoref_args,
DontTupleArguments,
NoExpectation);
fcx.tcx().types.err
}
}
}
// could be either an expr_binop or an expr_assign_binop
fn check_binop<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
expr: &'tcx ast::Expr,
op: ast::BinOp,
lhs: &'tcx ast::Expr,
rhs: &'tcx P<ast::Expr>,
is_binop_assignment: IsBinopAssignment) {
let tcx = fcx.ccx.tcx;
let lvalue_pref = match is_binop_assignment {
BinopAssignment => PreferMutLvalue,
SimpleBinop => NoPreference
};
check_expr_with_lvalue_pref(fcx, lhs, lvalue_pref);
// Callee does bot / err checking
let lhs_t =
structurally_resolve_type_or_else(fcx, lhs.span, fcx.expr_ty(lhs), || {
if ast_util::is_symmetric_binop(op.node) {
// Try RHS first
check_expr(fcx, &**rhs);
fcx.expr_ty(&**rhs)
} else {
fcx.tcx().types.err
}
});
if ty::type_is_integral(lhs_t) && ast_util::is_shift_binop(op.node) {
// Shift is a special case: rhs must be usize, no matter what lhs is
check_expr(fcx, &**rhs);
let rhs_ty = fcx.expr_ty(&**rhs);
let rhs_ty = structurally_resolved_type(fcx, rhs.span, rhs_ty);
if ty::type_is_integral(rhs_ty) {
fcx.write_ty(expr.id, lhs_t);
} else {
fcx.type_error_message(
expr.span,
|actual| {
format!(
"right-hand-side of a shift operation must have integral type, \
not `{}`",
actual)
},
rhs_ty,
None);
fcx.write_ty(expr.id, fcx.tcx().types.err);
}
return;
}
if ty::is_binopable(tcx, lhs_t, op) {
let tvar = fcx.infcx().next_ty_var();
demand::suptype(fcx, expr.span, tvar, lhs_t);
check_expr_has_type(fcx, &**rhs, tvar);
let result_t = match op.node {
ast::BiEq | ast::BiNe | ast::BiLt | ast::BiLe | ast::BiGe |
ast::BiGt => {
if ty::type_is_simd(tcx, lhs_t) {
if ty::type_is_fp(ty::simd_type(tcx, lhs_t)) {
fcx.type_error_message(expr.span,
|actual| {
format!("binary comparison \
operation `{}` not \
supported for floating \
point SIMD vector `{}`",
ast_util::binop_to_string(op.node),
actual)
},
lhs_t,
None
);
fcx.tcx().types.err
} else {
lhs_t
}
} else {
fcx.tcx().types.bool
}
},
_ => lhs_t,
};
fcx.write_ty(expr.id, result_t);
return;
}
if op.node == ast::BiOr || op.node == ast::BiAnd {
// This is an error; one of the operands must have the wrong
// type
fcx.write_error(expr.id);
fcx.write_error(rhs.id);
fcx.type_error_message(expr.span,
|actual| {
format!("binary operation `{}` cannot be applied \
to type `{}`",
ast_util::binop_to_string(op.node),
actual)
},
lhs_t,
None)
}
// Check for overloaded operators if not an assignment.
let result_t = if is_binop_assignment == SimpleBinop {
check_user_binop(fcx, expr, lhs, lhs_t, op, rhs)
} else {
fcx.type_error_message(expr.span,
|actual| {
format!("binary assignment \
operation `{}=` \
cannot be applied to \
type `{}`",
ast_util::binop_to_string(op.node),
actual)
},
lhs_t,
None);
check_expr(fcx, &**rhs);
fcx.tcx().types.err
};
fcx.write_ty(expr.id, result_t);
if ty::type_is_error(result_t) {
fcx.write_ty(rhs.id, result_t);
}
}
fn check_user_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
ex: &'tcx ast::Expr,
lhs_expr: &'tcx ast::Expr,
lhs_resolved_t: Ty<'tcx>,
op: ast::BinOp,
rhs: &'tcx P<ast::Expr>) -> Ty<'tcx> {
let tcx = fcx.ccx.tcx;
let lang = &tcx.lang_items;
let (name, trait_did) = match op.node {
ast::BiAdd => ("add", lang.add_trait()),
ast::BiSub => ("sub", lang.sub_trait()),
ast::BiMul => ("mul", lang.mul_trait()),
ast::BiDiv => ("div", lang.div_trait()),
ast::BiRem => ("rem", lang.rem_trait()),
ast::BiBitXor => ("bitxor", lang.bitxor_trait()),
ast::BiBitAnd => ("bitand", lang.bitand_trait()),
ast::BiBitOr => ("bitor", lang.bitor_trait()),
ast::BiShl => ("shl", lang.shl_trait()),
ast::BiShr => ("shr", lang.shr_trait()),
ast::BiLt => ("lt", lang.ord_trait()),
ast::BiLe => ("le", lang.ord_trait()),
ast::BiGe => ("ge", lang.ord_trait()),
ast::BiGt => ("gt", lang.ord_trait()),
ast::BiEq => ("eq", lang.eq_trait()),
ast::BiNe => ("ne", lang.eq_trait()),
ast::BiAnd | ast::BiOr => {
check_expr(fcx, &**rhs);
return tcx.types.err;
}
};
lookup_op_method(fcx, ex, lhs_resolved_t, token::intern(name),
trait_did, lhs_expr, Some(rhs), || {
fcx.type_error_message(ex.span, |actual| {
format!("binary operation `{}` cannot be applied to type `{}`",
ast_util::binop_to_string(op.node),
actual)
}, lhs_resolved_t, None)
}, if ast_util::is_by_value_binop(op.node) { AutorefArgs::No } else { AutorefArgs::Yes })
}
fn check_user_unop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
op_str: &str,
mname: &str,
trait_did: Option<ast::DefId>,
ex: &'tcx ast::Expr,
rhs_expr: &'tcx ast::Expr,
rhs_t: Ty<'tcx>,
op: ast::UnOp) -> Ty<'tcx> {
lookup_op_method(fcx, ex, rhs_t, token::intern(mname),
trait_did, rhs_expr, None, || {
fcx.type_error_message(ex.span, |actual| {
format!("cannot apply unary operator `{}` to type `{}`",
op_str, actual)
}, rhs_t, None);
}, if ast_util::is_by_value_unop(op) { AutorefArgs::No } else { AutorefArgs::Yes })
}
// Check field access expressions
fn check_field<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
expr: &'tcx ast::Expr,
@ -3479,35 +3168,10 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
fcx.write_ty(id, typ);
}
ast::ExprBinary(op, ref lhs, ref rhs) => {
check_binop(fcx, expr, op, &**lhs, rhs, SimpleBinop);
let lhs_ty = fcx.expr_ty(&**lhs);
let rhs_ty = fcx.expr_ty(&**rhs);
if ty::type_is_error(lhs_ty) ||
ty::type_is_error(rhs_ty) {
fcx.write_error(id);
}
op::check_binop(fcx, expr, op, lhs, rhs);
}
ast::ExprAssignOp(op, ref lhs, ref rhs) => {
check_binop(fcx, expr, op, &**lhs, rhs, BinopAssignment);
let lhs_t = fcx.expr_ty(&**lhs);
let result_t = fcx.expr_ty(expr);
demand::suptype(fcx, expr.span, result_t, lhs_t);
let tcx = fcx.tcx();
if !ty::expr_is_lval(tcx, &**lhs) {
span_err!(tcx.sess, lhs.span, E0067, "illegal left-hand side expression");
}
fcx.require_expr_have_sized_type(&**lhs, traits::AssignmentLhsSized);
// Overwrite result of check_binop...this preserves existing behavior
// but seems quite dubious with regard to user-defined methods
// and so forth. - Niko
if !ty::type_is_error(result_t) {
fcx.write_nil(expr.id);
}
op::check_binop_assign(fcx, expr, op, lhs, rhs);
}
ast::ExprUnary(unop, ref oprnd) => {
let expected_inner = expected.to_option(fcx).map_or(NoExpectation, |ty| {
@ -3580,9 +3244,9 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
oprnd_t);
if !(ty::type_is_integral(oprnd_t) ||
oprnd_t.sty == ty::ty_bool) {
oprnd_t = check_user_unop(fcx, "!", "not",
tcx.lang_items.not_trait(),
expr, &**oprnd, oprnd_t, unop);
oprnd_t = op::check_user_unop(fcx, "!", "not",
tcx.lang_items.not_trait(),
expr, &**oprnd, oprnd_t, unop);
}
}
ast::UnNeg => {
@ -3590,9 +3254,9 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
oprnd_t);
if !(ty::type_is_integral(oprnd_t) ||
ty::type_is_fp(oprnd_t)) {
oprnd_t = check_user_unop(fcx, "-", "neg",
tcx.lang_items.neg_trait(),
expr, &**oprnd, oprnd_t, unop);
oprnd_t = op::check_user_unop(fcx, "-", "neg",
tcx.lang_items.neg_trait(),
expr, &**oprnd, oprnd_t, unop);
}
}
}
@ -4073,9 +3737,8 @@ fn check_expr_with_unifier<'a, 'tcx, F>(fcx: &FnCtxt<'a, 'tcx>,
match result {
Some((index_ty, element_ty)) => {
// FIXME: we've already checked idx above, we should
// probably just demand subtype or something here.
check_expr_has_type(fcx, &**idx, index_ty);
let idx_expr_ty = fcx.expr_ty(idx);
demand::eqtype(fcx, expr.span, index_ty, idx_expr_ty);
fcx.write_ty(id, element_ty);
}
_ => {

View file

@ -0,0 +1,481 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Code related to processing overloaded binary and unary operators.
use super::{
check_expr,
check_expr_coercable_to_type,
check_expr_with_lvalue_pref,
demand,
method,
FnCtxt,
PreferMutLvalue,
structurally_resolved_type,
};
use middle::infer;
use middle::traits;
use middle::ty::{self, Ty};
use syntax::ast;
use syntax::ast_util;
use syntax::parse::token;
use util::ppaux::{Repr, UserString};
/// Check a `a <op>= b`
pub fn check_binop_assign<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
expr: &'tcx ast::Expr,
op: ast::BinOp,
lhs_expr: &'tcx ast::Expr,
rhs_expr: &'tcx ast::Expr)
{
let tcx = fcx.ccx.tcx;
check_expr_with_lvalue_pref(fcx, lhs_expr, PreferMutLvalue);
check_expr(fcx, rhs_expr);
let lhs_ty = structurally_resolved_type(fcx, lhs_expr.span, fcx.expr_ty(lhs_expr));
let rhs_ty = structurally_resolved_type(fcx, rhs_expr.span, fcx.expr_ty(rhs_expr));
if is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op) {
enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
fcx.write_nil(expr.id);
} else {
// error types are considered "builtin"
assert!(!ty::type_is_error(lhs_ty) || !ty::type_is_error(rhs_ty));
span_err!(tcx.sess, lhs_expr.span, E0368,
"binary assignment operation `{}=` cannot be applied to types `{}` and `{}`",
ast_util::binop_to_string(op.node),
lhs_ty.user_string(fcx.tcx()),
rhs_ty.user_string(fcx.tcx()));
fcx.write_error(expr.id);
}
let tcx = fcx.tcx();
if !ty::expr_is_lval(tcx, lhs_expr) {
span_err!(tcx.sess, lhs_expr.span, E0067, "illegal left-hand side expression");
}
fcx.require_expr_have_sized_type(lhs_expr, traits::AssignmentLhsSized);
}
/// Check a potentially overloaded binary operator.
pub fn check_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
expr: &'tcx ast::Expr,
op: ast::BinOp,
lhs_expr: &'tcx ast::Expr,
rhs_expr: &'tcx ast::Expr)
{
let tcx = fcx.ccx.tcx;
debug!("check_binop(expr.id={}, expr={}, op={:?}, lhs_expr={}, rhs_expr={})",
expr.id,
expr.repr(tcx),
op,
lhs_expr.repr(tcx),
rhs_expr.repr(tcx));
check_expr(fcx, lhs_expr);
let lhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_expr));
// Annoyingly, SIMD ops don't fit into the PartialEq/PartialOrd
// traits, because their return type is not bool. Perhaps this
// should change, but for now if LHS is SIMD we go down a
// different path that bypassess all traits.
if ty::type_is_simd(fcx.tcx(), lhs_ty) {
check_expr_coercable_to_type(fcx, rhs_expr, lhs_ty);
let rhs_ty = fcx.resolve_type_vars_if_possible(fcx.expr_ty(lhs_expr));
let return_ty = enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
fcx.write_ty(expr.id, return_ty);
return;
}
match BinOpCategory::from(op) {
BinOpCategory::Shortcircuit => {
// && and || are a simple case.
demand::suptype(fcx, lhs_expr.span, ty::mk_bool(tcx), lhs_ty);
check_expr_coercable_to_type(fcx, rhs_expr, ty::mk_bool(tcx));
fcx.write_ty(expr.id, ty::mk_bool(tcx));
}
_ => {
// Otherwise, we always treat operators as if they are
// overloaded. This is the way to be most flexible w/r/t
// types that get inferred.
let (rhs_ty, return_ty) =
check_overloaded_binop(fcx, expr, lhs_expr, lhs_ty, rhs_expr, op);
// Supply type inference hints if relevant. Probably these
// hints should be enforced during select as part of the
// `consider_unification_despite_ambiguity` routine, but this
// more convenient for now.
//
// The basic idea is to help type inference by taking
// advantage of things we know about how the impls for
// scalar types are arranged. This is important in a
// scenario like `1_u32 << 2`, because it lets us quickly
// deduce that the result type should be `u32`, even
// though we don't know yet what type 2 has and hence
// can't pin this down to a specific impl.
let rhs_ty = fcx.resolve_type_vars_if_possible(rhs_ty);
if
!ty::type_is_ty_var(lhs_ty) &&
!ty::type_is_ty_var(rhs_ty) &&
is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op)
{
let builtin_return_ty =
enforce_builtin_binop_types(fcx, lhs_expr, lhs_ty, rhs_expr, rhs_ty, op);
demand::suptype(fcx, expr.span, builtin_return_ty, return_ty);
}
fcx.write_ty(expr.id, return_ty);
}
}
}
fn enforce_builtin_binop_types<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
lhs_expr: &'tcx ast::Expr,
lhs_ty: Ty<'tcx>,
rhs_expr: &'tcx ast::Expr,
rhs_ty: Ty<'tcx>,
op: ast::BinOp)
-> Ty<'tcx>
{
debug_assert!(is_builtin_binop(fcx.tcx(), lhs_ty, rhs_ty, op));
let tcx = fcx.tcx();
match BinOpCategory::from(op) {
BinOpCategory::Shortcircuit => {
demand::suptype(fcx, lhs_expr.span, ty::mk_bool(tcx), lhs_ty);
demand::suptype(fcx, rhs_expr.span, ty::mk_bool(tcx), rhs_ty);
ty::mk_bool(tcx)
}
BinOpCategory::Shift => {
// For integers, the shift amount can be of any integral
// type. For simd, the type must match exactly.
if ty::type_is_simd(tcx, lhs_ty) {
demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
}
// result type is same as LHS always
lhs_ty
}
BinOpCategory::Math |
BinOpCategory::Bitwise => {
// both LHS and RHS and result will have the same type
demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
lhs_ty
}
BinOpCategory::Comparison => {
// both LHS and RHS and result will have the same type
demand::suptype(fcx, rhs_expr.span, lhs_ty, rhs_ty);
// if this is simd, result is same as lhs, else bool
if ty::type_is_simd(tcx, lhs_ty) {
let unit_ty = ty::simd_type(tcx, lhs_ty);
debug!("enforce_builtin_binop_types: lhs_ty={} unit_ty={}",
lhs_ty.repr(tcx),
unit_ty.repr(tcx));
if !ty::type_is_integral(unit_ty) {
tcx.sess.span_err(
lhs_expr.span,
&format!("binary comparison operation `{}` not supported \
for floating point SIMD vector `{}`",
ast_util::binop_to_string(op.node),
lhs_ty.user_string(tcx)));
tcx.types.err
} else {
lhs_ty
}
} else {
ty::mk_bool(tcx)
}
}
}
}
fn check_overloaded_binop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
expr: &'tcx ast::Expr,
lhs_expr: &'tcx ast::Expr,
lhs_ty: Ty<'tcx>,
rhs_expr: &'tcx ast::Expr,
op: ast::BinOp)
-> (Ty<'tcx>, Ty<'tcx>)
{
debug!("check_overloaded_binop(expr.id={}, lhs_ty={})",
expr.id,
lhs_ty.repr(fcx.tcx()));
let (name, trait_def_id) = name_and_trait_def_id(fcx, op);
// NB: As we have not yet type-checked the RHS, we don't have the
// type at hand. Make a variable to represent it. The whole reason
// for this indirection is so that, below, we can check the expr
// using this variable as the expected type, which sometimes lets
// us do better coercions than we would be able to do otherwise,
// particularly for things like `String + &String`.
let rhs_ty_var = fcx.infcx().next_ty_var();
let return_ty = match lookup_op_method(fcx, expr, lhs_ty, vec![rhs_ty_var],
token::intern(name), trait_def_id,
lhs_expr) {
Ok(return_ty) => return_ty,
Err(()) => {
// error types are considered "builtin"
if !ty::type_is_error(lhs_ty) {
span_err!(fcx.tcx().sess, lhs_expr.span, E0369,
"binary operation `{}` cannot be applied to type `{}`",
ast_util::binop_to_string(op.node),
lhs_ty.user_string(fcx.tcx()));
}
fcx.tcx().types.err
}
};
// see `NB` above
check_expr_coercable_to_type(fcx, rhs_expr, rhs_ty_var);
(rhs_ty_var, return_ty)
}
pub fn check_user_unop<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
op_str: &str,
mname: &str,
trait_did: Option<ast::DefId>,
ex: &'tcx ast::Expr,
operand_expr: &'tcx ast::Expr,
operand_ty: Ty<'tcx>,
op: ast::UnOp)
-> Ty<'tcx>
{
assert!(ast_util::is_by_value_unop(op));
match lookup_op_method(fcx, ex, operand_ty, vec![],
token::intern(mname), trait_did,
operand_expr) {
Ok(t) => t,
Err(()) => {
fcx.type_error_message(ex.span, |actual| {
format!("cannot apply unary operator `{}` to type `{}`",
op_str, actual)
}, operand_ty, None);
fcx.tcx().types.err
}
}
}
fn name_and_trait_def_id(fcx: &FnCtxt, op: ast::BinOp) -> (&'static str, Option<ast::DefId>) {
let lang = &fcx.tcx().lang_items;
match op.node {
ast::BiAdd => ("add", lang.add_trait()),
ast::BiSub => ("sub", lang.sub_trait()),
ast::BiMul => ("mul", lang.mul_trait()),
ast::BiDiv => ("div", lang.div_trait()),
ast::BiRem => ("rem", lang.rem_trait()),
ast::BiBitXor => ("bitxor", lang.bitxor_trait()),
ast::BiBitAnd => ("bitand", lang.bitand_trait()),
ast::BiBitOr => ("bitor", lang.bitor_trait()),
ast::BiShl => ("shl", lang.shl_trait()),
ast::BiShr => ("shr", lang.shr_trait()),
ast::BiLt => ("lt", lang.ord_trait()),
ast::BiLe => ("le", lang.ord_trait()),
ast::BiGe => ("ge", lang.ord_trait()),
ast::BiGt => ("gt", lang.ord_trait()),
ast::BiEq => ("eq", lang.eq_trait()),
ast::BiNe => ("ne", lang.eq_trait()),
ast::BiAnd | ast::BiOr => {
fcx.tcx().sess.span_bug(op.span, "&& and || are not overloadable")
}
}
}
fn lookup_op_method<'a, 'tcx>(fcx: &'a FnCtxt<'a, 'tcx>,
expr: &'tcx ast::Expr,
lhs_ty: Ty<'tcx>,
other_tys: Vec<Ty<'tcx>>,
opname: ast::Name,
trait_did: Option<ast::DefId>,
lhs_expr: &'a ast::Expr)
-> Result<Ty<'tcx>,()>
{
debug!("lookup_op_method(expr={}, lhs_ty={}, opname={:?}, trait_did={}, lhs_expr={})",
expr.repr(fcx.tcx()),
lhs_ty.repr(fcx.tcx()),
opname,
trait_did.repr(fcx.tcx()),
lhs_expr.repr(fcx.tcx()));
let method = match trait_did {
Some(trait_did) => {
// We do eager coercions to make using operators
// more ergonomic:
//
// - If the input is of type &'a T (resp. &'a mut T),
// then reborrow it to &'b T (resp. &'b mut T) where
// 'b <= 'a. This makes things like `x == y`, where
// `x` and `y` are both region pointers, work. We
// could also solve this with variance or different
// traits that don't force left and right to have same
// type.
let (adj_ty, adjustment) = match lhs_ty.sty {
ty::ty_rptr(r_in, mt) => {
let r_adj = fcx.infcx().next_region_var(infer::Autoref(lhs_expr.span));
fcx.mk_subr(infer::Reborrow(lhs_expr.span), r_adj, *r_in);
let adjusted_ty = ty::mk_rptr(fcx.tcx(), fcx.tcx().mk_region(r_adj), mt);
let autoptr = ty::AutoPtr(r_adj, mt.mutbl, None);
let adjustment = ty::AutoDerefRef { autoderefs: 1, autoref: Some(autoptr) };
(adjusted_ty, adjustment)
}
_ => {
(lhs_ty, ty::AutoDerefRef { autoderefs: 0, autoref: None })
}
};
debug!("adjusted_ty={} adjustment={:?}",
adj_ty.repr(fcx.tcx()),
adjustment);
method::lookup_in_trait_adjusted(fcx, expr.span, Some(lhs_expr), opname,
trait_did, adjustment, adj_ty, Some(other_tys))
}
None => None
};
match method {
Some(method) => {
let method_ty = method.ty;
// HACK(eddyb) Fully qualified path to work around a resolve bug.
let method_call = ::middle::ty::MethodCall::expr(expr.id);
fcx.inh.method_map.borrow_mut().insert(method_call, method);
// extract return type for method; all late bound regions
// should have been instantiated by now
let ret_ty = ty::ty_fn_ret(method_ty);
Ok(ty::no_late_bound_regions(fcx.tcx(), &ret_ty).unwrap().unwrap())
}
None => {
Err(())
}
}
}
// Binary operator categories. These categories summarize the behavior
// with respect to the builtin operationrs supported.
enum BinOpCategory {
/// &&, || -- cannot be overridden
Shortcircuit,
/// <<, >> -- when shifting a single integer, rhs can be any
/// integer type. For simd, types must match.
Shift,
/// +, -, etc -- takes equal types, produces same type as input,
/// applicable to ints/floats/simd
Math,
/// &, |, ^ -- takes equal types, produces same type as input,
/// applicable to ints/floats/simd/bool
Bitwise,
/// ==, !=, etc -- takes equal types, produces bools, except for simd,
/// which produce the input type
Comparison,
}
impl BinOpCategory {
fn from(op: ast::BinOp) -> BinOpCategory {
match op.node {
ast::BiShl | ast::BiShr =>
BinOpCategory::Shift,
ast::BiAdd |
ast::BiSub |
ast::BiMul |
ast::BiDiv |
ast::BiRem =>
BinOpCategory::Math,
ast::BiBitXor |
ast::BiBitAnd |
ast::BiBitOr =>
BinOpCategory::Bitwise,
ast::BiEq |
ast::BiNe |
ast::BiLt |
ast::BiLe |
ast::BiGe |
ast::BiGt =>
BinOpCategory::Comparison,
ast::BiAnd |
ast::BiOr =>
BinOpCategory::Shortcircuit,
}
}
}
/// Returns true if this is a built-in arithmetic operation (e.g. u32
/// + u32, i16x4 == i16x4) and false if these types would have to be
/// overloaded to be legal. There are two reasons that we distinguish
/// builtin operations from overloaded ones (vs trying to drive
/// everything uniformly through the trait system and intrinsics or
/// something like that):
///
/// 1. Builtin operations can trivially be evaluated in constants.
/// 2. For comparison operators applied to SIMD types the result is
/// not of type `bool`. For example, `i16x4==i16x4` yields a
/// type like `i16x4`. This means that the overloaded trait
/// `PartialEq` is not applicable.
///
/// Reason #2 is the killer. I tried for a while to always use
/// overloaded logic and just check the types in constants/trans after
/// the fact, and it worked fine, except for SIMD types. -nmatsakis
fn is_builtin_binop<'tcx>(cx: &ty::ctxt<'tcx>,
lhs: Ty<'tcx>,
rhs: Ty<'tcx>,
op: ast::BinOp)
-> bool
{
match BinOpCategory::from(op) {
BinOpCategory::Shortcircuit => {
true
}
BinOpCategory::Shift => {
ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
}
BinOpCategory::Math => {
ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
ty::type_is_floating_point(lhs) && ty::type_is_floating_point(rhs) ||
ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
}
BinOpCategory::Bitwise => {
ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
ty::type_is_integral(lhs) && ty::type_is_integral(rhs) ||
ty::type_is_floating_point(lhs) && ty::type_is_floating_point(rhs) ||
ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs) ||
ty::type_is_bool(lhs) && ty::type_is_bool(rhs)
}
BinOpCategory::Comparison => {
ty::type_is_error(lhs) || ty::type_is_error(rhs) ||
ty::type_is_scalar(lhs) && ty::type_is_scalar(rhs) ||
ty::type_is_simd(cx, lhs) && ty::type_is_simd(cx, rhs)
}
}
}

View file

@ -26,6 +26,7 @@ use util::ppaux::Repr;
use std::cell::Cell;
use syntax::ast;
use syntax::ast_util;
use syntax::codemap::{DUMMY_SP, Span};
use syntax::print::pprust::pat_to_string;
use syntax::visit;
@ -113,6 +114,31 @@ impl<'cx, 'tcx, 'v> Visitor<'v> for WritebackCx<'cx, 'tcx> {
return;
}
// Hacky hack: During type-checking, we treat *all* operators
// as potentially overloaded. But then, during writeback, if
// we observe that something like `a+b` is (known to be)
// operating on scalars, we clear the overload.
match e.node {
ast::ExprBinary(ref op, ref lhs, ref rhs) => {
let lhs_ty = self.fcx.expr_ty(lhs);
let lhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&lhs_ty);
let rhs_ty = self.fcx.expr_ty(rhs);
let rhs_ty = self.fcx.infcx().resolve_type_vars_if_possible(&rhs_ty);
if ty::type_is_scalar(lhs_ty) && ty::type_is_scalar(rhs_ty) {
self.fcx.inh.method_map.borrow_mut().remove(&MethodCall::expr(e.id));
// weird but true: the by-ref binops put an
// adjustment on the lhs but not the rhs; the
// adjustment for rhs is kind of baked into the
// system.
if !ast_util::is_by_value_binop(op.node) {
self.fcx.inh.adjustments.borrow_mut().remove(&lhs.id);
}
}
}
_ => { }
}
self.visit_node_id(ResolvingExpr(e.span), e.id);
self.visit_method_map_entry(ResolvingExpr(e.span),
MethodCall::expr(e.id));

View file

@ -179,7 +179,9 @@ register_diagnostics! {
E0321, // extended coherence rules for defaulted traits violated
E0322, // cannot implement Sized explicitly
E0366, // dropck forbid specialization to concrete type or region
E0367 // dropck forbid specialization to predicate not in struct/enum
E0367, // dropck forbid specialization to predicate not in struct/enum
E0368, // binary operation `<op>=` cannot be applied to types
E0369 // binary operation `<op>` cannot be applied to types
}
__build_diagnostic_array! { DIAGNOSTICS }

View file

@ -86,33 +86,17 @@ pub fn is_shift_binop(b: BinOp_) -> bool {
pub fn is_comparison_binop(b: BinOp_) -> bool {
match b {
BiEq | BiLt | BiLe | BiNe | BiGt | BiGe => true,
_ => false
BiEq | BiLt | BiLe | BiNe | BiGt | BiGe =>
true,
BiAnd | BiOr | BiAdd | BiSub | BiMul | BiDiv | BiRem |
BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr =>
false,
}
}
/// Returns `true` if the binary operator takes its arguments by value
pub fn is_by_value_binop(b: BinOp_) -> bool {
match b {
BiAdd | BiSub | BiMul | BiDiv | BiRem | BiBitXor | BiBitAnd | BiBitOr | BiShl | BiShr => {
true
}
_ => false
}
}
/// Returns `true` if the binary operator is symmetric in the sense that LHS
/// and RHS must have the same type. So the type of LHS can serve as an hint
/// for the type of RHS and vice versa.
pub fn is_symmetric_binop(b: BinOp_) -> bool {
match b {
BiAdd | BiSub | BiMul | BiDiv | BiRem |
BiBitXor | BiBitAnd | BiBitOr |
BiEq | BiLt | BiLe | BiNe | BiGt | BiGe => {
true
}
_ => false
}
!is_comparison_binop(b)
}
/// Returns `true` if the unary operator takes its argument by value

View file

@ -32,3 +32,22 @@ extern fn eh_personality() {}
pub trait Copy : PhantomFn<Self> {
// Empty.
}
#[lang="rem"]
pub trait Rem<RHS=Self> {
type Output = Self;
fn rem(self, rhs: RHS) -> Self::Output;
}
impl Rem for isize {
type Output = isize;
#[inline]
fn rem(self, other: isize) -> isize {
// if you use `self % other` here, as one would expect, you
// get back an error because of potential failure/overflow,
// which tries to invoke error fns that don't have the
// appropriate signatures anymore. So...just return 0.
0
}
}

View file

@ -29,7 +29,7 @@ fn lerp(a: f32, b: f32, v: f32) -> f32 { a * (1.0 - v) + b * v }
fn smooth(v: f32) -> f32 { v * v * (3.0 - 2.0 * v) }
fn random_gradient<R: Rng>(r: &mut R) -> Vec2 {
let v = PI * 2.0 * r.gen();
let v = PI * 2.0 * r.gen::<f32>();
Vec2 { x: v.cos(), y: v.sin() }
}

View file

@ -13,5 +13,5 @@ struct Foo;
fn main() {
let mut a = Foo;
let ref b = Foo;
a += *b; //~ Error: binary assignment operation `+=` cannot be applied to type `Foo`
a += *b; //~ Error: binary assignment operation `+=` cannot be applied to types `Foo` and `Foo`
}

View file

@ -35,5 +35,6 @@ trait Add<RHS=Self> {
fn ice<A>(a: A) {
let r = loop {};
r = r + a;
//~^ ERROR binary operation `+` cannot be applied to type `A`
//~^ ERROR not implemented
//~| ERROR not implemented
}

View file

@ -8,6 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// error-pattern:`||` cannot be applied to type `f32`
fn main() { let x = 1.0_f32 || 2.0_f32; }
//~^ ERROR mismatched types
//~| ERROR mismatched types

View file

@ -8,6 +8,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// error-pattern:`&&` cannot be applied to type `_`
fn main() { let x = 1 && 2; }
//~^ ERROR mismatched types
//~| ERROR mismatched types

View file

@ -13,4 +13,5 @@ fn main() {
fn g() { }
let x = f == g;
//~^ ERROR binary operation `==` cannot be applied
//~| ERROR mismatched types
}

View file

@ -11,19 +11,13 @@
fn main() {
let x = ();
1 +
x //~ ERROR mismatched types
//~| expected `_`
//~| found `()`
//~| expected integral variable
//~| found ()
x //~^ ERROR E0277
//~| ERROR E0277
;
let x: () = ();
1 +
x //~ ERROR mismatched types
//~| expected `_`
//~| found `()`
//~| expected integral variable
//~| found ()
x //~^ ERROR E0277
//~| ERROR E0277
;
}

View file

@ -23,4 +23,6 @@ fn main() {
unsafe { libc::exit(0 as libc::c_int); }
});
2_usize + (loop {});
//~^ ERROR E0277
//~| ERROR E0277
}

View file

@ -16,7 +16,8 @@ impl<A> vec_monad<A> for Vec<A> {
fn bind<B, F>(&self, mut f: F) where F: FnMut(A) -> Vec<B> {
let mut r = panic!();
for elt in self { r = r + f(*elt); }
//~^ ERROR binary operation `+` cannot be applied to type `collections::vec::Vec<B>`
//~^ ERROR E0277
//~| ERROR E0277
}
}
fn main() {

View file

@ -12,5 +12,5 @@
fn main() {
let x = |ref x: isize| -> isize { x += 1; };
//~^ ERROR binary assignment operation `+=` cannot be applied to type `&isize`
//~^ ERROR E0368
}

View file

@ -17,19 +17,19 @@ struct Panolpy {
fn foo(p: &Panolpy) {
22 >> p.char;
//~^ ERROR right-hand-side of a shift operation must have integral type
//~^ ERROR E0277
//~| ERROR E0277
22 >> p.str;
//~^ ERROR right-hand-side of a shift operation must have integral type
//~^ ERROR E0277
//~| ERROR E0277
22 >> p;
//~^ ERROR right-hand-side of a shift operation must have integral type
//~^ ERROR E0277
//~| ERROR E0277
// We could be more accepting in the case of a type not yet inferred, but not
// known to be an integer, but meh.
let x;
22 >> x;
//~^ ERROR the type of this value must be known in this context
22 >> x; // ambiguity error winds up being suppressed
22 >> 1;
// Integer literal types are OK

View file

@ -10,6 +10,7 @@
// ignore-tidy-linelength
#![feature(core)]
use std::simd::f32x4;

View file

@ -1,13 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
fn f() { if (1 == panic!()) { } else { } }
fn main() { }

View file

@ -8,9 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// error-pattern:quux
fn foo() -> ! { panic!("quux"); }
fn main() {
foo() //~ ERROR the type of this value must be known in this context
==
foo();
foo() == foo(); // these types wind up being defaulted to ()
}

View file

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// error-pattern:index out of bounds: the len is 3 but the index is
// error-pattern:assertion failed: index < self.len()
use std::usize;
use std::mem::size_of;

View file

@ -0,0 +1,32 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::ops::Mul;
#[derive(Copy)]
pub struct Foo {
x: f64,
}
impl Mul<Foo> for f64 {
type Output = Foo;
fn mul(self, rhs: Foo) -> Foo {
// intentionally do something that is not *
Foo { x: self + rhs.x }
}
}
pub fn main() {
let f: Foo = Foo { x: 5.0 };
let val: f64 = 3.0;
let f2: Foo = val * f;
assert_eq!(f2.x, 8.0);
}

View file

@ -56,7 +56,7 @@ pub fn main() {
}
// raw slice with explicit cast
let a = &[1, 2, 3] as *const [_];
let a = &[1, 2, 3] as *const [i32];
unsafe {
let b = (*a)[2];
assert!(b == 3);
@ -96,7 +96,7 @@ pub fn main() {
assert!(len == 3);
}
let a = &mut [1, 2, 3] as *mut [_];
let a = &mut [1, 2, 3] as *mut [i32];
unsafe {
let b = (*a)[2];
assert!(b == 3);

View file

@ -10,5 +10,8 @@
// pretty-expanded FIXME #23616
fn wsucc(n: isize) -> isize { 0 + { return n + 1 } }
use std::num::Int;
fn wsucc<T:Int>(n: T) -> T { n + { return n } }
pub fn main() { }

View file

@ -12,5 +12,5 @@
// pretty-expanded FIXME #23616
pub fn main() {
{|i| if 1 == i { }};
{|i: u32| if 1 == i { }};
}

View file

@ -17,7 +17,7 @@ use std::mem;
fn main() {
let y = 0u8;
let closure = move |x| y + x;
let closure = move |x: u8| y + x;
// Check that both closures are capturing by value
assert_eq!(1, mem::size_of_val(&closure));

View file

@ -12,13 +12,13 @@
// pretty-expanded FIXME #23616
fn main() {
if let Ok(x) = "3.1415".parse() {
if let Ok(x) = "3.1415".parse::<f64>() {
assert_eq!(false, x <= 0.0);
}
if let Ok(x) = "3.1415".parse() {
if let Ok(x) = "3.1415".parse::<f64>() {
assert_eq!(3.1415, x + 0.0);
}
if let Ok(mut x) = "3.1415".parse() {
if let Ok(mut x) = "3.1415".parse::<f64>() {
assert_eq!(8.1415, { x += 5.0; x });
}
}

View file

@ -25,19 +25,19 @@ fn main() {
assert!(thread::spawn(move|| { min_val::<i16>() / -1; }).join().is_err());
assert!(thread::spawn(move|| { min_val::<i32>() / -1; }).join().is_err());
assert!(thread::spawn(move|| { min_val::<i64>() / -1; }).join().is_err());
assert!(thread::spawn(move|| { 1isize / zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i8 / zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i16 / zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i32 / zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i64 / zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1isize / zero::<isize>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i8 / zero::<i8>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i16 / zero::<i16>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i32 / zero::<i32>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i64 / zero::<i64>(); }).join().is_err());
assert!(thread::spawn(move|| { min_val::<isize>() % -1; }).join().is_err());
assert!(thread::spawn(move|| { min_val::<i8>() % -1; }).join().is_err());
assert!(thread::spawn(move|| { min_val::<i16>() % -1; }).join().is_err());
assert!(thread::spawn(move|| { min_val::<i32>() % -1; }).join().is_err());
assert!(thread::spawn(move|| { min_val::<i64>() % -1; }).join().is_err());
assert!(thread::spawn(move|| { 1isize % zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i8 % zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i16 % zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i32 % zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1i64 % zero(); }).join().is_err());
assert!(thread::spawn(move|| { 1isize % zero::<isize>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i8 % zero::<i8>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i16 % zero::<i16>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i32 % zero::<i32>(); }).join().is_err());
assert!(thread::spawn(move|| { 1i64 % zero::<i64>(); }).join().is_err());
}

View file

@ -46,5 +46,5 @@ extern {}
#[start]
fn main(_: isize, _: *const *const u8) -> isize {
1 % 1
1_isize % 1_isize
}

View file

@ -19,8 +19,8 @@ use reexported_static_methods::Boz;
use reexported_static_methods::Bort;
pub fn main() {
assert_eq!(42, Foo::foo());
assert_eq!(84, Baz::bar());
assert_eq!(42_isize, Foo::foo());
assert_eq!(84_isize, Baz::bar());
assert!(Boz::boz(1));
assert_eq!("bort()".to_string(), Bort::bort());
}