Skip to content

Commit 5a39a0d

Browse files
committed
To handle more complex cases, modify the deferred call handler to be
specialized to closures, and invoke them as soon as we know the closure kind. I thought initially we would need a fixed-point inference algorithm but it appears I was mistaken, so we can do this.
1 parent e1f54f0 commit 5a39a0d

10 files changed

+354
-47
lines changed

src/librustc_typeck/check/callee.rs

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use super::check_argument_types;
1414
use super::check_expr;
1515
use super::check_method_argument_types;
1616
use super::demand;
17-
use super::DeferredResolution;
17+
use super::DeferredCallResolution;
1818
use super::err_args;
1919
use super::Expectation;
2020
use super::expected_types_for_fn_args;
@@ -99,8 +99,8 @@ pub fn check_call<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
9999
confirm_builtin_call(fcx, call_expr, callee_ty, arg_exprs, expected);
100100
}
101101

102-
Some(CallStep::Closure(fn_sig)) => {
103-
confirm_closure_call(fcx, call_expr, arg_exprs, expected, fn_sig);
102+
Some(CallStep::DeferredClosure(fn_sig)) => {
103+
confirm_deferred_closure_call(fcx, call_expr, arg_exprs, expected, fn_sig);
104104
}
105105

106106
Some(CallStep::Overloaded(method_callee)) => {
@@ -112,7 +112,7 @@ pub fn check_call<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
112112

113113
enum CallStep<'tcx> {
114114
Builtin,
115-
Closure(ty::FnSig<'tcx>),
115+
DeferredClosure(ty::FnSig<'tcx>),
116116
Overloaded(ty::MethodCallee<'tcx>)
117117
}
118118

@@ -138,21 +138,28 @@ fn try_overloaded_call_step<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>,
138138
}
139139

140140
ty::ty_closure(def_id, _, substs) => {
141-
let closure_ty =
142-
fcx.closure_type(def_id, substs);
143-
let fn_sig =
144-
fcx.infcx().replace_late_bound_regions_with_fresh_var(call_expr.span,
145-
infer::FnCall,
146-
&closure_ty.sig).0;
147-
fcx.record_deferred_resolution(box CallResolution {
148-
call_expr: call_expr,
149-
callee_expr: callee_expr,
150-
adjusted_ty: adjusted_ty,
151-
autoderefref: autoderefref,
152-
fn_sig: fn_sig.clone(),
153-
closure_def_id: def_id,
154-
});
155-
return Some(CallStep::Closure(fn_sig));
141+
assert_eq!(def_id.krate, ast::LOCAL_CRATE);
142+
143+
// Check whether this is a call to a closure where we
144+
// haven't yet decided on whether the closure is fn vs
145+
// fnmut vs fnonce. If so, we have to defer further processing.
146+
if fcx.closure_kind(def_id).is_none() {
147+
let closure_ty =
148+
fcx.closure_type(def_id, substs);
149+
let fn_sig =
150+
fcx.infcx().replace_late_bound_regions_with_fresh_var(call_expr.span,
151+
infer::FnCall,
152+
&closure_ty.sig).0;
153+
fcx.record_deferred_call_resolution(
154+
def_id,
155+
box CallResolution {call_expr: call_expr,
156+
callee_expr: callee_expr,
157+
adjusted_ty: adjusted_ty,
158+
autoderefref: autoderefref,
159+
fn_sig: fn_sig.clone(),
160+
closure_def_id: def_id});
161+
return Some(CallStep::DeferredClosure(fn_sig));
162+
}
156163
}
157164

158165
_ => {}
@@ -258,11 +265,11 @@ fn confirm_builtin_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
258265
write_call(fcx, call_expr, fn_sig.output);
259266
}
260267

261-
fn confirm_closure_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
262-
call_expr: &ast::Expr,
263-
arg_exprs: &'tcx [P<ast::Expr>],
264-
expected: Expectation<'tcx>,
265-
fn_sig: ty::FnSig<'tcx>)
268+
fn confirm_deferred_closure_call<'a,'tcx>(fcx: &FnCtxt<'a,'tcx>,
269+
call_expr: &ast::Expr,
270+
arg_exprs: &'tcx [P<ast::Expr>],
271+
expected: Expectation<'tcx>,
272+
fn_sig: ty::FnSig<'tcx>)
266273
{
267274
// `fn_sig` is the *signature* of the cosure being called. We
268275
// don't know the full details yet (`Fn` vs `FnMut` etc), but we
@@ -338,22 +345,18 @@ impl<'tcx> Repr<'tcx> for CallResolution<'tcx> {
338345
}
339346
}
340347

341-
impl<'tcx> DeferredResolution<'tcx> for CallResolution<'tcx> {
342-
fn attempt_resolution<'a>(&self, fcx: &FnCtxt<'a,'tcx>) -> bool {
343-
debug!("attempt_resolution() {}",
348+
impl<'tcx> DeferredCallResolution<'tcx> for CallResolution<'tcx> {
349+
fn resolve<'a>(&mut self, fcx: &FnCtxt<'a,'tcx>) {
350+
debug!("DeferredCallResolution::resolve() {}",
344351
self.repr(fcx.tcx()));
345352

346-
match fcx.closure_kind(self.closure_def_id) {
347-
Some(_) => { }
348-
None => {
349-
return false;
350-
}
351-
}
353+
// we should not be invoked until the closure kind has been
354+
// determined by upvar inference
355+
assert!(fcx.closure_kind(self.closure_def_id).is_some());
352356

353357
// We may now know enough to figure out fn vs fnmut etc.
354358
match try_overloaded_call_traits(fcx, self.call_expr, self.callee_expr,
355359
self.adjusted_ty, self.autoderefref.clone()) {
356-
None => false,
357360
Some(method_callee) => {
358361
// One problem is that when we get here, we are going
359362
// to have a newly instantiated function signature
@@ -382,8 +385,11 @@ impl<'tcx> DeferredResolution<'tcx> for CallResolution<'tcx> {
382385
self.fn_sig.output.unwrap());
383386

384387
write_overloaded_call_method_map(fcx, self.call_expr, method_callee);
385-
386-
true
388+
}
389+
None => {
390+
fcx.tcx().sess.span_bug(
391+
self.call_expr.span,
392+
"failed to find an overloaded call trait for closure call");
387393
}
388394
}
389395
}

src/librustc_typeck/check/mod.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ use util::nodemap::{DefIdMap, FnvHashMap, NodeMap};
110110
use util::lev_distance::lev_distance;
111111

112112
use std::cell::{Cell, Ref, RefCell};
113+
use std::collections::hash_map::Entry::{Occupied, Vacant};
113114
use std::mem::replace;
114115
use std::rc::Rc;
115116
use std::iter::repeat;
@@ -172,15 +173,21 @@ pub struct Inherited<'a, 'tcx: 'a> {
172173
// Tracks trait obligations incurred during this function body.
173174
fulfillment_cx: RefCell<traits::FulfillmentContext<'tcx>>,
174175

175-
//
176-
deferred_resolutions: RefCell<Vec<DeferredResolutionHandler<'tcx>>>,
176+
// When we process a call like `c()` where `c` is a closure type,
177+
// we may not have decided yet whether `c` is a `Fn`, `FnMut`, or
178+
// `FnOnce` closure. In that case, we defer full resolution of the
179+
// call until upvar inference can kick in and make the
180+
// decision. We keep these deferred resolutions sorted by the
181+
// def-id of the closure, so that once we decide, we can easily go
182+
// back and process them.
183+
deferred_call_resolutions: RefCell<DefIdMap<Vec<DeferredCallResolutionHandler<'tcx>>>>,
177184
}
178185

