From 653a1f8781cd4148d836915228ac28f13853457d Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 24 May 2012 23:44:58 -0700 Subject: [PATCH] integrate purity into type --- src/librustsyntax/parse/parser.rs | 32 ++-- src/librustsyntax/print/pprust.rs | 23 ++- src/rustc/metadata/tydecode.rs | 20 +- src/rustc/metadata/tyencode.rs | 12 +- src/rustc/middle/borrowck.rs | 177 ++++++++++-------- src/rustc/middle/trans/base.rs | 9 +- src/rustc/middle/ty.rs | 14 +- src/rustc/middle/typeck.rs | 4 +- src/rustc/middle/typeck/astconv.rs | 3 +- src/rustc/middle/typeck/check.rs | 5 +- src/rustc/middle/typeck/collect.rs | 13 +- src/rustc/middle/typeck/infer.rs | 33 +++- src/rustc/util/ppaux.rs | 22 ++- src/test/compile-fail/borrowck-lend-args.rs | 2 +- .../compile-fail/borrowck-pat-enum-in-box.rs | 2 +- src/test/compile-fail/borrowck-pat-enum.rs | 2 +- .../borrowck-unchecked-with-borrow.rs | 2 +- .../compile-fail/borrowck-uniq-via-box.rs | 18 +- .../compile-fail/borrowck-uniq-via-ref.rs | 18 +- src/test/compile-fail/impure-pred.rs | 2 +- src/test/compile-fail/pure-higher-order.rs | 48 +++++ 21 files changed, 320 insertions(+), 141 deletions(-) create mode 100644 src/test/compile-fail/pure-higher-order.rs diff --git a/src/librustsyntax/parse/parser.rs b/src/librustsyntax/parse/parser.rs index eb98c2bf592..8594aed9776 100644 --- a/src/librustsyntax/parse/parser.rs +++ b/src/librustsyntax/parse/parser.rs @@ -136,7 +136,18 @@ class parser { } fn get_id() -> node_id { next_node_id(self.sess) } - fn parse_ty_fn() -> fn_decl { + fn parse_ty_fn(purity: ast::purity) -> ty_ { + let proto = if self.eat_keyword("native") { + self.expect_keyword("fn"); + ast::proto_bare + } else { + self.expect_keyword("fn"); + self.parse_fn_ty_proto() + }; + ty_fn(proto, self.parse_ty_fn_decl(purity)) + } + + fn parse_ty_fn_decl(purity: ast::purity) -> fn_decl { let inputs = self.parse_seq(token::LPAREN, token::RPAREN, seq_sep(token::COMMA)) { |p| @@ -159,7 +170,7 @@ class parser { let constrs: [@constr] = []; let (ret_style, ret_ty) = self.parse_ret_ty(); ret {inputs: inputs.node, output: ret_ty, - purity: impure_fn, cf: ret_style, + purity: purity, cf: ret_style, constraints: constrs}; } @@ -170,7 +181,7 @@ class parser { let pur = p.parse_fn_purity(); let ident = p.parse_method_name(); let tps = p.parse_ty_params(); - let d = p.parse_ty_fn(), fhi = p.last_span.hi; + let d = p.parse_ty_fn_decl(pur), fhi = p.last_span.hi; self.expect(token::SEMI); {ident: ident, attrs: attrs, decl: {purity: pur with d}, tps: tps, span: mk_sp(flo, fhi)} @@ -384,16 +395,15 @@ class parser { let region = self.parse_region_dot(); let mt = self.parse_mt(); ty_rptr(region, mt) - } else if self.eat_keyword("fn") { - let proto = self.parse_fn_ty_proto(); - alt proto { - proto_bare { self.warn("fn is deprecated, use native fn"); } - _ { /* fallthrough */ } - } - ty_fn(proto, self.parse_ty_fn()) + } else if self.eat_keyword("pure") { + self.parse_ty_fn(ast::pure_fn) + } else if self.eat_keyword("unsafe") { + self.parse_ty_fn(ast::unsafe_fn) + } else if self.is_keyword("fn") { + self.parse_ty_fn(ast::impure_fn) } else if self.eat_keyword("native") { self.expect_keyword("fn"); - ty_fn(proto_bare, self.parse_ty_fn()) + ty_fn(proto_bare, self.parse_ty_fn_decl(ast::impure_fn)) } else if self.token == token::MOD_SEP || is_ident(self.token) { let path = self.parse_path_with_tps(colons_before_params); ty_path(path, self.get_id()) diff --git a/src/librustsyntax/print/pprust.rs b/src/librustsyntax/print/pprust.rs index 52c7ce241ab..8206bfd2a4a 100644 --- a/src/librustsyntax/print/pprust.rs +++ b/src/librustsyntax/print/pprust.rs @@ -1029,6 +1029,7 @@ fn print_expr(s: ps, &&expr: @ast::expr) { cbox(s, indent_unit); // head-box, will be closed by print-block at start ibox(s, 0u); + print_purity(s, decl.purity); word(s.s, proto_to_str(proto)); print_fn_args_and_ret(s, decl, *cap_clause); space(s.s); @@ -1322,10 +1323,8 @@ fn print_pat(s: ps, &&pat: @ast::pat) { fn print_fn(s: ps, decl: ast::fn_decl, name: ast::ident, typarams: [ast::ty_param]) { alt decl.purity { - ast::impure_fn { head(s, "fn"); } - ast::unsafe_fn { head(s, "unsafe fn"); } - ast::pure_fn { head(s, "pure fn"); } - ast::crust_fn { head(s, "crust fn"); } + ast::impure_fn { head(s, "fn") } + _ { head(s, purity_to_str(decl.purity) + " fn") } } word(s.s, name); print_type_params(s, typarams); @@ -1825,6 +1824,22 @@ fn opt_proto_to_str(opt_p: option) -> str { } } +fn purity_to_str(p: ast::purity) -> str { + alt p { + ast::impure_fn {"impure"} + ast::unsafe_fn {"unsafe"} + ast::pure_fn {"pure"} + ast::crust_fn {"crust"} + } +} + +fn print_purity(s: ps, p: ast::purity) { + alt p { + ast::impure_fn {} + _ { word_nbsp(s, purity_to_str(p)) } + } +} + fn proto_to_str(p: ast::proto) -> str { ret alt p { ast::proto_bare { "native fn" } diff --git a/src/rustc/metadata/tydecode.rs b/src/rustc/metadata/tydecode.rs index 7d242a7e9e0..4204a5d36d8 100644 --- a/src/rustc/metadata/tydecode.rs +++ b/src/rustc/metadata/tydecode.rs @@ -159,8 +159,8 @@ fn parse_constr(st: @pstate, conv: conv_did, ret @respan(sp, {path: pth, args: args, id: def}); } -fn parse_ty_rust_fn(st: @pstate, conv: conv_did, p: ast::proto) -> ty::t { - ret ty::mk_fn(st.tcx, {proto: p with parse_ty_fn(st, conv)}); +fn parse_ty_rust_fn(st: @pstate, conv: conv_did) -> ty::t { + ret ty::mk_fn(st.tcx, parse_ty_fn(st, conv)); } fn parse_proto(c: char) -> ast::proto { @@ -335,8 +335,7 @@ fn parse_ty(st: @pstate, conv: conv_did) -> ty::t { ret ty::mk_tup(st.tcx, params); } 'f' { - let proto = parse_proto(next(st)); - parse_ty_rust_fn(st, conv, proto) + parse_ty_rust_fn(st, conv) } 'r' { assert next(st) == '['; @@ -441,7 +440,18 @@ fn parse_hex(st: @pstate) -> uint { }; } +fn parse_purity(c: char) -> purity { + alt check c { + 'u' {unsafe_fn} + 'p' {pure_fn} + 'i' {impure_fn} + 'c' {crust_fn} + } +} + fn parse_ty_fn(st: @pstate, conv: conv_did) -> ty::fn_ty { + let proto = parse_proto(next(st)); + let purity = parse_purity(next(st)); assert (next(st) == '['); let mut inputs: [ty::arg] = []; while peek(st) != ']' { @@ -458,7 +468,7 @@ fn parse_ty_fn(st: @pstate, conv: conv_did) -> ty::fn_ty { st.pos += 1u; // eat the ']' let cs = parse_constrs(st, conv); let (ret_style, ret_ty) = parse_ret_ty(st, conv); - ret {proto: ast::proto_bare, inputs: inputs, output: ret_ty, + ret {purity: purity, proto: proto, inputs: inputs, output: ret_ty, ret_style: ret_style, constraints: cs}; } diff --git a/src/rustc/metadata/tyencode.rs b/src/rustc/metadata/tyencode.rs index cd2f86715e0..eb6631c7a50 100644 --- a/src/rustc/metadata/tyencode.rs +++ b/src/rustc/metadata/tyencode.rs @@ -262,7 +262,6 @@ fn enc_sty(w: io::writer, cx: @ctxt, st: ty::sty) { w.write_char(']'); } ty::ty_fn(f) { - enc_proto(w, f.proto); enc_ty_fn(w, cx, f); } ty::ty_res(def, ty, substs) { @@ -331,7 +330,18 @@ fn enc_mode(w: io::writer, cx: @ctxt, m: mode) { } } +fn enc_purity(w: io::writer, p: purity) { + alt p { + pure_fn { w.write_char('p'); } + impure_fn { w.write_char('i'); } + unsafe_fn { w.write_char('u'); } + crust_fn { w.write_char('c'); } + } +} + fn enc_ty_fn(w: io::writer, cx: @ctxt, ft: ty::fn_ty) { + enc_proto(w, ft.proto); + enc_purity(w, ft.purity); w.write_char('['); for ft.inputs.each {|arg| enc_mode(w, cx, arg.mode); diff --git a/src/rustc/middle/borrowck.rs b/src/rustc/middle/borrowck.rs index 71a1e9d2d21..936f01535c8 100644 --- a/src/rustc/middle/borrowck.rs +++ b/src/rustc/middle/borrowck.rs @@ -13,6 +13,7 @@ import result::{result, ok, err, extensions}; import syntax::print::pprust; import util::common::indenter; import ast_util::op_expr_callee_id; +import ty::to_str; export check_crate, root_map, mutbl_map; @@ -504,14 +505,14 @@ enum check_loan_ctxt = @{ // allow mutating immutable fields in the same class if // we are in a ctor, we track the self id mut in_ctor: bool, - - mut declared_purity: ast::purity + mut declared_purity: ast::purity, + mut fn_args: [ast::node_id] }; // if we are enforcing purity, why are we doing so? enum purity_cause { // enforcing purity because fn was declared pure: - pc_declaration, + pc_pure_fn, // enforce purity because we need to guarantee the // validity of some alias; `bckerr` describes the @@ -526,7 +527,8 @@ fn check_loans(bccx: borrowck_ctxt, req_maps: req_maps, reported: int_hash(), mut in_ctor: false, - mut declared_purity: ast::impure_fn}); + mut declared_purity: ast::impure_fn, + mut fn_args: []}); let vt = visit::mk_vt(@{visit_expr: check_loans_in_expr, visit_block: check_loans_in_block, visit_fn: check_loans_in_fn @@ -570,7 +572,7 @@ impl methods for check_loan_ctxt { // otherwise, remember what was declared as the // default, but we must scan for requirements // imposed by the borrow check - ast::pure_fn { some(pc_declaration) } + ast::pure_fn { some(pc_pure_fn) } ast::crust_fn | ast::impure_fn { none } }; @@ -627,70 +629,79 @@ impl methods for check_loan_ctxt { // when we are in a pure context, we check each call to ensure // that the function which is invoked is itself pure. - fn check_pure(pc: purity_cause, expr: @ast::expr) { + fn check_pure_callee_or_arg(pc: purity_cause, expr: @ast::expr) { let tcx = self.tcx(); - alt ty::get(tcx.ty(expr)).struct { - ty::ty_fn(_) { - // Extract purity or unsafety based on what kind of callee - // we've got. This would be cleaner if we just admitted - // that we have an effect system and carried the purity - // etc around in the type. - // First, check the def_map---if expr.id is present then - // expr must be a path (at least I think that's the idea---NDM) - let callee_purity = alt tcx.def_map.find(expr.id) { - some(ast::def_fn(_, p)) { p } - some(ast::def_variant(_, _)) { ast::pure_fn } - _ { - // otherwise it may be a method call that we can trace - // to the def'n site: - alt self.bccx.method_map.find(expr.id) { - some(typeck::method_static(did)) { - if did.crate == ast::local_crate { - alt tcx.items.get(did.node) { - ast_map::node_method(m, _, _) { m.decl.purity } - _ { tcx.sess.span_bug(expr.span, - "Node not bound \ - to a method") } - } - } else { - metadata::csearch::lookup_method_purity( - tcx.sess.cstore, - did) - } - } - some(typeck::method_param(iid, n_m, _, _)) | - some(typeck::method_iface(iid, n_m)) { - ty::iface_methods(tcx, iid)[n_m].purity - } - none { - // otherwise it's just some dang thing. We know - // it cannot be unsafe because we do not allow - // unsafe functions to be used as values (or, - // rather, we only allow that inside an unsafe - // block, and then it's up to the user to keep - // things confined). - ast::impure_fn - } - } - } - }; + #debug["check_pure_callee_or_arg(pc=%?, expr=%s, ty=%s)", + pc, pprust::expr_to_str(expr), + ty_to_str(self.tcx(), tcx.ty(expr))]; - alt callee_purity { - ast::crust_fn | ast::pure_fn { - /*ok*/ - } - ast::impure_fn | ast::unsafe_fn { + // Purity rules: an expr B is a legal callee or argument to a + // call within a pure function A if at least one of the + // following holds: + // + // (a) A was declared pure and B is one of its arguments; + // (b) B is a stack closure; + // (c) B is a pure fn; + // (d) B is not a fn. + + alt expr.node { + ast::expr_path(_) if pc == pc_pure_fn { + let def = self.tcx().def_map.get(expr.id); + let did = ast_util::def_id_of_def(def); + let is_fn_arg = + did.crate == ast::local_crate && + self.fn_args.contains(did.node); + if is_fn_arg { ret; } // case (a) above + } + ast::expr_fn_block(*) | ast::expr_fn(*) { + if self.is_stack_closure(expr.id) { ret; } // case (b) above + } + _ {} + } + + let expr_ty = tcx.ty(expr); + alt ty::get(expr_ty).struct { + ty::ty_fn(fn_ty) { + alt fn_ty.purity { + ast::pure_fn { ret; } // case (c) above + ast::impure_fn | ast::unsafe_fn | ast::crust_fn { self.report_purity_error( pc, expr.span, - "access to non-pure functions"); + #fmt["access to %s function", + pprust::purity_to_str(fn_ty.purity)]); } } } - _ { /* not a fn, ok */ } + _ { ret; } // case (d) above } } + // True if the expression with the given `id` is a stack closure. + // The expression must be an expr_fn(*) or expr_fn_block(*) + fn is_stack_closure(id: ast::node_id) -> bool { + let fn_ty = ty::node_id_to_type(self.tcx(), id); + let proto = ty::ty_fn_proto(fn_ty); + alt proto { + ast::proto_block | ast::proto_any {true} + ast::proto_bare | ast::proto_uniq | ast::proto_box {false} + } + } + + fn is_allowed_pure_arg(expr: @ast::expr) -> bool { + ret alt expr.node { + ast::expr_path(_) { + let def = self.tcx().def_map.get(expr.id); + let did = ast_util::def_id_of_def(def); + did.crate == ast::local_crate && self.fn_args.contains(did.node) + } + ast::expr_fn_block(*) | ast::expr_fn(*) { + self.is_stack_closure(expr.id) + } + _ {false} + }; + } + fn check_for_conflicting_loans(scope_id: ast::node_id) { let new_loanss = alt self.req_maps.req_loan_map.find(scope_id) { none { ret; } @@ -814,7 +825,7 @@ impl methods for check_loan_ctxt { fn report_purity_error(pc: purity_cause, sp: span, msg: str) { alt pc { - pc_declaration { + pc_pure_fn { self.tcx().sess.span_err( sp, #fmt["%s prohibited in pure context", msg]); @@ -888,23 +899,37 @@ fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk, #debug["purity on entry=%?", self.declared_purity]; save_and_restore(self.in_ctor) {|| save_and_restore(self.declared_purity) {|| - // In principle, we could consider fk_anon(*) or - // fk_fn_block(*) to be in a ctor, I suppose, but the - // purpose of the in_ctor flag is to allow modifications - // of otherwise immutable fields and typestate wouldn't be - // able to "see" into those functions anyway, so it - // wouldn't be very helpful. - alt fk { - visit::fk_ctor(*) { self.in_ctor = true; } - _ { self.in_ctor = false; } - }; + save_and_restore(self.fn_args) {|| + let is_stack_closure = self.is_stack_closure(id); - // NDM this doesn't seem algother right, what about fn items - // nested in pure fns? etc? + // In principle, we could consider fk_anon(*) or + // fk_fn_block(*) to be in a ctor, I suppose, but the + // purpose of the in_ctor flag is to allow modifications + // of otherwise immutable fields and typestate wouldn't be + // able to "see" into those functions anyway, so it + // wouldn't be very helpful. + alt fk { + visit::fk_ctor(*) { + self.in_ctor = true; + self.declared_purity = decl.purity; + self.fn_args = decl.inputs.map({|i| i.id}); + } + visit::fk_anon(*) | + visit::fk_fn_block(*) if is_stack_closure { + self.in_ctor = false; + // inherits the purity/fn_args from enclosing ctxt + } + visit::fk_anon(*) | visit::fk_fn_block(*) | + visit::fk_method(*) | visit::fk_item_fn(*) | + visit::fk_res(*) | visit::fk_dtor(*) { + self.in_ctor = false; + self.declared_purity = decl.purity; + self.fn_args = decl.inputs.map({|i| i.id}); + } + } - self.declared_purity = decl.purity; - - visit::visit_fn(fk, decl, body, sp, id, self, visitor); + visit::visit_fn(fk, decl, body, sp, id, self, visitor); + } } } #debug["purity on exit=%?", self.declared_purity]; @@ -960,8 +985,8 @@ fn check_loans_in_expr(expr: @ast::expr, alt self.purity(expr.id) { none {} some(pc) { - self.check_pure(pc, f); - for args.each { |arg| self.check_pure(pc, arg) } + self.check_pure_callee_or_arg(pc, f); + for args.each { |arg| self.check_pure_callee_or_arg(pc, arg) } } } let arg_tys = ty::ty_fn_args(ty::expr_ty(self.tcx(), f)); diff --git a/src/rustc/middle/trans/base.rs b/src/rustc/middle/trans/base.rs index a6cb56ca264..e4a7b00d01d 100644 --- a/src/rustc/middle/trans/base.rs +++ b/src/rustc/middle/trans/base.rs @@ -791,7 +791,8 @@ fn get_res_dtor(ccx: @crate_ctxt, did: ast::def_id, substs: [ty::t]) } else if did.crate == ast::local_crate { get_item_val(ccx, did.node) } else { - let fty = ty::mk_fn(ccx.tcx, {proto: ast::proto_bare, + let fty = ty::mk_fn(ccx.tcx, {purity: ast::impure_fn, + proto: ast::proto_bare, inputs: [{mode: ast::expl(ast::by_ref), ty: ty::mk_nil_ptr(ccx.tcx)}], output: ty::mk_nil(ccx.tcx), @@ -1949,12 +1950,14 @@ fn normalize_for_monomorphization(tcx: ty::ctxt, ty: ty::t) -> option { // FIXME[mono] could do this recursively. is that worthwhile? alt ty::get(ty).struct { ty::ty_box(mt) { some(ty::mk_opaque_box(tcx)) } - ty::ty_fn(fty) { some(ty::mk_fn(tcx, {proto: fty.proto, + ty::ty_fn(fty) { some(ty::mk_fn(tcx, {purity: ast::impure_fn, + proto: fty.proto, inputs: [], output: ty::mk_nil(tcx), ret_style: ast::return_val, constraints: []})) } - ty::ty_iface(_, _) { some(ty::mk_fn(tcx, {proto: ast::proto_box, + ty::ty_iface(_, _) { some(ty::mk_fn(tcx, {purity: ast::impure_fn, + proto: ast::proto_box, inputs: [], output: ty::mk_nil(tcx), ret_style: ast::return_val, diff --git a/src/rustc/middle/ty.rs b/src/rustc/middle/ty.rs index 6da81e26372..878c58f2402 100644 --- a/src/rustc/middle/ty.rs +++ b/src/rustc/middle/ty.rs @@ -157,6 +157,7 @@ export atttce_unresolved, atttce_resolved; export mach_sty; export ty_sort_str; export normalize_ty; +export to_str; // Data types @@ -290,7 +291,8 @@ enum closure_kind { ck_uniq, } -type fn_ty = {proto: ast::proto, +type fn_ty = {purity: ast::purity, + proto: ast::proto, inputs: [arg], output: t, ret_style: ret_style, @@ -378,6 +380,7 @@ enum terr_vstore_kind { enum type_err { terr_mismatch, terr_ret_style_mismatch(ast::ret_style, ast::ret_style), + terr_purity_mismatch(purity, purity), terr_mutability, terr_proto_mismatch(ast::proto, ast::proto), terr_box_mutability, @@ -425,6 +428,12 @@ impl of vid for region_vid { fn to_str() -> str { #fmt["", self.to_uint()] } } +impl of to_str::to_str for purity { + fn to_str() -> str { + purity_to_str(self) + } +} + fn param_bounds_to_kind(bounds: param_bounds) -> kind { let mut kind = kind_noncopyable(); for vec::each(*bounds) {|bound| @@ -2375,6 +2384,9 @@ fn type_err_to_str(cx: ctxt, err: type_err) -> str { ret to_str(actual) + " function found where " + to_str(expect) + " function was expected"; } + terr_purity_mismatch(f1, f2) { + ret #fmt["expected %s fn but found %s fn", f1.to_str(), f2.to_str()]; + } terr_proto_mismatch(e, a) { ret #fmt["closure protocol mismatch (%s vs %s)", proto_to_str(e), proto_to_str(a)]; diff --git a/src/rustc/middle/typeck.rs b/src/rustc/middle/typeck.rs index 71164588dc5..5c70d585cd4 100644 --- a/src/rustc/middle/typeck.rs +++ b/src/rustc/middle/typeck.rs @@ -197,8 +197,8 @@ fn check_main_fn_ty(ccx: @crate_ctxt, let tcx = ccx.tcx; let main_t = ty::node_id_to_type(tcx, main_id); alt ty::get(main_t).struct { - ty::ty_fn({proto: ast::proto_bare, inputs, output, - ret_style: ast::return_val, constraints}) { + ty::ty_fn({purity: ast::impure_fn, proto: ast::proto_bare, + inputs, output, ret_style: ast::return_val, constraints}) { alt tcx.items.find(main_id) { some(ast_map::node_item(it,_)) { alt it.node { diff --git a/src/rustc/middle/typeck/astconv.rs b/src/rustc/middle/typeck/astconv.rs index 0ba90e07984..d8ce3d83a60 100644 --- a/src/rustc/middle/typeck/astconv.rs +++ b/src/rustc/middle/typeck/astconv.rs @@ -429,7 +429,8 @@ fn ty_of_fn_decl( let out_constrs = vec::map(decl.constraints) {|constr| ty::ast_constr_to_constr(self.tcx(), constr) }; - {proto: proto, inputs: input_tys, + + {purity: decl.purity, proto: proto, inputs: input_tys, output: output_ty, ret_style: decl.cf, constraints: out_constrs} } } diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs index deede6b5e6f..7169f7e924f 100644 --- a/src/rustc/middle/typeck/check.rs +++ b/src/rustc/middle/typeck/check.rs @@ -1379,7 +1379,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, i += 1u; } - let ft = ty::mk_fn(tcx, {proto: proto, + let ft = ty::mk_fn(tcx, {purity: ast::impure_fn, proto: proto, inputs: out_args, output: rt, ret_style: cf, constraints: constrs}); fcx.write_ty(id, ft); @@ -1625,7 +1625,8 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, let ty_nilp = ty::mk_ptr(tcx, {ty: ty::mk_nil(tcx), mutbl: ast::m_imm}); let m = ast::expl(ty::default_arg_mode_for_ty(ty_uint)); - ty::mk_fn(tcx, {proto: ast::proto_any, + ty::mk_fn(tcx, {purity: ast::impure_fn, + proto: ast::proto_any, inputs: [{mode: m, ty: ty_uint}, {mode: m, ty: ty_uint}], output: ty_nilp, diff --git a/src/rustc/middle/typeck/collect.rs b/src/rustc/middle/typeck/collect.rs index 71051954fb7..55bdc8cbb37 100644 --- a/src/rustc/middle/typeck/collect.rs +++ b/src/rustc/middle/typeck/collect.rs @@ -113,7 +113,8 @@ fn get_enum_variant_types(ccx: @crate_ctxt, let arg_ty = ccx.to_ty(rs, va.ty); {mode: ast::expl(ast::by_copy), ty: arg_ty} }; - ty::mk_fn(tcx, {proto: ast::proto_box, + ty::mk_fn(tcx, {purity: ast::pure_fn, + proto: ast::proto_box, inputs: args, output: enum_ty, ret_style: ast::return_val, @@ -344,12 +345,14 @@ fn convert(ccx: @crate_ctxt, it: @ast::item) { let t_res = ty::mk_res(tcx, def_id, t_arg.ty, substs); let t_ctor = ty::mk_fn(tcx, { + purity: ast::pure_fn, proto: ast::proto_box, inputs: [{mode: ast::expl(ast::by_copy), ty: t_arg.ty}], output: t_res, ret_style: ast::return_val, constraints: [] }); let t_dtor = ty::mk_fn(tcx, { + purity: ast::impure_fn, proto: ast::proto_box, inputs: [t_arg], output: ty::mk_nil(tcx), ret_style: ast::return_val, constraints: [] @@ -521,7 +524,8 @@ fn check_intrinsic_type(ccx: @crate_ctxt, it: @ast::native_item) { ret; } }; - let fty = ty::mk_fn(tcx, {proto: ast::proto_bare, + let fty = ty::mk_fn(tcx, {purity: ast::impure_fn, + proto: ast::proto_bare, inputs: inputs, output: output, ret_style: ast::return_val, constraints: []}); @@ -594,6 +598,8 @@ fn ty_of_item(ccx: @crate_ctxt, it: @ast::item) let tpt = {bounds: bounds, rp: ast::rp_none, // functions do not have a self ty: ty::mk_fn(ccx.tcx, tofd)}; + #debug["type of %s (id %d) is %s", + it.ident, it.id, ty_to_str(tcx, tpt.ty)]; ccx.tcx.tcache.insert(local_def(it.id), tpt); ret tpt; } @@ -715,7 +721,8 @@ fn ty_of_native_fn_decl(ccx: @crate_ctxt, let input_tys = decl.inputs.map { |a| ty_of_arg(ccx, rb, a, none) }; let output_ty = ast_ty_to_ty(ccx, rb, decl.output); - let t_fn = ty::mk_fn(ccx.tcx, {proto: ast::proto_bare, + let t_fn = ty::mk_fn(ccx.tcx, {purity: decl.purity, + proto: ast::proto_bare, inputs: input_tys, output: output_ty, ret_style: ast::return_val, diff --git a/src/rustc/middle/typeck/infer.rs b/src/rustc/middle/typeck/infer.rs index 97bd43cc527..2e2d4e629ce 100644 --- a/src/rustc/middle/typeck/infer.rs +++ b/src/rustc/middle/typeck/infer.rs @@ -149,13 +149,14 @@ import std::map::hashmap; import middle::ty; import middle::ty::{ty_vid, tys_in_fn_ty, region_vid, vid}; import syntax::{ast, ast_util}; -import syntax::ast::{ret_style}; +import syntax::ast::{ret_style, purity}; import util::ppaux::{ty_to_str, mt_to_str}; import result::{result, extensions, ok, err, map_vec, map_vec2, iter_vec2}; import ty::{mk_fn, type_is_bot}; import check::regionmanip::{replace_bound_regions_in_fn_ty}; import driver::session::session; import util::common::{indent, indenter}; +import ast::{unsafe_fn, impure_fn, pure_fn, crust_fn}; export infer_ctxt; export new_infer_ctxt; @@ -1179,6 +1180,7 @@ iface combine { fn args(a: ty::arg, b: ty::arg) -> cres; fn protos(p1: ast::proto, p2: ast::proto) -> cres; fn ret_styles(r1: ret_style, r2: ret_style) -> cres; + fn purities(f1: purity, f2: purity) -> cres; fn contraregions(a: ty::region, b: ty::region) -> cres; fn regions(a: ty::region, b: ty::region) -> cres; fn vstores(vk: ty::terr_vstore_kind, @@ -1342,14 +1344,17 @@ fn super_fns( self.ret_styles(a_f.ret_style, b_f.ret_style).chain {|rs| argvecs(self, a_f.inputs, b_f.inputs).chain {|inputs| self.tys(a_f.output, b_f.output).chain {|output| + self.purities(a_f.purity, b_f.purity).chain {|purity| //FIXME self.infcx().constrvecs(a_f.constraints, //FIXME b_f.constraints).then {|| - ok({proto: p, + ok({purity: purity, + proto: p, inputs: inputs, output: output, ret_style: rs, constraints: a_f.constraints}) //FIXME } + } } } } @@ -1579,6 +1584,12 @@ impl of combine for sub { } } + fn purities(f1: purity, f2: purity) -> cres { + self.lub().purities(f1, f2).compare(f2) {|| + ty::terr_purity_mismatch(f2, f1) + } + } + fn ret_styles(a: ret_style, b: ret_style) -> cres { self.lub().ret_styles(a, b).compare(b) {|| ty::terr_ret_style_mismatch(b, a) @@ -1739,6 +1750,15 @@ impl of combine for lub { } } + fn purities(f1: purity, f2: purity) -> cres { + alt (f1, f2) { + (unsafe_fn, _) | (_, unsafe_fn) {ok(unsafe_fn)} + (impure_fn, _) | (_, impure_fn) {ok(impure_fn)} + (crust_fn, _) | (_, crust_fn) {ok(crust_fn)} + (pure_fn, pure_fn) {ok(pure_fn)} + } + } + fn ret_styles(r1: ret_style, r2: ret_style) -> cres { alt (r1, r2) { (ast::return_val, _) | @@ -1931,6 +1951,15 @@ impl of combine for glb { } } + fn purities(f1: purity, f2: purity) -> cres { + alt (f1, f2) { + (pure_fn, _) | (_, pure_fn) {ok(pure_fn)} + (crust_fn, _) | (_, crust_fn) {ok(crust_fn)} + (impure_fn, _) | (_, impure_fn) {ok(impure_fn)} + (unsafe_fn, unsafe_fn) {ok(unsafe_fn)} + } + } + fn ret_styles(r1: ret_style, r2: ret_style) -> cres { alt (r1, r2) { (ast::return_val, ast::return_val) { diff --git a/src/rustc/util/ppaux.rs b/src/rustc/util/ppaux.rs index 0e3682963f6..47cdd8cf153 100644 --- a/src/rustc/util/ppaux.rs +++ b/src/rustc/util/ppaux.rs @@ -5,7 +5,7 @@ import metadata::encoder; import syntax::codemap; import syntax::print::pprust; import syntax::print::pprust::{path_to_str, constr_args_to_str, proto_to_str, - mode_to_str}; + mode_to_str, purity_to_str}; import syntax::{ast, ast_util}; import syntax::ast_map; import driver::session::session; @@ -107,10 +107,17 @@ fn ty_to_str(cx: ctxt, typ: t) -> str { }; modestr + ty_to_str(cx, ty) } - fn fn_to_str(cx: ctxt, proto: ast::proto, ident: option, + fn fn_to_str(cx: ctxt, purity: ast::purity, proto: ast::proto, + ident: option, inputs: [arg], output: t, cf: ast::ret_style, constrs: [@constr]) -> str { - let mut s = proto_to_str(proto); + let mut s; + + s = alt purity { + ast::impure_fn {""} + _ {purity_to_str(purity) + " "} + }; + s += proto_to_str(proto); alt ident { some(i) { s += " "; s += i; } _ { } } s += "("; let mut strs = []; @@ -128,8 +135,9 @@ fn ty_to_str(cx: ctxt, typ: t) -> str { ret s; } fn method_to_str(cx: ctxt, m: method) -> str { - ret fn_to_str(cx, m.fty.proto, some(m.ident), m.fty.inputs, - m.fty.output, m.fty.ret_style, m.fty.constraints) + ";"; + ret fn_to_str( + cx, m.fty.purity, m.fty.proto, some(m.ident), m.fty.inputs, + m.fty.output, m.fty.ret_style, m.fty.constraints) + ";"; } fn field_to_str(cx: ctxt, f: field) -> str { ret f.ident + ": " + mt_to_str(cx, f.mt); @@ -178,8 +186,8 @@ fn ty_to_str(cx: ctxt, typ: t) -> str { "(" + str::connect(strs, ",") + ")" } ty_fn(f) { - fn_to_str(cx, f.proto, none, f.inputs, f.output, f.ret_style, - f.constraints) + fn_to_str(cx, f.purity, f.proto, none, f.inputs, + f.output, f.ret_style, f.constraints) } ty_var(v) { v.to_str() } ty_param(id, _) { diff --git a/src/test/compile-fail/borrowck-lend-args.rs b/src/test/compile-fail/borrowck-lend-args.rs index b9036efa870..aa2358121e2 100644 --- a/src/test/compile-fail/borrowck-lend-args.rs +++ b/src/test/compile-fail/borrowck-lend-args.rs @@ -9,7 +9,7 @@ fn borrow_from_arg_imm_ref(&&v: ~int) { fn borrow_from_arg_mut_ref(&v: ~int) { borrow(v); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn borrow_from_arg_move(-v: ~int) { diff --git a/src/test/compile-fail/borrowck-pat-enum-in-box.rs b/src/test/compile-fail/borrowck-pat-enum-in-box.rs index deeb2a57415..32135b5ad8f 100644 --- a/src/test/compile-fail/borrowck-pat-enum-in-box.rs +++ b/src/test/compile-fail/borrowck-pat-enum-in-box.rs @@ -31,7 +31,7 @@ fn process(_i: int) {} fn match_const_box_and_do_bad_things(v: &const @const option) { alt *v { @some(i) { //! ERROR illegal borrow unless pure: enum variant in aliasable, mutable location - process(i) //! NOTE impure due to access to non-pure functions + process(i) //! NOTE impure due to access to impure function } @none {} } diff --git a/src/test/compile-fail/borrowck-pat-enum.rs b/src/test/compile-fail/borrowck-pat-enum.rs index 30e9db65f0e..753b51b0251 100644 --- a/src/test/compile-fail/borrowck-pat-enum.rs +++ b/src/test/compile-fail/borrowck-pat-enum.rs @@ -37,7 +37,7 @@ fn match_const_reg_unused(v: &const option) { fn match_const_reg_impure(v: &const option) { alt *v { some(i) {impure(i)} //! ERROR illegal borrow unless pure: enum variant in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function none {} } } diff --git a/src/test/compile-fail/borrowck-unchecked-with-borrow.rs b/src/test/compile-fail/borrowck-unchecked-with-borrow.rs index d16c4a16acc..cefe3ba0553 100644 --- a/src/test/compile-fail/borrowck-unchecked-with-borrow.rs +++ b/src/test/compile-fail/borrowck-unchecked-with-borrow.rs @@ -9,7 +9,7 @@ fn foo(v: &const option) { some(i) { //!^ ERROR illegal borrow unless pure: enum variant in aliasable, mutable location unchecked { - impure(i); //! NOTE impure due to access to non-pure functions + impure(i); //! NOTE impure due to access to impure function } } none { diff --git a/src/test/compile-fail/borrowck-uniq-via-box.rs b/src/test/compile-fail/borrowck-uniq-via-box.rs index a6a94663b8f..4433dba0c19 100644 --- a/src/test/compile-fail/borrowck-uniq-via-box.rs +++ b/src/test/compile-fail/borrowck-uniq-via-box.rs @@ -5,22 +5,22 @@ fn borrow(_v: &int) {} fn box_mut(v: @mut ~int) { borrow(*v); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_rec_mut(v: @{mut f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_mut_rec(v: @mut {f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_mut_recs(v: @mut {f: {g: {h: ~int}}}) { borrow(v.f.g.h); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_imm(v: @~int) { @@ -37,27 +37,27 @@ fn box_imm_recs(v: @{f: {g: {h: ~int}}}) { fn box_const(v: @const ~int) { borrow(*v); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_rec_const(v: @{const f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_recs_const(v: @{f: {g: {const h: ~int}}}) { borrow(v.f.g.h); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_const_rec(v: @const {f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_const_recs(v: @const {f: {g: {h: ~int}}}) { borrow(v.f.g.h); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn main() { diff --git a/src/test/compile-fail/borrowck-uniq-via-ref.rs b/src/test/compile-fail/borrowck-uniq-via-ref.rs index 166b3284c9b..b0fae6622d0 100644 --- a/src/test/compile-fail/borrowck-uniq-via-ref.rs +++ b/src/test/compile-fail/borrowck-uniq-via-ref.rs @@ -4,22 +4,22 @@ fn borrow(_v: &int) {} fn box_mut(v: &mut ~int) { borrow(*v); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_rec_mut(v: &{mut f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_mut_rec(v: &mut {f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_mut_recs(v: &mut {f: {g: {h: ~int}}}) { borrow(v.f.g.h); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_imm(v: &~int) { @@ -36,27 +36,27 @@ fn box_imm_recs(v: &{f: {g: {h: ~int}}}) { fn box_const(v: &const ~int) { borrow(*v); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_rec_const(v: &{const f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_recs_const(v: &{f: {g: {const h: ~int}}}) { borrow(v.f.g.h); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_const_rec(v: &const {f: ~int}) { borrow(v.f); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn box_const_recs(v: &const {f: {g: {h: ~int}}}) { borrow(v.f.g.h); //! ERROR illegal borrow unless pure: unique value in aliasable, mutable location - //!^ NOTE impure due to access to non-pure functions + //!^ NOTE impure due to access to impure function } fn main() { diff --git a/src/test/compile-fail/impure-pred.rs b/src/test/compile-fail/impure-pred.rs index 9c6d0431e18..41990fe167c 100644 --- a/src/test/compile-fail/impure-pred.rs +++ b/src/test/compile-fail/impure-pred.rs @@ -3,7 +3,7 @@ fn g() { } pure fn f(_q: int) -> bool { - g(); //! ERROR access to non-pure functions prohibited in pure context + g(); //! ERROR access to impure function prohibited in pure context ret true; } diff --git a/src/test/compile-fail/pure-higher-order.rs b/src/test/compile-fail/pure-higher-order.rs new file mode 100644 index 00000000000..22d43c27e1a --- /dev/null +++ b/src/test/compile-fail/pure-higher-order.rs @@ -0,0 +1,48 @@ +// Test rules governing higher-order pure fns. + +pure fn range(from: uint, to: uint, f: fn(uint)) { + let mut i = from; + while i < to { + f(i); // Note: legal to call argument, even if it is not pure. + i += 1u; + } +} + +pure fn range2(from: uint, to: uint, f: fn(uint)) { + range(from, to) { |i| + f(i*2u); + } +} + +pure fn range3(from: uint, to: uint, f: fn(uint)) { + range(from, to, f) +} + +pure fn range4(from: uint, to: uint) { + range(from, to, print) //! ERROR access to impure function prohibited in pure context +} + +pure fn range5(from: uint, to: uint, x: {f: fn(uint)}) { + range(from, to, x.f) //! ERROR access to impure function prohibited in pure context +} + +pure fn range6(from: uint, to: uint, x: @{f: fn(uint)}) { + range(from, to, x.f) //! ERROR access to impure function prohibited in pure context +} + +pure fn range7(from: uint, to: uint) { + range(from, to) { |i| + print(i); //! ERROR access to impure function prohibited in pure context + } +} + +pure fn range8(from: uint, to: uint) { + range(from, to, noop); +} + +fn print(i: uint) { #error["i=%u", i]; } + +pure fn noop(_i: uint) {} + +fn main() { +} \ No newline at end of file