diff --git a/mk/target.mk b/mk/target.mk index 3d639bdc98c..c049ce5c296 100644 --- a/mk/target.mk +++ b/mk/target.mk @@ -49,7 +49,7 @@ $$(TLIB$(1)_T_$(2)_H_$(3))/$$(CFG_LIBRUSTSYNTAX): \ $$(TCORELIB_DEFAULT$(1)_T_$(2)_H_$(3)) \ $$(TSTDLIB_DEFAULT$(1)_T_$(2)_H_$(3)) @$$(call E, compile_and_link: $$@) - $$(STAGE$(1)_T_$(2)_H_$(3)) -o $$@ $$< && touch $$@ + $$(STAGE$(1)_T_$(2)_H_$(3)) $(BORROWCK) -o $$@ $$< && touch $$@ endef diff --git a/src/rustc/driver/driver.rs b/src/rustc/driver/driver.rs index 75c75c3217b..a1a082da994 100644 --- a/src/rustc/driver/driver.rs +++ b/src/rustc/driver/driver.rs @@ -4,7 +4,7 @@ import session::session; import syntax::parse; import syntax::{ast, codemap}; import syntax::attr; -import middle::{trans, resolve, freevars, kind, ty, typeck, fn_usage, +import middle::{trans, resolve, freevars, kind, ty, typeck, last_use, lint}; import syntax::print::{pp, pprust}; import util::{ppaux, filesearch}; @@ -158,12 +158,13 @@ fn compile_upto(sess: session, cfg: ast::crate_cfg, bind middle::block_use::check_crate(ty_cx, crate)); time(time_passes, "loop checking", bind middle::check_loop::check_crate(ty_cx, crate)); - time(time_passes, "function usage", - bind fn_usage::check_crate_fn_usage(ty_cx, crate)); time(time_passes, "alt checking", bind middle::check_alt::check_crate(ty_cx, crate)); time(time_passes, "typestate checking", bind middle::tstate::ck::check_crate(ty_cx, crate)); + let _root_map = time( + time_passes, "borrow checking", + bind middle::borrowck::check_crate(ty_cx, method_map, crate)); let mutbl_map = time(time_passes, "mutability checking", bind middle::mutbl::check_crate(ty_cx, crate)); @@ -401,6 +402,14 @@ fn build_session_options(match: getopts::match, let target_opt = getopts::opt_maybe_str(match, "target"); let mut no_asm_comments = getopts::opt_present(match, "no-asm-comments"); let debug_rustc = getopts::opt_present(match, "debug-rustc"); + let borrowck = alt getopts::opt_maybe_str(match, "borrowck") { + none { 0u } + some("warn") { 1u } + some("err") { 2u } + some(_) { + early_error(demitter, "borrowck may be warn or err") + } + }; alt output_type { // unless we're emitting huamn-readable assembly, omit comments. link::output_type_llvm_assembly | link::output_type_assembly {} @@ -455,7 +464,8 @@ fn build_session_options(match: getopts::match, parse_only: parse_only, no_trans: no_trans, no_asm_comments: no_asm_comments, - debug_rustc: debug_rustc}; + debug_rustc: debug_rustc, + borrowck: borrowck}; ret sopts; } @@ -533,7 +543,8 @@ fn opts() -> [getopts::opt] { optmulti("cfg"), optflag("test"), optflag("lib"), optflag("bin"), optflag("static"), optflag("gc"), optflag("no-asm-comments"), - optflag("debug-rustc")]; + optflag("debug-rustc"), + optopt("borrowck")]; } type output_filenames = @{out_filename: str, obj_filename:str}; diff --git a/src/rustc/driver/session.rs b/src/rustc/driver/session.rs index 88d8b5ed640..2e9dffb1c2c 100644 --- a/src/rustc/driver/session.rs +++ b/src/rustc/driver/session.rs @@ -47,7 +47,8 @@ type options = parse_only: bool, no_trans: bool, no_asm_comments: bool, - debug_rustc: bool}; + debug_rustc: bool, + borrowck: uint}; // 0=off,1=warn,2=err type crate_metadata = {name: str, data: [u8]}; @@ -139,6 +140,7 @@ fn basic_options() -> @options { no_trans: false, no_asm_comments: false, debug_rustc: false, + borrowck: 0u, } } diff --git a/src/rustc/metadata/astencode.rs b/src/rustc/metadata/astencode.rs index 9fd4689b91a..30d1e7f501a 100644 --- a/src/rustc/metadata/astencode.rs +++ b/src/rustc/metadata/astencode.rs @@ -844,9 +844,10 @@ fn encode_side_tables_for_id(ecx: @e::encode_ctxt, } } - option::iter(tcx.borrowings.find(id)) {|_i| + option::iter(tcx.borrowings.find(id)) {|s| ebml_w.tag(c::tag_table_borrowings) {|| ebml_w.id(id); + ebml_w.wr_tagged_i64(c::tag_table_val as uint, s as i64); } } } @@ -919,8 +920,6 @@ fn decode_side_tables(xcx: extended_decode_ctxt, dcx.maps.copy_map.insert(id, ()); } else if tag == (c::tag_table_spill as uint) { dcx.maps.spill_map.insert(id, ()); - } else if tag == (c::tag_table_borrowings as uint) { - dcx.tcx.borrowings.insert(id, ()); } else { let val_doc = entry_doc[c::tag_table_val]; let val_dsr = ebml::ebml_deserializer(val_doc); @@ -952,7 +951,10 @@ fn decode_side_tables(xcx: extended_decode_ctxt, val_dsr.read_method_origin(xcx)); } else if tag == (c::tag_table_vtable_map as uint) { dcx.maps.vtable_map.insert(id, - val_dsr.read_vtable_res(xcx)); + val_dsr.read_vtable_res(xcx)); + } else if tag == (c::tag_table_borrowings as uint) { + let scope_id = ebml::doc_as_i64(val_doc) as int; + dcx.tcx.borrowings.insert(id, scope_id); } else { xcx.dcx.tcx.sess.bug( #fmt["unknown tag found in side tables: %x", tag]); diff --git a/src/rustc/middle/borrowck.rs b/src/rustc/middle/borrowck.rs new file mode 100644 index 00000000000..021f0cd4b77 --- /dev/null +++ b/src/rustc/middle/borrowck.rs @@ -0,0 +1,1334 @@ +import syntax::ast; +import syntax::ast::{m_mutbl, m_imm, m_const}; +import syntax::visit; +import syntax::ast_util; +import syntax::codemap::span; +import util::ppaux::{ty_to_str, region_to_str}; +import driver::session::session; +import std::map::{int_hash, hashmap, set}; +import std::list; +import std::list::{list, cons, nil}; +import result::{result, ok, err, extensions}; +import syntax::print::pprust; +import util::common::indenter; + +export check_crate, root_map; + +fn check_crate(tcx: ty::ctxt, + method_map: typeck::method_map, + crate: @ast::crate) -> root_map { + + // big hack to keep this off except when I want it on + let msg_level = alt os::getenv("RUST_BORROWCK") { + none {tcx.sess.opts.borrowck} + some(v) {option::get(uint::from_str(v))} + }; + + let bccx = @{tcx: tcx, + method_map: method_map, + msg_level: msg_level, + root_map: int_hash(), + in_ctor: none}; + + if msg_level > 0u { + let req_loan_map = gather_loans(bccx, crate); + check_loans(bccx, req_loan_map, crate); + } + ret bccx.root_map; +} + +const TREAT_CONST_AS_IMM: bool = true; + +// ---------------------------------------------------------------------- +// Type definitions + +type borrowck_ctxt = @{tcx: ty::ctxt, + method_map: typeck::method_map, + msg_level: uint, + root_map: root_map, + + // Keep track of whether we're inside a ctor, so as to + // allow mutating immutable fields in the same class if + // we are in a ctor, we track the self id + in_ctor: option}; + +// a map mapping id's of expressions of task-local type (@T, []/@, etc) where +// the box needs to be kept live to the id of the scope for which they must +// stay live. +type root_map = hashmap; + +enum bckerr_code { + err_mutbl(ast::mutability, ast::mutability), + err_mut_uniq, + err_mut_variant, + err_preserve_gc +} + +type bckerr = {cmt: cmt, code: bckerr_code}; + +type bckres = result; + +enum categorization { + cat_rvalue(rvalue_kind), // result of eval'ing some rvalue + cat_local(ast::node_id), // local variable + cat_arg(ast::node_id), // formal argument + cat_deref(cmt, ptr_kind), // deref of a ptr + cat_comp(cmt, comp_kind), // adjust to locate an internal component +} + +// different kinds of pointers: +enum ptr_kind {uniq_ptr, gc_ptr, region_ptr, unsafe_ptr} + +// I am coining the term "components" to mean "pieces of a data +// structure accessible without a dereference": +enum comp_kind {comp_tuple, comp_res, comp_variant, + comp_field(str), comp_index} + +// We pun on *T to mean both actual deref of a ptr as well +// as accessing of components: +enum deref_kind {deref_ptr(ptr_kind), deref_comp(comp_kind)} + +// different kinds of expressions we might evaluate +enum rvalue_kind { + rv_method, + rv_static_item, + rv_upvar, + rv_misc, + rv_self +} + +// a complete categorization of a value indicating where it originated +// and how it is located, as well as the mutability of the memory in +// which the value is stored. +type cmt = @{id: ast::node_id, // id of expr/pat producing this value + span: span, // span of same expr/pat + cat: categorization, // categorization of expr + lp: option<@loan_path>, // loan path for expr, if any + mutbl: ast::mutability, // mutability of expr as lvalue + ty: ty::t}; // type of the expr + +// a loan path is like a category, but it exists only when the data is +// interior to the stack frame. loan paths are used as the key to a +// map indicating what is borrowed at any point in time. +enum loan_path { + lp_local(ast::node_id), + lp_arg(ast::node_id), + lp_deref(@loan_path, ptr_kind), + lp_comp(@loan_path, comp_kind) +} + +// a complete record of a loan that was granted +type loan = {lp: @loan_path, cmt: cmt, mutbl: ast::mutability}; + +fn sup_mutbl(req_m: ast::mutability, + act_m: ast::mutability) -> bool { + alt (req_m, act_m) { + (m_const, _) | + (m_imm, m_imm) | + (m_mutbl, m_mutbl) { + true + } + + (_, m_const) | + (m_imm, m_mutbl) | + (m_mutbl, m_imm) { + false + } + } +} + +fn check_sup_mutbl(req_m: ast::mutability, + cmt: cmt) -> bckres<()> { + if sup_mutbl(req_m, cmt.mutbl) { + ok(()) + } else { + err({cmt:cmt, code:err_mutbl(req_m, cmt.mutbl)}) + } +} + +// ---------------------------------------------------------------------- +// Gathering loans +// +// The borrow check proceeds in two phases. In phase one, we gather the full +// set of loans that are required at any point. These are sorted according to +// their associated scopes. In phase two, checking loans, we will then make +// sure that all of these loans are honored. + +// Maps a scope to a list of loans that were issued within that scope. +type req_loan_map = hashmap; + +enum gather_loan_ctxt = @{bccx: borrowck_ctxt, req_loan_map: req_loan_map}; + +fn gather_loans(bccx: borrowck_ctxt, crate: @ast::crate) -> req_loan_map { + let glcx = gather_loan_ctxt(@{bccx: bccx, req_loan_map: int_hash()}); + let v = visit::mk_vt(@{visit_expr: req_loans_in_expr + with *visit::default_visitor()}); + visit::visit_crate(*crate, glcx, v); + ret glcx.req_loan_map; +} + +fn req_loans_in_expr(ex: @ast::expr, + &&self: gather_loan_ctxt, + vt: visit::vt) { + let bccx = self.bccx; + let tcx = bccx.tcx; + + // If this expression is borrowed, have to ensure it remains valid: + for tcx.borrowings.find(ex.id).each { |scope_id| + let cmt = self.bccx.cat_borrow_of_expr(ex); + self.guarantee_valid(cmt, m_const, ty::re_scope(scope_id)); + } + + // Special checks for various kinds of expressions: + alt ex.node { + ast::expr_addr_of(mutbl, base) { + let base_cmt = self.bccx.cat_expr(base); + + // make sure that if we taking an &mut or &imm ptr, the thing it + // points at is mutable or immutable respectively: + self.bccx.report_if_err( + check_sup_mutbl(mutbl, base_cmt).chain { |_ok| + // make sure that the thing we are pointing out stays valid + // for the lifetime `scope_r`: + let scope_r = + alt check ty::get(tcx.ty(ex)).struct { + ty::ty_rptr(r, _) { r } + }; + self.guarantee_valid(base_cmt, mutbl, scope_r); + ok(()) + }); + } + + ast::expr_call(f, args, _) { + let arg_tys = ty::ty_fn_args(ty::expr_ty(self.tcx(), f)); + vec::iter2(args, arg_tys) { |arg, arg_ty| + alt ty::resolved_mode(self.tcx(), arg_ty.mode) { + ast::by_mutbl_ref { + let arg_cmt = self.bccx.cat_expr(arg); + self.guarantee_valid(arg_cmt, m_mutbl, ty::re_scope(ex.id)); + } + ast::by_ref { + let arg_cmt = self.bccx.cat_expr(arg); + if TREAT_CONST_AS_IMM { + self.guarantee_valid(arg_cmt, m_imm, + ty::re_scope(ex.id)); + } else { + self.guarantee_valid(arg_cmt, m_const, + ty::re_scope(ex.id)); + } + } + ast::by_move | ast::by_copy | ast::by_val {} + } + } + } + + ast::expr_alt(ex_v, arms, _) { + let cmt = self.bccx.cat_expr(ex_v); + for arms.each { |arm| + for arm.pats.each { |pat| + self.gather_pat(cmt, pat, arm.body.node.id); + } + } + } + + _ { /*ok*/ } + } + + // Check any contained expressions: + visit::visit_expr(ex, self, vt); +} + +impl methods for gather_loan_ctxt { + fn tcx() -> ty::ctxt { self.bccx.tcx } + + // guarantees that addr_of(cmt) will be valid for the duration of + // `scope_r`, or reports an error. This may entail taking out loans, + // which will be added to the `req_loan_map`. + fn guarantee_valid(cmt: cmt, + mutbl: ast::mutability, + scope_r: ty::region) { + + #debug["guarantee_valid(cmt=%s, mutbl=%s, scope_r=%s)", + self.bccx.cmt_to_repr(cmt), + self.bccx.mut_to_str(mutbl), + region_to_str(self.tcx(), scope_r)]; + let _i = indenter(); + + alt cmt.lp { + // If this expression is a loanable path, we MUST take out a loan. + // This is somewhat non-obvious. You might think, for example, that + // if we have an immutable local variable `x` whose value is being + // borrowed, we could rely on `x` not to change. This is not so, + // however, because even immutable locals can be moved. So we take + // out a loan on `x`, guaranteeing that it remains immutable for the + // duration of the reference: if there is an attempt to move it + // within that scope, the loan will be detected and an error will be + // reported. + some(_) { + alt scope_r { + ty::re_scope(scope_id) { + alt self.bccx.loan(cmt, mutbl) { + ok(loans) { self.add_loans(scope_id, loans); } + err(e) { self.bccx.report(e); } + } + } + _ { + self.bccx.span_err( + cmt.span, + #fmt["Cannot guarantee the stability \ + of this expression for the entirety of \ + its lifetime, %s", + region_to_str(self.tcx(), scope_r)]); + } + } + } + + // The path is not loanable: in that case, we must try and preserve + // it dynamically (or see that it is preserved by virtue of being + // rooted in some immutable path) + none { + self.bccx.report_if_err( + check_sup_mutbl(mutbl, cmt).chain { |_ok| + let opt_scope_id = alt scope_r { + ty::re_scope(scope_id) { some(scope_id) } + _ { none } + }; + + self.bccx.preserve(cmt, opt_scope_id) + }) + } + } + } + + fn add_loans(scope_id: ast::node_id, loans: @const [loan]) { + alt self.req_loan_map.find(scope_id) { + some(l) { + *l += [loans]; + } + none { + self.req_loan_map.insert(scope_id, @mut [loans]); + } + } + } + + fn gather_pat(cmt: cmt, pat: @ast::pat, alt_id: ast::node_id) { + + // Here, `cmt` is the categorization for the value being + // matched and pat is the pattern it is being matched against. + // + // In general, the way that this works is that we + + #debug["gather_pat: id=%d pat=%s cmt=%s alt_id=%d", + pat.id, pprust::pat_to_str(pat), + self.bccx.cmt_to_repr(cmt), alt_id]; + let _i = indenter(); + + let tcx = self.tcx(); + alt pat.node { + ast::pat_wild { + // _ + } + + ast::pat_enum(_, none) { + // variant(*) + } + ast::pat_enum(_, some(subpats)) { + // variant(x, y, z) + for subpats.each { |subpat| + let subcmt = self.bccx.cat_variant(pat, cmt, subpat); + self.gather_pat(subcmt, subpat, alt_id); + } + } + + ast::pat_ident(_, none) if self.pat_is_variant(pat) { + // nullary variant + #debug["nullary variant"]; + } + ast::pat_ident(id, o_pat) { + // x or x @ p --- `x` must remain valid for the scope of the alt + #debug["defines identifier %s", pprust::path_to_str(id)]; + self.guarantee_valid(cmt, m_const, ty::re_scope(alt_id)); + for o_pat.each { |p| self.gather_pat(cmt, p, alt_id); } + } + + ast::pat_rec(field_pats, _) { + // {f1: p1, ..., fN: pN} + for field_pats.each { |fp| + let cmt_field = self.bccx.cat_field(pat, cmt, fp.ident, + tcx.ty(fp.pat)); + self.gather_pat(cmt_field, fp.pat, alt_id); + } + } + + ast::pat_tup(subpats) { + // (p1, ..., pN) + for subpats.each { |subpat| + let subcmt = self.bccx.cat_tuple_elt(pat, cmt, subpat); + self.gather_pat(subcmt, subpat, alt_id); + } + } + + ast::pat_box(subpat) | ast::pat_uniq(subpat) { + // @p1, ~p1 + alt self.bccx.cat_deref(pat, cmt, true) { + some(subcmt) { self.gather_pat(subcmt, subpat, alt_id); } + none { tcx.sess.span_bug(pat.span, "Non derefable type"); } + } + } + + ast::pat_lit(_) | ast::pat_range(_, _) { /*always ok*/ } + } + } + + fn pat_is_variant(pat: @ast::pat) -> bool { + pat_util::pat_is_variant(self.bccx.tcx.def_map, pat) + } +} + +// ---------------------------------------------------------------------- +// Checking loans +// +// Phase 2 of check: we walk down the tree and check that: +// 1. assignments are always made to mutable locations; +// 2. loans made in overlapping scopes do not conflict +// 3. assignments do not affect things loaned out as immutable +// 4. moves to dnot affect things loaned out in any way + +enum check_loan_ctxt = @{ + bccx: borrowck_ctxt, + req_loan_map: req_loan_map +}; + +fn check_loans(bccx: borrowck_ctxt, + req_loan_map: req_loan_map, + crate: @ast::crate) { + let clcx = check_loan_ctxt(@{bccx: bccx, req_loan_map: req_loan_map}); + let vt = visit::mk_vt(@{visit_expr: check_loans_in_expr, + visit_block: check_loans_in_block + with *visit::default_visitor()}); + visit::visit_crate(*crate, clcx, vt); +} + +impl methods for check_loan_ctxt { + fn tcx() -> ty::ctxt { self.bccx.tcx } + + fn walk_loans(scope_id: ast::node_id, + f: fn(loan) -> bool) { + let mut scope_id = scope_id; + let parents = self.tcx().region_map.parents; + let req_loan_map = self.req_loan_map; + + loop { + for req_loan_map.find(scope_id).each { |loanss| + for (*loanss).each { |loans| + for (*loans).each { |loan| + if !f(loan) { ret; } + } + } + } + + alt parents.find(scope_id) { + none { ret; } + some(next_scope_id) { scope_id = next_scope_id; } + } + } + } + + fn walk_loans_of(scope_id: ast::node_id, + lp: @loan_path, + f: fn(loan) -> bool) { + for self.walk_loans(scope_id) { |loan| + if loan.lp == lp { + if !f(loan) { ret; } + } + } + } + + fn check_for_conflicting_loans(scope_id: ast::node_id) { + let new_loanss = alt self.req_loan_map.find(scope_id) { + none { ret; } + some(loanss) { loanss } + }; + + let par_scope_id = self.tcx().region_map.parents.get(scope_id); + for self.walk_loans(par_scope_id) { |old_loan| + for (*new_loanss).each { |new_loans| + for (*new_loans).each { |new_loan| + if old_loan.lp != new_loan.lp { cont; } + alt (old_loan.mutbl, new_loan.mutbl) { + (m_const, _) | (_, m_const) | + (m_mutbl, m_mutbl) | (m_imm, m_imm) { + /*ok*/ + } + + (m_mutbl, m_imm) | (m_imm, m_mutbl) { + self.bccx.span_err( + new_loan.cmt.span, + #fmt["loan of %s as %s \ + conflicts with prior loan", + self.bccx.cmt_to_str(new_loan.cmt), + self.bccx.mut_to_str(new_loan.mutbl)]); + self.bccx.span_note( + old_loan.cmt.span, + #fmt["prior loan as %s granted here", + self.bccx.mut_to_str(old_loan.mutbl)]); + } + } + } + } + } + } + + fn check_assignment(ex: @ast::expr) { + let cmt = self.bccx.cat_expr(ex); + + #debug["check_assignment(cmt=%s)", + self.bccx.cmt_to_repr(cmt)]; + + alt cmt.mutbl { + m_mutbl { /*ok*/ } + m_const | m_imm { + self.bccx.span_err( + ex.span, + #fmt["Cannot assign to %s", self.bccx.cmt_to_str(cmt)]); + ret; + } + } + + // check for a conflicting loan as well + let lp = alt cmt.lp { + none { ret; } + some(lp) { lp } + }; + for self.walk_loans_of(ex.id, lp) { |loan| + alt loan.mutbl { + m_mutbl | m_const { /*ok*/ } + m_imm { + self.bccx.span_err( + ex.span, + #fmt["cannot assign to %s due to outstanding loan", + self.bccx.cmt_to_str(cmt)]); + self.bccx.span_note( + loan.cmt.span, + #fmt["loan of %s granted here", + self.bccx.cmt_to_str(loan.cmt)]); + ret; + } + } + } + } + + fn check_move_out(ex: @ast::expr) { + let cmt = self.bccx.cat_expr(ex); + + alt cmt.cat { + // Rvalues and locals can be moved: + cat_rvalue(_) | cat_local(_) { /*ok*/ } + + // Owned arguments can be moved: + cat_arg(_) if cmt.mutbl == m_mutbl { /*ok*/ } + + // Nothing else. + _ { + self.bccx.span_err( + ex.span, + #fmt["Cannot move from %s", self.bccx.cmt_to_str(cmt)]); + ret; + } + } + + // check for a conflicting loan: + let lp = alt cmt.lp { + none { ret; } + some(lp) { lp } + }; + for self.walk_loans_of(ex.id, lp) { |loan| + self.bccx.span_err( + ex.span, + #fmt["Cannot move from %s due to outstanding loan", + self.bccx.cmt_to_str(cmt)]); + self.bccx.span_note( + loan.cmt.span, + #fmt["Loan of %s granted here", + self.bccx.cmt_to_str(loan.cmt)]); + ret; + } + } +} + +fn check_loans_in_expr(expr: @ast::expr, + &&self: check_loan_ctxt, + vt: visit::vt) { + self.check_for_conflicting_loans(expr.id); + alt expr.node { + ast::expr_swap(l, r) { + self.check_assignment(l); + self.check_assignment(r); + } + ast::expr_move(dest, src) { + self.check_assignment(dest); + self.check_move_out(src); + } + ast::expr_assign(dest, _) | + ast::expr_assign_op(_, dest, _) { + self.check_assignment(dest); + } + ast::expr_call(f, args, _) { + let arg_tys = ty::ty_fn_args(ty::expr_ty(self.tcx(), f)); + vec::iter2(args, arg_tys) { |arg, arg_ty| + alt ty::resolved_mode(self.tcx(), arg_ty.mode) { + ast::by_move { + self.check_move_out(arg); + } + ast::by_mutbl_ref | + ast::by_ref { + // these are translated into loans + } + ast::by_copy | ast::by_val { + } + } + } + } + _ { } + } + visit::visit_expr(expr, self, vt); +} + +fn check_loans_in_block(blk: ast::blk, + &&self: check_loan_ctxt, + vt: visit::vt) { + self.check_for_conflicting_loans(blk.node.id); + visit::visit_block(blk, self, vt); +} + +// ---------------------------------------------------------------------- +// Categorization +// +// Imagine a routine ToAddr(Expr) that evaluates an expression and returns an +// address where the result is to be found. If Expr is an lvalue, then this +// is the address of the lvalue. If Expr is an rvalue, this is the address of +// some temporary spot in memory where the result is stored. +// +// Now, cat_expr() classies the expression Expr and the address A=ToAddr(Expr) +// as follows: +// +// - cat: what kind of expression was this? This is a subset of the +// full expression forms which only includes those that we care about +// for the purpose of the analysis. +// - mutbl: mutability of the address A +// - ty: the type of data found at the address A +// +// The resulting categorization tree differs somewhat from the expressions +// themselves. For example, auto-derefs are explicit. Also, an index a[b] is +// decomposed into two operations: a derefence to reach the array data and +// then an index to jump forward to the relevant item. + +// Categorizes a derefable type. Note that we include vectors and strings as +// derefable (we model an index as the combination of a deref and then a +// pointer adjustment). +fn deref_kind(tcx: ty::ctxt, t: ty::t) -> deref_kind { + alt ty::get(t).struct { + ty::ty_uniq(*) | ty::ty_vec(*) | ty::ty_str | + ty::ty_evec(_, ty::vstore_uniq) | + ty::ty_estr(ty::vstore_uniq) { + deref_ptr(uniq_ptr) + } + + ty::ty_rptr(*) | + ty::ty_evec(_, ty::vstore_slice(_)) | + ty::ty_estr(ty::vstore_slice(_)) { + deref_ptr(region_ptr) + } + + ty::ty_box(*) | + ty::ty_evec(_, ty::vstore_box) | + ty::ty_estr(ty::vstore_box) { + deref_ptr(gc_ptr) + } + + ty::ty_ptr(*) { + deref_ptr(unsafe_ptr) + } + + ty::ty_enum(*) { + deref_comp(comp_variant) + } + + ty::ty_res(*) { + deref_comp(comp_res) + } + + _ { + tcx.sess.bug( + #fmt["deref_cat() invoked on non-derefable type %s", + ty_to_str(tcx, t)]); + } + } +} + +iface ast_node { + fn id() -> ast::node_id; + fn span() -> span; +} + +impl of ast_node for @ast::expr { + fn id() -> ast::node_id { self.id } + fn span() -> span { self.span } +} + +impl of ast_node for @ast::pat { + fn id() -> ast::node_id { self.id } + fn span() -> span { self.span } +} + +impl methods for ty::ctxt { + fn ty(node: N) -> ty::t { + ty::node_id_to_type(self, node.id()) + } +} + +impl categorize_methods for borrowck_ctxt { + fn cat_borrow_of_expr(expr: @ast::expr) -> cmt { + // a borrowed expression must be either an @, ~, or a vec/@, vec/~ + let expr_ty = ty::expr_ty(self.tcx, expr); + alt ty::get(expr_ty).struct { + ty::ty_vec(*) | ty::ty_evec(*) | + ty::ty_str | ty::ty_estr(*) { + self.cat_index(expr, expr) + } + + ty::ty_uniq(*) | ty::ty_box(*) | ty::ty_rptr(*) { + let cmt = self.cat_expr(expr); + self.cat_deref(expr, cmt, true).get() + } + + _ { + self.tcx.sess.span_bug( + expr.span, + #fmt["Borrowing of non-derefable type `%s`", + ty_to_str(self.tcx, expr_ty)]); + } + } + } + + fn cat_expr(expr: @ast::expr) -> cmt { + let tcx = self.tcx; + let expr_ty = tcx.ty(expr); + + #debug["cat_expr: id=%d expr=%s", + expr.id, pprust::expr_to_str(expr)]; + + if self.method_map.contains_key(expr.id) { + ret @{id:expr.id, span:expr.span, + cat:cat_rvalue(rv_method), lp:none, + mutbl:m_imm, ty:expr_ty}; + } + + alt expr.node { + ast::expr_unary(ast::deref, e_base) { + let base_cmt = self.cat_expr(e_base); + alt self.cat_deref(expr, base_cmt, true) { + some(cmt) { ret cmt; } + none { + tcx.sess.span_bug( + e_base.span, + #fmt["Explicit deref of non-derefable type `%s`", + ty_to_str(tcx, tcx.ty(e_base))]); + } + } + } + + ast::expr_field(base, f_name, _) { + let base_cmt = self.cat_autoderef(expr, base); + self.cat_field(expr, base_cmt, f_name, expr_ty) + } + + ast::expr_index(base, _) { + self.cat_index(expr, base) + } + + ast::expr_path(_) { + let def = self.tcx.def_map.get(expr.id); + self.cat_def(expr, expr_ty, def) + } + + ast::expr_addr_of(*) | ast::expr_call(*) | ast::expr_bind(*) | + ast::expr_swap(*) | ast::expr_move(*) | ast::expr_assign(*) | + ast::expr_assign_op(*) | ast::expr_fn(*) | ast::expr_fn_block(*) | + ast::expr_assert(*) | ast::expr_check(*) | ast::expr_ret(*) | + ast::expr_be(*) | ast::expr_loop_body(*) | ast::expr_unary(*) | + ast::expr_copy(*) | ast::expr_cast(*) | ast::expr_fail(*) | + ast::expr_vstore(*) | ast::expr_vec(*) | ast::expr_tup(*) | + ast::expr_if_check(*) | ast::expr_if(*) | ast::expr_log(*) | + ast::expr_new(*) | ast::expr_binary(*) | ast::expr_while(*) | + ast::expr_do_while(*) | ast::expr_block(*) | ast::expr_loop(*) | + ast::expr_alt(*) | ast::expr_lit(*) | ast::expr_break | + ast::expr_mac(*) | ast::expr_cont | ast::expr_rec(*) { + @{id:expr.id, span:expr.span, + cat:cat_rvalue(rv_misc), lp:none, + mutbl:m_imm, ty:expr_ty} + } + } + } + + fn cat_field(node: N, base_cmt: cmt, + f_name: str, f_ty: ty::t) -> cmt { + let f_mutbl = alt field_mutbl(self.tcx, base_cmt.ty, f_name) { + some(f_mutbl) { f_mutbl } + none { + self.tcx.sess.span_bug( + node.span(), + #fmt["Cannot find field `%s` in type `%s`", + f_name, ty_to_str(self.tcx, base_cmt.ty)]); + } + }; + let m = alt f_mutbl { + m_imm { base_cmt.mutbl } // imm: as mutable as the container + m_mutbl | m_const { f_mutbl } + }; + let lp = base_cmt.lp.map { |lp| + @lp_comp(lp, comp_field(f_name)) + }; + @{id: node.id(), span: node.span(), + cat: cat_comp(base_cmt, comp_field(f_name)), lp:lp, + mutbl: m, ty: f_ty} + } + + fn cat_deref(node: N, base_cmt: cmt, + expl: bool) -> option { + ty::deref(self.tcx, base_cmt.ty, expl).map { |mt| + alt deref_kind(self.tcx, base_cmt.ty) { + deref_ptr(ptr) { + let lp = base_cmt.lp.chain { |l| + // Given that the ptr itself is loanable, we can + // loan out deref'd uniq ptrs as the data they are + // the only way to reach the data they point at. + // Other ptr types admit aliases and are therefore + // not loanable. + alt ptr { + uniq_ptr {some(@lp_deref(l, ptr))} + gc_ptr | region_ptr | unsafe_ptr {none} + } + }; + @{id:node.id(), span:node.span(), + cat:cat_deref(base_cmt, ptr), lp:lp, + mutbl:mt.mutbl, ty:mt.ty} + } + + deref_comp(comp) { + let lp = base_cmt.lp.map { |l| @lp_comp(l, comp) }; + @{id:node.id(), span:node.span(), + cat:cat_comp(base_cmt, comp), lp:lp, + mutbl:mt.mutbl, ty:mt.ty} + } + } + } + } + + fn cat_autoderef(expr: @ast::expr, base: @ast::expr) -> cmt { + // Creates a string of implicit derefences so long as base is + // dereferencable. n.b., it is important that these dereferences are + // associated with the field/index that caused the autoderef (expr). + // This is used later to adjust ref counts and so forth in trans. + + // Given something like base.f where base has type @m1 @m2 T, we want + // to yield the equivalent categories to (**base).f. + let mut cmt = self.cat_expr(base); + loop { + alt self.cat_deref(expr, cmt, false) { + none { ret cmt; } + some(cmt1) { cmt = cmt1; } + } + } + } + + fn cat_index(expr: @ast::expr, base: @ast::expr) -> cmt { + let base_cmt = self.cat_autoderef(expr, base); + + let mt = alt ty::index(self.tcx, base_cmt.ty) { + some(mt) { mt } + none { + self.tcx.sess.span_bug( + expr.span, + #fmt["Explicit index of non-index type `%s`", + ty_to_str(self.tcx, base_cmt.ty)]); + } + }; + + let ptr = alt deref_kind(self.tcx, base_cmt.ty) { + deref_ptr(ptr) { ptr } + deref_comp(_) { + self.tcx.sess.span_bug( + expr.span, + "Deref of indexable type yielded comp kind"); + } + }; + + // make deref of vectors explicit, as explained in the comment at + // the head of this section + let deref_lp = base_cmt.lp.map { |lp| @lp_deref(lp, ptr) }; + let deref_cmt = @{id:expr.id, span:expr.span, + cat:cat_deref(base_cmt, ptr), lp:deref_lp, + mutbl:mt.mutbl, ty:mt.ty}; + let index_lp = deref_lp.map { |lp| @lp_comp(lp, comp_index) }; + @{id:expr.id, span:expr.span, + cat:cat_comp(deref_cmt, comp_index), lp:index_lp, + mutbl:mt.mutbl, ty:mt.ty} + } + + fn cat_variant(variant: N, cmt: cmt, arg: N) -> cmt { + @{id: variant.id(), span: variant.span(), + cat: cat_comp(cmt, comp_variant), + lp: cmt.lp.map { |l| @lp_comp(l, comp_variant) }, + mutbl: cmt.mutbl, // imm iff in an immutable context + ty: self.tcx.ty(arg)} + } + + fn cat_tuple_elt(pat: N, cmt: cmt, elt: N) -> cmt { + @{id: pat.id(), span: pat.span(), + cat: cat_comp(cmt, comp_tuple), + lp: cmt.lp.map { |l| @lp_comp(l, comp_tuple) }, + mutbl: cmt.mutbl, // imm iff in an immutable context + ty: self.tcx.ty(elt)} + } + + fn cat_def(expr: @ast::expr, + expr_ty: ty::t, + def: ast::def) -> cmt { + alt def { + ast::def_fn(_, _) | ast::def_mod(_) | + ast::def_native_mod(_) | ast::def_const(_) | + ast::def_use(_) | ast::def_variant(_, _) | + ast::def_ty(_) | ast::def_prim_ty(_) | + ast::def_ty_param(_, _) | ast::def_class(_) | + ast::def_region(_) { + @{id:expr.id, span:expr.span, + cat:cat_rvalue(rv_static_item), lp:none, + mutbl:m_imm, ty:expr_ty} + } + + ast::def_arg(vid, mode) { + // Idea: make this could be rewritten to model by-ref + // stuff as `&const` and `&mut`? + + // m: mutability of the argument + // lp: loan path, must be none for aliasable things + let {m,lp} = alt ty::resolved_mode(self.tcx, mode) { + ast::by_mutbl_ref { + {m:m_mutbl, lp:none} + } + ast::by_move | ast::by_copy { + {m:m_mutbl, lp:some(@lp_arg(vid))} + } + ast::by_ref { + if TREAT_CONST_AS_IMM { + {m:m_imm, lp:some(@lp_arg(vid))} + } else { + {m:m_const, lp:none} + } + } + ast::by_val { + {m:m_imm, lp:some(@lp_arg(vid))} + } + }; + @{id:expr.id, span:expr.span, + cat:cat_arg(vid), lp:lp, + mutbl:m, ty:expr_ty} + } + + ast::def_self(_) { + @{id:expr.id, span:expr.span, + cat:cat_rvalue(rv_self), lp:none, + mutbl:m_imm, ty:expr_ty} + } + + ast::def_upvar(upvid, inner, fn_node_id) { + let ty = ty::node_id_to_type(self.tcx, fn_node_id); + let proto = ty::ty_fn_proto(ty); + alt proto { + ast::proto_any | ast::proto_block { + self.cat_def(expr, expr_ty, *inner) + } + ast::proto_bare | ast::proto_uniq | ast::proto_box { + // FIXME #2152 allow mutation of moved upvars + @{id:expr.id, span:expr.span, + cat:cat_rvalue(rv_upvar), lp:none, + mutbl:m_imm, ty:expr_ty} + } + } + } + + ast::def_local(vid, mutbl) { + let m = if mutbl {m_mutbl} else {m_imm}; + @{id:expr.id, span:expr.span, + cat:cat_local(vid), lp:some(@lp_local(vid)), + mutbl:m, ty:expr_ty} + } + + ast::def_binding(vid) { + // no difference between a binding and any other local variable + // from out point of view, except that they are always immutable + @{id:expr.id, span:expr.span, + cat:cat_local(vid), lp:some(@lp_local(vid)), + mutbl:m_imm, ty:expr_ty} + } + } + } + + fn cat_to_str(mutbl: str, cat: categorization) -> str { + alt cat { + cat_rvalue(rv_method) { "method" } + cat_rvalue(rv_static_item) { "static item" } + cat_rvalue(rv_upvar) { "upvar" } + cat_rvalue(rv_misc) { "non-lvalue" } + cat_rvalue(rv_self) { "self reference" } + cat_local(_) { mutbl + " local variable" } + cat_arg(_) { mutbl + " argument" } + cat_deref(_, _) { mutbl + " dereference" } + cat_comp(_, comp_field(_)) { mutbl + " field" } + cat_comp(_, comp_index) { mutbl + " vector/string contents" } + cat_comp(_, comp_tuple) { mutbl + " tuple contents" } + cat_comp(_, comp_res) { mutbl + " resource contents" } + cat_comp(_, comp_variant) { mutbl + " enum contents" } + } + } + + fn cat_to_repr(cat: categorization) -> str { + alt cat { + cat_rvalue(rv_method) { "method" } + cat_rvalue(rv_static_item) { "static_item" } + cat_rvalue(rv_upvar) { "upvar" } + cat_rvalue(rv_misc) { "rvalue" } + cat_rvalue(rv_self) { "self" } + cat_local(node_id) { #fmt["local(%d)", node_id] } + cat_arg(node_id) { #fmt["arg(%d)", node_id] } + cat_deref(cmt, ptr) { + #fmt["%s->(%s)", self.cat_to_repr(cmt.cat), self.ptr_sigil(ptr)] + } + cat_comp(cmt, comp) { + #fmt["%s.%s", self.cat_to_repr(cmt.cat), self.comp_to_repr(comp)] + } + } + } + + fn mut_to_str(mutbl: ast::mutability) -> str { + alt mutbl { + m_mutbl { "mutable" } + m_const { "const" } + m_imm { "immutable" } + } + } + + fn ptr_sigil(ptr: ptr_kind) -> str { + alt ptr { + uniq_ptr { "~" } + gc_ptr { "@" } + region_ptr { "&" } + unsafe_ptr { "*" } + } + } + + fn comp_to_repr(comp: comp_kind) -> str { + alt comp { + comp_field(fld) { fld } + comp_index { "[]" } + comp_tuple { "()" } + comp_res { "" } + comp_variant { "" } + } + } + + fn lp_to_str(lp: @loan_path) -> str { + alt *lp { + lp_local(node_id) { + #fmt["local(%d)", node_id] + } + lp_arg(node_id) { + #fmt["arg(%d)", node_id] + } + lp_deref(lp, ptr) { + #fmt["%s->(%s)", self.lp_to_str(lp), + self.ptr_sigil(ptr)] + } + lp_comp(lp, comp) { + #fmt["%s.%s", self.lp_to_str(lp), + self.comp_to_repr(comp)] + } + } + } + + fn cmt_to_repr(cmt: cmt) -> str { + #fmt["{%s id:%d m:%s lp:%s ty:%s}", + self.cat_to_repr(cmt.cat), + cmt.id, + self.mut_to_str(cmt.mutbl), + cmt.lp.map_default("none", { |p| self.lp_to_str(p) }), + ty_to_str(self.tcx, cmt.ty)] + } + + fn cmt_to_str(cmt: cmt) -> str { + let mut_str = self.mut_to_str(cmt.mutbl); + self.cat_to_str(mut_str, cmt.cat) + } + + fn bckerr_code_to_str(code: bckerr_code) -> str { + alt code { + err_mutbl(req, act) { + #fmt["mutability mismatch, required %s but found %s", + self.mut_to_str(req), self.mut_to_str(act)] + } + err_mut_uniq { + "unique value in aliasable, mutable location" + } + err_mut_variant { + "enum variant in aliasable, mutable location" + } + err_preserve_gc { + "GC'd value would have to be preserved for longer \ + than the scope of the function" + } + } + } + + fn report_if_err(bres: bckres<()>) { + alt bres { + ok(()) { } + err(e) { self.report(e); } + } + } + + fn report(err: bckerr) { + self.span_err( + err.cmt.span, + #fmt["illegal borrow: %s", + self.bckerr_code_to_str(err.code)]); + } + + fn span_err(s: span, m: str) { + if self.msg_level == 1u { + self.tcx.sess.span_warn(s, m); + } else { + self.tcx.sess.span_err(s, m); + } + } + + fn span_note(s: span, m: str) { + self.tcx.sess.span_note(s, m); + } +} + +fn field_mutbl(tcx: ty::ctxt, + base_ty: ty::t, + f_name: str) -> option { + // Need to refactor so that records/class fields can be treated uniformly. + alt ty::get(base_ty).struct { + ty::ty_rec(fields) { + for fields.each { |f| + if f.ident == f_name { + ret some(f.mt.mutbl); + } + } + } + ty::ty_class(did, substs) { + for ty::lookup_class_fields(tcx, did).each { |fld| + if fld.ident == f_name { + let m = alt fld.mutability { + ast::class_mutable { ast::m_mutbl } + ast::class_immutable { ast::m_imm } + }; + ret some(m); + } + } + } + _ { } + } + + ret none; +} + +// ---------------------------------------------------------------------- +// Preserve(Ex, S) holds if ToAddr(Ex) will remain valid for the entirety of +// the scope S. + +enum preserve_ctxt = @{ + bccx: borrowck_ctxt, opt_scope_id: option +}; + +impl preserve_methods for borrowck_ctxt { + fn preserve(cmt: cmt, + opt_scope_id: option) -> bckres<()> { + preserve_ctxt(@{bccx:self, opt_scope_id:opt_scope_id}).preserve(cmt) + } +} + +impl preserve_methods for preserve_ctxt { + fn preserve(cmt: cmt) -> bckres<()> { + #debug["preserve(%s)", self.bccx.cmt_to_repr(cmt)]; + let _i = indenter(); + + alt cmt.cat { + cat_rvalue(_) { + ok(()) + } + cat_local(_) { + // This should never happen. Local variables are always lendable, + // so either `loan()` should be called or there must be some + // intermediate @ or &---they are not lendable but do not recurse. + self.bccx.tcx.sess.span_bug( + cmt.span, + "preserve() called with local"); + } + cat_arg(_) { + // This can happen as not all args are lendable (e.g., && + // modes). In that case, the caller guarantees stability. + // This is basically a deref of a region ptr. + ok(()) + } + cat_comp(cmt_base, comp_field(_)) | + cat_comp(cmt_base, comp_index) | + cat_comp(cmt_base, comp_tuple) | + cat_comp(cmt_base, comp_res) { + // Most embedded components: if the base is stable, the + // type never changes. + self.preserve(cmt_base) + } + cat_comp(cmt1, comp_variant) { + self.require_imm(cmt, cmt1, err_mut_variant) + } + cat_deref(cmt1, uniq_ptr) { + self.require_imm(cmt, cmt1, err_mut_uniq) + } + cat_deref(_, region_ptr) { + // References are always "stable" by induction (when the + // reference of type &MT was created, the memory must have + // been stable) + ok(()) + } + cat_deref(_, unsafe_ptr) { + // Unsafe pointers are the user's problem + ok(()) + } + cat_deref(_, gc_ptr) { + // GC'd pointers of type @MT: always stable because we can inc + // the ref count or keep a GC root as necessary. We need to + // insert this id into the root_map, however. + alt self.opt_scope_id { + some(scope_id) { + self.bccx.root_map.insert(cmt.id, scope_id); + ok(()) + } + none { + err({cmt:cmt, code:err_preserve_gc}) + } + } + } + } + } + + fn require_imm(cmt: cmt, cmt1: cmt, code: bckerr_code) -> bckres<()> { + // Variant contents and unique pointers: must be immutably + // rooted to a preserved address. + alt cmt1.mutbl { + m_mutbl | m_const { err({cmt:cmt, code:code}) } + m_imm { self.preserve(cmt1) } + } + } +} + +// ---------------------------------------------------------------------- +// Loan(Ex, M, S) = Ls holds if ToAddr(Ex) will remain valid for the entirety +// of the scope S, presuming that the returned set of loans `Ls` are honored. + +type loan_ctxt = @{ + bccx: borrowck_ctxt, + loans: @mut [loan] +}; + +impl loan_methods for borrowck_ctxt { + fn loan(cmt: cmt, + mutbl: ast::mutability) -> bckres<@const [loan]> { + let lc = @{bccx: self, loans: @mut []}; + alt lc.loan(cmt, mutbl) { + ok(()) { ok(lc.loans) } + err(e) { err(e) } + } + } +} + +impl loan_methods for loan_ctxt { + fn ok_with_loan_of(cmt: cmt, + mutbl: ast::mutability) -> bckres<()> { + // Note: all cmt's that we deal with will have a non-none lp, because + // the entry point into this routine, `borrowck_ctxt::loan()`, rejects + // any cmt with a none-lp. + *self.loans += [{lp:option::get(cmt.lp), + cmt:cmt, + mutbl:mutbl}]; + ok(()) + } + + fn loan(cmt: cmt, req_mutbl: ast::mutability) -> bckres<()> { + + #debug["loan(%s, %s)", + self.bccx.cmt_to_repr(cmt), + self.bccx.mut_to_str(req_mutbl)]; + let _i = indenter(); + + // see stable() above; should only be called when `cmt` is lendable + if cmt.lp.is_none() { + self.bccx.tcx.sess.span_bug( + cmt.span, + "loan() called with non-lendable value"); + } + + alt cmt.cat { + cat_rvalue(_) { + self.bccx.tcx.sess.span_bug( + cmt.span, + "rvalue with a non-none lp"); + } + cat_local(_) | cat_arg(_) { + self.ok_with_loan_of(cmt, req_mutbl) + } + cat_comp(cmt_base, comp_field(_)) | + cat_comp(cmt_base, comp_index) | + cat_comp(cmt_base, comp_tuple) | + cat_comp(cmt_base, comp_res) { + // For most components, the type of the embedded data is + // stable. Therefore, the base structure need only be + // const---unless the component must be immutable. In + // that case, it must also be embedded in an immutable + // location, or else the whole structure could be + // overwritten and the component along with it. + let base_mutbl = alt req_mutbl { + m_imm { m_imm } + m_const | m_mutbl { m_const } + }; + + self.loan(cmt_base, base_mutbl).chain { |_ok| + self.ok_with_loan_of(cmt, req_mutbl) + } + } + cat_comp(cmt1, comp_variant) | + cat_deref(cmt1, uniq_ptr) { + // Variant components: the base must be immutable, because + // if it is overwritten, the types of the embedded data + // could change. + // + // Unique pointers: the base must be immutable, because if + // it is overwritten, the unique content will be freed. + self.loan(cmt1, m_imm).chain { |_ok| + self.ok_with_loan_of(cmt, req_mutbl) + } + } + cat_deref(cmt1, unsafe_ptr) | + cat_deref(cmt1, gc_ptr) | + cat_deref(cmt1, region_ptr) { + // Aliased data is simply not lendable. + self.bccx.tcx.sess.span_bug( + cmt.span, + "aliased ptr with a non-none lp"); + } + } + } +} diff --git a/src/rustc/middle/fn_usage.rs b/src/rustc/middle/fn_usage.rs deleted file mode 100644 index 6d710db5f2a..00000000000 --- a/src/rustc/middle/fn_usage.rs +++ /dev/null @@ -1,95 +0,0 @@ -import std::map::hashmap; -import syntax::ast; -import syntax::visit; -import syntax::print::pprust::expr_to_str; -import driver::session::session; - -export check_crate_fn_usage; - -type fn_usage_ctx = { - tcx: ty::ctxt, - unsafe_fn_legal: bool, - generic_bare_fn_legal: bool -}; - -fn fn_usage_expr(expr: @ast::expr, - ctx: fn_usage_ctx, - v: visit::vt) { - alt expr.node { - ast::expr_path(path) { - if !ctx.unsafe_fn_legal { - alt ctx.tcx.def_map.find(expr.id) { - some(ast::def_fn(_, ast::unsafe_fn)) { - log(error, ("expr=", expr_to_str(expr))); - ctx.tcx.sess.span_fatal( - expr.span, - "unsafe functions can only be called"); - } - - _ {} - } - } - if !ctx.generic_bare_fn_legal - && ty::expr_has_ty_params(ctx.tcx, expr) { - alt ty::get(ty::expr_ty(ctx.tcx, expr)).struct { - ty::ty_fn({proto: ast::proto_bare, _}) { - ctx.tcx.sess.span_fatal( - expr.span, - "generic bare functions can only be called or bound"); - } - _ { } - } - } - } - - ast::expr_call(f, args, _) { - let f_ctx = {unsafe_fn_legal: true, - generic_bare_fn_legal: true with ctx}; - v.visit_expr(f, f_ctx, v); - - let args_ctx = {unsafe_fn_legal: false, - generic_bare_fn_legal: false with ctx}; - visit::visit_exprs(args, args_ctx, v); - } - - ast::expr_bind(f, args) { - let f_ctx = {unsafe_fn_legal: false, - generic_bare_fn_legal: true with ctx}; - v.visit_expr(f, f_ctx, v); - - let args_ctx = {unsafe_fn_legal: false, - generic_bare_fn_legal: false with ctx}; - for args.each {|arg| - visit::visit_expr_opt(arg, args_ctx, v); - } - } - - _ { - let subctx = {unsafe_fn_legal: false, - generic_bare_fn_legal: false with ctx}; - visit::visit_expr(expr, subctx, v); - } - } -} - -fn check_crate_fn_usage(tcx: ty::ctxt, crate: @ast::crate) { - let visit = - visit::mk_vt( - @{visit_expr: fn_usage_expr - with *visit::default_visitor()}); - let ctx = { - tcx: tcx, - unsafe_fn_legal: false, - generic_bare_fn_legal: false - }; - visit::visit_crate(*crate, ctx, visit); -} - - -// Local Variables: -// mode: rust -// fill-column: 78; -// indent-tabs-mode: nil -// c-basic-offset: 4 -// buffer-file-coding-system: utf-8-unix -// End: diff --git a/src/rustc/middle/infer.rs b/src/rustc/middle/infer.rs index cd46f53c357..abbd2cb5349 100644 --- a/src/rustc/middle/infer.rs +++ b/src/rustc/middle/infer.rs @@ -24,6 +24,15 @@ export resolve_deep_var; export compare_tys; export fixup_err, fixup_err_to_str; +// Extra information needed to perform an assignment that may borrow. +// The `expr_id` is the is of the expression whose type is being +// assigned, and `borrow_scope` is the region scope to use if the +// value should be borrowed. +type assignment = { + expr_id: ast::node_id, + borrow_scope: ast::node_id +}; + type bound = option; type bounds = {lb: bound, ub: bound}; @@ -84,12 +93,12 @@ fn mk_eqty(cx: infer_ctxt, a: ty::t, b: ty::t) -> ures { indent {|| cx.commit {|| cx.eq_tys(a, b) } }.to_ures() } -fn mk_assignty(cx: infer_ctxt, a_node_id: ast::node_id, - a: ty::t, b: ty::t) -> ures { +fn mk_assignty(cx: infer_ctxt, anmnt: assignment, + a: ty::t, b: ty::t) -> ures { #debug["mk_assignty(%? / %s <: %s)", - a_node_id, a.to_str(cx), b.to_str(cx)]; + anmnt, a.to_str(cx), b.to_str(cx)]; indent {|| cx.commit {|| - cx.assign_tys(a_node_id, a, b) + cx.assign_tys(anmnt, a, b) } }.to_ures() } @@ -765,8 +774,7 @@ impl methods for resolve_state { // this upper-bound might be stricter than what is truly needed. impl assignment for infer_ctxt { - fn assign_tys(a_node_id: ast::node_id, - a: ty::t, b: ty::t) -> ures { + fn assign_tys(anmnt: assignment, a: ty::t, b: ty::t) -> ures { fn select(fst: option, snd: option) -> option { alt fst { @@ -780,8 +788,8 @@ impl assignment for infer_ctxt { } } - #debug["assign_tys(a_node_id=%?, %s -> %s)", - a_node_id, a.to_str(self), b.to_str(self)]; + #debug["assign_tys(anmnt=%?, %s -> %s)", + anmnt, a.to_str(self), b.to_str(self)]; let _r = indenter(); alt (ty::get(a).struct, ty::get(b).struct) { @@ -794,34 +802,34 @@ impl assignment for infer_ctxt { let {root:_, bounds: b_bounds} = self.get(self.vb, b_id); let a_bnd = select(a_bounds.ub, a_bounds.lb); let b_bnd = select(b_bounds.lb, b_bounds.ub); - self.assign_tys_or_sub(a_node_id, a, b, a_bnd, b_bnd) + self.assign_tys_or_sub(anmnt, a, b, a_bnd, b_bnd) } (ty::ty_var(a_id), _) { let {root:_, bounds:a_bounds} = self.get(self.vb, a_id); let a_bnd = select(a_bounds.ub, a_bounds.lb); - self.assign_tys_or_sub(a_node_id, a, b, a_bnd, some(b)) + self.assign_tys_or_sub(anmnt, a, b, a_bnd, some(b)) } (_, ty::ty_var(b_id)) { let {root:_, bounds: b_bounds} = self.get(self.vb, b_id); let b_bnd = select(b_bounds.lb, b_bounds.ub); - self.assign_tys_or_sub(a_node_id, a, b, some(a), b_bnd) + self.assign_tys_or_sub(anmnt, a, b, some(a), b_bnd) } (_, _) { - self.assign_tys_or_sub(a_node_id, a, b, some(a), some(b)) + self.assign_tys_or_sub(anmnt, a, b, some(a), some(b)) } } } fn assign_tys_or_sub( - a_node_id: ast::node_id, + anmnt: assignment, a: ty::t, b: ty::t, a_bnd: option, b_bnd: option) -> ures { - #debug["assign_tys_or_sub(a_node_id=%?, %s -> %s, %s -> %s)", - a_node_id, a.to_str(self), b.to_str(self), + #debug["assign_tys_or_sub(anmnt=%?, %s -> %s, %s -> %s)", + anmnt, a.to_str(self), b.to_str(self), a_bnd.to_str(self), b_bnd.to_str(self)]; let _r = indenter(); @@ -837,34 +845,34 @@ impl assignment for infer_ctxt { alt (ty::get(a_bnd).struct, ty::get(b_bnd).struct) { (ty::ty_box(mt_a), ty::ty_rptr(r_b, mt_b)) { let nr_b = ty::mk_box(self.tcx, mt_b); - self.crosspolinate(a_node_id, a, nr_b, r_b) + self.crosspolinate(anmnt, a, nr_b, r_b) } (ty::ty_uniq(mt_a), ty::ty_rptr(r_b, mt_b)) { let nr_b = ty::mk_uniq(self.tcx, mt_b); - self.crosspolinate(a_node_id, a, nr_b, r_b) + self.crosspolinate(anmnt, a, nr_b, r_b) } (ty::ty_estr(vs_a), ty::ty_estr(ty::vstore_slice(r_b))) if is_borrowable(vs_a) { let nr_b = ty::mk_estr(self.tcx, vs_a); - self.crosspolinate(a_node_id, a, nr_b, r_b) + self.crosspolinate(anmnt, a, nr_b, r_b) } (ty::ty_str, ty::ty_estr(ty::vstore_slice(r_b))) { let nr_b = ty::mk_str(self.tcx); - self.crosspolinate(a_node_id, a, nr_b, r_b) + self.crosspolinate(anmnt, a, nr_b, r_b) } (ty::ty_evec(mt_a, vs_a), ty::ty_evec(mt_b, ty::vstore_slice(r_b))) if is_borrowable(vs_a) { let nr_b = ty::mk_evec(self.tcx, mt_b, vs_a); - self.crosspolinate(a_node_id, a, nr_b, r_b) + self.crosspolinate(anmnt, a, nr_b, r_b) } (ty::ty_vec(mt_a), ty::ty_evec(mt_b, ty::vstore_slice(r_b))) { let nr_b = ty::mk_vec(self.tcx, mt_b); - self.crosspolinate(a_node_id, a, nr_b, r_b) + self.crosspolinate(anmnt, a, nr_b, r_b) } _ { self.sub_tys(a, b) @@ -877,25 +885,25 @@ impl assignment for infer_ctxt { } } - fn crosspolinate(a_node_id: ast::node_id, + fn crosspolinate(anmnt: assignment, a: ty::t, nr_b: ty::t, r_b: ty::region) -> ures { - #debug["crosspolinate(a_node_id=%?, a=%s, nr_b=%s, r_b=%s)", - a_node_id, a.to_str(self), nr_b.to_str(self), + #debug["crosspolinate(anmnt=%?, a=%s, nr_b=%s, r_b=%s)", + anmnt, a.to_str(self), nr_b.to_str(self), r_b.to_str(self)]; indent {|| self.sub_tys(a, nr_b).then {|| - let a_scope_id = self.tcx.region_map.parents.get(a_node_id); - let r_a = ty::re_scope(a_scope_id); - #debug["a_scope_id=%?", a_scope_id]; + let r_a = ty::re_scope(anmnt.borrow_scope); + #debug["anmnt=%?", anmnt]; sub(self).contraregions(r_a, r_b).chain {|_r| // if successful, add an entry indicating that // borrowing occurred - #debug["borrowing expression #%?", a_node_id]; - self.tcx.borrowings.insert(a_node_id, ()); + #debug["borrowing expression #%?", anmnt]; + self.tcx.borrowings.insert(anmnt.expr_id, + anmnt.borrow_scope); uok() } } diff --git a/src/rustc/middle/region.rs b/src/rustc/middle/region.rs index 87ba22fc8bb..65169643bab 100644 --- a/src/rustc/middle/region.rs +++ b/src/rustc/middle/region.rs @@ -136,6 +136,7 @@ import driver::session::session; import middle::ty; import syntax::{ast, visit}; import syntax::codemap::span; +import syntax::print::pprust; import util::common::new_def_hash; import std::list; @@ -151,10 +152,12 @@ type binding = {node_id: ast::node_id, br: ty::bound_region}; type region_map = { - /* Mapping from a block/function expression to its parent. */ + // Mapping from a block/function expression to its parent. parents: hashmap, - /* Mapping from a local variable to its containing block. */ + // Mapping from arguments and local variables to the block in + // which they are declared. Arguments are considered to be declared + // within the body of the function. local_blocks: hashmap }; @@ -163,7 +166,43 @@ type ctxt = { def_map: resolve::def_map, region_map: @region_map, - parent: parent + // These two fields (parent and closure_parent) specify the parent + // scope of the current expression. The parent scope is the + // innermost block, call, or alt expression during the execution + // of which the current expression will be evaluated. Generally + // speaking, the innermost parent scope is also the closest + // suitable ancestor in the AST tree. + // + // However, there are two subtle cases where the parent scope for + // an expression is not strictly derived from the AST. The first + // such exception concerns call arguments and the second concerns + // closures (which, at least today, are always call arguments). + // Consider: + // + // { // block a + // foo( // call b + // x, + // y, + // fn&() { + // // fn body c + // }) + // } + // + // Here, the parent of the three argument expressions is + // actually the block `a`, not the call `b`, because they will + // be evaluated before the call conceptually takes place. + // However, the body of the closure is parented by the call + // `b` (it cannot be invoked except during that call, after + // all). + // + // To capture these patterns, we use two fields. The first, + // parent, is the parent scope of a normal expression. The + // second, closure_parent, is the parent scope that a closure body + // ought to use. These only differ in the case of calls, where + // the closure parent is the call, but the parent is the container + // of the call. + parent: parent, + closure_parent: parent }; // Returns true if `subscope` is equal to or is lexically nested inside @@ -213,18 +252,20 @@ fn nearest_common_ancestor(region_map: @region_map, scope_a: ast::node_id, // where they diverge. If one vector is a suffix of the other, // then the corresponding scope is a superscope of the other. + if a_ancestors[a_index] != b_ancestors[b_index] { + ret none; + } + loop { - if a_ancestors[a_index] != b_ancestors[b_index] { - if a_index == a_ancestors.len() { - ret none; - } else { - ret some(a_ancestors[a_index + 1u]); - } - } + // Loop invariant: a_ancestors[a_index] == b_ancestors[b_index] + // for all indices between a_index and the end of the array if a_index == 0u { ret some(scope_a); } if b_index == 0u { ret some(scope_b); } a_index -= 1u; b_index -= 1u; + if a_ancestors[a_index] != b_ancestors[b_index] { + ret some(a_ancestors[a_index + 1u]); + } } } @@ -243,6 +284,7 @@ fn record_parent(cx: ctxt, child_id: ast::node_id) { alt cx.parent { none { /* no-op */ } some(parent_id) { + #debug["parent of node %d is node %d", child_id, parent_id]; cx.region_map.parents.insert(child_id, parent_id); } } @@ -253,8 +295,8 @@ fn resolve_block(blk: ast::blk, cx: ctxt, visitor: visit::vt) { record_parent(cx, blk.node.id); // Descend. - let new_cx: ctxt = {parent: some(blk.node.id) - with cx}; + let new_cx: ctxt = {parent: some(blk.node.id), + closure_parent: some(blk.node.id) with cx}; visit::visit_block(blk, new_cx, visitor); } @@ -286,16 +328,16 @@ fn resolve_pat(pat: @ast::pat, cx: ctxt, visitor: visit::vt) { fn resolve_expr(expr: @ast::expr, cx: ctxt, visitor: visit::vt) { record_parent(cx, expr.id); alt expr.node { - ast::expr_fn(*) | ast::expr_fn_block(*) { - let new_cx = {parent: some(expr.id) with cx}; - visit::visit_expr(expr, new_cx, visitor); - } - ast::expr_call(_, _, _) { - let new_cx = {parent: some(expr.id) with cx}; + ast::expr_call(*) { + #debug["node %d: %s", expr.id, pprust::expr_to_str(expr)]; + let new_cx = {closure_parent: some(expr.id) with cx}; visit::visit_expr(expr, new_cx, visitor); } ast::expr_alt(subexpr, _, _) { - let new_cx = {parent: some(expr.id) with cx}; + #debug["node %d: %s", expr.id, pprust::expr_to_str(expr)]; + let new_cx = {parent: some(expr.id), + closure_parent: some(expr.id) + with cx}; visit::visit_expr(expr, new_cx, visitor); } _ { @@ -312,20 +354,53 @@ fn resolve_local(local: @ast::local, cx: ctxt, visitor: visit::vt) { fn resolve_item(item: @ast::item, cx: ctxt, visitor: visit::vt) { // Items create a new outer block scope as far as we're concerned. - let new_cx: ctxt = {parent: some(item.id) with cx}; + let new_cx: ctxt = {closure_parent: some(item.id), + parent: some(item.id) with cx}; visit::visit_item(item, new_cx, visitor); } +fn resolve_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk, + sp: span, id: ast::node_id, cx: ctxt, + visitor: visit::vt) { + + let fn_cx = alt fk { + visit::fk_item_fn(*) | visit::fk_method(*) | visit::fk_res(*) | + visit::fk_ctor(*) { + // Top-level functions are a root scope. + {parent: some(id), closure_parent: some(id) with cx} + } + + visit::fk_anon(*) | visit::fk_fn_block(*) { + // Closures use the closure_parent. + {parent: cx.closure_parent with cx} + } + }; + + #debug["visiting fn with body %d. cx.parent: %? \ + cx.closure_parent: %? fn_cx.parent: %?", + body.node.id, cx.parent, + cx.closure_parent, fn_cx.parent]; + + for decl.inputs.each { |input| + cx.region_map.local_blocks.insert( + input.id, body.node.id); + } + + visit::visit_fn(fk, decl, body, sp, id, fn_cx, visitor); +} + fn resolve_crate(sess: session, def_map: resolve::def_map, crate: @ast::crate) -> @region_map { let cx: ctxt = {sess: sess, def_map: def_map, region_map: @{parents: map::int_hash(), local_blocks: map::int_hash()}, - parent: none}; + parent: none, + closure_parent: none}; let visitor = visit::mk_vt(@{ visit_block: resolve_block, visit_item: resolve_item, + visit_fn: resolve_fn, visit_arm: resolve_arm, visit_pat: resolve_pat, visit_expr: resolve_expr, diff --git a/src/rustc/middle/trans/build.rs b/src/rustc/middle/trans/build.rs index 6c2cd8b57de..83c0dd0d7bc 100644 --- a/src/rustc/middle/trans/build.rs +++ b/src/rustc/middle/trans/build.rs @@ -18,7 +18,7 @@ fn count_insn(cx: block, category: str) { if (cx.ccx().sess.opts.count_llvm_insns) { let h = cx.ccx().stats.llvm_insns; - let mut v = cx.ccx().stats.llvm_insn_ctxt; + let v = cx.ccx().stats.llvm_insn_ctxt; // Build version of path with cycles removed. diff --git a/src/rustc/middle/ty.rs b/src/rustc/middle/ty.rs index 87daf62d800..70f32771e00 100644 --- a/src/rustc/middle/ty.rs +++ b/src/rustc/middle/ty.rs @@ -29,6 +29,8 @@ export constr; export constr_general; export constr_table; export ctxt; +export deref, deref_sty; +export index, index_sty; export def_has_ty_params; export expr_has_ty_params; export expr_ty; @@ -126,7 +128,7 @@ export type_is_unique; export type_is_c_like_enum; export type_structurally_contains; export type_structurally_contains_uniques; -export type_autoderef; +export type_autoderef, deref, deref_sty; export type_param; export type_needs_unwind_cleanup; export canon_mode; @@ -228,7 +230,8 @@ type ctxt = iface_method_cache: hashmap, ty_param_bounds: hashmap, inferred_modes: hashmap, - borrowings: hashmap, + // maps the id of borrowed expr to scope of borrowed ptr + borrowings: hashmap, normalized_cache: hashmap}; enum tbox_flag { @@ -572,6 +575,8 @@ fn mk_float(cx: ctxt) -> t { mk_t(cx, ty_float(ast::ty_f)) } fn mk_uint(cx: ctxt) -> t { mk_t(cx, ty_uint(ast::ty_u)) } +fn mk_u8(cx: ctxt) -> t { mk_t(cx, ty_uint(ast::ty_u8)) } + fn mk_mach_int(cx: ctxt, tm: ast::int_ty) -> t { mk_t(cx, ty_int(tm)) } fn mk_mach_uint(cx: ctxt, tm: ast::uint_ty) -> t { mk_t(cx, ty_uint(tm)) } @@ -1711,25 +1716,63 @@ fn vars_in_type(ty: t) -> [ty_vid] { rslt } +// Returns the type and mutability of *t. +// +// The parameter `expl` indicates if this is an *explicit* dereference. Some +// types---notably unsafe ptrs---can only be dereferenced explicitly. +fn deref(cx: ctxt, t: t, expl: bool) -> option { + deref_sty(cx, get(t).struct, expl) +} +fn deref_sty(cx: ctxt, sty: sty, expl: bool) -> option { + alt sty { + ty_rptr(_, mt) | ty_box(mt) | ty_uniq(mt) { + some(mt) + } + + ty_ptr(mt) if expl { + some(mt) + } + + ty_res(_, inner, substs) { + let inner = subst(cx, substs, inner); + some({ty: inner, mutbl: ast::m_imm}) + } + + ty_enum(did, substs) { + let variants = enum_variants(cx, did); + if vec::len(*variants) == 1u && vec::len(variants[0].args) == 1u { + let v_t = subst(cx, substs, variants[0].args[0]); + some({ty: v_t, mutbl: ast::m_imm}) + } else { + none + } + } + + _ { none } + } +} + fn type_autoderef(cx: ctxt, t: t) -> t { - let mut t1 = t; + let mut t = t; loop { - alt get(t1).struct { - ty_box(mt) | ty_uniq(mt) | ty::ty_rptr(_, mt) { t1 = mt.ty; } - ty_res(_, inner, substs) { - t1 = subst(cx, substs, inner); - } - ty_enum(did, substs) { - let variants = enum_variants(cx, did); - if vec::len(*variants) != 1u || vec::len(variants[0].args) != 1u { - break; - } - t1 = subst(cx, substs, variants[0].args[0]); - } - _ { break; } + alt deref(cx, t, false) { + none { ret t; } + some(mt) { t = mt.ty; } } } - ret t1; +} + +// Returns the type and mutability of t[i] +fn index(cx: ctxt, t: t) -> option { + index_sty(cx, get(t).struct) +} + +fn index_sty(cx: ctxt, sty: sty) -> option { + alt sty { + ty_vec(mt) | ty_evec(mt, _) { some(mt) } + ty_str | ty_estr(_) { some({ty: mk_u8(cx), mutbl: ast::m_imm}) } + _ { none } + } } fn hash_bound_region(br: bound_region) -> uint { diff --git a/src/rustc/middle/typeck.rs b/src/rustc/middle/typeck.rs index 6023d830229..584a45bc6b7 100644 --- a/src/rustc/middle/typeck.rs +++ b/src/rustc/middle/typeck.rs @@ -8,7 +8,7 @@ import metadata::csearch; import driver::session::session; import util::common::*; import syntax::codemap::span; -import pat_util::*; +import pat_util::{pat_is_variant, pat_id_map}; import middle::ty; import middle::ty::{arg, field, node_type_table, mk_nil, ty_param_bounds_and_ty, lookup_public_fields}; @@ -216,9 +216,17 @@ fn ty_param_bounds_and_ty_for_def(fcx: @fn_ctxt, sp: span, defn: ast::def) -> }) }; } + + ast::def_fn(id, ast::unsafe_fn) { + // Unsafe functions can only be touched in an unsafe context + fcx.require_unsafe(sp, "access to unsafe function"); + ret ty::lookup_item_type(fcx.ccx.tcx, id); + } + ast::def_fn(id, _) | ast::def_const(id) | - ast::def_variant(_, id) | ast::def_class(id) - { ret ty::lookup_item_type(fcx.ccx.tcx, id); } + ast::def_variant(_, id) | ast::def_class(id) { + ret ty::lookup_item_type(fcx.ccx.tcx, id); + } ast::def_binding(nid) { assert (fcx.locals.contains_key(nid)); let typ = ty::mk_var(fcx.ccx.tcx, lookup_local(fcx, sp, nid)); @@ -1340,6 +1348,29 @@ impl methods for @fn_ctxt { fn mk_eqty(sub: ty::t, sup: ty::t) -> result<(), ty::type_err> { infer::mk_eqty(self.infcx, sub, sup) } + + fn require_impure(sp: span) { + alt self.purity { + ast::unsafe_fn { ret; } + ast::impure_fn | ast::crust_fn { ret; } + ast::pure_fn { + self.ccx.tcx.sess.span_err( + sp, + "found impure expression in pure function decl"); + } + } + } + + fn require_unsafe(sp: span, op: str) { + alt self.purity { + ast::unsafe_fn {/*ok*/} + _ { + self.ccx.tcx.sess.span_err( + sp, + #fmt["%s requires unsafe function or block", op]); + } + } + } } fn mk_ty_params(ccx: @crate_ctxt, atps: [ast::ty_param]) @@ -1715,13 +1746,14 @@ mod collect { } -// FIXME This is almost a duplicate of ty::type_autoderef, with structure_of -// instead of ty::struct. fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> ty::t { let mut t1 = t; let mut enum_dids = []; loop { - alt structure_of(fcx, sp, t1) { + let sty = structure_of(fcx, sp, t1); + + // Some extra checks to detect weird cycles and so forth: + alt sty { ty::ty_box(inner) | ty::ty_uniq(inner) | ty::ty_rptr(_, inner) { alt ty::get(t1).struct { ty::ty_var(v1) { @@ -1730,10 +1762,6 @@ fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> ty::t { } _ { } } - t1 = inner.ty; - } - ty::ty_res(_, inner, substs) { - t1 = ty::subst(fcx.ccx.tcx, substs, inner); } ty::ty_enum(did, substs) { // Watch out for a type like `enum t = @t`. Such a type would @@ -1745,14 +1773,14 @@ fn do_autoderef(fcx: @fn_ctxt, sp: span, t: ty::t) -> ty::t { ret t1; } vec::push(enum_dids, did); - - let variants = ty::enum_variants(fcx.ccx.tcx, did); - if vec::len(*variants) != 1u || vec::len(variants[0].args) != 1u { - ret t1; - } - t1 = ty::subst(fcx.ccx.tcx, substs, variants[0].args[0]); } - _ { ret t1; } + _ { /*ok*/ } + } + + // Otherwise, deref if type is derefable: + alt ty::deref_sty(fcx.ccx.tcx, sty, false) { + none { ret t1; } + some(mt) { t1 = mt.ty; } } }; } @@ -1812,9 +1840,11 @@ mod demand { } // Checks that the type `actual` can be assigned to `expected`. - fn assign(fcx: @fn_ctxt, sp: span, expected: ty::t, expr: @ast::expr) { + fn assign(fcx: @fn_ctxt, sp: span, borrow_scope: ast::node_id, + expected: ty::t, expr: @ast::expr) { let expr_ty = fcx.expr_ty(expr); - alt infer::mk_assignty(fcx.infcx, expr.id, expr_ty, expected) { + let anmnt = {expr_id: expr.id, borrow_scope: borrow_scope}; + alt infer::mk_assignty(fcx.infcx, anmnt, expr_ty, expected) { result::ok(()) { /* ok */ } result::err(err) { fcx.report_mismatched_types(sp, expected, expr_ty, err); @@ -2093,7 +2123,7 @@ fn valid_range_bounds(ccx: @crate_ctxt, from: @ast::expr, to: @ast::expr) type pat_ctxt = { fcx: @fn_ctxt, - map: pat_util::pat_id_map, + map: pat_id_map, alt_region: ty::region, block_region: ty::region, /* Equal to either alt_region or block_region. */ @@ -2265,12 +2295,11 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) { } fcx.write_ty(pat.id, b_ty); } - ast::pat_ident(name, sub) - if !pat_util::pat_is_variant(tcx.def_map, pat) { + ast::pat_ident(name, sub) if !pat_is_variant(tcx.def_map, pat) { let vid = lookup_local(pcx.fcx, pat.span, pat.id); let mut typ = ty::mk_var(tcx, vid); demand::suptype(pcx.fcx, pat.span, expected, typ); - let canon_id = pcx.map.get(path_to_ident(name)); + let canon_id = pcx.map.get(pat_util::path_to_ident(name)); if canon_id != pat.id { let tv_id = lookup_local(pcx.fcx, pat.span, canon_id); let ct = ty::mk_var(tcx, tv_id); @@ -2383,27 +2412,6 @@ fn check_pat(pcx: pat_ctxt, pat: @ast::pat, expected: ty::t) { } } -fn require_unsafe(sess: session, f_purity: ast::purity, sp: span) { - alt f_purity { - ast::unsafe_fn { ret; } - _ { - sess.span_err( - sp, - "unsafe operation requires unsafe function or block"); - } - } -} - -fn require_impure(sess: session, f_purity: ast::purity, sp: span) { - alt f_purity { - ast::unsafe_fn { ret; } - ast::impure_fn | ast::crust_fn { ret; } - ast::pure_fn { - sess.span_err(sp, "found impure expression in pure function decl"); - } - } -} - fn require_pure_call(ccx: @crate_ctxt, caller_purity: ast::purity, callee: @ast::expr, sp: span) { if caller_purity == ast::unsafe_fn { ret; } @@ -2866,9 +2874,11 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, // A generic function to factor out common logic from call and bind // expressions. fn check_call_or_bind( - fcx: @fn_ctxt, sp: span, fty: ty::t, + fcx: @fn_ctxt, sp: span, call_expr_id: ast::node_id, fty: ty::t, args: [option<@ast::expr>]) -> {fty: ty::t, bot: bool} { + let mut bot = false; + let fty = universally_quantify_before_call(fcx, sp, fty); #debug["check_call_or_bind: after universal quant., fty=%s", fcx.ty_to_str(fty)]; @@ -2916,10 +2926,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, // functions. This is so that we have more information about the types // of arguments when we typecheck the functions. This isn't really the // right way to do this. - let check_args = fn@(check_blocks: bool) -> bool { - let mut i = 0u; - let mut bot = false; - for args.each {|a_opt| + for [false, true].each { |check_blocks| + for args.eachi {|i, a_opt| alt a_opt { some(a) { let is_block = alt a.node { @@ -2930,18 +2938,15 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, let arg_ty = arg_tys[i]; bot |= check_expr_with_unifier( fcx, a, some(arg_ty)) {|| - demand::assign(fcx, a.span, arg_ty, a); + demand::assign(fcx, a.span, call_expr_id, + arg_ty, a); }; } } none { } } - i += 1u; } - ret bot; - }; - - let bot = check_args(false) | check_args(true); + } {fty: fty, bot: bot} } @@ -2965,7 +2970,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, // Call the generic checker. let fty = { let args_opt = args.map { |arg| some(arg) }; - let r = check_call_or_bind(fcx, sp, fn_ty, args_opt); + let r = check_call_or_bind(fcx, sp, call_expr_id, + fn_ty, args_opt); bot |= r.bot; r.fty }; @@ -3046,7 +3052,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, some(origin) { let {fty: method_ty, bot: bot} = { let method_ty = fcx.node_ty(callee_id); - check_call_or_bind(fcx, op_ex.span, method_ty, args) + check_call_or_bind(fcx, op_ex.span, op_ex.id, + method_ty, args) }; fcx.ccx.method_map.insert(op_ex.id, origin); some((ty::ty_fn_ret(method_ty), bot)) @@ -3257,7 +3264,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, bot |= check_binop(fcx, expr, op, lhs, rhs); } ast::expr_assign_op(op, lhs, rhs) { - require_impure(tcx.sess, fcx.purity, expr.span); + fcx.require_impure(expr.span); bot |= check_binop(fcx, expr, op, lhs, rhs); let lhs_t = fcx.expr_ty(lhs); let result_t = fcx.expr_ty(expr); @@ -3291,30 +3298,37 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, oper_t = ty::mk_uniq(tcx, {ty: oper_t, mutbl: mutbl}); } ast::deref { - alt structure_of(fcx, expr.span, oper_t) { - ty::ty_box(inner) { oper_t = inner.ty; } - ty::ty_uniq(inner) { oper_t = inner.ty; } - ty::ty_res(_, inner, _) { oper_t = inner; } - ty::ty_enum(id, substs) { - let variants = ty::enum_variants(tcx, id); - if vec::len(*variants) != 1u || - vec::len(variants[0].args) != 1u { - tcx.sess.span_fatal(expr.span, - "can only dereference enums " + - "with a single variant which has a " - + "single argument"); + let sty = structure_of(fcx, expr.span, oper_t); + + // deref'ing an unsafe pointer requires that we be in an unsafe + // context + alt sty { + ty::ty_ptr(*) { + fcx.require_unsafe( + expr.span, + "dereference of unsafe pointer"); + } + _ { /*ok*/ } + } + + alt ty::deref_sty(tcx, sty, true) { + some(mt) { oper_t = mt.ty } + none { + alt sty { + ty::ty_enum(*) { + tcx.sess.span_fatal( + expr.span, + "can only dereference enums \ + with a single variant which has a \ + single argument"); + } + _ { + tcx.sess.span_fatal( + expr.span, + #fmt["type %s cannot be dereferenced", + fcx.ty_to_str(oper_t)]); + } } - oper_t = ty::subst(tcx, substs, variants[0].args[0]); - } - ty::ty_ptr(inner) { - oper_t = inner.ty; - require_unsafe(tcx.sess, fcx.purity, expr.span); - } - ty::ty_rptr(_, inner) { oper_t = inner.ty; } - _ { - tcx.sess.span_err(expr.span, - #fmt("Type %s cannot be dereferenced", - ty_to_str(tcx, oper_t))); } } } @@ -3410,21 +3424,20 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, fcx.write_ty(id, fcx.expr_ty(a)); } ast::expr_move(lhs, rhs) { - require_impure(tcx.sess, fcx.purity, expr.span); + fcx.require_impure(expr.span); bot = check_assignment(fcx, expr.span, lhs, rhs, id); } ast::expr_assign(lhs, rhs) { - require_impure(tcx.sess, fcx.purity, expr.span); + fcx.require_impure(expr.span); bot = check_assignment(fcx, expr.span, lhs, rhs, id); } ast::expr_swap(lhs, rhs) { - require_impure(tcx.sess, fcx.purity, expr.span); + fcx.require_impure(expr.span); bot = check_assignment(fcx, expr.span, lhs, rhs, id); } ast::expr_if(cond, thn, elsopt) { - bot = - check_expr_with(fcx, cond, ty::mk_bool(tcx)) | - check_then_else(fcx, thn, elsopt, id, expr.span); + bot = check_expr_with(fcx, cond, ty::mk_bool(tcx)) | + check_then_else(fcx, thn, elsopt, id, expr.span); } ast::expr_while(cond, body) { bot = check_expr_with(fcx, cond, ty::mk_bool(tcx)); @@ -3451,7 +3464,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, for arms.each {|arm| let pcx = { fcx: fcx, - map: pat_util::pat_id_map(tcx.def_map, arm.pats[0]), + map: pat_id_map(tcx.def_map, arm.pats[0]), alt_region: ty::re_scope(expr.id), block_region: ty::re_scope(arm.body.node.id), pat_region: ty::re_scope(expr.id) @@ -3545,7 +3558,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, let {fty, bot: ccob_bot} = { let fn_ty = fcx.expr_ty(f); - check_call_or_bind(fcx, expr.span, fn_ty, args) + check_call_or_bind(fcx, expr.span, expr.id, fn_ty, args) }; bot |= ccob_bot; @@ -3792,19 +3805,12 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, let base_t = do_autoderef(fcx, expr.span, raw_base_t); bot |= check_expr(fcx, idx, none); let idx_t = fcx.expr_ty(idx); - alt structure_of(fcx, expr.span, base_t) { - ty::ty_evec(mt, _) | - ty::ty_vec(mt) { + alt ty::index_sty(tcx, structure_of(fcx, expr.span, base_t)) { + some(mt) { require_integral(fcx, idx.span, idx_t); fcx.write_ty(id, mt.ty); } - ty::ty_estr(_) | - ty::ty_str { - require_integral(fcx, idx.span, idx_t); - let typ = ty::mk_mach_uint(tcx, ast::ty_u8); - fcx.write_ty(id, typ); - } - _ { + none { let resolved = structurally_resolved_type(fcx, expr.span, raw_base_t); alt lookup_op_method(fcx, expr, resolved, "[]", @@ -3919,7 +3925,7 @@ fn check_decl_local(fcx: @fn_ctxt, local: @ast::local) -> bool { fcx.ccx.tcx.region_map.local_blocks.get(local.node.id)); let pcx = { fcx: fcx, - map: pat_util::pat_id_map(fcx.ccx.tcx.def_map, local.node.pat), + map: pat_id_map(fcx.ccx.tcx.def_map, local.node.pat), alt_region: region, block_region: region, pat_region: region diff --git a/src/rustc/rustc.rc b/src/rustc/rustc.rc index a76133b4c82..c50ea26eb15 100644 --- a/src/rustc/rustc.rc +++ b/src/rustc/rustc.rc @@ -39,12 +39,12 @@ mod middle { mod ast_map; mod resolve; mod typeck; - mod fn_usage; mod check_loop; mod check_alt; mod check_const; mod lint; mod mutbl; + mod borrowck; mod alias; mod last_use; mod block_use; diff --git a/src/test/compile-fail/borrowck-hold-box.rs b/src/test/compile-fail/borrowck-hold-box.rs new file mode 100644 index 00000000000..04bd3b2532d --- /dev/null +++ b/src/test/compile-fail/borrowck-hold-box.rs @@ -0,0 +1,17 @@ +// compile-flags:--borrowck=err + +fn borrow(v: &int, f: fn(x: &int)) { + f(v); +} + +fn box_imm() { + let mut v = ~3; + borrow(v) { |w| //! NOTE loan of mutable local variable granted here + v = ~4; //! ERROR cannot assign to mutable local variable due to outstanding loan + assert *v == 3; + assert *w == 4; + } +} + +fn main() { +} diff --git a/src/test/compile-fail/borrowck-lend-args.rs b/src/test/compile-fail/borrowck-lend-args.rs new file mode 100644 index 00000000000..46c4ff4a23d --- /dev/null +++ b/src/test/compile-fail/borrowck-lend-args.rs @@ -0,0 +1,26 @@ +// compile-flags:--borrowck=err + +fn borrow(_v: &int) {} + +fn borrow_from_arg_imm_ref(&&v: ~int) { + borrow(v); // ERROR unique value in aliasable, mutable location +} + +fn borrow_from_arg_mut_ref(&v: ~int) { + borrow(v); //! ERROR unique value in aliasable, mutable location +} + +fn borrow_from_arg_move(-v: ~int) { + borrow(v); +} + +fn borrow_from_arg_copy(+v: ~int) { + borrow(v); +} + +fn borrow_from_arg_val(++v: ~int) { + borrow(v); +} + +fn main() { +} diff --git a/src/test/compile-fail/borrowck-lend-flow.rs b/src/test/compile-fail/borrowck-lend-flow.rs new file mode 100644 index 00000000000..afe23a40a5d --- /dev/null +++ b/src/test/compile-fail/borrowck-lend-flow.rs @@ -0,0 +1,96 @@ +// compile-flags:--borrowck=err + +// Note: the borrowck analysis is currently flow-insensitive. +// Therefore, some of these errors are marked as spurious and could be +// corrected by a simple change to the analysis. The others are +// either genuine or would require more advanced changes. The latter +// cases are noted. + +fn borrow(_v: &int) {} + +fn inc(v: &mut ~int) { + *v = ~(**v + 1); +} + +fn post_aliased_const() { + let mut v = ~3; + borrow(v); + let _w = &const v; +} + +fn post_aliased_mut() { + // SPURIOUS--flow + let mut v = ~3; + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + let _w = &mut v; //! NOTE prior loan as mutable granted here +} + +fn post_aliased_scope(cond: bool) { + // NDM--scope of & + let mut v = ~3; + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + if cond { inc(&mut v); } //! NOTE prior loan as mutable granted here +} + +fn loop_aliased_mut() { + let mut v = ~3, w = ~4; + let mut _x = &mut w; + loop { + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + _x = &mut v; //! NOTE prior loan as mutable granted here + } +} + +fn while_aliased_mut(cond: bool) { + let mut v = ~3, w = ~4; + let mut _x = &mut w; + while cond { + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + _x = &mut v; //! NOTE prior loan as mutable granted here + } +} + +fn while_aliased_mut_cond(cond: bool, cond2: bool) { + let mut v = ~3, w = ~4; + let mut _x = &mut w; + while cond { + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + if cond2 { + _x = &mut v; //! NOTE prior loan as mutable granted here + } + } +} + +fn do_while_aliased_mut(cond: bool) { + let mut v = ~3, w = ~4; + let mut _x = &mut w; + do { + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + _x = &mut v; //! NOTE prior loan as mutable granted here + } while cond; +} + +fn loop_in_block() { + let mut v = ~3, w = ~4; + let mut _x = &mut w; + uint::range(0u, 10u) {|_i| + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + _x = &mut v; //! NOTE prior loan as mutable granted here + } +} + +fn at_most_once_block() { + fn at_most_once(f: fn()) { f() } + + // Here, the borrow check has no way of knowing that the block is + // executed at most once. + + let mut v = ~3, w = ~4; + let mut _x = &mut w; + at_most_once {|| + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan + _x = &mut v; //! NOTE prior loan as mutable granted here + } +} + +fn main() {} diff --git a/src/test/compile-fail/borrowck-pat-enum-in-box.rs b/src/test/compile-fail/borrowck-pat-enum-in-box.rs new file mode 100644 index 00000000000..b37cb07787b --- /dev/null +++ b/src/test/compile-fail/borrowck-pat-enum-in-box.rs @@ -0,0 +1,21 @@ +// compile-flags:--borrowck=err + +fn match_imm_box(v: &const @option) -> int { + alt *v { + @some(i) {i} + @none {0} + } +} + +fn match_const_box(v: &const @const option) -> int { + alt *v { + @some(i) { + //!^ ERROR enum variant in aliasable, mutable location + i + } + @none {0} + } +} + +fn main() { +} diff --git a/src/test/compile-fail/borrowck-pat-enum.rs b/src/test/compile-fail/borrowck-pat-enum.rs new file mode 100644 index 00000000000..9ad4689a9a8 --- /dev/null +++ b/src/test/compile-fail/borrowck-pat-enum.rs @@ -0,0 +1,45 @@ +// compile-flags:--borrowck=err + +fn match_ref(&&v: option) -> int { + alt v { + some(i) { + //^ ERROR enum variant in aliasable, mutable location + i + } + none {0} + } +} + +fn match_ref_unused(&&v: option) { + alt v { + some(_) {} + none {} + } +} + +fn match_const_reg(v: &const option) -> int { + alt *v { + some(i) { + //!^ ERROR enum variant in aliasable, mutable location + i + } + none {0} + } +} + +fn match_const_reg_unused(v: &const option) { + alt *v { + some(_) {} + none {} + } +} + +fn match_imm_reg(v: &option) -> int { + alt *v { + some(i) {i} + none {0} + } +} + +fn main() { +} diff --git a/src/test/compile-fail/borrowck-pat-reassign-binding.rs b/src/test/compile-fail/borrowck-pat-reassign-binding.rs new file mode 100644 index 00000000000..b75b165ef58 --- /dev/null +++ b/src/test/compile-fail/borrowck-pat-reassign-binding.rs @@ -0,0 +1,14 @@ +// compile-flags:--borrowck=err +// xfail-pretty -- comments are infaithfully preserved + +fn main() { + let mut x: option = none; + alt x { //! NOTE loan of mutable local variable granted here + none {} + some(i) { + // Not ok: i is an outstanding ptr into x. + x = some(i+1); + //!^ ERROR cannot assign to mutable local variable due to outstanding loan + } + } +} diff --git a/src/test/compile-fail/borrowck-pat-reassign-sometimes-binding.rs b/src/test/compile-fail/borrowck-pat-reassign-sometimes-binding.rs new file mode 100644 index 00000000000..ef2ba2fcfbd --- /dev/null +++ b/src/test/compile-fail/borrowck-pat-reassign-sometimes-binding.rs @@ -0,0 +1,16 @@ +// compile-flags:--borrowck=err +// xfail-pretty -- comments are infaithfully preserved + +fn main() { + let mut x = none; + alt x { //! NOTE loan of mutable local variable granted here + none { + // It is ok to reassign x here, because there is in + // fact no outstanding loan of x! + x = some(0); + } + some(i) { + x = some(1); //! ERROR cannot assign to mutable local variable due to outstanding loan + } + } +} diff --git a/src/test/compile-fail/borrowck-uniq-via-box.rs b/src/test/compile-fail/borrowck-uniq-via-box.rs new file mode 100644 index 00000000000..f650f4cab5e --- /dev/null +++ b/src/test/compile-fail/borrowck-uniq-via-box.rs @@ -0,0 +1,55 @@ +// compile-flags:--borrowck=err + +fn borrow(_v: &int) {} + +fn box_mut(v: @mut ~int) { + borrow(*v); //! ERROR illegal borrow: unique value in aliasable, mutable location + +} + +fn box_rec_mut(v: @{mut f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_mut_rec(v: @mut {f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_mut_recs(v: @mut {f: {g: {h: ~int}}}) { + borrow(v.f.g.h); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_imm(v: @~int) { + borrow(*v); // OK +} + +fn box_imm_rec(v: @{f: ~int}) { + borrow(v.f); // OK +} + +fn box_imm_recs(v: @{f: {g: {h: ~int}}}) { + borrow(v.f.g.h); // OK +} + +fn box_const(v: @const ~int) { + borrow(*v); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_rec_const(v: @{const f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_recs_const(v: @{f: {g: {const h: ~int}}}) { + borrow(v.f.g.h); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_const_rec(v: @const {f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_const_recs(v: @const {f: {g: {h: ~int}}}) { + borrow(v.f.g.h); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn main() { +} diff --git a/src/test/compile-fail/borrowck-uniq-via-lend.rs b/src/test/compile-fail/borrowck-uniq-via-lend.rs new file mode 100644 index 00000000000..49911d3220d --- /dev/null +++ b/src/test/compile-fail/borrowck-uniq-via-lend.rs @@ -0,0 +1,52 @@ +// compile-flags:--borrowck=err + +fn borrow(_v: &int) {} + +fn local() { + let mut v = ~3; + borrow(v); +} + +fn local_rec() { + let mut v = {f: ~3}; + borrow(v.f); +} + +fn local_recs() { + let mut v = {f: {g: {h: ~3}}}; + borrow(v.f.g.h); +} + +fn aliased_imm() { // NDM: Spurious + let mut v = ~3; + let _w = &v; //! ERROR illegal borrow: mutability mismatch, required immutable but found mutable + borrow(v); +} + +fn aliased_const() { + let mut v = ~3; + let _w = &const v; + borrow(v); +} + +fn aliased_mut() { + let mut v = ~3; + let _w = &mut v; //! NOTE prior loan as mutable granted here + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan +} + +fn aliased_other() { + let mut v = ~3, w = ~4; + let _x = &mut w; + borrow(v); +} + +fn aliased_other_reassign() { + let mut v = ~3, w = ~4; + let mut _x = &mut w; + _x = &mut v; //! NOTE prior loan as mutable granted here + borrow(v); //! ERROR loan of mutable local variable as immutable conflicts with prior loan +} + +fn main() { +} diff --git a/src/test/compile-fail/borrowck-uniq-via-ref.rs b/src/test/compile-fail/borrowck-uniq-via-ref.rs new file mode 100644 index 00000000000..9c0b6712b13 --- /dev/null +++ b/src/test/compile-fail/borrowck-uniq-via-ref.rs @@ -0,0 +1,54 @@ +// compile-flags:--borrowck=err +fn borrow(_v: &int) {} + +fn box_mut(v: &mut ~int) { + borrow(*v); //! ERROR illegal borrow: unique value in aliasable, mutable location + +} + +fn box_rec_mut(v: &{mut f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_mut_rec(v: &mut {f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_mut_recs(v: &mut {f: {g: {h: ~int}}}) { + borrow(v.f.g.h); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_imm(v: &~int) { + borrow(*v); // OK +} + +fn box_imm_rec(v: &{f: ~int}) { + borrow(v.f); // OK +} + +fn box_imm_recs(v: &{f: {g: {h: ~int}}}) { + borrow(v.f.g.h); // OK +} + +fn box_const(v: &const ~int) { + borrow(*v); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_rec_const(v: &{const f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_recs_const(v: &{f: {g: {const h: ~int}}}) { + borrow(v.f.g.h); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_const_rec(v: &const {f: ~int}) { + borrow(v.f); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn box_const_recs(v: &const {f: {g: {h: ~int}}}) { + borrow(v.f.g.h); //! ERROR illegal borrow: unique value in aliasable, mutable location +} + +fn main() { +} diff --git a/src/test/compile-fail/fn-bare-generic-instantiate1.rs b/src/test/compile-fail/fn-bare-generic-instantiate1.rs deleted file mode 100644 index 6d610288c38..00000000000 --- a/src/test/compile-fail/fn-bare-generic-instantiate1.rs +++ /dev/null @@ -1,10 +0,0 @@ -// error-pattern: generic bare functions can only be called or bound -// Issue #1038 - -fn main() { - fn foo() { } - - // This wants to build a closure over type int, - // but there's no way to do that while still being a bare function - let f: fn() = foo::; -} \ No newline at end of file diff --git a/src/test/compile-fail/fn-bare-generic-instantiate2.rs b/src/test/compile-fail/fn-bare-generic-instantiate2.rs deleted file mode 100644 index e9e21af1480..00000000000 --- a/src/test/compile-fail/fn-bare-generic-instantiate2.rs +++ /dev/null @@ -1,13 +0,0 @@ -// error-pattern: generic bare functions can only be called or bound -// Issue #1038 - -fn main() { - fn foo(i: T) { } - - // This wants to build a closure over type int, - // but there's no way to do that while still being a bare function - f(foo); -} - -fn f(i: fn(&&int)) { -} \ No newline at end of file diff --git a/src/test/compile-fail/native-unsafe-fn.rs b/src/test/compile-fail/native-unsafe-fn.rs index 3d2c0e1f02b..8615c7795a9 100644 --- a/src/test/compile-fail/native-unsafe-fn.rs +++ b/src/test/compile-fail/native-unsafe-fn.rs @@ -1,5 +1,4 @@ // -*- rust -*- -// error-pattern: unsafe functions can only be called #[abi = "cdecl"] native mod test { @@ -8,6 +7,7 @@ native mod test { fn main() { let x = test::free; + //!^ ERROR access to unsafe function requires unsafe function or block } diff --git a/src/test/compile-fail/regions-leaking-ptr.rs b/src/test/compile-fail/regions-leaking-ptr.rs index 33f1c1b4ab9..e7b89b17c54 100644 --- a/src/test/compile-fail/regions-leaking-ptr.rs +++ b/src/test/compile-fail/regions-leaking-ptr.rs @@ -2,10 +2,10 @@ // This generates a ton of error msgs at the moment. fn broken() -> int { let mut x = 3; - let mut y = [&x]; //! ERROR reference is not valid + let mut y = [&mut x]; //! ERROR reference is not valid while x < 10 { - let z = x; - y += [&z]; + let mut z = x; + y += [&mut z]; x += 1; } vec::foldl(0, y) {|v, p| v + *p } diff --git a/src/test/compile-fail/unsafe-fn-assign-deref-ptr.rs b/src/test/compile-fail/unsafe-fn-assign-deref-ptr.rs index cf5dac3ff89..089c4a74505 100644 --- a/src/test/compile-fail/unsafe-fn-assign-deref-ptr.rs +++ b/src/test/compile-fail/unsafe-fn-assign-deref-ptr.rs @@ -1,11 +1,9 @@ // -*- rust -*- -// error-pattern: unsafe operation requires unsafe function or block fn f(p: *u8) { - *p = 0u8; + *p = 0u8; //! ERROR dereference of unsafe pointer requires unsafe function or block ret; } fn main() { - f(); } diff --git a/src/test/compile-fail/unsafe-fn-autoderef.rs b/src/test/compile-fail/unsafe-fn-autoderef.rs new file mode 100644 index 00000000000..56d0f96fb3e --- /dev/null +++ b/src/test/compile-fail/unsafe-fn-autoderef.rs @@ -0,0 +1,22 @@ +// -*- rust -*- + +type rec = {f: int}; +fn f(p: *rec) -> int { + + // Test that * ptrs do not autoderef. There is a deeper reason for + // prohibiting this, beyond making unsafe things annoying (which doesn't + // actually seem desirable to me). The deeper reason is that if you + // have a type like: + // + // enum foo = *foo; + // + // you end up with an infinite auto-deref chain, which is + // currently impossible (in all other cases, infinite auto-derefs + // are prohibited by various checks, such as that the enum is + // instantiable and so forth). + + ret p.f; //! ERROR attempted access of field f on type *rec +} + +fn main() { +} diff --git a/src/test/compile-fail/unsafe-fn-deref-ptr.rs b/src/test/compile-fail/unsafe-fn-deref-ptr.rs index 238acc8729c..dd6a9c7a405 100644 --- a/src/test/compile-fail/unsafe-fn-deref-ptr.rs +++ b/src/test/compile-fail/unsafe-fn-deref-ptr.rs @@ -1,10 +1,8 @@ // -*- rust -*- -// error-pattern: unsafe operation requires unsafe function or block fn f(p: *u8) -> u8 { - ret *p; + ret *p; //! ERROR dereference of unsafe pointer requires unsafe function or block } fn main() { - f(); } diff --git a/src/test/compile-fail/unsafe-fn-used-as-value.rs b/src/test/compile-fail/unsafe-fn-used-as-value.rs index e2fa46b0374..dfcc2c85fcf 100644 --- a/src/test/compile-fail/unsafe-fn-used-as-value.rs +++ b/src/test/compile-fail/unsafe-fn-used-as-value.rs @@ -1,9 +1,8 @@ // -*- rust -*- -// error-pattern: unsafe functions can only be called unsafe fn f() { ret; } fn main() { - let x = f; + let x = f; //! ERROR access to unsafe function requires unsafe function or block x(); } diff --git a/src/test/compile-fail/unsafe-fn-used-in-bind.rs b/src/test/compile-fail/unsafe-fn-used-in-bind.rs index 2622afe2e39..0e9a93390ec 100644 --- a/src/test/compile-fail/unsafe-fn-used-in-bind.rs +++ b/src/test/compile-fail/unsafe-fn-used-in-bind.rs @@ -1,9 +1,9 @@ // -*- rust -*- -// error-pattern: unsafe functions can only be called unsafe fn f(x: int, y: int) -> int { ret x + y; } fn main() { let x = bind f(3, _); + //!^ ERROR access to unsafe function requires unsafe function or block let y = x(4); } diff --git a/src/test/run-pass/borrowck-pat-reassign-no-binding.rs b/src/test/run-pass/borrowck-pat-reassign-no-binding.rs new file mode 100644 index 00000000000..11d516dce82 --- /dev/null +++ b/src/test/run-pass/borrowck-pat-reassign-no-binding.rs @@ -0,0 +1,13 @@ +// compile-flags:--borrowck=err + +fn main() { + let mut x = none; + alt x { + none { + // It is ok to reassign x here, because there is in + // fact no outstanding loan of x! + x = some(0); + } + some(_) { } + } +}