From cfaa0f4b91b27abfb3b4fba4d0bb31a5e3da7c89 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Fri, 24 Jun 2011 14:19:58 -0700 Subject: [PATCH] Fail typechecking for bad binop/type combinations Includes assignment operations. Add regression tests for lots of less useful, less used or unexpected combinations, as well as a selection of compile-fail tests. Closes #500 (again!) --- src/comp/middle/ty.rs | 91 ++++++++++ src/comp/middle/typeck.rs | 37 ++-- src/test/compile-fail/binop-add-tup-assign.rs | 7 + src/test/compile-fail/binop-add-tup.rs | 6 + src/test/compile-fail/binop-bitxor-str.rs | 6 + src/test/compile-fail/binop-logic-float.rs | 6 + src/test/compile-fail/binop-logic-int.rs | 6 + src/test/compile-fail/binop-mul-bool.rs | 6 + src/test/compile-fail/binop-shift-port.rs | 8 + src/test/compile-fail/binop-sub-obj.rs | 6 + src/test/run-pass/binops.rs | 162 ++++++++++++++++++ src/test/run-pass/bitwise.rs | 1 + 12 files changed, 330 insertions(+), 12 deletions(-) create mode 100644 src/test/compile-fail/binop-add-tup-assign.rs create mode 100644 src/test/compile-fail/binop-add-tup.rs create mode 100644 src/test/compile-fail/binop-bitxor-str.rs create mode 100644 src/test/compile-fail/binop-logic-float.rs create mode 100644 src/test/compile-fail/binop-logic-int.rs create mode 100644 src/test/compile-fail/binop-mul-bool.rs create mode 100644 src/test/compile-fail/binop-shift-port.rs create mode 100644 src/test/compile-fail/binop-sub-obj.rs create mode 100644 src/test/run-pass/binops.rs diff --git a/src/comp/middle/ty.rs b/src/comp/middle/ty.rs index e8b31aa3a63..69a59b42105 100644 --- a/src/comp/middle/ty.rs +++ b/src/comp/middle/ty.rs @@ -54,6 +54,7 @@ export get_element_type; export hash_ty; export idx_nil; export is_lval; +export is_binopable; export item_table; export lookup_item_type; export method; @@ -2792,6 +2793,96 @@ fn strip_boxes(&ctxt cx, &ty::t t) -> ty::t { } fail; } + +fn is_binopable(&ctxt cx, t ty, ast::binop op) -> bool { + + const int tycat_other = 0; + const int tycat_bool = 1; + const int tycat_int = 2; + const int tycat_float = 3; + const int tycat_str = 4; + const int tycat_vec = 5; + const int tycat_struct = 6; + + const int opcat_add = 0; + const int opcat_sub = 1; + const int opcat_mult = 2; + const int opcat_shift = 3; + const int opcat_rel = 4; + const int opcat_eq = 5; + const int opcat_bit = 6; + const int opcat_logic = 7; + + fn opcat(ast::binop op) -> int { + alt (op) { + case (ast::add) { opcat_add } + case (ast::sub) { opcat_sub } + case (ast::mul) { opcat_mult } + case (ast::div) { opcat_mult } + case (ast::rem) { opcat_mult } + case (ast::and) { opcat_logic } + case (ast::or) { opcat_logic } + case (ast::bitxor) { opcat_bit } + case (ast::bitand) { opcat_bit } + case (ast::bitor) { opcat_bit } + case (ast::lsl) { opcat_shift } + case (ast::lsr) { opcat_shift } + case (ast::asr) { opcat_shift } + case (ast::eq) { opcat_eq } + case (ast::ne) { opcat_eq } + case (ast::lt) { opcat_rel } + case (ast::le) { opcat_rel } + case (ast::ge) { opcat_rel } + case (ast::gt) { opcat_rel } + } + } + + fn tycat(&ctxt cx, t ty) -> int { + alt (struct(cx, strip_boxes(cx, ty))) { + case (ty_bool) { tycat_bool } + case (ty_int) { tycat_int } + case (ty_uint) { tycat_int } + case (ty_machine(ty_i8)) { tycat_int } + case (ty_machine(ty_i16)) { tycat_int } + case (ty_machine(ty_i32)) { tycat_int } + case (ty_machine(ty_i64)) { tycat_int } + case (ty_machine(ty_u8)) { tycat_int } + case (ty_machine(ty_u16)) { tycat_int } + case (ty_machine(ty_u32)) { tycat_int } + case (ty_machine(ty_u64)) { tycat_int } + case (ty_float) { tycat_float } + case (ty_machine(ty_f32)) { tycat_float } + case (ty_machine(ty_f64)) { tycat_float } + case (ty_char) { tycat_int } + case (ty_ptr(_)) { tycat_int } + case (ty_str) { tycat_str } + case (ty_istr) { tycat_str } + case (ty_vec(_)) { tycat_vec } + case (ty_ivec(_)) { tycat_vec } + case (ty_tup(_)) { tycat_struct } + case (ty_rec(_)) { tycat_struct } + case (ty_tag(_, _)) { tycat_struct } + case (_) { tycat_other } + } + } + + const bool t = true; + const bool f = false; + + /*. add, shift, bit + . sub, rel, logic + . mult, eq, */ + auto tbl = [[f, f, f, f, t, t, f, f], /*other*/ + [f, f, f, f, t, t, t, t], /*bool*/ + [t, t, t, t, t, t, t, f], /*int*/ + [t, t, t, f, t, t, f, f], /*float*/ + [t, f, f, f, t, t, f, f], /*str*/ + [t, f, f, f, t, t, f, f], /*vec*/ + [f, f, f, f, t, t, f, f]];/*struct*/ + + ret tbl.(tycat(cx, ty)).(opcat(op)); +} + // Local Variables: // mode: rust // fill-column: 78; diff --git a/src/comp/middle/typeck.rs b/src/comp/middle/typeck.rs index 4ad7400dc99..b851f5e9fb9 100644 --- a/src/comp/middle/typeck.rs +++ b/src/comp/middle/typeck.rs @@ -1476,6 +1476,19 @@ fn check_expr(&@fn_ctxt fcx, &@ast::expr expr) { write::ty_only_fixup(fcx, id, if_t); } + // Checks the compatibility + fn check_binop_type_compat(&@fn_ctxt fcx, common::span span, + ty::t ty, ast::binop binop) { + auto resolved_t = resolve_type_vars_if_possible(fcx, ty); + if (!ty::is_binopable(fcx.ccx.tcx, resolved_t, binop)) { + auto binopstr = ast::binop_to_str(binop); + auto t_str = ty_to_str(fcx.ccx.tcx, resolved_t); + auto errmsg = "binary operation " + binopstr + + " cannot be applied to type `" + t_str + "`"; + fcx.ccx.tcx.sess.span_fatal(span, errmsg); + } + } + auto id = expr.id; alt (expr.node) { case (ast::expr_lit(?lit)) { @@ -1490,19 +1503,17 @@ fn check_expr(&@fn_ctxt fcx, &@ast::expr expr) { auto rhs_t = expr_ty(fcx.ccx.tcx, rhs); demand::autoderef(fcx, rhs.span, lhs_t, rhs_t, AUTODEREF_OK); + check_binop_type_compat(fcx, expr.span, lhs_t, binop); - // FIXME: Binops have a bit more subtlety than this. - - auto t = strip_boxes(fcx, expr.span, lhs_t); - alt (binop) { - case (ast::eq) { t = ty::mk_bool(fcx.ccx.tcx); } - case (ast::lt) { t = ty::mk_bool(fcx.ccx.tcx); } - case (ast::le) { t = ty::mk_bool(fcx.ccx.tcx); } - case (ast::ne) { t = ty::mk_bool(fcx.ccx.tcx); } - case (ast::ge) { t = ty::mk_bool(fcx.ccx.tcx); } - case (ast::gt) { t = ty::mk_bool(fcx.ccx.tcx); } - case (_) {/* fall through */ } - } + auto t = alt (binop) { + case (ast::eq) { ty::mk_bool(fcx.ccx.tcx) } + case (ast::lt) { ty::mk_bool(fcx.ccx.tcx) } + case (ast::le) { ty::mk_bool(fcx.ccx.tcx) } + case (ast::ne) { ty::mk_bool(fcx.ccx.tcx) } + case (ast::ge) { ty::mk_bool(fcx.ccx.tcx) } + case (ast::gt) { ty::mk_bool(fcx.ccx.tcx) } + case (_) { strip_boxes(fcx, expr.span, lhs_t) } + }; write::ty_only_fixup(fcx, id, t); } case (ast::expr_unary(?unop, ?oper)) { @@ -1646,6 +1657,8 @@ fn check_expr(&@fn_ctxt fcx, &@ast::expr expr) { case (ast::expr_assign_op(?op, ?lhs, ?rhs)) { require_impure(fcx.ccx.tcx.sess, fcx.purity, expr.span); check_assignment(fcx, expr.span, lhs, rhs, id); + check_binop_type_compat(fcx, expr.span, + expr_ty(fcx.ccx.tcx, lhs), op); } case (ast::expr_send(?lhs, ?rhs)) { require_impure(fcx.ccx.tcx.sess, fcx.purity, expr.span); diff --git a/src/test/compile-fail/binop-add-tup-assign.rs b/src/test/compile-fail/binop-add-tup-assign.rs new file mode 100644 index 00000000000..6283494fc30 --- /dev/null +++ b/src/test/compile-fail/binop-add-tup-assign.rs @@ -0,0 +1,7 @@ +// xfail-stage0 +// error-pattern:+ cannot be applied to type `tup(bool)` + +fn main() { + auto x = tup(true); + x += tup(false); +} \ No newline at end of file diff --git a/src/test/compile-fail/binop-add-tup.rs b/src/test/compile-fail/binop-add-tup.rs new file mode 100644 index 00000000000..2c45d9ae541 --- /dev/null +++ b/src/test/compile-fail/binop-add-tup.rs @@ -0,0 +1,6 @@ +// xfail-stage0 +// error-pattern:+ cannot be applied to type `tup(bool)` + +fn main() { + auto x = tup(true) + tup(false); +} \ No newline at end of file diff --git a/src/test/compile-fail/binop-bitxor-str.rs b/src/test/compile-fail/binop-bitxor-str.rs new file mode 100644 index 00000000000..d66a3696638 --- /dev/null +++ b/src/test/compile-fail/binop-bitxor-str.rs @@ -0,0 +1,6 @@ +// xfail-stage0 +// error-pattern:\^ cannot be applied to type `str` + +fn main() { + auto x = "a" ^ "b"; +} \ No newline at end of file diff --git a/src/test/compile-fail/binop-logic-float.rs b/src/test/compile-fail/binop-logic-float.rs new file mode 100644 index 00000000000..55448c83be3 --- /dev/null +++ b/src/test/compile-fail/binop-logic-float.rs @@ -0,0 +1,6 @@ +// xfail-stage0 +// error-pattern:|| cannot be applied to type `f32` + +fn main() { + auto x = 1.0_f32 || 2.0_f32; +} \ No newline at end of file diff --git a/src/test/compile-fail/binop-logic-int.rs b/src/test/compile-fail/binop-logic-int.rs new file mode 100644 index 00000000000..e777c2ee2ed --- /dev/null +++ b/src/test/compile-fail/binop-logic-int.rs @@ -0,0 +1,6 @@ +// xfail-stage0 +// error-pattern:&& cannot be applied to type `int` + +fn main() { + auto x = 1 && 2; +} \ No newline at end of file diff --git a/src/test/compile-fail/binop-mul-bool.rs b/src/test/compile-fail/binop-mul-bool.rs new file mode 100644 index 00000000000..6ab45421ab9 --- /dev/null +++ b/src/test/compile-fail/binop-mul-bool.rs @@ -0,0 +1,6 @@ +// xfail-stage0 +// error-pattern:* cannot be applied to type `bool` + +fn main() { + auto x = true * false; +} \ No newline at end of file diff --git a/src/test/compile-fail/binop-shift-port.rs b/src/test/compile-fail/binop-shift-port.rs new file mode 100644 index 00000000000..8c06de847b0 --- /dev/null +++ b/src/test/compile-fail/binop-shift-port.rs @@ -0,0 +1,8 @@ +// xfail-stage0 +// error-pattern:>> cannot be applied to type `port\[int\]` + +fn main() { + let port[int] p1 = port(); + let port[int] p2 = port(); + auto x = p1 >> p2; +} \ No newline at end of file diff --git a/src/test/compile-fail/binop-sub-obj.rs b/src/test/compile-fail/binop-sub-obj.rs new file mode 100644 index 00000000000..54f5584bb6f --- /dev/null +++ b/src/test/compile-fail/binop-sub-obj.rs @@ -0,0 +1,6 @@ +// xfail-stage0 +// error-pattern:\- cannot be applied to type `obj + +fn main() { + auto x = obj(){} - obj(){}; +} \ No newline at end of file diff --git a/src/test/run-pass/binops.rs b/src/test/run-pass/binops.rs new file mode 100644 index 00000000000..e9679f960dd --- /dev/null +++ b/src/test/run-pass/binops.rs @@ -0,0 +1,162 @@ +// Binop corner cases + +fn test_nil() { + assert () == (); + assert !(() != ()); + // FIXME (#576): The current implementation of relational ops on nil + // is nonsensical + assert () < (); + assert () <= (); + assert !(() > ()); + assert !(() >= ()); +} + +fn test_bool() { + assert !(true < false); + assert !(true <= false); + assert (true > false); + assert (true >= false); + + assert (false < true); + assert (false <= true); + assert !(false > true); + assert !(false >= true); + + // Bools support bitwise binops + assert (false & false == false); + assert (true & false == false); + assert (true & true == true); + assert (false | false == false); + assert (true | false == true); + assert (true | true == true); + assert (false ^ false == false); + assert (true ^ false == true); + assert (true ^ true == false); +} + +fn test_char() { + auto ch10 = 10 as char; + auto ch4 = 4 as char; + auto ch2 = 2 as char; + assert ch10 + ch4 == 14 as char; + assert ch10 - ch4 == 6 as char; + assert ch10 * ch4 == 40 as char; + assert ch10 / ch4 == ch2; + assert ch10 % ch4 == ch2; + assert ch10 >> ch2 == ch2; + assert ch10 >>> ch2 == ch2; + assert ch10 << ch4 == 160 as char; + assert ch10 | ch4 == 14 as char; + assert ch10 & ch2 == ch2; + assert ch10 ^ ch2 == 8 as char; +} + +fn test_box() { + assert @10 == 10; + assert 0xFF & @0xF0 == 0xF0; + assert tup(1, 3) < @tup(1, 4); + assert @rec(a = 'x') != @rec(a = 'y'); +} + +fn test_port() { + let port[int] p1 = port(); + let port[int] p2 = port(); + + // FIXME (#577) comparison of ports + // assert (p1 != p2); + // assert !(p1 < p2); + // etc +} + +fn test_chan() { + let port[int] p = port(); + auto ch1 = chan(p); + auto ch2 = chan(p); + + // FIXME (#577) comparison of channels + // assert (ch1 != ch2); + // etc +} + +fn test_ptr() { + // FIXME: Don't know what binops apply to pointers. Don't know how + // to make or use pointers +} + +fn test_task() { + fn f() {} + auto t1 = spawn f(); + auto t2 = spawn f(); + // FIXME (#577) comparison of tasks + //assert t1 != t2; +} + +fn test_fn() { + fn f() {} + fn g() {} + fn h(int i) {} + auto f1 = f; + auto f2 = f; + auto g1 = g; + auto h1 = h; + auto h2 = h; + assert (f1 == f2); + assert (f1 == f); + + assert (f1 == g1); + assert (h1 == h2); + assert !(f1 != f2); + assert !(h1 < h2); + assert (h1 <= h2); + assert !(h1 > h2); + assert (h1 >= h2); +} + +native "rust" mod native_mod { + fn str_byte_len(str s) -> vec[u8]; + fn str_alloc(uint n_bytes) -> str; +} + +// FIXME: comparison of native fns +fn test_native_fn() { + /*assert (native_mod::str_byte_len == native_mod::str_byte_len); + assert (native_mod::str_byte_len != native_mod::str_alloc);*/ +} + +fn test_obj() { + auto o1 = obj() { }; + auto o2 = obj() { }; + + assert (o1 == o2); + assert !(o1 != o2); + assert !(o1 < o2); + assert (o1 <= o2); + assert !(o1 > o2); + assert (o1 >= o2); + + obj constr1(int i) { }; + obj constr2(int i) { }; + + auto o5 = constr1(10); + auto o6 = constr1(10); + auto o7 = constr1(11); + auto o8 = constr2(11); + + assert (o5 == o6); + assert (o6 == o7); + assert (o7 == o8); +} + +fn main() { + test_nil(); + test_bool(); + test_char(); + test_box(); + test_port(); + test_chan(); + test_ptr(); + test_task(); + test_fn(); + test_native_fn(); + test_obj(); +} \ No newline at end of file diff --git a/src/test/run-pass/bitwise.rs b/src/test/run-pass/bitwise.rs index 1134e6e6ced..e9a74162ed6 100644 --- a/src/test/run-pass/bitwise.rs +++ b/src/test/run-pass/bitwise.rs @@ -18,4 +18,5 @@ fn main() { assert (0xf0 >> 4 == 0xf); assert (-16 >>> 2 == -4); assert (0b1010_1010 | 0b0101_0101 == 0xff); + assert (-1000 >> 3 == 536870787); } \ No newline at end of file