From e55c5ceac239417784f9fe8c37f92e974b9cc06e Mon Sep 17 00:00:00 2001 From: Michael Sullivan Date: Fri, 24 Aug 2012 14:01:08 -0700 Subject: [PATCH] Infer purity for || style closures. Closes #3023. --- src/rustc/middle/borrowck/check_loans.rs | 11 +-- src/rustc/middle/ty.rs | 14 ++++ src/rustc/middle/typeck/check.rs | 87 ++++++++++------------ src/test/compile-fail/purity-infer-fail.rs | 6 ++ src/test/run-pass/purity-infer.rs | 5 ++ 5 files changed, 72 insertions(+), 51 deletions(-) create mode 100644 src/test/compile-fail/purity-infer-fail.rs create mode 100644 src/test/run-pass/purity-infer.rs diff --git a/src/rustc/middle/borrowck/check_loans.rs b/src/rustc/middle/borrowck/check_loans.rs index 7c18133ae02d..6d9713dcd068 100644 --- a/src/rustc/middle/borrowck/check_loans.rs +++ b/src/rustc/middle/borrowck/check_loans.rs @@ -521,8 +521,11 @@ fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk, do save_and_restore(self.declared_purity) { do save_and_restore(self.fn_args) { let is_stack_closure = self.is_stack_closure(id); - let purity = - ty::ty_fn_purity(ty::node_id_to_type(self.tcx(), id)); + let fty = ty::node_id_to_type(self.tcx(), id); + self.declared_purity = ty::determine_inherited_purity( + copy self.declared_purity, + ty::ty_fn_purity(fty), + ty::ty_fn_proto(fty)); // In principle, we could consider fk_anon(*) or // fk_fn_block(*) to be in a ctor, I suppose, but the @@ -533,19 +536,17 @@ fn check_loans_in_fn(fk: visit::fn_kind, decl: ast::fn_decl, body: ast::blk, match fk { visit::fk_ctor(*) => { self.in_ctor = true; - self.declared_purity = 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 + // inherits the fn_args from enclosing ctxt } visit::fk_anon(*) | visit::fk_fn_block(*) | visit::fk_method(*) | visit::fk_item_fn(*) | visit::fk_dtor(*) => { self.in_ctor = false; - self.declared_purity = purity; self.fn_args = @decl.inputs.map(|i| i.id ); } } diff --git a/src/rustc/middle/ty.rs b/src/rustc/middle/ty.rs index b5411dd597c8..9bdf2074ba1e 100644 --- a/src/rustc/middle/ty.rs +++ b/src/rustc/middle/ty.rs @@ -187,6 +187,7 @@ export region_variance, rv_covariant, rv_invariant, rv_contravariant; export serialize_region_variance, deserialize_region_variance; export opt_region_variance; export serialize_opt_region_variance, deserialize_opt_region_variance; +export determine_inherited_purity; // Data types @@ -3406,6 +3407,19 @@ pure fn is_blockish(proto: fn_proto) -> bool { } } +// Determine what purity to check a nested function under +pure fn determine_inherited_purity(parent_purity: ast::purity, + child_purity: ast::purity, + child_proto: ty::fn_proto) -> ast::purity { + // If the closure is a stack closure and hasn't had some non-standard + // purity inferred for it, then check it under its parent's purity. + // Otherwise, use its own + if ty::is_blockish(child_proto) && child_purity == ast::impure_fn { + parent_purity + } else { child_purity } +} + + // Local Variables: // mode: rust // fill-column: 78; diff --git a/src/rustc/middle/typeck/check.rs b/src/rustc/middle/typeck/check.rs index aa51f2dd4a2c..b8ee661b1b0d 100644 --- a/src/rustc/middle/typeck/check.rs +++ b/src/rustc/middle/typeck/check.rs @@ -228,10 +228,10 @@ fn check_fn(ccx: @crate_ctxt, node_type_substs: map::int_hash()} } some(fcx) => { - assert fn_ty.purity == ast::impure_fn; {infcx: fcx.infcx, locals: fcx.locals, - purity: fcx.purity, + purity: ty::determine_inherited_purity(fcx.purity, fn_ty.purity, + fn_ty.proto), node_types: fcx.node_types, node_type_substs: fcx.node_type_substs} } @@ -1187,14 +1187,9 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, } } - enum fn_or_ast_proto { - foap_fn_proto(ty::fn_proto), - foap_ast_proto(ast::proto) - } - fn check_expr_fn(fcx: @fn_ctxt, expr: @ast::expr, - fn_or_ast_proto: fn_or_ast_proto, + ast_proto_opt: option, decl: ast::fn_decl, body: ast::blk, is_loop_body: bool, @@ -1205,44 +1200,48 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, // avoid capture of bound regions in the expected type. See // def'n of br_cap_avoid() for a more lengthy explanation of // what's going on here. - let expected_tys = do unpack_expected(fcx, expected) |sty| { - match sty { - ty::ty_fn(ref fn_ty) => { + // Also try to pick up inferred purity and proto, defaulting + // to impure and block. Note that we only will use those for + // block syntax lambdas; that is, lambdas without explicit + // protos. + let expected_sty = unpack_expected(fcx, expected, |x| some(x)); + let (expected_tys, expected_purity, expected_proto) = + match expected_sty { + some(ty::ty_fn(ref fn_ty)) => { let {fn_ty, _} = replace_bound_regions_in_fn_ty( tcx, @nil, none, fn_ty, |br| ty::re_bound(ty::br_cap_avoid(expr.id, @br))); - some({inputs:fn_ty.inputs, - output:fn_ty.output}) + (some({inputs:fn_ty.inputs, + output:fn_ty.output}), + fn_ty.purity, + fn_ty.proto) } - _ => {none} - } - }; + _ => { + (none, ast::impure_fn, ty::proto_vstore(ty::vstore_box)) + } + }; - let ast_proto; - match fn_or_ast_proto { - foap_fn_proto(fn_proto) => { - // Generate a fake AST prototype. We'll fill in the type with - // the real one later. - // XXX: This is a hack. - ast_proto = ast::proto_box; - } - foap_ast_proto(existing_ast_proto) => { - ast_proto = existing_ast_proto; - } - } - let purity = ast::impure_fn; + // Generate AST prototypes and purity. + // If this is a block lambda (ast_proto == none), these values + // are bogus. We'll fill in the type with the real one later. + // XXX: This is a hack. + let ast_proto = ast_proto_opt.get_default(ast::proto_box); + let ast_purity = ast::impure_fn; // construct the function type - let mut fn_ty = astconv::ty_of_fn_decl(fcx, fcx, ast_proto, purity, - @~[], + let mut fn_ty = astconv::ty_of_fn_decl(fcx, fcx, + ast_proto, ast_purity, @~[], decl, expected_tys, expr.span); // Patch up the function declaration, if necessary. - match fn_or_ast_proto { - foap_fn_proto(fn_proto) => fn_ty.proto = fn_proto, - foap_ast_proto(_) => {} + match ast_proto_opt { + none => { + fn_ty.purity = expected_purity; + fn_ty.proto = expected_proto; + } + some(_) => { } } let fty = ty::mk_fn(tcx, fn_ty); @@ -1602,17 +1601,13 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, bot = alt::check_alt(fcx, expr, discrim, arms); } ast::expr_fn(proto, decl, body, cap_clause) => { - check_expr_fn(fcx, expr, foap_ast_proto(proto), + check_expr_fn(fcx, expr, some(proto), decl, body, false, expected); capture::check_capture_clause(tcx, expr.id, cap_clause); } ast::expr_fn_block(decl, body, cap_clause) => { - // Take the prototype from the expected type, but default to block: - let proto = do unpack_expected(fcx, expected) |sty| { - match sty { ty::ty_fn({proto, _}) => some(proto), _ => none } - }.get_default(ty::proto_vstore(ty::vstore_box)); - check_expr_fn(fcx, expr, foap_fn_proto(proto), + check_expr_fn(fcx, expr, none, decl, body, false, expected); capture::check_capture_clause(tcx, expr.id, cap_clause); @@ -1625,7 +1620,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, // 1. a closure that returns a bool is expected // 2. the cloure that was given returns unit let expected_sty = unpack_expected(fcx, expected, |x| some(x)); - let (inner_ty, proto) = match expected_sty { + let inner_ty = match expected_sty { some(ty::ty_fn(fty)) => { match fcx.mk_subty(false, expr.span, fty.output, ty::mk_bool(tcx)) { @@ -1637,7 +1632,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, fcx.infcx.ty_to_str(fty.output))); } } - (ty::mk_fn(tcx, {output: ty::mk_nil(tcx) with fty}), fty.proto) + ty::mk_fn(tcx, {output: ty::mk_nil(tcx) with fty}) } _ => { tcx.sess.span_fatal(expr.span, ~"a `loop` function's last \ @@ -1647,7 +1642,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, }; match check b.node { ast::expr_fn_block(decl, body, cap_clause) => { - check_expr_fn(fcx, b, foap_fn_proto(proto), + check_expr_fn(fcx, b, none, decl, body, true, some(inner_ty)); demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b)); @@ -1665,9 +1660,9 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, } ast::expr_do_body(b) => { let expected_sty = unpack_expected(fcx, expected, |x| some(x)); - let (inner_ty, proto) = match expected_sty { + let inner_ty = match expected_sty { some(ty::ty_fn(fty)) => { - (ty::mk_fn(tcx, fty), fty.proto) + ty::mk_fn(tcx, fty) } _ => { tcx.sess.span_fatal(expr.span, ~"Non-function passed to a `do` \ @@ -1677,7 +1672,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, }; match check b.node { ast::expr_fn_block(decl, body, cap_clause) => { - check_expr_fn(fcx, b, foap_fn_proto(proto), + check_expr_fn(fcx, b, none, decl, body, true, some(inner_ty)); demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b)); diff --git a/src/test/compile-fail/purity-infer-fail.rs b/src/test/compile-fail/purity-infer-fail.rs new file mode 100644 index 000000000000..691829edf597 --- /dev/null +++ b/src/test/compile-fail/purity-infer-fail.rs @@ -0,0 +1,6 @@ +fn something(f: pure fn()) { f(); } + +fn main() { + let mut x = ~[]; + something(|| vec::push(x, 0) ); //~ ERROR access to impure function prohibited in pure context +} diff --git a/src/test/run-pass/purity-infer.rs b/src/test/run-pass/purity-infer.rs new file mode 100644 index 000000000000..4d031be616fc --- /dev/null +++ b/src/test/run-pass/purity-infer.rs @@ -0,0 +1,5 @@ + +fn something(f: pure fn()) { f(); } +fn main() { + something(|| log(error, "hi!") ); +}