179-
trait DeferredResolution<'tcx> {
180-
fn attempt_resolution<'a>(&self, fcx: &FnCtxt<'a,'tcx>) -> bool;
186+
trait DeferredCallResolution<'tcx> {
187+
fn resolve<'a>(&mut self, fcx: &FnCtxt<'a,'tcx>);
181188
}
182189

183-
type DeferredResolutionHandler<'tcx> = Box<DeferredResolution<'tcx>+'tcx>;
190+
type DeferredCallResolutionHandler<'tcx> = Box<DeferredCallResolution<'tcx>+'tcx>;
184191

185192
/// When type-checking an expression, we propagate downward
186193
/// whatever type hint we are able in the form of an `Expectation`.
@@ -391,7 +398,7 @@ impl<'a, 'tcx> Inherited<'a, 'tcx> {
391398
closure_kinds: RefCell::new(DefIdMap()),
392399
fn_sig_map: RefCell::new(NodeMap()),
393400
fulfillment_cx: RefCell::new(traits::FulfillmentContext::new()),
394-
deferred_resolutions: RefCell::new(Vec::new()),
401+
deferred_call_resolutions: RefCell::new(DefIdMap()),
395402
}
396403
}
397404

@@ -1295,8 +1302,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12951302
if ty::type_has_ty_infer(t) || ty::type_is_error(t) { Err(()) } else { Ok(t) }
12961303
}
12971304

