Skip to content

Commit

Permalink
librustc: Add a hack to allow unboxed closures to achieve the 'static
Browse files Browse the repository at this point in the history
bound if possible.

Closes rust-lang#16560.
  • Loading branch information
pcwalton committed Aug 20, 2014
1 parent 3f5d0b5 commit eb43525
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/librustc/middle/freevars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use syntax::codemap::Span;
use syntax::visit::Visitor;
use syntax::visit;

#[deriving(Clone, Decodable, Encodable, Show)]
#[deriving(Clone, Decodable, Encodable, PartialEq, Eq, Show)]
pub enum CaptureMode {
/// Copy/move the value from this llvm ValueRef into the environment.
CaptureByValue,
Expand Down
2 changes: 2 additions & 0 deletions src/librustc/middle/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2254,6 +2254,8 @@ pub fn type_contents(cx: &ctxt, ty: t) -> TypeContents {
// FIXME(#14449): `borrowed_contents` below assumes `&mut`
// unboxed closure.
let upvars = unboxed_closure_upvars(cx, did);
debug!("region of unboxed closure is {}",
r.repr(cx));
TypeContents::union(upvars.as_slice(),
|f| tc_ty(cx, f.ty, cache)) |
borrowed_contents(r, MutMutable)
Expand Down
121 changes: 89 additions & 32 deletions src/librustc/middle/typeck/check/regionck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,14 +666,23 @@ fn check_expr_fn_block(rcx: &mut Rcx,
});
}
ty::ty_unboxed_closure(_, region) => {
debug!("regionck: constraining region of unboxed closure");
freevars::with_freevars(tcx, expr.id, |freevars| {
// No free variables means that there is no environment and
// hence the closure has static lifetime. Otherwise, the
// closure must not outlive the variables it closes over
// by-reference.
if !freevars.is_empty() {
constrain_free_variables(rcx, region, expr, freevars);
}
if freevars.is_empty() {
// No free variables means that there is no
// environment and hence the closure has static
// lifetime. Otherwise, the closure must not outlive
// the variables it closes over by-reference.
rcx.fcx.mk_subr(false,
infer::MiscRegion(expr.span),
ty::ReStatic,
region);
} else {
constrain_free_variables(rcx,
region,
expr,
freevars);
}
})
}
_ => ()
Expand Down Expand Up @@ -709,36 +718,79 @@ fn check_expr_fn_block(rcx: &mut Rcx,
let infcx = rcx.fcx.infcx();
debug!("constrain_free_variables({}, {})",
region.repr(tcx), expr.repr(tcx));
let capture_mode = freevars::get_capture_mode(tcx, expr.id);
let mut all_static = capture_mode == freevars::CaptureByValue;
for freevar in freevars.iter() {
debug!("freevar def is {:?}", freevar.def);

// Identify the variable being closed over and its node-id.
let def = freevar.def;
let def_id = def.def_id();
assert!(def_id.krate == ast::LOCAL_CRATE);
let upvar_id = ty::UpvarId { var_id: def_id.node,
closure_expr_id: expr.id };

// Create a region variable to represent this borrow. This borrow
// must outlive the region on the closure.
let origin = infer::UpvarRegion(upvar_id, expr.span);
let freevar_region = infcx.next_region_var(origin);
rcx.fcx.mk_subr(true, infer::FreeVariable(freevar.span, def_id.node),
region, freevar_region);

// Create a UpvarBorrow entry. Note that we begin with a
// const borrow_kind, but change it to either mut or
// immutable as dictated by the uses.
let upvar_borrow = ty::UpvarBorrow { kind: ty::ImmBorrow,
region: freevar_region };
rcx.fcx.inh.upvar_borrow_map.borrow_mut().insert(upvar_id,
upvar_borrow);

// Guarantee that the closure does not outlive the variable itself.
let en_region = region_of_def(rcx.fcx, def);
debug!("en_region = {}", en_region.repr(tcx));
rcx.fcx.mk_subr(true, infer::FreeVariable(freevar.span, def_id.node),
region, en_region);
let upvar_id = ty::UpvarId {
var_id: def_id.node,
closure_expr_id: expr.id,
};

if capture_mode == freevars::CaptureByRef {
// Create a region variable to represent this borrow. This
// borrow must outlive the region on the closure.
let origin = infer::UpvarRegion(upvar_id, expr.span);
let freevar_region = infcx.next_region_var(origin);
rcx.fcx.mk_subr(true,
infer::FreeVariable(freevar.span,
def_id.node),
region,
freevar_region);

// Create a UpvarBorrow entry. Note that we begin with a
// const borrow_kind, but change it to either mut or
// immutable as dictated by the uses.
let upvar_borrow = ty::UpvarBorrow {
kind: ty::ImmBorrow,
region: freevar_region,
};
rcx.fcx
.inh
.upvar_borrow_map
.borrow_mut()
.insert(upvar_id, upvar_borrow);

// Guarantee that the closure does not outlive the variable
// itself.
let en_region = region_of_def(rcx.fcx, def);
debug!("en_region = {}", en_region.repr(tcx));
rcx.fcx.mk_subr(true,
infer::FreeVariable(freevar.span,
def_id.node),
region,
en_region);
}

// FIXME(pcwalton): As a hack, if the type of the variable being
// closed over has no regions and this is a by-value capture,
// record a `'static` bound.
let local_type = rcx.fcx.local_ty(freevar.span, def_id.node);
let local_type =
rcx.fcx.infcx().resolve_type_vars_if_possible(local_type);
if all_static && !ty::type_is_static(rcx.tcx(), local_type) {
debug!("regionck: not static: {}",
rcx.fcx
.local_ty(freevar.span, def_id.node)
.repr(rcx.tcx()));
all_static = false
}
}

// FIXME(pcwalton): As a hack, if the type of the variable being
// closed over has no regions and this is a by-value capture,
// record a `'static` bound.
if all_static {
debug!("regionck: all static!");
rcx.fcx.mk_subr(false,
infer::MiscRegion(expr.span),
ty::ReStatic,
region);
}
}

