Infer purity for || style closures. Closes #3023.

This commit is contained in:
Michael Sullivan 2012-08-24 14:01:08 -07:00
parent b5dd01eb2a
commit e55c5ceac2
5 changed files with 72 additions and 51 deletions

View File

@ -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 );
}
}

View File

@ -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;

View File

@ -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<ast::proto>,
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));

View File

@ -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
}

View File

@ -0,0 +1,5 @@
fn something(f: pure fn()) { f(); }
fn main() {
something(|| log(error, "hi!") );
}