1298-
fn record_deferred_resolution(&self, r: DeferredResolutionHandler<'tcx>) {
1299-
self.inh.deferred_resolutions.borrow_mut().push(r);
1305+
fn record_deferred_call_resolution(&self,
1306+
closure_def_id: ast::DefId,
1307+
r: DeferredCallResolutionHandler<'tcx>) {
1308+
let mut deferred_call_resolutions = self.inh.deferred_call_resolutions.borrow_mut();
1309+
let mut vec = match deferred_call_resolutions.entry(closure_def_id) {
1310+
Occupied(entry) => entry.into_mut(),
1311+
Vacant(entry) => entry.insert(Vec::new()),
1312+
};
1313+
vec.push(r);
1314+
}
1315+
1316+
fn remove_deferred_call_resolutions(&self,
1317+
closure_def_id: ast::DefId)
1318+
-> Vec<DeferredCallResolutionHandler<'tcx>>
1319+
{
1320+
let mut deferred_call_resolutions = self.inh.deferred_call_resolutions.borrow_mut();
1321+
deferred_call_resolutions.remove(&closure_def_id).unwrap_or(Vec::new())
13001322
}
13011323

13021324
pub fn tag(&self) -> String {

src/librustc_typeck/check/upvar.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ pub fn closure_analyze_fn(fcx: &FnCtxt,
6767

6868
let mut adjust = AdjustBorrowKind::new(fcx, &closures_with_inferred_kinds);
6969
adjust.visit_block(body);
70+
71+
// it's our job to process these.
72+
assert!(fcx.inh.deferred_call_resolutions.borrow().is_empty());
7073
}
7174

7275
///////////////////////////////////////////////////////////////////////////
@@ -186,10 +189,56 @@ impl<'a,'tcx> AdjustBorrowKind<'a,'tcx> {
186189

187190
self.visit_block(body);
188191

189-
debug!("analyzing fn body with id {}", body.id);
192+
debug!("analyzing closure `{}` with fn body id `{}`", id, body.id);
190193

191194
let mut euv = euv::ExprUseVisitor::new(self, self.fcx);
192195
euv.walk_fn(decl, body);
196+
197+
// If we had not yet settled on a closure kind for this closure,
198+
// then we should have by now. Process and remove any deferred resolutions.
199+
//
200+
// Interesting fact: all calls to this closure must come
201+
// *after* its definition. Initially, I thought that some
202+
// kind of fixed-point iteration would be required, due to the
203+
// possibility of twisted examples like this one:
204+
//
205+
// ```rust
206+
// let mut closure0 = None;
207+
// let vec = vec!(1, 2, 3);
208+
//
209+
// loop {
210+
// {
211+
// let closure1 = || {
212+
// match closure0.take() {
213+
// Some(c) => {
214+
// return c(); // (*) call to `closure0` before it is defined
215+
// }
216+
// None => { }
217+
// }
218+
// };
219+
// closure1();
220+
// }
221+
//
222+
// closure0 = || vec;
223+
// }
224+
// ```
225+
//
226+
// However, this turns out to be wrong. Examples like this
227+
// fail to compile because the type of the variable `c` above
228+
// is an inference variable. And in fact since closure types
229+
// cannot be written, there is no way to make this example
230+
// work without a boxed closure. This implies that we can't
231+
// have two closures that recursively call one another without
232+
// some form of boxing (and hence explicit writing of a
233+
// closure kind) involved. Huzzah. -nmatsakis
234+
let closure_def_id = ast_util::local_def(id);
235+
if self.closures_with_inferred_kinds.contains(&id) {
236+
let mut deferred_call_resolutions =
237+
self.fcx.remove_deferred_call_resolutions(closure_def_id);
238+
for deferred_call_resolution in deferred_call_resolutions.iter_mut() {
239+
deferred_call_resolution.resolve(self.fcx);
240+
}
241+
}
193242
}
194243

195244
fn adjust_upvar_borrow_kind_for_consume(&self,

src/librustc_typeck/check/vtable.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,6 @@ fn check_object_type_binds_all_associated_types<'tcx>(tcx: &ty::ctxt<'tcx>,
280280
pub fn select_all_fcx_obligations_and_apply_defaults(fcx: &FnCtxt) {
281281
debug!("select_all_fcx_obligations_and_apply_defaults");
282282

283-
fcx.inh.deferred_resolutions.borrow_mut()
284-
.retain(|r| !r.attempt_resolution(fcx));
285283
select_fcx_obligations_where_possible(fcx);
286284
fcx.default_type_parameters();
287285
select_fcx_obligations_where_possible(fcx);
@@ -290,6 +288,10 @@ pub fn select_all_fcx_obligations_and_apply_defaults(fcx: &FnCtxt) {
290288
pub fn select_all_fcx_obligations_or_error(fcx: &FnCtxt) {
291289
debug!("select_all_fcx_obligations_or_error");
292290

291+
// upvar inference should have ensured that all deferrred call
292+
// resolutions are handled by now.
293+
assert!(fcx.inh.deferred_call_resolutions.borrow().is_empty());
294+
293295
select_all_fcx_obligations_and_apply_defaults(fcx);
294296
let mut fulfillment_cx = fcx.inh.fulfillment_cx.borrow_mut();
295297
let r = fulfillment_cx.select_all_or_error(fcx.infcx(), fcx);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Various unsuccessful attempts to put the unboxed closure kind
12+
// inference into an awkward position that might require fixed point
13+
// iteration (basically where inferring the kind of a closure `c`
14+
// would require knowing the kind of `c`). I currently believe this is
15+
// impossible.
16+
17+
fn a() {
18+
// This case of recursion wouldn't even require fixed-point
19+
// iteration, but it still doesn't work. The weird structure with
20+
// the `Option` is to avoid giving any useful hints about the `Fn`
21+
// kind via the expected type.
22+
let mut factorial: Option<Box<Fn(u32) -> u32>> = None;
23+
24+
let f = |x: u32| -> u32 {
25+
let g = factorial.as_ref().unwrap();
26+
if x == 0 {1} else {x * g(x-1)}
27+
};
28+
29+
factorial = Some(Box::new(f));
30+
//~^ ERROR cannot assign to `factorial` because it is borrowed
31+
}
32+
33+
fn main() { }
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Various unsuccessful attempts to put the unboxed closure kind
12+
// inference into an awkward position that might require fixed point
13+
// iteration (basically where inferring the kind of a closure `c`
14+
// would require knowing the kind of `c`). I currently believe this is
15+
// impossible.
16+
17+
fn a() {
18+
let mut closure0 = None;
19+
let vec = vec!(1, 2, 3);
20+
21+
loop {
22+
{
23+
let closure1 = || {
24+
match closure0.take() {
25+
Some(c) => {
26+
return c();
27+
//~^ ERROR the type of this value must be known in this context
28+
}
29+
None => { }
30+
}
31+
};
32+
closure1();
33+
}
34+
35+
closure0 = || vec;
36+
}
37+
}
38+
39+
fn main() { }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Test that we are able to infer a suitable kind for this closure
12+
// that is just called (`FnMut`).
13+
14+
fn main() {
15+
let mut counter = 0;
16+
17+
// Here this must be inferred to FnMut so that it can mutate counter,
18+
// but we forgot the mut.
19+
let tick1 = || {
20+
counter += 1;
21+
};
22+
23+
// In turn, tick2 must be inferred to FnMut so that it can call
24+
// tick1, but we forgot the mut. The error message we currently
25+
// get seems... suboptimal.
26+
let tick2 = || { //~ ERROR closure cannot assign to immutable local variable `tick1`
27+
tick1();
28+
};
29+
30+
tick2(); //~ ERROR cannot borrow
31+
}
32+

0 commit comments

Comments
 (0)