Expand Down Expand Up @@ -1022,9 +1074,14 @@ fn constrain_regions_in_type_of_node(
rcx.fcx.inh.adjustments.borrow().find(&id),
|method_call| rcx.resolve_method_type(method_call));
debug!("constrain_regions_in_type_of_node(\
ty={}, ty0={}, id={}, minimum_lifetime={:?})",
ty={},\
ty0={},\
id={},\
minimum_lifetime={:?},\
origin={:?})",
ty_to_string(tcx, ty), ty_to_string(tcx, ty0),
id, minimum_lifetime);
id, minimum_lifetime,
origin);
constrain_regions_in_type(rcx, minimum_lifetime, origin, ty);
}

Expand Down
16 changes: 16 additions & 0 deletions src/librustc/middle/typeck/infer/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,19 @@ impl<'a> ErrorReporting for InferCtxt<'a> {
sup,
"");
}
infer::MiscRegion(span) => {
self.tcx.sess.span_err(span, "reference is not valid");
note_and_explain_region(
self.tcx,
"the reference is valid for ",
sub,
"");
note_and_explain_region(
self.tcx,
"but the referenced data is only valid for ",
sup,
"");
}
}
}

Expand Down Expand Up @@ -1425,6 +1438,9 @@ impl<'a> ErrorReportingHelpers for InferCtxt<'a> {
"...so that the pointer does not outlive the \
data it points at");
}
infer::MiscRegion(span) => {
self.tcx.sess.span_note(span, "...so that reference is valid")
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/librustc/middle/typeck/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ pub enum SubregionOrigin {

// An auto-borrow that does not enclose the expr where it occurs
AutoBorrow(Span),

// Not yet categorized
MiscRegion(Span),
}

/// Reasons to create a region inference variable
Expand Down Expand Up @@ -905,6 +908,7 @@ impl SubregionOrigin {
CallReturn(a) => a,
AddrOf(a) => a,
AutoBorrow(a) => a,
MiscRegion(a) => a,
}
}
}
Expand Down Expand Up @@ -948,6 +952,7 @@ impl Repr for SubregionOrigin {
CallReturn(a) => format!("CallReturn({})", a.repr(tcx)),
AddrOf(a) => format!("AddrOf({})", a.repr(tcx)),
AutoBorrow(a) => format!("AutoBorrow({})", a.repr(tcx)),
MiscRegion(a) => format!("MiscRegion({})", a.repr(tcx)),
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/test/run-pass/unboxed-closures-send.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(unboxed_closures)]

use std::mem;
use std::ops::Fn;

// Unsugared closure
struct Closure(u8);

impl Fn<(u8,), u8> for Closure {
extern "rust-call" fn call(&self, (y,): (u8,)) -> u8 {
let &Closure(x) = self;

x + y
}
}

fn main() {
let y = 0u8;
let closure = |&: x: u8| x + y;
let unsugared_closure = Closure(y);

// Check that both closures are capturing by value
println!("{}", mem::size_of_val(&closure)); // prints 1
println!("{}", mem::size_of_val(&unsugared_closure)); // prints 1

spawn(proc() {
let ok = unsugared_closure;
let err = closure;
})
}

1 comment on commit eb43525

@nikomatsakis
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r+

Please sign in to comment.