Skip to content

Commit

Permalink
typeck: leak auto trait obligations through impl Trait.
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyb committed Aug 12, 2016
1 parent d92e594 commit 08bf9f6
Show file tree
Hide file tree
Showing 13 changed files with 613 additions and 163 deletions.
149 changes: 137 additions & 12 deletions src/librustc/traits/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

use dep_graph::DepGraph;
use infer::{InferCtxt, InferOk};
use ty::{self, Ty, TypeFoldable, ToPolyTraitRef, TyCtxt};
use ty::{self, Ty, TypeFoldable, ToPolyTraitRef, TyCtxt, ToPredicate};
use ty::subst::{Substs, Subst};
use rustc_data_structures::obligation_forest::{ObligationForest, Error};
use rustc_data_structures::obligation_forest::{ForestObligation, ObligationProcessor};
use std::marker::PhantomData;
Expand All @@ -22,10 +23,9 @@ use util::nodemap::{FnvHashSet, NodeMap};
use super::CodeAmbiguity;
use super::CodeProjectionError;
use super::CodeSelectionError;
use super::FulfillmentError;
use super::FulfillmentErrorCode;
use super::ObligationCause;
use super::PredicateObligation;
use super::{FulfillmentError, FulfillmentErrorCode, SelectionError};
use super::{ObligationCause, BuiltinDerivedObligation};
use super::{PredicateObligation, TraitObligation, Obligation};
use super::project;
use super::select::SelectionContext;
use super::Unimplemented;
Expand All @@ -51,6 +51,7 @@ pub struct GlobalFulfilledPredicates<'tcx> {
/// along. Once all type inference constraints have been generated, the
/// method `select_all_or_error` can be used to report any remaining
/// ambiguous cases as errors.
pub struct FulfillmentContext<'tcx> {
// A list of all obligations that have been registered with this
// fulfillment context.
Expand Down Expand Up @@ -84,6 +85,10 @@ pub struct FulfillmentContext<'tcx> {
// obligations (otherwise, it's easy to fail to walk to a
// particular node-id).
region_obligations: NodeMap<Vec<RegionObligation<'tcx>>>,

// A list of obligations that need to be deferred to
// a later time for them to be properly fulfilled.
deferred_obligations: Vec<DeferredObligation<'tcx>>,
}

#[derive(Clone)]
Expand All @@ -99,13 +104,98 @@ pub struct PendingPredicateObligation<'tcx> {
pub stalled_on: Vec<Ty<'tcx>>,
}

/// An obligation which cannot be fulfilled in the context
/// it was registered in, such as auto trait obligations on
/// `impl Trait`, which require the concrete type to be
/// available, only guaranteed after finishing type-checking.
#[derive(Clone, Debug)]
pub struct DeferredObligation<'tcx> {
pub predicate: ty::PolyTraitPredicate<'tcx>,
pub cause: ObligationCause<'tcx>
}

impl<'a, 'gcx, 'tcx> DeferredObligation<'tcx> {
/// If possible, create a `DeferredObligation` from
/// a trait predicate which had failed selection,
/// but could succeed later.
pub fn from_select_error(tcx: TyCtxt<'a, 'gcx, 'tcx>,
obligation: &TraitObligation<'tcx>,
selection_err: &SelectionError<'tcx>)
-> Option<DeferredObligation<'tcx>> {
if let Unimplemented = *selection_err {
if DeferredObligation::must_defer(tcx, &obligation.predicate) {
return Some(DeferredObligation {
predicate: obligation.predicate.clone(),
cause: obligation.cause.clone()
});
}
}

None
}

/// Returns true if the given trait predicate can be
/// fulfilled at a later time.
pub fn must_defer(tcx: TyCtxt<'a, 'gcx, 'tcx>,
predicate: &ty::PolyTraitPredicate<'tcx>)
-> bool {
// Auto trait obligations on `impl Trait`.
if tcx.trait_has_default_impl(predicate.def_id()) {
let substs = predicate.skip_binder().trait_ref.substs;
if substs.types.as_slice().len() == 1 && substs.regions.is_empty() {
if let ty::TyAnon(..) = predicate.skip_binder().self_ty().sty {
return true;
}
}
}

false
}

/// If possible, return the nested obligations required
/// to fulfill this obligation.
pub fn try_select(&self, tcx: TyCtxt<'a, 'gcx, 'tcx>)
-> Option<Vec<PredicateObligation<'tcx>>> {
if let ty::TyAnon(def_id, substs) = self.predicate.skip_binder().self_ty().sty {
// We can resolve the `impl Trait` to its concrete type.
if let Some(ty_scheme) = tcx.opt_lookup_item_type(def_id) {
let concrete_ty = ty_scheme.ty.subst(tcx, substs);
let concrete_substs = Substs::new_trait(vec![], vec![], concrete_ty);
let predicate = ty::TraitRef {
def_id: self.predicate.def_id(),
substs: tcx.mk_substs(concrete_substs)
}.to_predicate();

let original_obligation = Obligation::new(self.cause.clone(),
self.predicate.clone());
let cause = original_obligation.derived_cause(BuiltinDerivedObligation);
return Some(vec![Obligation::new(cause, predicate)]);
}
}

None
}

/// Return the `PredicateObligation` this was created from.
pub fn to_obligation(&self) -> PredicateObligation<'tcx> {
let predicate = ty::Predicate::Trait(self.predicate.clone());
Obligation::new(self.cause.clone(), predicate)
}

/// Return an error as if this obligation had failed.
pub fn to_error(&self) -> FulfillmentError<'tcx> {
FulfillmentError::new(self.to_obligation(), CodeSelectionError(Unimplemented))
}
}

impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {
/// Creates a new fulfillment context.
pub fn new() -> FulfillmentContext<'tcx> {
FulfillmentContext {
predicates: ObligationForest::new(),
rfc1592_obligations: Vec::new(),
region_obligations: NodeMap(),
deferred_obligations: vec![],
}
}

Expand Down Expand Up @@ -224,10 +314,16 @@ impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {
{
self.select_where_possible(infcx)?;

// Fail all of the deferred obligations that haven't
// been otherwise removed from the context.
let deferred_errors = self.deferred_obligations.iter()
.map(|d| d.to_error());

let errors: Vec<_> =
self.predicates.to_errors(CodeAmbiguity)
.into_iter()
.map(|e| to_fulfillment_error(e))
.chain(deferred_errors)
.collect();
if errors.is_empty() {
Ok(())
Expand All @@ -248,6 +344,10 @@ impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {
self.predicates.pending_obligations()
}

pub fn take_deferred_obligations(&mut self) -> Vec<DeferredObligation<'tcx>> {
mem::replace(&mut self.deferred_obligations, vec![])
}

/// Attempts to select obligations using `selcx`. If `only_new_obligations` is true, then it
/// only attempts to select obligations that haven't been seen before.
fn select(&mut self, selcx: &mut SelectionContext<'a, 'gcx, 'tcx>)
Expand All @@ -261,9 +361,10 @@ impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {

// Process pending obligations.
let outcome = self.predicates.process_obligations(&mut FulfillProcessor {
selcx: selcx,
region_obligations: &mut self.region_obligations,
rfc1592_obligations: &mut self.rfc1592_obligations
selcx: selcx,
region_obligations: &mut self.region_obligations,
rfc1592_obligations: &mut self.rfc1592_obligations,
deferred_obligations: &mut self.deferred_obligations
});
debug!("select: outcome={:?}", outcome);

Expand Down Expand Up @@ -298,7 +399,8 @@ impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {
struct FulfillProcessor<'a, 'b: 'a, 'gcx: 'tcx, 'tcx: 'b> {
selcx: &'a mut SelectionContext<'b, 'gcx, 'tcx>,
region_obligations: &'a mut NodeMap<Vec<RegionObligation<'tcx>>>,
rfc1592_obligations: &'a mut Vec<PredicateObligation<'tcx>>
rfc1592_obligations: &'a mut Vec<PredicateObligation<'tcx>>,
deferred_obligations: &'a mut Vec<DeferredObligation<'tcx>>
}

impl<'a, 'b, 'gcx, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'gcx, 'tcx> {
Expand All @@ -312,7 +414,8 @@ impl<'a, 'b, 'gcx, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'gcx,
process_predicate(self.selcx,
obligation,
self.region_obligations,
self.rfc1592_obligations)
self.rfc1592_obligations,
self.deferred_obligations)
.map(|os| os.map(|os| os.into_iter().map(|o| PendingPredicateObligation {
obligation: o,
stalled_on: vec![]
Expand Down Expand Up @@ -354,7 +457,8 @@ fn process_predicate<'a, 'gcx, 'tcx>(
selcx: &mut SelectionContext<'a, 'gcx, 'tcx>,
pending_obligation: &mut PendingPredicateObligation<'tcx>,
region_obligations: &mut NodeMap<Vec<RegionObligation<'tcx>>>,
rfc1592_obligations: &mut Vec<PredicateObligation<'tcx>>)
rfc1592_obligations: &mut Vec<PredicateObligation<'tcx>>,
deferred_obligations: &mut Vec<DeferredObligation<'tcx>>)
-> Result<Option<Vec<PredicateObligation<'tcx>>>,
FulfillmentErrorCode<'tcx>>
{
Expand Down Expand Up @@ -422,7 +526,22 @@ fn process_predicate<'a, 'gcx, 'tcx>(
Err(selection_err) => {
info!("selecting trait `{:?}` at depth {} yielded Err",
data, obligation.recursion_depth);
Err(CodeSelectionError(selection_err))

let defer = DeferredObligation::from_select_error(selcx.tcx(),
&trait_obligation,
&selection_err);
if let Some(deferred_obligation) = defer {
if let Some(nested) = deferred_obligation.try_select(selcx.tcx()) {
Ok(Some(nested))
} else {
// Pretend that the obligation succeeded,
// but record it for later.
deferred_obligations.push(deferred_obligation);
Ok(Some(vec![]))
}
} else {
Err(CodeSelectionError(selection_err))
}
}
}
}
Expand Down Expand Up @@ -629,6 +748,12 @@ impl<'a, 'gcx, 'tcx> GlobalFulfilledPredicates<'gcx> {
// already has the required read edges, so we don't need
// to add any more edges here.
if data.is_global() {
// Don't cache predicates which were fulfilled
// by deferring them for later fulfillment.
if DeferredObligation::must_defer(tcx, data) {
return;
}

if let Some(data) = tcx.lift_to_global(data) {
if self.set.insert(data.clone()) {
debug!("add_if_global: global predicate `{:?}` added", data);
Expand Down
1 change: 1 addition & 0 deletions src/librustc/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub use self::coherence::orphan_check;
pub use self::coherence::overlapping_impls;
pub use self::coherence::OrphanCheckErr;
pub use self::fulfill::{FulfillmentContext, GlobalFulfilledPredicates, RegionObligation};
pub use self::fulfill::DeferredObligation;
pub use self::project::MismatchedProjectionTypes;
pub use self::project::{normalize, normalize_projection_type, Normalized};
pub use self::project::{ProjectionCache, ProjectionCacheSnapshot, Reveal};
Expand Down
19 changes: 11 additions & 8 deletions src/librustc/traits/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2128,7 +2128,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
obligation)
};

let cause = self.derived_cause(obligation, BuiltinDerivedObligation);
let cause = obligation.derived_cause(BuiltinDerivedObligation);
self.collect_predicates_for_types(cause,
obligation.recursion_depth+1,
trait_def,
Expand Down Expand Up @@ -2208,7 +2208,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
{
debug!("vtable_default_impl: nested={:?}", nested);

let cause = self.derived_cause(obligation, BuiltinDerivedObligation);
let cause = obligation.derived_cause(BuiltinDerivedObligation);
let mut obligations = self.collect_predicates_for_types(
cause,
obligation.recursion_depth+1,
Expand All @@ -2219,7 +2219,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
let poly_trait_ref = obligation.predicate.to_poly_trait_ref();
let (trait_ref, skol_map) =
this.infcx().skolemize_late_bound_regions(&poly_trait_ref, snapshot);
let cause = this.derived_cause(obligation, ImplDerivedObligation);
let cause = obligation.derived_cause(ImplDerivedObligation);
this.impl_or_trait_obligations(cause,
obligation.recursion_depth + 1,
trait_def_id,
Expand Down Expand Up @@ -2254,7 +2254,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
this.rematch_impl(impl_def_id, obligation,
snapshot);
debug!("confirm_impl_candidate substs={:?}", substs);
let cause = this.derived_cause(obligation, ImplDerivedObligation);
let cause = obligation.derived_cause(ImplDerivedObligation);
this.vtable_impl(impl_def_id, substs, cause,
obligation.recursion_depth + 1,
skol_map, snapshot)
Expand Down Expand Up @@ -2907,12 +2907,13 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
}).collect();
self.infcx().plug_leaks(skol_map, snapshot, &predicates)
}
}

impl<'tcx> TraitObligation<'tcx> {
#[allow(unused_comparisons)]
fn derived_cause(&self,
obligation: &TraitObligation<'tcx>,
variant: fn(DerivedObligationCause<'tcx>) -> ObligationCauseCode<'tcx>)
-> ObligationCause<'tcx>
pub fn derived_cause(&self,
variant: fn(DerivedObligationCause<'tcx>) -> ObligationCauseCode<'tcx>)
-> ObligationCause<'tcx>
{
/*!
* Creates a cause for obligations that are derived from
Expand All @@ -2924,6 +2925,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
* reporting.
*/

let obligation = self;

// NOTE(flaper87): As of now, it keeps track of the whole error
// chain. Ideally, we should have a way to configure this either
// by using -Z verbose or just a CLI argument.
Expand Down
Loading

0 comments on commit 08bf9f6

Please sign in to comment.