Skip to content

Commit

Permalink
evaluate obligations in LIFO order during closure projection
Browse files Browse the repository at this point in the history
This is an annoying gotcha with the projection cache's handling of
nested obligations.

Nested projection obligations enter the issue in this case:
```
DEBUG:rustc::traits::project: AssociatedTypeNormalizer: depth=3
normalized
<std::iter::Map<std::ops::Range<i32>,
[[email protected]:5:30: 5:53]> as
std::iter::IntoIterator>::Item to _#7t with 12 add'l obligations
```

Here the normalization result is the result of the nested impl
`<[[email protected]:5:30: 5:53] as FnMut(i32)>::Output`,
which is an additional obligation that is a part of "add'l obligations".

By itself, this is proper behaviour - the additional obligation is
returned, and the RFC 447 rules ensure that it is processed before the
output `#_7t` is used in any way.

However, the projection cache breaks this - it caches the
`<std::iter::Map<std::ops::Range<i32>,[[email protected]:5:30:
5:53]> as std::iter::IntoIterator>::Item = #_7t` resolution. Now
everybody else that attempts to look up the projection will just get
`#_7t` *without* any additional obligations. This obviously causes all
sorts of trouble (here a spurious `EvaluatedToAmbig` results in
specializations not being discarded
[here](https://github.com/rust-lang/rust/blob/9ca50bd4d50b55456e88a8c3ad8fcc9798f57522/src/librustc/traits/select.rs#L1705)).

The compiler works even with this projection cache gotcha because in most
cases during "one-pass evaluation". we tend to process obligations in LIFO
order - after an obligation is added to the cache, we process its nested
obligations before we do anything else (and if we have a cycle, we handle
it specifically) - which makes sure the inference variables are resolved
before they are used.

That "LIFO" order That was not done when projecting out of a closure, so
let's just fix that for the time being.

Fixes rust-lang#38033.
  • Loading branch information
arielb1 authored and brson committed Dec 14, 2016
1 parent 8f23633 commit d9c5089
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/librustc/traits/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1222,8 +1222,8 @@ fn confirm_closure_candidate<'cx, 'gcx, 'tcx>(
obligation,
&closure_type.sig,
util::TupleArgumentsFlag::No)
.with_addl_obligations(obligations)
.with_addl_obligations(vtable.nested)
.with_addl_obligations(obligations)
}

fn confirm_callable_candidate<'cx, 'gcx, 'tcx>(
Expand Down
88 changes: 88 additions & 0 deletions src/test/run-pass/issue-38033.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2016 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.

use std::marker;
use std::mem;

fn main() {
let workers = (0..0).map(|_| result::<u32, ()>());
drop(join_all(workers).poll());
}

trait Future {
type Item;
type Error;

fn poll(&mut self) -> Result<Self::Item, Self::Error>;
}

trait IntoFuture {
type Future: Future<Item=Self::Item, Error=Self::Error>;
type Item;
type Error;

fn into_future(self) -> Self::Future;
}

impl<F: Future> IntoFuture for F {
type Future = F;
type Item = F::Item;
type Error = F::Error;

fn into_future(self) -> F {
self
}
}

struct FutureResult<T, E> {
_inner: marker::PhantomData<(T, E)>,
}

fn result<T, E>() -> FutureResult<T, E> {
loop {}
}

impl<T, E> Future for FutureResult<T, E> {
type Item = T;
type Error = E;

fn poll(&mut self) -> Result<T, E> {
loop {}
}
}

struct JoinAll<I>
where I: IntoIterator,
I::Item: IntoFuture,
{
elems: Vec<<I::Item as IntoFuture>::Item>,
}

fn join_all<I>(_: I) -> JoinAll<I>
where I: IntoIterator,
I::Item: IntoFuture,
{
JoinAll { elems: vec![] }
}

impl<I> Future for JoinAll<I>
where I: IntoIterator,
I::Item: IntoFuture,
{
type Item = Vec<<I::Item as IntoFuture>::Item>;
type Error = <I::Item as IntoFuture>::Error;

fn poll(&mut self) -> Result<Self::Item, Self::Error> {
let elems = mem::replace(&mut self.elems, Vec::new());
Ok(elems.into_iter().map(|e| {
e
}).collect::<Vec<_>>())
}
}

0 comments on commit d9c5089

Please sign in to comment.