From 7365bee5066aae03e52a67c3fac6a8129140d90f Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Fri, 9 Mar 2018 22:49:37 +0100 Subject: [PATCH 01/10] Begun refactoring auto trait discovery for use outside rustc. --- src/librustc/traits/auto_trait.rs | 804 ++++++++++++++++++++++++++++++ src/librustc/traits/mod.rs | 2 + 2 files changed, 806 insertions(+) create mode 100644 src/librustc/traits/auto_trait.rs diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs new file mode 100644 index 0000000000000..7cdec4b84f6ad --- /dev/null +++ b/src/librustc/traits/auto_trait.rs @@ -0,0 +1,804 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use super::*; + +use std::collections::VecDeque; +use std::collections::hash_map::Entry; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; + +use hir::WherePredicate; + +use infer::{InferCtxt, RegionObligation}; +use infer::region_constraints::{Constraint, RegionConstraintData}; + +use ty::{Region, RegionVid}; +use ty::fold::TypeFolder; + +// TODO(twk): this is obviously not nice to duplicate like that +#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] +enum RegionTarget<'tcx> { + Region(Region<'tcx>), + RegionVid(RegionVid) +} + +#[derive(Default, Debug, Clone)] +struct RegionDeps<'tcx> { + larger: FxHashSet>, + smaller: FxHashSet> +} + +enum AutoTraitResult { + ExplicitImpl, + PositiveImpl, /*(ty::Generics), TODO(twk)*/ + NegativeImpl, +} + +impl AutoTraitResult { + fn is_auto(&self) -> bool { + match *self { + AutoTraitResult::PositiveImpl | AutoTraitResult::NegativeImpl => true, + _ => false, + } + } +} + +pub struct AutoTraitFinder<'a, 'tcx: 'a> { + pub tcx: &'a TyCtxt<'a, 'tcx, 'tcx>, +} + +impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { + fn find_auto_trait_generics( + &self, + did: DefId, + trait_did: DefId, + generics: &ty::Generics, + ) -> AutoTraitResult { + let tcx = self.tcx; + let ty = self.tcx.type_of(did); + + let orig_params = tcx.param_env(did); + + let trait_ref = ty::TraitRef { + def_id: trait_did, + substs: tcx.mk_substs_trait(ty, &[]), + }; + + let trait_pred = ty::Binder(trait_ref); + + let bail_out = tcx.infer_ctxt().enter(|infcx| { + let mut selcx = SelectionContext::with_negative(&infcx, true); + let result = selcx.select(&Obligation::new( + ObligationCause::dummy(), + orig_params, + trait_pred.to_poly_trait_predicate(), + )); + match result { + Ok(Some(Vtable::VtableImpl(_))) => { + debug!( + "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): \ + manual impl found, bailing out", + did, trait_did, generics + ); + return true; + } + _ => return false, + }; + }); + + // If an explicit impl exists, it always takes priority over an auto impl + if bail_out { + return AutoTraitResult::ExplicitImpl; + } + + return tcx.infer_ctxt().enter(|mut infcx| { + let mut fresh_preds = FxHashSet(); + + // Due to the way projections are handled by SelectionContext, we need to run + // evaluate_predicates twice: once on the original param env, and once on the result of + // the first evaluate_predicates call. + // + // The problem is this: most of rustc, including SelectionContext and traits::project, + // are designed to work with a concrete usage of a type (e.g. Vec + // fn() { Vec }. This information will generally never change - given + // the 'T' in fn() { ... }, we'll never know anything else about 'T'. + // If we're unable to prove that 'T' implements a particular trait, we're done - + // there's nothing left to do but error out. + // + // However, synthesizing an auto trait impl works differently. Here, we start out with + // a set of initial conditions - the ParamEnv of the struct/enum/union we're dealing + // with - and progressively discover the conditions we need to fulfill for it to + // implement a certain auto trait. This ends up breaking two assumptions made by trait + // selection and projection: + // + // * We can always cache the result of a particular trait selection for the lifetime of + // an InfCtxt + // * Given a projection bound such as '::SomeItem = K', if 'T: + // SomeTrait' doesn't hold, then we don't need to care about the 'SomeItem = K' + // + // We fix the first assumption by manually clearing out all of the InferCtxt's caches + // in between calls to SelectionContext.select. This allows us to keep all of the + // intermediate types we create bound to the 'tcx lifetime, rather than needing to lift + // them between calls. + // + // We fix the second assumption by reprocessing the result of our first call to + // evaluate_predicates. Using the example of '::SomeItem = K', our first + // pass will pick up 'T: SomeTrait', but not 'SomeItem = K'. On our second pass, + // traits::project will see that 'T: SomeTrait' is in our ParamEnv, allowing + // SelectionContext to return it back to us. + + let (new_env, user_env) = match self.evaluate_predicates( + &mut infcx, + did, + trait_did, + ty, + orig_params.clone(), + orig_params, + &mut fresh_preds, + false, + ) { + Some(e) => e, + None => return AutoTraitResult::NegativeImpl, + }; + + let (full_env, _full_user_env) = self.evaluate_predicates( + &mut infcx, + did, + trait_did, + ty, + new_env.clone(), + user_env, + &mut fresh_preds, + true, + ).unwrap_or_else(|| { + panic!( + "Failed to fully process: {:?} {:?} {:?}", + ty, trait_did, orig_params + ) + }); + + debug!( + "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): fulfilling \ + with {:?}", + did, trait_did, generics, full_env + ); + infcx.clear_caches(); + + // At this point, we already have all of the bounds we need. FulfillmentContext is used + // to store all of the necessary region/lifetime bounds in the InferContext, as well as + // an additional sanity check. + let mut fulfill = FulfillmentContext::new(); + fulfill.register_bound( + &infcx, + full_env, + ty, + trait_did, + ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID), + ); + fulfill.select_all_or_error(&infcx).unwrap_or_else(|e| { + panic!( + "Unable to fulfill trait {:?} for '{:?}': {:?}", + trait_did, ty, e + ) + }); + + let names_map: FxHashMap = generics + .regions + .iter() + .map(|l| (l.name.as_str().to_string(), l.name.to_string())) + // TODO(twk): Lifetime branding + .collect(); + + let body_ids: FxHashSet<_> = infcx + .region_obligations + .borrow() + .iter() + .map(|&(id, _)| id) + .collect(); + + for id in body_ids { + infcx.process_registered_region_obligations(&[], None, full_env.clone(), id); + } + + let region_data = infcx + .borrow_region_constraints() + .region_constraint_data() + .clone(); + + let lifetime_predicates = self.handle_lifetimes(®ion_data, &names_map); + let vid_to_region = self.map_vid_to_region(®ion_data); + + debug!( + "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): computed \ + lifetime information '{:?}' '{:?}'", + did, trait_did, generics, lifetime_predicates, vid_to_region + ); + + /* let new_generics = self.param_env_to_generics( + infcx.tcx, + did, + full_user_env, + generics.clone(), + lifetime_predicates, + vid_to_region, + ); */ + + debug!( + "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): finished with \ + ", + did, trait_did, generics /* , new_generics */ + ); + return AutoTraitResult::PositiveImpl; + }); + } + + // The core logic responsible for computing the bounds for our synthesized impl. + // + // To calculate the bounds, we call SelectionContext.select in a loop. Like FulfillmentContext, + // we recursively select the nested obligations of predicates we encounter. However, whenver we + // encounter an UnimplementedError involving a type parameter, we add it to our ParamEnv. Since + // our goal is to determine when a particular type implements an auto trait, Unimplemented + // errors tell us what conditions need to be met. + // + // This method ends up working somewhat similary to FulfillmentContext, but with a few key + // differences. FulfillmentContext works under the assumption that it's dealing with concrete + // user code. According, it considers all possible ways that a Predicate could be met - which + // isn't always what we want for a synthesized impl. For example, given the predicate 'T: + // Iterator', FulfillmentContext can end up reporting an Unimplemented error for T: + // IntoIterator - since there's an implementation of Iteratpr where T: IntoIterator, + // FulfillmentContext will drive SelectionContext to consider that impl before giving up. If we + // were to rely on FulfillmentContext's decision, we might end up synthesizing an impl like + // this: + // 'impl Send for Foo where T: IntoIterator' + // + // While it might be technically true that Foo implements Send where T: IntoIterator, + // the bound is overly restrictive - it's really only necessary that T: Iterator. + // + // For this reason, evaluate_predicates handles predicates with type variables specially. When + // we encounter an Unimplemented error for a bound such as 'T: Iterator', we immediately add it + // to our ParamEnv, and add it to our stack for recursive evaluation. When we later select it, + // we'll pick up any nested bounds, without ever inferring that 'T: IntoIterator' needs to + // hold. + // + // One additonal consideration is supertrait bounds. Normally, a ParamEnv is only ever + // consutrcted once for a given type. As part of the construction process, the ParamEnv will + // have any supertrait bounds normalized - e.g. if we have a type 'struct Foo', the + // ParamEnv will contain 'T: Copy' and 'T: Clone', since 'Copy: Clone'. When we construct our + // own ParamEnv, we need to do this outselves, through traits::elaborate_predicates, or else + // SelectionContext will choke on the missing predicates. However, this should never show up in + // the final synthesized generics: we don't want our generated docs page to contain something + // like 'T: Copy + Clone', as that's redundant. Therefore, we keep track of a separate + // 'user_env', which only holds the predicates that will actually be displayed to the user. + fn evaluate_predicates<'b, 'gcx, 'c>( + &self, + infcx: &mut InferCtxt<'b, 'tcx, 'c>, + ty_did: DefId, + trait_did: DefId, + ty: ty::Ty<'c>, + param_env: ty::ParamEnv<'c>, + user_env: ty::ParamEnv<'c>, + fresh_preds: &mut FxHashSet>, + only_projections: bool, + ) -> Option<(ty::ParamEnv<'c>, ty::ParamEnv<'c>)> { + let tcx = infcx.tcx; + + let mut select = SelectionContext::new(&infcx); + + let mut already_visited = FxHashSet(); + let mut predicates = VecDeque::new(); + predicates.push_back(ty::Binder(ty::TraitPredicate { + trait_ref: ty::TraitRef { + def_id: trait_did, + substs: infcx.tcx.mk_substs_trait(ty, &[]), + }, + })); + + let mut computed_preds: FxHashSet<_> = param_env.caller_bounds.iter().cloned().collect(); + let mut user_computed_preds: FxHashSet<_> = + user_env.caller_bounds.iter().cloned().collect(); + + let mut new_env = param_env.clone(); + let dummy_cause = ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID); + + while let Some(pred) = predicates.pop_front() { + infcx.clear_caches(); + + if !already_visited.insert(pred.clone()) { + continue; + } + + let result = select.select(&Obligation::new(dummy_cause.clone(), new_env, pred)); + + match &result { + &Ok(Some(ref vtable)) => { + let obligations = vtable.clone().nested_obligations().into_iter(); + + if !self.evaluate_nested_obligations( + ty, + obligations, + &mut user_computed_preds, + fresh_preds, + &mut predicates, + &mut select, + only_projections, + ) { + return None; + } + } + &Ok(None) => {} + &Err(SelectionError::Unimplemented) => { + if self.is_of_param(pred.skip_binder().trait_ref.substs) { + already_visited.remove(&pred); + user_computed_preds.insert(ty::Predicate::Trait(pred.clone())); + predicates.push_back(pred); + } else { + debug!( + "evaluate_nested_obligations: Unimplemented found, bailing: {:?} {:?} \ + {:?}", + ty, + pred, + pred.skip_binder().trait_ref.substs + ); + return None; + } + } + _ => panic!("Unexpected error for '{:?}': {:?}", ty, result), + }; + + computed_preds.extend(user_computed_preds.iter().cloned()); + let normalized_preds = + elaborate_predicates(tcx, computed_preds.clone().into_iter().collect()); + new_env = ty::ParamEnv::new( + tcx.mk_predicates(normalized_preds), + param_env.reveal, + ty::UniverseIndex::ROOT, + ); + } + + let final_user_env = ty::ParamEnv::new( + tcx.mk_predicates(user_computed_preds.into_iter()), + user_env.reveal, + ty::UniverseIndex::ROOT, + ); + debug!( + "evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \ + '{:?}'", + ty_did, trait_did, new_env, final_user_env + ); + + return Some((new_env, final_user_env)); + } + + // This method calculates two things: Lifetime constraints of the form 'a: 'b, + // and region constraints of the form ReVar: 'a + // + // This is essentially a simplified version of lexical_region_resolve. However, + // handle_lifetimes determines what *needs be* true in order for an impl to hold. + // lexical_region_resolve, along with much of the rest of the compiler, is concerned + // with determining if a given set up constraints/predicates *are* met, given some + // starting conditions (e.g. user-provided code). For this reason, it's easier + // to perform the calculations we need on our own, rather than trying to make + // existing inference/solver code do what we want. + fn handle_lifetimes<'cx>( + &self, + regions: &RegionConstraintData<'cx>, + names_map: &FxHashMap, // TODO(twk): lifetime branding + ) -> Vec { + // Our goal is to 'flatten' the list of constraints by eliminating + // all intermediate RegionVids. At the end, all constraints should + // be between Regions (aka region variables). This gives us the information + // we need to create the Generics. + let mut finished = FxHashMap(); + + let mut vid_map: FxHashMap = FxHashMap(); + + // Flattening is done in two parts. First, we insert all of the constraints + // into a map. Each RegionTarget (either a RegionVid or a Region) maps + // to its smaller and larger regions. Note that 'larger' regions correspond + // to sub-regions in Rust code (e.g. in 'a: 'b, 'a is the larger region). + for constraint in regions.constraints.keys() { + match constraint { + &Constraint::VarSubVar(r1, r2) => { + { + let deps1 = vid_map + .entry(RegionTarget::RegionVid(r1)) + .or_insert_with(|| Default::default()); + deps1.larger.insert(RegionTarget::RegionVid(r2)); + } + + let deps2 = vid_map + .entry(RegionTarget::RegionVid(r2)) + .or_insert_with(|| Default::default()); + deps2.smaller.insert(RegionTarget::RegionVid(r1)); + } + &Constraint::RegSubVar(region, vid) => { + let deps = vid_map + .entry(RegionTarget::RegionVid(vid)) + .or_insert_with(|| Default::default()); + deps.smaller.insert(RegionTarget::Region(region)); + } + &Constraint::VarSubReg(vid, region) => { + let deps = vid_map + .entry(RegionTarget::RegionVid(vid)) + .or_insert_with(|| Default::default()); + deps.larger.insert(RegionTarget::Region(region)); + } + &Constraint::RegSubReg(r1, r2) => { + // The constraint is already in the form that we want, so we're done with it + // Desired order is 'larger, smaller', so flip then + if self.region_name(r1) != self.region_name(r2) { + finished + .entry(self.region_name(r2).unwrap()) + .or_insert_with(|| Vec::new()) + .push(r1); + } + } + } + } + + // Here, we 'flatten' the map one element at a time. + // All of the element's sub and super regions are connected + // to each other. For example, if we have a graph that looks like this: + // + // (A, B) - C - (D, E) + // Where (A, B) are subregions, and (D,E) are super-regions + // + // then after deleting 'C', the graph will look like this: + // ... - A - (D, E ...) + // ... - B - (D, E, ...) + // (A, B, ...) - D - ... + // (A, B, ...) - E - ... + // + // where '...' signifies the existing sub and super regions of an entry + // When two adjacent ty::Regions are encountered, we've computed a final + // constraint, and add it to our list. Since we make sure to never re-add + // deleted items, this process will always finish. + while !vid_map.is_empty() { + let target = vid_map.keys().next().expect("Keys somehow empty").clone(); + let deps = vid_map.remove(&target).expect("Entry somehow missing"); + + for smaller in deps.smaller.iter() { + for larger in deps.larger.iter() { + match (smaller, larger) { + (&RegionTarget::Region(r1), &RegionTarget::Region(r2)) => { + if self.region_name(r1) != self.region_name(r2) { + finished + .entry(self.region_name(r2).unwrap()) + .or_insert_with(|| Vec::new()) + .push(r1) // Larger, smaller + } + } + (&RegionTarget::RegionVid(_), &RegionTarget::Region(_)) => { + if let Entry::Occupied(v) = vid_map.entry(*smaller) { + let smaller_deps = v.into_mut(); + smaller_deps.larger.insert(*larger); + smaller_deps.larger.remove(&target); + } + } + (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => { + if let Entry::Occupied(v) = vid_map.entry(*larger) { + let deps = v.into_mut(); + deps.smaller.insert(*smaller); + deps.smaller.remove(&target); + } + } + (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => { + if let Entry::Occupied(v) = vid_map.entry(*smaller) { + let smaller_deps = v.into_mut(); + smaller_deps.larger.insert(*larger); + smaller_deps.larger.remove(&target); + } + + if let Entry::Occupied(v) = vid_map.entry(*larger) { + let larger_deps = v.into_mut(); + larger_deps.smaller.insert(*smaller); + larger_deps.smaller.remove(&target); + } + } + } + } + } + } + + let lifetime_predicates = names_map + .iter() + .flat_map(|(name, _lifetime)| { + let empty = Vec::new(); + let bounds: FxHashSet = finished // TODO(twk): lifetime branding + .get(name) + .unwrap_or(&empty) + .iter() + .map(|region| self.get_lifetime(region, names_map)) + .collect(); + + if bounds.is_empty() { + return None; + } + /* Some(WherePredicate::RegionPredicate { + lifetime: lifetime.clone(), + bounds: bounds.into_iter().collect(), + }) */ + None // TODO(twk): use the correct WherePredicate and rebuild the code above + }) + .collect(); + + lifetime_predicates + } + + fn region_name(&self, region: Region) -> Option { + match region { + &ty::ReEarlyBound(r) => Some(r.name.as_str().to_string()), + _ => None, + } + } + + // TODO(twk): lifetime branding + fn get_lifetime(&self, region: Region, names_map: &FxHashMap) -> String { + self.region_name(region) + .map(|name| { + names_map.get(&name).unwrap_or_else(|| { + panic!("Missing lifetime with name {:?} for {:?}", name, region) + }) + }) + // TODO(twk): .unwrap_or(&Lifetime::statik()) + .unwrap_or(&"'static".to_string()) + .clone() + } + + // This is very similar to handle_lifetimes. However, instead of matching ty::Region's + // to each other, we match ty::RegionVid's to ty::Region's + fn map_vid_to_region<'cx>( + &self, + regions: &RegionConstraintData<'cx>, + ) -> FxHashMap> { + let mut vid_map: FxHashMap, RegionDeps<'cx>> = FxHashMap(); + let mut finished_map = FxHashMap(); + + for constraint in regions.constraints.keys() { + match constraint { + &Constraint::VarSubVar(r1, r2) => { + { + let deps1 = vid_map + .entry(RegionTarget::RegionVid(r1)) + .or_insert_with(|| Default::default()); + deps1.larger.insert(RegionTarget::RegionVid(r2)); + } + + let deps2 = vid_map + .entry(RegionTarget::RegionVid(r2)) + .or_insert_with(|| Default::default()); + deps2.smaller.insert(RegionTarget::RegionVid(r1)); + } + &Constraint::RegSubVar(region, vid) => { + { + let deps1 = vid_map + .entry(RegionTarget::Region(region)) + .or_insert_with(|| Default::default()); + deps1.larger.insert(RegionTarget::RegionVid(vid)); + } + + let deps2 = vid_map + .entry(RegionTarget::RegionVid(vid)) + .or_insert_with(|| Default::default()); + deps2.smaller.insert(RegionTarget::Region(region)); + } + &Constraint::VarSubReg(vid, region) => { + finished_map.insert(vid, region); + } + &Constraint::RegSubReg(r1, r2) => { + { + let deps1 = vid_map + .entry(RegionTarget::Region(r1)) + .or_insert_with(|| Default::default()); + deps1.larger.insert(RegionTarget::Region(r2)); + } + + let deps2 = vid_map + .entry(RegionTarget::Region(r2)) + .or_insert_with(|| Default::default()); + deps2.smaller.insert(RegionTarget::Region(r1)); + } + } + } + + while !vid_map.is_empty() { + let target = vid_map.keys().next().expect("Keys somehow empty").clone(); + let deps = vid_map.remove(&target).expect("Entry somehow missing"); + + for smaller in deps.smaller.iter() { + for larger in deps.larger.iter() { + match (smaller, larger) { + (&RegionTarget::Region(_), &RegionTarget::Region(_)) => { + if let Entry::Occupied(v) = vid_map.entry(*smaller) { + let smaller_deps = v.into_mut(); + smaller_deps.larger.insert(*larger); + smaller_deps.larger.remove(&target); + } + + if let Entry::Occupied(v) = vid_map.entry(*larger) { + let larger_deps = v.into_mut(); + larger_deps.smaller.insert(*smaller); + larger_deps.smaller.remove(&target); + } + } + (&RegionTarget::RegionVid(v1), &RegionTarget::Region(r1)) => { + finished_map.insert(v1, r1); + } + (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => { + // Do nothing - we don't care about regions that are smaller than vids + } + (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => { + if let Entry::Occupied(v) = vid_map.entry(*smaller) { + let smaller_deps = v.into_mut(); + smaller_deps.larger.insert(*larger); + smaller_deps.larger.remove(&target); + } + + if let Entry::Occupied(v) = vid_map.entry(*larger) { + let larger_deps = v.into_mut(); + larger_deps.smaller.insert(*smaller); + larger_deps.smaller.remove(&target); + } + } + } + } + } + } + finished_map + } + + fn is_of_param(&self, substs: &Substs) -> bool { + if substs.is_noop() { + return false; + } + + return match substs.type_at(0).sty { + ty::TyParam(_) => true, + ty::TyProjection(p) => self.is_of_param(p.substs), + _ => false, + }; + } + + fn evaluate_nested_obligations<'b, 'c, 'd, 'cx, + T: Iterator>>>( + &self, + ty: ty::Ty, + nested: T, + computed_preds: &'b mut FxHashSet>, + fresh_preds: &'b mut FxHashSet>, + predicates: &'b mut VecDeque>, + select: &mut SelectionContext<'c, 'd, 'cx>, + only_projections: bool, + ) -> bool { + let dummy_cause = ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID); + + for (obligation, predicate) in nested + .filter(|o| o.recursion_depth == 1) + .map(|o| (o.clone(), o.predicate.clone())) + { + let is_new_pred = + fresh_preds.insert(self.clean_pred(select.infcx(), predicate.clone())); + + match &predicate { + &ty::Predicate::Trait(ref p) => { + let substs = &p.skip_binder().trait_ref.substs; + + if self.is_of_param(substs) && !only_projections && is_new_pred { + computed_preds.insert(predicate); + } + predicates.push_back(p.clone()); + } + &ty::Predicate::Projection(p) => { + // If the projection isn't all type vars, then + // we don't want to add it as a bound + if self.is_of_param(p.skip_binder().projection_ty.substs) && is_new_pred { + computed_preds.insert(predicate); + } else { + match poly_project_and_unify_type( + select, + &obligation.with(p.clone()), + ) { + Err(e) => { + debug!( + "evaluate_nested_obligations: Unable to unify predicate \ + '{:?}' '{:?}', bailing out", + ty, e + ); + return false; + } + Ok(Some(v)) => { + if !self.evaluate_nested_obligations( + ty, + v.clone().iter().cloned(), + computed_preds, + fresh_preds, + predicates, + select, + only_projections, + ) { + return false; + } + } + Ok(None) => { + panic!("Unexpected result when selecting {:?} {:?}", ty, obligation) + } + } + } + } + &ty::Predicate::RegionOutlives(ref binder) => { + if let Err(_) = select + .infcx() + .region_outlives_predicate(&dummy_cause, binder) + { + return false; + } + } + &ty::Predicate::TypeOutlives(ref binder) => { + match ( + binder.no_late_bound_regions(), + binder.map_bound_ref(|pred| pred.0).no_late_bound_regions(), + ) { + (None, Some(t_a)) => { + select.infcx().register_region_obligation( + ast::DUMMY_NODE_ID, + RegionObligation { + sup_type: t_a, + sub_region: select.infcx().tcx.types.re_static, + cause: dummy_cause.clone(), + }, + ); + } + (Some(ty::OutlivesPredicate(t_a, r_b)), _) => { + select.infcx().register_region_obligation( + ast::DUMMY_NODE_ID, + RegionObligation { + sup_type: t_a, + sub_region: r_b, + cause: dummy_cause.clone(), + }, + ); + } + _ => {} + }; + } + _ => panic!("Unexpected predicate {:?} {:?}", ty, predicate), + }; + } + return true; + } + + fn clean_pred<'c, 'd, 'cx>( + &self, + infcx: &InferCtxt<'c, 'd, 'cx>, + p: ty::Predicate<'cx>, + ) -> ty::Predicate<'cx> { + infcx.freshen(p) + } +} + +// Replaces all ReVars in a type with ty::Region's, using the provided map +struct RegionReplacer<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> { + vid_to_region: &'a FxHashMap>, + tcx: TyCtxt<'a, 'gcx, 'tcx>, +} + +impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for RegionReplacer<'a, 'gcx, 'tcx> { + fn tcx<'b>(&'b self) -> TyCtxt<'b, 'gcx, 'tcx> { + self.tcx + } + + fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { + (match r { + &ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(), + _ => None, + }).unwrap_or_else(|| r.super_fold_with(self)) + } +} diff --git a/src/librustc/traits/mod.rs b/src/librustc/traits/mod.rs index 728d9f1a0270b..b6b0b91fc535c 100644 --- a/src/librustc/traits/mod.rs +++ b/src/librustc/traits/mod.rs @@ -52,6 +52,8 @@ pub use self::util::supertrait_def_ids; pub use self::util::SupertraitDefIds; pub use self::util::transitive_bounds; +#[allow(dead_code)] +pub mod auto_trait; mod coherence; pub mod error_reporting; mod engine; From 136abb9fb5ec89f0958602ea8d1fb4058a2d3461 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Sun, 1 Apr 2018 22:38:47 +0200 Subject: [PATCH 02/10] Made some bits of the auto trait machinery public. --- src/librustc/traits/auto_trait.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index 7cdec4b84f6ad..a55150ab11ba9 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -25,18 +25,18 @@ use ty::fold::TypeFolder; // TODO(twk): this is obviously not nice to duplicate like that #[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] -enum RegionTarget<'tcx> { +pub enum RegionTarget<'tcx> { Region(Region<'tcx>), RegionVid(RegionVid) } #[derive(Default, Debug, Clone)] -struct RegionDeps<'tcx> { +pub struct RegionDeps<'tcx> { larger: FxHashSet>, smaller: FxHashSet> } -enum AutoTraitResult { +pub enum AutoTraitResult { ExplicitImpl, PositiveImpl, /*(ty::Generics), TODO(twk)*/ NegativeImpl, @@ -56,7 +56,7 @@ pub struct AutoTraitFinder<'a, 'tcx: 'a> { } impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { - fn find_auto_trait_generics( + pub fn find_auto_trait_generics( &self, did: DefId, trait_did: DefId, @@ -277,9 +277,9 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // the final synthesized generics: we don't want our generated docs page to contain something // like 'T: Copy + Clone', as that's redundant. Therefore, we keep track of a separate // 'user_env', which only holds the predicates that will actually be displayed to the user. - fn evaluate_predicates<'b, 'gcx, 'c>( + pub fn evaluate_predicates<'b, 'gcx, 'c>( &self, - infcx: &mut InferCtxt<'b, 'tcx, 'c>, + infcx: & InferCtxt<'b, 'tcx, 'c>, ty_did: DefId, trait_did: DefId, ty: ty::Ty<'c>, @@ -387,7 +387,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // starting conditions (e.g. user-provided code). For this reason, it's easier // to perform the calculations we need on our own, rather than trying to make // existing inference/solver code do what we want. - fn handle_lifetimes<'cx>( + pub fn handle_lifetimes<'cx>( &self, regions: &RegionConstraintData<'cx>, names_map: &FxHashMap, // TODO(twk): lifetime branding @@ -533,7 +533,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { lifetime_predicates } - fn region_name(&self, region: Region) -> Option { + pub fn region_name(&self, region: Region) -> Option { match region { &ty::ReEarlyBound(r) => Some(r.name.as_str().to_string()), _ => None, @@ -541,7 +541,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } // TODO(twk): lifetime branding - fn get_lifetime(&self, region: Region, names_map: &FxHashMap) -> String { + pub fn get_lifetime(&self, region: Region, names_map: &FxHashMap) -> String { self.region_name(region) .map(|name| { names_map.get(&name).unwrap_or_else(|| { @@ -555,7 +555,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // This is very similar to handle_lifetimes. However, instead of matching ty::Region's // to each other, we match ty::RegionVid's to ty::Region's - fn map_vid_to_region<'cx>( + pub fn map_vid_to_region<'cx>( &self, regions: &RegionConstraintData<'cx>, ) -> FxHashMap> { @@ -655,7 +655,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { finished_map } - fn is_of_param(&self, substs: &Substs) -> bool { + pub fn is_of_param(&self, substs: &Substs) -> bool { if substs.is_noop() { return false; } @@ -667,7 +667,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { }; } - fn evaluate_nested_obligations<'b, 'c, 'd, 'cx, + pub fn evaluate_nested_obligations<'b, 'c, 'd, 'cx, T: Iterator>>>( &self, ty: ty::Ty, @@ -775,7 +775,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { return true; } - fn clean_pred<'c, 'd, 'cx>( + pub fn clean_pred<'c, 'd, 'cx>( &self, infcx: &InferCtxt<'c, 'd, 'cx>, p: ty::Predicate<'cx>, @@ -785,7 +785,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } // Replaces all ReVars in a type with ty::Region's, using the provided map -struct RegionReplacer<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> { +pub struct RegionReplacer<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> { vid_to_region: &'a FxHashMap>, tcx: TyCtxt<'a, 'gcx, 'tcx>, } From 5e1f92cd841b8c925f5149cb362f550c354d36bf Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Thu, 5 Apr 2018 20:10:15 +0200 Subject: [PATCH 03/10] Reworked auto trait functionality in rustdoc. --- src/librustc/traits/auto_trait.rs | 220 ++--------- src/librustdoc/clean/auto_trait.rs | 566 ++--------------------------- src/librustdoc/clean/mod.rs | 10 +- 3 files changed, 62 insertions(+), 734 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index a55150ab11ba9..62148a8e2bfa8 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -15,8 +15,6 @@ use std::collections::hash_map::Entry; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use hir::WherePredicate; - use infer::{InferCtxt, RegionObligation}; use infer::region_constraints::{Constraint, RegionConstraintData}; @@ -36,32 +34,42 @@ pub struct RegionDeps<'tcx> { smaller: FxHashSet> } -pub enum AutoTraitResult { +pub enum AutoTraitResult { ExplicitImpl, - PositiveImpl, /*(ty::Generics), TODO(twk)*/ + PositiveImpl(A), NegativeImpl, } -impl AutoTraitResult { +impl AutoTraitResult { fn is_auto(&self) -> bool { match *self { - AutoTraitResult::PositiveImpl | AutoTraitResult::NegativeImpl => true, + AutoTraitResult::PositiveImpl(_) | AutoTraitResult::NegativeImpl => true, _ => false, } } } +pub struct AutoTraitInfo<'cx> { + pub full_user_env: ty::ParamEnv<'cx>, + pub region_data: RegionConstraintData<'cx>, + pub names_map: FxHashMap, + pub vid_to_region: FxHashMap>, +} + pub struct AutoTraitFinder<'a, 'tcx: 'a> { pub tcx: &'a TyCtxt<'a, 'tcx, 'tcx>, } impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { - pub fn find_auto_trait_generics( + pub fn find_auto_trait_generics( &self, did: DefId, trait_did: DefId, generics: &ty::Generics, - ) -> AutoTraitResult { + auto_trait_callback: F) + -> AutoTraitResult + where F: for<'b, 'cx, 'cx2> Fn(&InferCtxt<'b, 'cx, 'cx2>, AutoTraitInfo<'cx2>) -> A + { let tcx = self.tcx; let ty = self.tcx.type_of(did); @@ -72,7 +80,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { substs: tcx.mk_substs_trait(ty, &[]), }; - let trait_pred = ty::Binder(trait_ref); + let trait_pred = ty::Binder::bind(trait_ref); let bail_out = tcx.infer_ctxt().enter(|infcx| { let mut selcx = SelectionContext::with_negative(&infcx, true); @@ -149,7 +157,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { None => return AutoTraitResult::NegativeImpl, }; - let (full_env, _full_user_env) = self.evaluate_predicates( + let (full_env, full_user_env) = self.evaluate_predicates( &mut infcx, did, trait_did, @@ -193,8 +201,9 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let names_map: FxHashMap = generics .regions .iter() - .map(|l| (l.name.as_str().to_string(), l.name.to_string())) - // TODO(twk): Lifetime branding + .map(|l| (l.name.to_string(), l.name.to_string())) + // TODO(twk): Lifetime branding and why is this map a set?! + // l.clean(self.cx) was present in the original code .collect(); let body_ids: FxHashSet<_> = infcx @@ -213,33 +222,16 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { .region_constraint_data() .clone(); - let lifetime_predicates = self.handle_lifetimes(®ion_data, &names_map); let vid_to_region = self.map_vid_to_region(®ion_data); - debug!( - "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): computed \ - lifetime information '{:?}' '{:?}'", - did, trait_did, generics, lifetime_predicates, vid_to_region - ); + let info = AutoTraitInfo { full_user_env, region_data, names_map, vid_to_region }; - /* let new_generics = self.param_env_to_generics( - infcx.tcx, - did, - full_user_env, - generics.clone(), - lifetime_predicates, - vid_to_region, - ); */ - - debug!( - "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): finished with \ - ", - did, trait_did, generics /* , new_generics */ - ); - return AutoTraitResult::PositiveImpl; + return AutoTraitResult::PositiveImpl(auto_trait_callback(&infcx, info)); }); } +} +impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // The core logic responsible for computing the bounds for our synthesized impl. // // To calculate the bounds, we call SelectionContext.select in a loop. Like FulfillmentContext, @@ -294,7 +286,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let mut already_visited = FxHashSet(); let mut predicates = VecDeque::new(); - predicates.push_back(ty::Binder(ty::TraitPredicate { + predicates.push_back(ty::Binder::bind(ty::TraitPredicate { trait_ref: ty::TraitRef { def_id: trait_did, substs: infcx.tcx.mk_substs_trait(ty, &[]), @@ -359,14 +351,12 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { new_env = ty::ParamEnv::new( tcx.mk_predicates(normalized_preds), param_env.reveal, - ty::UniverseIndex::ROOT, ); } let final_user_env = ty::ParamEnv::new( tcx.mk_predicates(user_computed_preds.into_iter()), user_env.reveal, - ty::UniverseIndex::ROOT, ); debug!( "evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \ @@ -377,165 +367,9 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { return Some((new_env, final_user_env)); } - // This method calculates two things: Lifetime constraints of the form 'a: 'b, - // and region constraints of the form ReVar: 'a - // - // This is essentially a simplified version of lexical_region_resolve. However, - // handle_lifetimes determines what *needs be* true in order for an impl to hold. - // lexical_region_resolve, along with much of the rest of the compiler, is concerned - // with determining if a given set up constraints/predicates *are* met, given some - // starting conditions (e.g. user-provided code). For this reason, it's easier - // to perform the calculations we need on our own, rather than trying to make - // existing inference/solver code do what we want. - pub fn handle_lifetimes<'cx>( - &self, - regions: &RegionConstraintData<'cx>, - names_map: &FxHashMap, // TODO(twk): lifetime branding - ) -> Vec { - // Our goal is to 'flatten' the list of constraints by eliminating - // all intermediate RegionVids. At the end, all constraints should - // be between Regions (aka region variables). This gives us the information - // we need to create the Generics. - let mut finished = FxHashMap(); - - let mut vid_map: FxHashMap = FxHashMap(); - - // Flattening is done in two parts. First, we insert all of the constraints - // into a map. Each RegionTarget (either a RegionVid or a Region) maps - // to its smaller and larger regions. Note that 'larger' regions correspond - // to sub-regions in Rust code (e.g. in 'a: 'b, 'a is the larger region). - for constraint in regions.constraints.keys() { - match constraint { - &Constraint::VarSubVar(r1, r2) => { - { - let deps1 = vid_map - .entry(RegionTarget::RegionVid(r1)) - .or_insert_with(|| Default::default()); - deps1.larger.insert(RegionTarget::RegionVid(r2)); - } - - let deps2 = vid_map - .entry(RegionTarget::RegionVid(r2)) - .or_insert_with(|| Default::default()); - deps2.smaller.insert(RegionTarget::RegionVid(r1)); - } - &Constraint::RegSubVar(region, vid) => { - let deps = vid_map - .entry(RegionTarget::RegionVid(vid)) - .or_insert_with(|| Default::default()); - deps.smaller.insert(RegionTarget::Region(region)); - } - &Constraint::VarSubReg(vid, region) => { - let deps = vid_map - .entry(RegionTarget::RegionVid(vid)) - .or_insert_with(|| Default::default()); - deps.larger.insert(RegionTarget::Region(region)); - } - &Constraint::RegSubReg(r1, r2) => { - // The constraint is already in the form that we want, so we're done with it - // Desired order is 'larger, smaller', so flip then - if self.region_name(r1) != self.region_name(r2) { - finished - .entry(self.region_name(r2).unwrap()) - .or_insert_with(|| Vec::new()) - .push(r1); - } - } - } - } - - // Here, we 'flatten' the map one element at a time. - // All of the element's sub and super regions are connected - // to each other. For example, if we have a graph that looks like this: - // - // (A, B) - C - (D, E) - // Where (A, B) are subregions, and (D,E) are super-regions - // - // then after deleting 'C', the graph will look like this: - // ... - A - (D, E ...) - // ... - B - (D, E, ...) - // (A, B, ...) - D - ... - // (A, B, ...) - E - ... - // - // where '...' signifies the existing sub and super regions of an entry - // When two adjacent ty::Regions are encountered, we've computed a final - // constraint, and add it to our list. Since we make sure to never re-add - // deleted items, this process will always finish. - while !vid_map.is_empty() { - let target = vid_map.keys().next().expect("Keys somehow empty").clone(); - let deps = vid_map.remove(&target).expect("Entry somehow missing"); - - for smaller in deps.smaller.iter() { - for larger in deps.larger.iter() { - match (smaller, larger) { - (&RegionTarget::Region(r1), &RegionTarget::Region(r2)) => { - if self.region_name(r1) != self.region_name(r2) { - finished - .entry(self.region_name(r2).unwrap()) - .or_insert_with(|| Vec::new()) - .push(r1) // Larger, smaller - } - } - (&RegionTarget::RegionVid(_), &RegionTarget::Region(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { - let smaller_deps = v.into_mut(); - smaller_deps.larger.insert(*larger); - smaller_deps.larger.remove(&target); - } - } - (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*larger) { - let deps = v.into_mut(); - deps.smaller.insert(*smaller); - deps.smaller.remove(&target); - } - } - (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { - let smaller_deps = v.into_mut(); - smaller_deps.larger.insert(*larger); - smaller_deps.larger.remove(&target); - } - - if let Entry::Occupied(v) = vid_map.entry(*larger) { - let larger_deps = v.into_mut(); - larger_deps.smaller.insert(*smaller); - larger_deps.smaller.remove(&target); - } - } - } - } - } - } - - let lifetime_predicates = names_map - .iter() - .flat_map(|(name, _lifetime)| { - let empty = Vec::new(); - let bounds: FxHashSet = finished // TODO(twk): lifetime branding - .get(name) - .unwrap_or(&empty) - .iter() - .map(|region| self.get_lifetime(region, names_map)) - .collect(); - - if bounds.is_empty() { - return None; - } - /* Some(WherePredicate::RegionPredicate { - lifetime: lifetime.clone(), - bounds: bounds.into_iter().collect(), - }) */ - None // TODO(twk): use the correct WherePredicate and rebuild the code above - }) - .collect(); - - lifetime_predicates - } - pub fn region_name(&self, region: Region) -> Option { match region { - &ty::ReEarlyBound(r) => Some(r.name.as_str().to_string()), + &ty::ReEarlyBound(r) => Some(r.name.to_string()), _ => None, } } diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 477b576ad217e..d3e2dac72ee33 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use rustc::traits::auto_trait as auto; use rustc::ty::TypeFoldable; use std::fmt::Debug; @@ -15,9 +16,16 @@ use super::*; pub struct AutoTraitFinder<'a, 'tcx: 'a, 'rcx: 'a> { pub cx: &'a core::DocContext<'a, 'tcx, 'rcx>, + pub f: auto::AutoTraitFinder<'a, 'tcx>, } impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> { + pub fn new(cx: &'a core::DocContext<'a, 'tcx, 'rcx>) -> Self { + let f = auto::AutoTraitFinder { tcx: &cx.tcx }; + + AutoTraitFinder { cx, f } + } + pub fn get_with_def_id(&self, def_id: DefId) -> Vec { let ty = self.cx.tcx.type_of(def_id); @@ -276,443 +284,37 @@ impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> { trait_did: DefId, generics: &ty::Generics, ) -> AutoTraitResult { - let tcx = self.cx.tcx; - let ty = self.cx.tcx.type_of(did); - - let orig_params = tcx.param_env(did); - - let trait_ref = ty::TraitRef { - def_id: trait_did, - substs: tcx.mk_substs_trait(ty, &[]), - }; - - let trait_pred = ty::Binder::bind(trait_ref); + match self.f.find_auto_trait_generics(did, trait_did, generics, + |infcx, mut info| { + let region_data = info.region_data; + let names_map = + info.names_map + .drain() + .map(|(name, lifetime)| (name, Lifetime(lifetime))) + .collect(); + let lifetime_predicates = + self.handle_lifetimes(®ion_data, &names_map); + let new_generics = self.param_env_to_generics( + infcx.tcx, + did, + info.full_user_env, + generics.clone(), + lifetime_predicates, + info.vid_to_region, + ); - let bail_out = tcx.infer_ctxt().enter(|infcx| { - let mut selcx = SelectionContext::with_negative(&infcx, true); - let result = selcx.select(&Obligation::new( - ObligationCause::dummy(), - orig_params, - trait_pred.to_poly_trait_predicate(), - )); - match result { - Ok(Some(Vtable::VtableImpl(_))) => { debug!( "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): \ - manual impl found, bailing out", - did, trait_did, generics + finished with {:?}", + did, trait_did, generics, new_generics ); - return true; - } - _ => return false, - }; - }); - - // If an explicit impl exists, it always takes priority over an auto impl - if bail_out { - return AutoTraitResult::ExplicitImpl; - } - - return tcx.infer_ctxt().enter(|mut infcx| { - let mut fresh_preds = FxHashSet(); - - // Due to the way projections are handled by SelectionContext, we need to run - // evaluate_predicates twice: once on the original param env, and once on the result of - // the first evaluate_predicates call. - // - // The problem is this: most of rustc, including SelectionContext and traits::project, - // are designed to work with a concrete usage of a type (e.g. Vec - // fn() { Vec }. This information will generally never change - given - // the 'T' in fn() { ... }, we'll never know anything else about 'T'. - // If we're unable to prove that 'T' implements a particular trait, we're done - - // there's nothing left to do but error out. - // - // However, synthesizing an auto trait impl works differently. Here, we start out with - // a set of initial conditions - the ParamEnv of the struct/enum/union we're dealing - // with - and progressively discover the conditions we need to fulfill for it to - // implement a certain auto trait. This ends up breaking two assumptions made by trait - // selection and projection: - // - // * We can always cache the result of a particular trait selection for the lifetime of - // an InfCtxt - // * Given a projection bound such as '::SomeItem = K', if 'T: - // SomeTrait' doesn't hold, then we don't need to care about the 'SomeItem = K' - // - // We fix the first assumption by manually clearing out all of the InferCtxt's caches - // in between calls to SelectionContext.select. This allows us to keep all of the - // intermediate types we create bound to the 'tcx lifetime, rather than needing to lift - // them between calls. - // - // We fix the second assumption by reprocessing the result of our first call to - // evaluate_predicates. Using the example of '::SomeItem = K', our first - // pass will pick up 'T: SomeTrait', but not 'SomeItem = K'. On our second pass, - // traits::project will see that 'T: SomeTrait' is in our ParamEnv, allowing - // SelectionContext to return it back to us. - - let (new_env, user_env) = match self.evaluate_predicates( - &mut infcx, - did, - trait_did, - ty, - orig_params.clone(), - orig_params, - &mut fresh_preds, - false, - ) { - Some(e) => e, - None => return AutoTraitResult::NegativeImpl, - }; - - let (full_env, full_user_env) = self.evaluate_predicates( - &mut infcx, - did, - trait_did, - ty, - new_env.clone(), - user_env, - &mut fresh_preds, - true, - ).unwrap_or_else(|| { - panic!( - "Failed to fully process: {:?} {:?} {:?}", - ty, trait_did, orig_params - ) - }); - - debug!( - "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): fulfilling \ - with {:?}", - did, trait_did, generics, full_env - ); - infcx.clear_caches(); - - // At this point, we already have all of the bounds we need. FulfillmentContext is used - // to store all of the necessary region/lifetime bounds in the InferContext, as well as - // an additional sanity check. - let mut fulfill = FulfillmentContext::new(); - fulfill.register_bound( - &infcx, - full_env, - ty, - trait_did, - ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID), - ); - fulfill.select_all_or_error(&infcx).unwrap_or_else(|e| { - panic!( - "Unable to fulfill trait {:?} for '{:?}': {:?}", - trait_did, ty, e - ) - }); - - let names_map: FxHashMap = generics - .regions - .iter() - .map(|l| (l.name.to_string(), l.clean(self.cx))) - .collect(); - - let body_ids: FxHashSet<_> = infcx - .region_obligations - .borrow() - .iter() - .map(|&(id, _)| id) - .collect(); - - for id in body_ids { - infcx.process_registered_region_obligations(&[], None, full_env.clone(), id); - } - - let region_data = infcx - .borrow_region_constraints() - .region_constraint_data() - .clone(); - - let lifetime_predicates = self.handle_lifetimes(®ion_data, &names_map); - let vid_to_region = self.map_vid_to_region(®ion_data); - - debug!( - "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): computed \ - lifetime information '{:?}' '{:?}'", - did, trait_did, generics, lifetime_predicates, vid_to_region - ); - - let new_generics = self.param_env_to_generics( - infcx.tcx, - did, - full_user_env, - generics.clone(), - lifetime_predicates, - vid_to_region, - ); - debug!( - "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): finished with \ - {:?}", - did, trait_did, generics, new_generics - ); - return AutoTraitResult::PositiveImpl(new_generics); - }); - } - - fn clean_pred<'c, 'd, 'cx>( - &self, - infcx: &InferCtxt<'c, 'd, 'cx>, - p: ty::Predicate<'cx>, - ) -> ty::Predicate<'cx> { - infcx.freshen(p) - } - - fn evaluate_nested_obligations<'b, 'c, 'd, 'cx, - T: Iterator>>>( - &self, - ty: ty::Ty, - nested: T, - computed_preds: &'b mut FxHashSet>, - fresh_preds: &'b mut FxHashSet>, - predicates: &'b mut VecDeque>, - select: &mut traits::SelectionContext<'c, 'd, 'cx>, - only_projections: bool, - ) -> bool { - let dummy_cause = ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID); - - for (obligation, predicate) in nested - .filter(|o| o.recursion_depth == 1) - .map(|o| (o.clone(), o.predicate.clone())) - { - let is_new_pred = - fresh_preds.insert(self.clean_pred(select.infcx(), predicate.clone())); - - match &predicate { - &ty::Predicate::Trait(ref p) => { - let substs = &p.skip_binder().trait_ref.substs; - - if self.is_of_param(substs) && !only_projections && is_new_pred { - computed_preds.insert(predicate); - } - predicates.push_back(p.clone()); - } - &ty::Predicate::Projection(p) => { - // If the projection isn't all type vars, then - // we don't want to add it as a bound - if self.is_of_param(p.skip_binder().projection_ty.substs) && is_new_pred { - computed_preds.insert(predicate); - } else { - match traits::poly_project_and_unify_type( - select, - &obligation.with(p.clone()), - ) { - Err(e) => { - debug!( - "evaluate_nested_obligations: Unable to unify predicate \ - '{:?}' '{:?}', bailing out", - ty, e - ); - return false; - } - Ok(Some(v)) => { - if !self.evaluate_nested_obligations( - ty, - v.clone().iter().cloned(), - computed_preds, - fresh_preds, - predicates, - select, - only_projections, - ) { - return false; - } - } - Ok(None) => { - panic!("Unexpected result when selecting {:?} {:?}", ty, obligation) - } - } - } - } - &ty::Predicate::RegionOutlives(ref binder) => { - if let Err(_) = select - .infcx() - .region_outlives_predicate(&dummy_cause, binder) - { - return false; - } - } - &ty::Predicate::TypeOutlives(ref binder) => { - match ( - binder.no_late_bound_regions(), - binder.map_bound_ref(|pred| pred.0).no_late_bound_regions(), - ) { - (None, Some(t_a)) => { - select.infcx().register_region_obligation( - ast::DUMMY_NODE_ID, - RegionObligation { - sup_type: t_a, - sub_region: select.infcx().tcx.types.re_static, - cause: dummy_cause.clone(), - }, - ); - } - (Some(ty::OutlivesPredicate(t_a, r_b)), _) => { - select.infcx().register_region_obligation( - ast::DUMMY_NODE_ID, - RegionObligation { - sup_type: t_a, - sub_region: r_b, - cause: dummy_cause.clone(), - }, - ); - } - _ => {} - }; - } - _ => panic!("Unexpected predicate {:?} {:?}", ty, predicate), - }; - } - return true; - } - - // The core logic responsible for computing the bounds for our synthesized impl. - // - // To calculate the bounds, we call SelectionContext.select in a loop. Like FulfillmentContext, - // we recursively select the nested obligations of predicates we encounter. However, whenver we - // encounter an UnimplementedError involving a type parameter, we add it to our ParamEnv. Since - // our goal is to determine when a particular type implements an auto trait, Unimplemented - // errors tell us what conditions need to be met. - // - // This method ends up working somewhat similary to FulfillmentContext, but with a few key - // differences. FulfillmentContext works under the assumption that it's dealing with concrete - // user code. According, it considers all possible ways that a Predicate could be met - which - // isn't always what we want for a synthesized impl. For example, given the predicate 'T: - // Iterator', FulfillmentContext can end up reporting an Unimplemented error for T: - // IntoIterator - since there's an implementation of Iteratpr where T: IntoIterator, - // FulfillmentContext will drive SelectionContext to consider that impl before giving up. If we - // were to rely on FulfillmentContext's decision, we might end up synthesizing an impl like - // this: - // 'impl Send for Foo where T: IntoIterator' - // - // While it might be technically true that Foo implements Send where T: IntoIterator, - // the bound is overly restrictive - it's really only necessary that T: Iterator. - // - // For this reason, evaluate_predicates handles predicates with type variables specially. When - // we encounter an Unimplemented error for a bound such as 'T: Iterator', we immediately add it - // to our ParamEnv, and add it to our stack for recursive evaluation. When we later select it, - // we'll pick up any nested bounds, without ever inferring that 'T: IntoIterator' needs to - // hold. - // - // One additonal consideration is supertrait bounds. Normally, a ParamEnv is only ever - // consutrcted once for a given type. As part of the construction process, the ParamEnv will - // have any supertrait bounds normalized - e.g. if we have a type 'struct Foo', the - // ParamEnv will contain 'T: Copy' and 'T: Clone', since 'Copy: Clone'. When we construct our - // own ParamEnv, we need to do this outselves, through traits::elaborate_predicates, or else - // SelectionContext will choke on the missing predicates. However, this should never show up in - // the final synthesized generics: we don't want our generated docs page to contain something - // like 'T: Copy + Clone', as that's redundant. Therefore, we keep track of a separate - // 'user_env', which only holds the predicates that will actually be displayed to the user. - fn evaluate_predicates<'b, 'gcx, 'c>( - &self, - infcx: &mut InferCtxt<'b, 'tcx, 'c>, - ty_did: DefId, - trait_did: DefId, - ty: ty::Ty<'c>, - param_env: ty::ParamEnv<'c>, - user_env: ty::ParamEnv<'c>, - fresh_preds: &mut FxHashSet>, - only_projections: bool, - ) -> Option<(ty::ParamEnv<'c>, ty::ParamEnv<'c>)> { - let tcx = infcx.tcx; - - let mut select = traits::SelectionContext::new(&infcx); - - let mut already_visited = FxHashSet(); - let mut predicates = VecDeque::new(); - predicates.push_back(ty::Binder::bind(ty::TraitPredicate { - trait_ref: ty::TraitRef { - def_id: trait_did, - substs: infcx.tcx.mk_substs_trait(ty, &[]), - }, - })); - - let mut computed_preds: FxHashSet<_> = param_env.caller_bounds.iter().cloned().collect(); - let mut user_computed_preds: FxHashSet<_> = - user_env.caller_bounds.iter().cloned().collect(); - - let mut new_env = param_env.clone(); - let dummy_cause = ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID); - - while let Some(pred) = predicates.pop_front() { - infcx.clear_caches(); - - if !already_visited.insert(pred.clone()) { - continue; - } - - let result = select.select(&Obligation::new(dummy_cause.clone(), new_env, pred)); - - match &result { - &Ok(Some(ref vtable)) => { - let obligations = vtable.clone().nested_obligations().into_iter(); - - if !self.evaluate_nested_obligations( - ty, - obligations, - &mut user_computed_preds, - fresh_preds, - &mut predicates, - &mut select, - only_projections, - ) { - return None; - } - } - &Ok(None) => {} - &Err(SelectionError::Unimplemented) => { - if self.is_of_param(pred.skip_binder().trait_ref.substs) { - already_visited.remove(&pred); - user_computed_preds.insert(ty::Predicate::Trait(pred.clone())); - predicates.push_back(pred); - } else { - debug!( - "evaluate_nested_obligations: Unimplemented found, bailing: {:?} {:?} \ - {:?}", - ty, - pred, - pred.skip_binder().trait_ref.substs - ); - return None; - } - } - _ => panic!("Unexpected error for '{:?}': {:?}", ty, result), - }; - computed_preds.extend(user_computed_preds.iter().cloned()); - let normalized_preds = - traits::elaborate_predicates(tcx, computed_preds.clone().into_iter().collect()); - new_env = ty::ParamEnv::new( - tcx.mk_predicates(normalized_preds), - param_env.reveal, - ); - } - - let final_user_env = ty::ParamEnv::new( - tcx.mk_predicates(user_computed_preds.into_iter()), - user_env.reveal, - ); - debug!( - "evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \ - '{:?}'", - ty_did, trait_did, new_env, final_user_env - ); - - return Some((new_env, final_user_env)); - } - - fn is_of_param(&self, substs: &Substs) -> bool { - if substs.is_noop() { - return false; + new_generics + }) { + auto::AutoTraitResult::ExplicitImpl => AutoTraitResult::ExplicitImpl, + auto::AutoTraitResult::NegativeImpl => AutoTraitResult::NegativeImpl, + auto::AutoTraitResult::PositiveImpl(res) => AutoTraitResult::PositiveImpl(res), } - - return match substs.type_at(0).sty { - ty::TyParam(_) => true, - ty::TyProjection(p) => self.is_of_param(p.substs), - _ => false, - }; } fn get_lifetime(&self, region: Region, names_map: &FxHashMap) -> Lifetime { @@ -733,108 +335,6 @@ impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> { } } - // This is very similar to handle_lifetimes. However, instead of matching ty::Region's - // to each other, we match ty::RegionVid's to ty::Region's - fn map_vid_to_region<'cx>( - &self, - regions: &RegionConstraintData<'cx>, - ) -> FxHashMap> { - let mut vid_map: FxHashMap, RegionDeps<'cx>> = FxHashMap(); - let mut finished_map = FxHashMap(); - - for constraint in regions.constraints.keys() { - match constraint { - &Constraint::VarSubVar(r1, r2) => { - { - let deps1 = vid_map - .entry(RegionTarget::RegionVid(r1)) - .or_insert_with(|| Default::default()); - deps1.larger.insert(RegionTarget::RegionVid(r2)); - } - - let deps2 = vid_map - .entry(RegionTarget::RegionVid(r2)) - .or_insert_with(|| Default::default()); - deps2.smaller.insert(RegionTarget::RegionVid(r1)); - } - &Constraint::RegSubVar(region, vid) => { - { - let deps1 = vid_map - .entry(RegionTarget::Region(region)) - .or_insert_with(|| Default::default()); - deps1.larger.insert(RegionTarget::RegionVid(vid)); - } - - let deps2 = vid_map - .entry(RegionTarget::RegionVid(vid)) - .or_insert_with(|| Default::default()); - deps2.smaller.insert(RegionTarget::Region(region)); - } - &Constraint::VarSubReg(vid, region) => { - finished_map.insert(vid, region); - } - &Constraint::RegSubReg(r1, r2) => { - { - let deps1 = vid_map - .entry(RegionTarget::Region(r1)) - .or_insert_with(|| Default::default()); - deps1.larger.insert(RegionTarget::Region(r2)); - } - - let deps2 = vid_map - .entry(RegionTarget::Region(r2)) - .or_insert_with(|| Default::default()); - deps2.smaller.insert(RegionTarget::Region(r1)); - } - } - } - - while !vid_map.is_empty() { - let target = vid_map.keys().next().expect("Keys somehow empty").clone(); - let deps = vid_map.remove(&target).expect("Entry somehow missing"); - - for smaller in deps.smaller.iter() { - for larger in deps.larger.iter() { - match (smaller, larger) { - (&RegionTarget::Region(_), &RegionTarget::Region(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { - let smaller_deps = v.into_mut(); - smaller_deps.larger.insert(*larger); - smaller_deps.larger.remove(&target); - } - - if let Entry::Occupied(v) = vid_map.entry(*larger) { - let larger_deps = v.into_mut(); - larger_deps.smaller.insert(*smaller); - larger_deps.smaller.remove(&target); - } - } - (&RegionTarget::RegionVid(v1), &RegionTarget::Region(r1)) => { - finished_map.insert(v1, r1); - } - (&RegionTarget::Region(_), &RegionTarget::RegionVid(_)) => { - // Do nothing - we don't care about regions that are smaller than vids - } - (&RegionTarget::RegionVid(_), &RegionTarget::RegionVid(_)) => { - if let Entry::Occupied(v) = vid_map.entry(*smaller) { - let smaller_deps = v.into_mut(); - smaller_deps.larger.insert(*larger); - smaller_deps.larger.remove(&target); - } - - if let Entry::Occupied(v) = vid_map.entry(*larger) { - let larger_deps = v.into_mut(); - larger_deps.smaller.insert(*smaller); - larger_deps.smaller.remove(&target); - } - } - } - } - } - } - finished_map - } - // This method calculates two things: Lifetime constraints of the form 'a: 'b, // and region constraints of the form ReVar: 'a // diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index fb05cbfe32c70..9b067abd1af6b 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -40,17 +40,13 @@ use rustc::hir::{self, HirVec}; use rustc::hir::def::{self, Def, CtorKind}; use rustc::hir::def_id::{CrateNum, DefId, DefIndex, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc::hir::def_id::DefIndexAddressSpace; -use rustc::traits; use rustc::ty::subst::Substs; use rustc::ty::{self, TyCtxt, Region, RegionVid, Ty, AdtKind}; use rustc::middle::stability; use rustc::util::nodemap::{FxHashMap, FxHashSet}; use rustc_typeck::hir_ty_to_ty; -use rustc::infer::{InferCtxt, RegionObligation}; use rustc::infer::region_constraints::{RegionConstraintData, Constraint}; -use rustc::traits::*; use std::collections::hash_map::Entry; -use std::collections::VecDeque; use std::fmt; use std::default::Default; @@ -3524,14 +3520,12 @@ pub struct Impl { } pub fn get_auto_traits_with_node_id(cx: &DocContext, id: ast::NodeId, name: String) -> Vec { - let finder = AutoTraitFinder { cx }; + let finder = AutoTraitFinder::new(cx); finder.get_with_node_id(id, name) } pub fn get_auto_traits_with_def_id(cx: &DocContext, id: DefId) -> Vec { - let finder = AutoTraitFinder { - cx, - }; + let finder = AutoTraitFinder::new(cx); finder.get_with_def_id(id) } From 67c226aecece6f22d7a25a24cc2cb4654a29c0d1 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Thu, 5 Apr 2018 21:21:17 +0200 Subject: [PATCH 04/10] Cleaned up for tidy checks. --- src/librustc/traits/auto_trait.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index 62148a8e2bfa8..f2416eb7c8f23 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -21,7 +21,7 @@ use infer::region_constraints::{Constraint, RegionConstraintData}; use ty::{Region, RegionVid}; use ty::fold::TypeFolder; -// TODO(twk): this is obviously not nice to duplicate like that +// FIXME(twk): this is obviously not nice to duplicate like that #[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] pub enum RegionTarget<'tcx> { Region(Region<'tcx>), @@ -374,7 +374,6 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } } - // TODO(twk): lifetime branding pub fn get_lifetime(&self, region: Region, names_map: &FxHashMap) -> String { self.region_name(region) .map(|name| { @@ -382,7 +381,6 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { panic!("Missing lifetime with name {:?} for {:?}", name, region) }) }) - // TODO(twk): .unwrap_or(&Lifetime::statik()) .unwrap_or(&"'static".to_string()) .clone() } From 0a4a85e8ae5dc687cee5f940a54ce841dc9ab717 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Sat, 7 Apr 2018 00:12:51 +0200 Subject: [PATCH 05/10] Adjusted types and visibility in auto trait machinery. --- src/librustc/traits/auto_trait.rs | 11 +++++++---- src/librustdoc/clean/auto_trait.rs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index f2416eb7c8f23..2ee22d494e1c3 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -57,18 +57,21 @@ pub struct AutoTraitInfo<'cx> { } pub struct AutoTraitFinder<'a, 'tcx: 'a> { - pub tcx: &'a TyCtxt<'a, 'tcx, 'tcx>, + tcx: &'a TyCtxt<'a, 'tcx, 'tcx>, } impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { - pub fn find_auto_trait_generics( + pub fn new(tcx: &'a TyCtxt<'a, 'tcx, 'tcx>) -> Self { + AutoTraitFinder { tcx } + } + + pub fn find_auto_trait_generics( &self, did: DefId, trait_did: DefId, generics: &ty::Generics, - auto_trait_callback: F) + auto_trait_callback: impl for<'i> Fn(&InferCtxt<'_, 'tcx, 'i>, AutoTraitInfo<'i>) -> A) -> AutoTraitResult - where F: for<'b, 'cx, 'cx2> Fn(&InferCtxt<'b, 'cx, 'cx2>, AutoTraitInfo<'cx2>) -> A { let tcx = self.tcx; let ty = self.tcx.type_of(did); diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index d3e2dac72ee33..58ee42402d51c 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -21,7 +21,7 @@ pub struct AutoTraitFinder<'a, 'tcx: 'a, 'rcx: 'a> { impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> { pub fn new(cx: &'a core::DocContext<'a, 'tcx, 'rcx>) -> Self { - let f = auto::AutoTraitFinder { tcx: &cx.tcx }; + let f = auto::AutoTraitFinder::new(&cx.tcx); AutoTraitFinder { cx, f } } From 309e4035aa65e37c37bf2763029df9bcff81da87 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Sat, 7 Apr 2018 00:25:25 +0200 Subject: [PATCH 06/10] Reformatted and commented various bits of the auto trait machinery. --- src/librustc/traits/auto_trait.rs | 397 +++++++++++++++--------------- 1 file changed, 199 insertions(+), 198 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index 2ee22d494e1c3..7e4d450502217 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -25,13 +25,13 @@ use ty::fold::TypeFolder; #[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] pub enum RegionTarget<'tcx> { Region(Region<'tcx>), - RegionVid(RegionVid) + RegionVid(RegionVid), } #[derive(Default, Debug, Clone)] pub struct RegionDeps<'tcx> { larger: FxHashSet>, - smaller: FxHashSet> + smaller: FxHashSet>, } pub enum AutoTraitResult { @@ -43,7 +43,8 @@ pub enum AutoTraitResult { impl AutoTraitResult { fn is_auto(&self) -> bool { match *self { - AutoTraitResult::PositiveImpl(_) | AutoTraitResult::NegativeImpl => true, + AutoTraitResult::PositiveImpl(_) | + AutoTraitResult::NegativeImpl => true, _ => false, } } @@ -65,14 +66,17 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { AutoTraitFinder { tcx } } - pub fn find_auto_trait_generics( - &self, - did: DefId, - trait_did: DefId, - generics: &ty::Generics, - auto_trait_callback: impl for<'i> Fn(&InferCtxt<'_, 'tcx, 'i>, AutoTraitInfo<'i>) -> A) - -> AutoTraitResult - { + /// Determine whether an auto trait is implemented for a type, and if this is the case if + /// non-trivial bounds need to be fulfilled, invoke a callback to compute a value representing + /// these in a fashion suitable for the caller. + pub fn find_auto_trait_generics(&self, + did: DefId, + trait_did: DefId, + generics: &ty::Generics, + auto_trait_callback: + impl for<'i> Fn(&InferCtxt<'_, 'tcx, 'i>, + AutoTraitInfo<'i>) -> A) + -> AutoTraitResult { let tcx = self.tcx; let ty = self.tcx.type_of(did); @@ -85,32 +89,33 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let trait_pred = ty::Binder::bind(trait_ref); - let bail_out = tcx.infer_ctxt().enter(|infcx| { - let mut selcx = SelectionContext::with_negative(&infcx, true); - let result = selcx.select(&Obligation::new( - ObligationCause::dummy(), - orig_params, - trait_pred.to_poly_trait_predicate(), - )); - match result { - Ok(Some(Vtable::VtableImpl(_))) => { - debug!( - "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): \ + let bail_out = tcx.infer_ctxt() + .enter(|infcx| { + let mut selcx = SelectionContext::with_negative(&infcx, true); + let result = + selcx.select(&Obligation::new(ObligationCause::dummy(), + orig_params, + trait_pred.to_poly_trait_predicate())); + match result { + Ok(Some(Vtable::VtableImpl(_))) => { + debug!("find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): \ manual impl found, bailing out", - did, trait_did, generics - ); - return true; - } - _ => return false, - }; - }); + did, + trait_did, + generics); + return true; + } + _ => return false, + }; + }); // If an explicit impl exists, it always takes priority over an auto impl if bail_out { return AutoTraitResult::ExplicitImpl; } - return tcx.infer_ctxt().enter(|mut infcx| { + return tcx.infer_ctxt() + .enter(|mut infcx| { let mut fresh_preds = FxHashSet(); // Due to the way projections are handled by SelectionContext, we need to run @@ -146,60 +151,59 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // traits::project will see that 'T: SomeTrait' is in our ParamEnv, allowing // SelectionContext to return it back to us. - let (new_env, user_env) = match self.evaluate_predicates( - &mut infcx, - did, - trait_did, - ty, - orig_params.clone(), - orig_params, - &mut fresh_preds, - false, - ) { - Some(e) => e, - None => return AutoTraitResult::NegativeImpl, - }; - - let (full_env, full_user_env) = self.evaluate_predicates( - &mut infcx, - did, - trait_did, - ty, - new_env.clone(), - user_env, - &mut fresh_preds, - true, - ).unwrap_or_else(|| { - panic!( - "Failed to fully process: {:?} {:?} {:?}", - ty, trait_did, orig_params - ) - }); - - debug!( - "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): fulfilling \ + let (new_env, user_env) = + match self.evaluate_predicates(&mut infcx, + did, + trait_did, + ty, + orig_params.clone(), + orig_params, + &mut fresh_preds, + false) { + Some(e) => e, + None => return AutoTraitResult::NegativeImpl, + }; + + let (full_env, full_user_env) = self.evaluate_predicates(&mut infcx, + did, + trait_did, + ty, + new_env.clone(), + user_env, + &mut fresh_preds, + true) + .unwrap_or_else(|| { + panic!("Failed to fully process: {:?} {:?} {:?}", + ty, + trait_did, + orig_params) + }); + + debug!("find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): fulfilling \ with {:?}", - did, trait_did, generics, full_env - ); + did, + trait_did, + generics, + full_env); infcx.clear_caches(); // At this point, we already have all of the bounds we need. FulfillmentContext is used // to store all of the necessary region/lifetime bounds in the InferContext, as well as // an additional sanity check. let mut fulfill = FulfillmentContext::new(); - fulfill.register_bound( - &infcx, - full_env, - ty, - trait_did, - ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID), - ); - fulfill.select_all_or_error(&infcx).unwrap_or_else(|e| { - panic!( - "Unable to fulfill trait {:?} for '{:?}': {:?}", - trait_did, ty, e - ) - }); + fulfill.register_bound(&infcx, + full_env, + ty, + trait_did, + ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID)); + fulfill + .select_all_or_error(&infcx) + .unwrap_or_else(|e| { + panic!("Unable to fulfill trait {:?} for '{:?}': {:?}", + trait_did, + ty, + e) + }); let names_map: FxHashMap = generics .regions @@ -227,7 +231,12 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let vid_to_region = self.map_vid_to_region(®ion_data); - let info = AutoTraitInfo { full_user_env, region_data, names_map, vid_to_region }; + let info = AutoTraitInfo { + full_user_env, + region_data, + names_map, + vid_to_region, + }; return AutoTraitResult::PositiveImpl(auto_trait_callback(&infcx, info)); }); @@ -272,17 +281,16 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // the final synthesized generics: we don't want our generated docs page to contain something // like 'T: Copy + Clone', as that's redundant. Therefore, we keep track of a separate // 'user_env', which only holds the predicates that will actually be displayed to the user. - pub fn evaluate_predicates<'b, 'gcx, 'c>( - &self, - infcx: & InferCtxt<'b, 'tcx, 'c>, - ty_did: DefId, - trait_did: DefId, - ty: ty::Ty<'c>, - param_env: ty::ParamEnv<'c>, - user_env: ty::ParamEnv<'c>, - fresh_preds: &mut FxHashSet>, - only_projections: bool, - ) -> Option<(ty::ParamEnv<'c>, ty::ParamEnv<'c>)> { + pub fn evaluate_predicates<'b, 'gcx, 'c>(&self, + infcx: &InferCtxt<'b, 'tcx, 'c>, + ty_did: DefId, + trait_did: DefId, + ty: ty::Ty<'c>, + param_env: ty::ParamEnv<'c>, + user_env: ty::ParamEnv<'c>, + fresh_preds: &mut FxHashSet>, + only_projections: bool) + -> Option<(ty::ParamEnv<'c>, ty::ParamEnv<'c>)> { let tcx = infcx.tcx; let mut select = SelectionContext::new(&infcx); @@ -290,11 +298,11 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let mut already_visited = FxHashSet(); let mut predicates = VecDeque::new(); predicates.push_back(ty::Binder::bind(ty::TraitPredicate { - trait_ref: ty::TraitRef { - def_id: trait_did, - substs: infcx.tcx.mk_substs_trait(ty, &[]), - }, - })); + trait_ref: ty::TraitRef { + def_id: trait_did, + substs: infcx.tcx.mk_substs_trait(ty, &[]), + }, + })); let mut computed_preds: FxHashSet<_> = param_env.caller_bounds.iter().cloned().collect(); let mut user_computed_preds: FxHashSet<_> = @@ -316,15 +324,13 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { &Ok(Some(ref vtable)) => { let obligations = vtable.clone().nested_obligations().into_iter(); - if !self.evaluate_nested_obligations( - ty, - obligations, - &mut user_computed_preds, - fresh_preds, - &mut predicates, - &mut select, - only_projections, - ) { + if !self.evaluate_nested_obligations(ty, + obligations, + &mut user_computed_preds, + fresh_preds, + &mut predicates, + &mut select, + only_projections) { return None; } } @@ -335,13 +341,11 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { user_computed_preds.insert(ty::Predicate::Trait(pred.clone())); predicates.push_back(pred); } else { - debug!( - "evaluate_nested_obligations: Unimplemented found, bailing: {:?} {:?} \ - {:?}", - ty, - pred, - pred.skip_binder().trait_ref.substs - ); + debug!("evaluate_nested_obligations: Unimplemented found, bailing: \ + {:?} {:?} {:?}", + ty, + pred, + pred.skip_binder().trait_ref.substs); return None; } } @@ -351,21 +355,17 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { computed_preds.extend(user_computed_preds.iter().cloned()); let normalized_preds = elaborate_predicates(tcx, computed_preds.clone().into_iter().collect()); - new_env = ty::ParamEnv::new( - tcx.mk_predicates(normalized_preds), - param_env.reveal, - ); + new_env = ty::ParamEnv::new(tcx.mk_predicates(normalized_preds), param_env.reveal); } - let final_user_env = ty::ParamEnv::new( - tcx.mk_predicates(user_computed_preds.into_iter()), - user_env.reveal, - ); - debug!( - "evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \ + let final_user_env = ty::ParamEnv::new(tcx.mk_predicates(user_computed_preds.into_iter()), + user_env.reveal); + debug!("evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \ '{:?}'", - ty_did, trait_did, new_env, final_user_env - ); + ty_did, + trait_did, + new_env, + final_user_env); return Some((new_env, final_user_env)); } @@ -380,9 +380,13 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { pub fn get_lifetime(&self, region: Region, names_map: &FxHashMap) -> String { self.region_name(region) .map(|name| { - names_map.get(&name).unwrap_or_else(|| { - panic!("Missing lifetime with name {:?} for {:?}", name, region) - }) + names_map + .get(&name) + .unwrap_or_else(|| { + panic!("Missing lifetime with name {:?} for {:?}", + name, + region) + }) }) .unwrap_or(&"'static".to_string()) .clone() @@ -390,10 +394,9 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // This is very similar to handle_lifetimes. However, instead of matching ty::Region's // to each other, we match ty::RegionVid's to ty::Region's - pub fn map_vid_to_region<'cx>( - &self, - regions: &RegionConstraintData<'cx>, - ) -> FxHashMap> { + pub fn map_vid_to_region<'cx>(&self, + regions: &RegionConstraintData<'cx>) + -> FxHashMap> { let mut vid_map: FxHashMap, RegionDeps<'cx>> = FxHashMap(); let mut finished_map = FxHashMap(); @@ -496,31 +499,34 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } return match substs.type_at(0).sty { - ty::TyParam(_) => true, - ty::TyProjection(p) => self.is_of_param(p.substs), - _ => false, - }; + ty::TyParam(_) => true, + ty::TyProjection(p) => self.is_of_param(p.substs), + _ => false, + }; } - pub fn evaluate_nested_obligations<'b, 'c, 'd, 'cx, - T: Iterator>>>( - &self, - ty: ty::Ty, - nested: T, - computed_preds: &'b mut FxHashSet>, - fresh_preds: &'b mut FxHashSet>, - predicates: &'b mut VecDeque>, - select: &mut SelectionContext<'c, 'd, 'cx>, - only_projections: bool, - ) -> bool { + pub fn evaluate_nested_obligations<'b, + 'c, + 'd, + 'cx, + T: Iterator>>> + (&self, + ty: ty::Ty, + nested: T, + computed_preds: &'b mut FxHashSet>, + fresh_preds: &'b mut FxHashSet>, + predicates: &'b mut VecDeque>, + select: &mut SelectionContext<'c, 'd, 'cx>, + only_projections: bool) + -> bool { let dummy_cause = ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID); - for (obligation, predicate) in nested - .filter(|o| o.recursion_depth == 1) - .map(|o| (o.clone(), o.predicate.clone())) - { - let is_new_pred = - fresh_preds.insert(self.clean_pred(select.infcx(), predicate.clone())); + for (obligation, predicate) in + nested + .filter(|o| o.recursion_depth == 1) + .map(|o| (o.clone(), o.predicate.clone())) { + let is_new_pred = fresh_preds + .insert(self.clean_pred(select.infcx(), predicate.clone())); match &predicate { &ty::Predicate::Trait(ref p) => { @@ -537,28 +543,22 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { if self.is_of_param(p.skip_binder().projection_ty.substs) && is_new_pred { computed_preds.insert(predicate); } else { - match poly_project_and_unify_type( - select, - &obligation.with(p.clone()), - ) { + match poly_project_and_unify_type(select, &obligation.with(p.clone())) { Err(e) => { - debug!( - "evaluate_nested_obligations: Unable to unify predicate \ + debug!("evaluate_nested_obligations: Unable to unify predicate \ '{:?}' '{:?}', bailing out", - ty, e - ); + ty, + e); return false; } Ok(Some(v)) => { - if !self.evaluate_nested_obligations( - ty, - v.clone().iter().cloned(), - computed_preds, - fresh_preds, - predicates, - select, - only_projections, - ) { + if !self.evaluate_nested_obligations(ty, + v.clone().iter().cloned(), + computed_preds, + fresh_preds, + predicates, + select, + only_projections) { return false; } } @@ -570,36 +570,37 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } &ty::Predicate::RegionOutlives(ref binder) => { if let Err(_) = select - .infcx() - .region_outlives_predicate(&dummy_cause, binder) - { + .infcx() + .region_outlives_predicate(&dummy_cause, binder) { return false; } } &ty::Predicate::TypeOutlives(ref binder) => { - match ( - binder.no_late_bound_regions(), - binder.map_bound_ref(|pred| pred.0).no_late_bound_regions(), - ) { + match (binder.no_late_bound_regions(), + binder.map_bound_ref(|pred| pred.0).no_late_bound_regions()) { (None, Some(t_a)) => { - select.infcx().register_region_obligation( - ast::DUMMY_NODE_ID, - RegionObligation { - sup_type: t_a, - sub_region: select.infcx().tcx.types.re_static, - cause: dummy_cause.clone(), - }, - ); + select + .infcx() + .register_region_obligation(ast::DUMMY_NODE_ID, + RegionObligation { + sup_type: t_a, + sub_region: select + .infcx() + .tcx + .types + .re_static, + cause: dummy_cause.clone(), + }); } (Some(ty::OutlivesPredicate(t_a, r_b)), _) => { - select.infcx().register_region_obligation( - ast::DUMMY_NODE_ID, - RegionObligation { - sup_type: t_a, - sub_region: r_b, - cause: dummy_cause.clone(), - }, - ); + select + .infcx() + .register_region_obligation(ast::DUMMY_NODE_ID, + RegionObligation { + sup_type: t_a, + sub_region: r_b, + cause: dummy_cause.clone(), + }); } _ => {} }; @@ -610,11 +611,10 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { return true; } - pub fn clean_pred<'c, 'd, 'cx>( - &self, - infcx: &InferCtxt<'c, 'd, 'cx>, - p: ty::Predicate<'cx>, - ) -> ty::Predicate<'cx> { + pub fn clean_pred<'c, 'd, 'cx>(&self, + infcx: &InferCtxt<'c, 'd, 'cx>, + p: ty::Predicate<'cx>) + -> ty::Predicate<'cx> { infcx.freshen(p) } } @@ -632,8 +632,9 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for RegionReplacer<'a, 'gcx, 'tcx> { fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { (match r { - &ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(), - _ => None, - }).unwrap_or_else(|| r.super_fold_with(self)) + &ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(), + _ => None, + }) + .unwrap_or_else(|| r.super_fold_with(self)) } } From eff15b49f311f4c71afd48ea31fd9510c5f93e07 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Thu, 12 Apr 2018 11:58:34 +0200 Subject: [PATCH 07/10] Reformatted source for auto trait machinery. --- src/librustc/traits/auto_trait.rs | 381 +++++++++++++++--------------- 1 file changed, 193 insertions(+), 188 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index 7e4d450502217..a4beb6fab123a 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -10,16 +10,16 @@ use super::*; -use std::collections::VecDeque; use std::collections::hash_map::Entry; +use std::collections::VecDeque; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use infer::{InferCtxt, RegionObligation}; use infer::region_constraints::{Constraint, RegionConstraintData}; +use infer::{InferCtxt, RegionObligation}; -use ty::{Region, RegionVid}; use ty::fold::TypeFolder; +use ty::{Region, RegionVid}; // FIXME(twk): this is obviously not nice to duplicate like that #[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] @@ -43,8 +43,7 @@ pub enum AutoTraitResult { impl AutoTraitResult { fn is_auto(&self) -> bool { match *self { - AutoTraitResult::PositiveImpl(_) | - AutoTraitResult::NegativeImpl => true, + AutoTraitResult::PositiveImpl(_) | AutoTraitResult::NegativeImpl => true, _ => false, } } @@ -69,14 +68,13 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { /// Determine whether an auto trait is implemented for a type, and if this is the case if /// non-trivial bounds need to be fulfilled, invoke a callback to compute a value representing /// these in a fashion suitable for the caller. - pub fn find_auto_trait_generics(&self, - did: DefId, - trait_did: DefId, - generics: &ty::Generics, - auto_trait_callback: - impl for<'i> Fn(&InferCtxt<'_, 'tcx, 'i>, - AutoTraitInfo<'i>) -> A) - -> AutoTraitResult { + pub fn find_auto_trait_generics( + &self, + did: DefId, + trait_did: DefId, + generics: &ty::Generics, + auto_trait_callback: impl for<'i> Fn(&InferCtxt<'_, 'tcx, 'i>, AutoTraitInfo<'i>) -> A, + ) -> AutoTraitResult { let tcx = self.tcx; let ty = self.tcx.type_of(did); @@ -89,33 +87,32 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let trait_pred = ty::Binder::bind(trait_ref); - let bail_out = tcx.infer_ctxt() - .enter(|infcx| { - let mut selcx = SelectionContext::with_negative(&infcx, true); - let result = - selcx.select(&Obligation::new(ObligationCause::dummy(), - orig_params, - trait_pred.to_poly_trait_predicate())); - match result { - Ok(Some(Vtable::VtableImpl(_))) => { - debug!("find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): \ + let bail_out = tcx.infer_ctxt().enter(|infcx| { + let mut selcx = SelectionContext::with_negative(&infcx, true); + let result = selcx.select(&Obligation::new( + ObligationCause::dummy(), + orig_params, + trait_pred.to_poly_trait_predicate(), + )); + match result { + Ok(Some(Vtable::VtableImpl(_))) => { + debug!( + "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): \ manual impl found, bailing out", - did, - trait_did, - generics); - return true; - } - _ => return false, - }; - }); + did, trait_did, generics + ); + return true; + } + _ => return false, + }; + }); // If an explicit impl exists, it always takes priority over an auto impl if bail_out { return AutoTraitResult::ExplicitImpl; } - return tcx.infer_ctxt() - .enter(|mut infcx| { + return tcx.infer_ctxt().enter(|mut infcx| { let mut fresh_preds = FxHashSet(); // Due to the way projections are handled by SelectionContext, we need to run @@ -151,59 +148,60 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // traits::project will see that 'T: SomeTrait' is in our ParamEnv, allowing // SelectionContext to return it back to us. - let (new_env, user_env) = - match self.evaluate_predicates(&mut infcx, - did, - trait_did, - ty, - orig_params.clone(), - orig_params, - &mut fresh_preds, - false) { - Some(e) => e, - None => return AutoTraitResult::NegativeImpl, - }; - - let (full_env, full_user_env) = self.evaluate_predicates(&mut infcx, - did, - trait_did, - ty, - new_env.clone(), - user_env, - &mut fresh_preds, - true) - .unwrap_or_else(|| { - panic!("Failed to fully process: {:?} {:?} {:?}", - ty, - trait_did, - orig_params) - }); - - debug!("find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): fulfilling \ + let (new_env, user_env) = match self.evaluate_predicates( + &mut infcx, + did, + trait_did, + ty, + orig_params.clone(), + orig_params, + &mut fresh_preds, + false, + ) { + Some(e) => e, + None => return AutoTraitResult::NegativeImpl, + }; + + let (full_env, full_user_env) = self.evaluate_predicates( + &mut infcx, + did, + trait_did, + ty, + new_env.clone(), + user_env, + &mut fresh_preds, + true, + ).unwrap_or_else(|| { + panic!( + "Failed to fully process: {:?} {:?} {:?}", + ty, trait_did, orig_params + ) + }); + + debug!( + "find_auto_trait_generics(did={:?}, trait_did={:?}, generics={:?}): fulfilling \ with {:?}", - did, - trait_did, - generics, - full_env); + did, trait_did, generics, full_env + ); infcx.clear_caches(); // At this point, we already have all of the bounds we need. FulfillmentContext is used // to store all of the necessary region/lifetime bounds in the InferContext, as well as // an additional sanity check. let mut fulfill = FulfillmentContext::new(); - fulfill.register_bound(&infcx, - full_env, - ty, - trait_did, - ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID)); - fulfill - .select_all_or_error(&infcx) - .unwrap_or_else(|e| { - panic!("Unable to fulfill trait {:?} for '{:?}': {:?}", - trait_did, - ty, - e) - }); + fulfill.register_bound( + &infcx, + full_env, + ty, + trait_did, + ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID), + ); + fulfill.select_all_or_error(&infcx).unwrap_or_else(|e| { + panic!( + "Unable to fulfill trait {:?} for '{:?}': {:?}", + trait_did, ty, e + ) + }); let names_map: FxHashMap = generics .regions @@ -281,16 +279,17 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // the final synthesized generics: we don't want our generated docs page to contain something // like 'T: Copy + Clone', as that's redundant. Therefore, we keep track of a separate // 'user_env', which only holds the predicates that will actually be displayed to the user. - pub fn evaluate_predicates<'b, 'gcx, 'c>(&self, - infcx: &InferCtxt<'b, 'tcx, 'c>, - ty_did: DefId, - trait_did: DefId, - ty: ty::Ty<'c>, - param_env: ty::ParamEnv<'c>, - user_env: ty::ParamEnv<'c>, - fresh_preds: &mut FxHashSet>, - only_projections: bool) - -> Option<(ty::ParamEnv<'c>, ty::ParamEnv<'c>)> { + pub fn evaluate_predicates<'b, 'gcx, 'c>( + &self, + infcx: &InferCtxt<'b, 'tcx, 'c>, + ty_did: DefId, + trait_did: DefId, + ty: ty::Ty<'c>, + param_env: ty::ParamEnv<'c>, + user_env: ty::ParamEnv<'c>, + fresh_preds: &mut FxHashSet>, + only_projections: bool, + ) -> Option<(ty::ParamEnv<'c>, ty::ParamEnv<'c>)> { let tcx = infcx.tcx; let mut select = SelectionContext::new(&infcx); @@ -298,11 +297,11 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { let mut already_visited = FxHashSet(); let mut predicates = VecDeque::new(); predicates.push_back(ty::Binder::bind(ty::TraitPredicate { - trait_ref: ty::TraitRef { - def_id: trait_did, - substs: infcx.tcx.mk_substs_trait(ty, &[]), - }, - })); + trait_ref: ty::TraitRef { + def_id: trait_did, + substs: infcx.tcx.mk_substs_trait(ty, &[]), + }, + })); let mut computed_preds: FxHashSet<_> = param_env.caller_bounds.iter().cloned().collect(); let mut user_computed_preds: FxHashSet<_> = @@ -324,13 +323,15 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { &Ok(Some(ref vtable)) => { let obligations = vtable.clone().nested_obligations().into_iter(); - if !self.evaluate_nested_obligations(ty, - obligations, - &mut user_computed_preds, - fresh_preds, - &mut predicates, - &mut select, - only_projections) { + if !self.evaluate_nested_obligations( + ty, + obligations, + &mut user_computed_preds, + fresh_preds, + &mut predicates, + &mut select, + only_projections, + ) { return None; } } @@ -341,11 +342,13 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { user_computed_preds.insert(ty::Predicate::Trait(pred.clone())); predicates.push_back(pred); } else { - debug!("evaluate_nested_obligations: Unimplemented found, bailing: \ + debug!( + "evaluate_nested_obligations: Unimplemented found, bailing: \ {:?} {:?} {:?}", - ty, - pred, - pred.skip_binder().trait_ref.substs); + ty, + pred, + pred.skip_binder().trait_ref.substs + ); return None; } } @@ -358,14 +361,15 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { new_env = ty::ParamEnv::new(tcx.mk_predicates(normalized_preds), param_env.reveal); } - let final_user_env = ty::ParamEnv::new(tcx.mk_predicates(user_computed_preds.into_iter()), - user_env.reveal); - debug!("evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \ + let final_user_env = ty::ParamEnv::new( + tcx.mk_predicates(user_computed_preds.into_iter()), + user_env.reveal, + ); + debug!( + "evaluate_nested_obligations(ty_did={:?}, trait_did={:?}): succeeded with '{:?}' \ '{:?}'", - ty_did, - trait_did, - new_env, - final_user_env); + ty_did, trait_did, new_env, final_user_env + ); return Some((new_env, final_user_env)); } @@ -380,13 +384,9 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { pub fn get_lifetime(&self, region: Region, names_map: &FxHashMap) -> String { self.region_name(region) .map(|name| { - names_map - .get(&name) - .unwrap_or_else(|| { - panic!("Missing lifetime with name {:?} for {:?}", - name, - region) - }) + names_map.get(&name).unwrap_or_else(|| { + panic!("Missing lifetime with name {:?} for {:?}", name, region) + }) }) .unwrap_or(&"'static".to_string()) .clone() @@ -394,9 +394,10 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { // This is very similar to handle_lifetimes. However, instead of matching ty::Region's // to each other, we match ty::RegionVid's to ty::Region's - pub fn map_vid_to_region<'cx>(&self, - regions: &RegionConstraintData<'cx>) - -> FxHashMap> { + pub fn map_vid_to_region<'cx>( + &self, + regions: &RegionConstraintData<'cx>, + ) -> FxHashMap> { let mut vid_map: FxHashMap, RegionDeps<'cx>> = FxHashMap(); let mut finished_map = FxHashMap(); @@ -499,34 +500,36 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } return match substs.type_at(0).sty { - ty::TyParam(_) => true, - ty::TyProjection(p) => self.is_of_param(p.substs), - _ => false, - }; + ty::TyParam(_) => true, + ty::TyProjection(p) => self.is_of_param(p.substs), + _ => false, + }; } - pub fn evaluate_nested_obligations<'b, - 'c, - 'd, - 'cx, - T: Iterator>>> - (&self, - ty: ty::Ty, - nested: T, - computed_preds: &'b mut FxHashSet>, - fresh_preds: &'b mut FxHashSet>, - predicates: &'b mut VecDeque>, - select: &mut SelectionContext<'c, 'd, 'cx>, - only_projections: bool) - -> bool { + pub fn evaluate_nested_obligations< + 'b, + 'c, + 'd, + 'cx, + T: Iterator>>, + >( + &self, + ty: ty::Ty, + nested: T, + computed_preds: &'b mut FxHashSet>, + fresh_preds: &'b mut FxHashSet>, + predicates: &'b mut VecDeque>, + select: &mut SelectionContext<'c, 'd, 'cx>, + only_projections: bool, + ) -> bool { let dummy_cause = ObligationCause::misc(DUMMY_SP, ast::DUMMY_NODE_ID); - for (obligation, predicate) in - nested - .filter(|o| o.recursion_depth == 1) - .map(|o| (o.clone(), o.predicate.clone())) { - let is_new_pred = fresh_preds - .insert(self.clean_pred(select.infcx(), predicate.clone())); + for (obligation, predicate) in nested + .filter(|o| o.recursion_depth == 1) + .map(|o| (o.clone(), o.predicate.clone())) + { + let is_new_pred = + fresh_preds.insert(self.clean_pred(select.infcx(), predicate.clone())); match &predicate { &ty::Predicate::Trait(ref p) => { @@ -545,20 +548,23 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } else { match poly_project_and_unify_type(select, &obligation.with(p.clone())) { Err(e) => { - debug!("evaluate_nested_obligations: Unable to unify predicate \ + debug!( + "evaluate_nested_obligations: Unable to unify predicate \ '{:?}' '{:?}', bailing out", - ty, - e); + ty, e + ); return false; } Ok(Some(v)) => { - if !self.evaluate_nested_obligations(ty, - v.clone().iter().cloned(), - computed_preds, - fresh_preds, - predicates, - select, - only_projections) { + if !self.evaluate_nested_obligations( + ty, + v.clone().iter().cloned(), + computed_preds, + fresh_preds, + predicates, + select, + only_projections, + ) { return false; } } @@ -570,37 +576,36 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { } &ty::Predicate::RegionOutlives(ref binder) => { if let Err(_) = select - .infcx() - .region_outlives_predicate(&dummy_cause, binder) { + .infcx() + .region_outlives_predicate(&dummy_cause, binder) + { return false; } } &ty::Predicate::TypeOutlives(ref binder) => { - match (binder.no_late_bound_regions(), - binder.map_bound_ref(|pred| pred.0).no_late_bound_regions()) { + match ( + binder.no_late_bound_regions(), + binder.map_bound_ref(|pred| pred.0).no_late_bound_regions(), + ) { (None, Some(t_a)) => { - select - .infcx() - .register_region_obligation(ast::DUMMY_NODE_ID, - RegionObligation { - sup_type: t_a, - sub_region: select - .infcx() - .tcx - .types - .re_static, - cause: dummy_cause.clone(), - }); + select.infcx().register_region_obligation( + ast::DUMMY_NODE_ID, + RegionObligation { + sup_type: t_a, + sub_region: select.infcx().tcx.types.re_static, + cause: dummy_cause.clone(), + }, + ); } (Some(ty::OutlivesPredicate(t_a, r_b)), _) => { - select - .infcx() - .register_region_obligation(ast::DUMMY_NODE_ID, - RegionObligation { - sup_type: t_a, - sub_region: r_b, - cause: dummy_cause.clone(), - }); + select.infcx().register_region_obligation( + ast::DUMMY_NODE_ID, + RegionObligation { + sup_type: t_a, + sub_region: r_b, + cause: dummy_cause.clone(), + }, + ); } _ => {} }; @@ -611,10 +616,11 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { return true; } - pub fn clean_pred<'c, 'd, 'cx>(&self, - infcx: &InferCtxt<'c, 'd, 'cx>, - p: ty::Predicate<'cx>) - -> ty::Predicate<'cx> { + pub fn clean_pred<'c, 'd, 'cx>( + &self, + infcx: &InferCtxt<'c, 'd, 'cx>, + p: ty::Predicate<'cx>, + ) -> ty::Predicate<'cx> { infcx.freshen(p) } } @@ -632,9 +638,8 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for RegionReplacer<'a, 'gcx, 'tcx> { fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { (match r { - &ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(), - _ => None, - }) - .unwrap_or_else(|| r.super_fold_with(self)) + &ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned(), + _ => None, + }).unwrap_or_else(|| r.super_fold_with(self)) } } From d101753cd8a010abef238e616ac2f5bd5cbbf756 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Sun, 15 Apr 2018 14:56:14 +0200 Subject: [PATCH 08/10] Added a header comment to auto trait innards. --- src/librustc/traits/auto_trait.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index a4beb6fab123a..4d52d7132dfd2 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -8,6 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Support code for rustdoc and external tools . You really don't +//! want to be using this unless you need to. + use super::*; use std::collections::hash_map::Entry; From 388defad2d754cf9fc6ec69d67692f437393fd03 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Sun, 15 Apr 2018 15:47:56 +0200 Subject: [PATCH 09/10] Simplified name mapping in auto trait handling. --- src/librustc/traits/auto_trait.rs | 8 +++----- src/librustdoc/clean/auto_trait.rs | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index 4d52d7132dfd2..db8f59ce954b3 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -55,7 +55,7 @@ impl AutoTraitResult { pub struct AutoTraitInfo<'cx> { pub full_user_env: ty::ParamEnv<'cx>, pub region_data: RegionConstraintData<'cx>, - pub names_map: FxHashMap, + pub names_map: FxHashSet, pub vid_to_region: FxHashMap>, } @@ -206,12 +206,10 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { ) }); - let names_map: FxHashMap = generics + let names_map: FxHashSet = generics .regions .iter() - .map(|l| (l.name.to_string(), l.name.to_string())) - // TODO(twk): Lifetime branding and why is this map a set?! - // l.clean(self.cx) was present in the original code + .map(|l| l.name.to_string()) .collect(); let body_ids: FxHashSet<_> = infcx diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index 58ee42402d51c..f8c0c40443048 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -290,7 +290,7 @@ impl<'a, 'tcx, 'rcx> AutoTraitFinder<'a, 'tcx, 'rcx> { let names_map = info.names_map .drain() - .map(|(name, lifetime)| (name, Lifetime(lifetime))) + .map(|name| (name.clone(), Lifetime(name))) .collect(); let lifetime_predicates = self.handle_lifetimes(®ion_data, &names_map); From 890139256c341c54779ba229942b4d90771a7690 Mon Sep 17 00:00:00 2001 From: Inokentiy Babushkin Date: Thu, 26 Apr 2018 22:22:06 +0200 Subject: [PATCH 10/10] Updated comments in auto trait machinery. --- src/librustc/traits/auto_trait.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/librustc/traits/auto_trait.rs b/src/librustc/traits/auto_trait.rs index db8f59ce954b3..5d708f60604f1 100644 --- a/src/librustc/traits/auto_trait.rs +++ b/src/librustc/traits/auto_trait.rs @@ -68,9 +68,24 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> { AutoTraitFinder { tcx } } - /// Determine whether an auto trait is implemented for a type, and if this is the case if - /// non-trivial bounds need to be fulfilled, invoke a callback to compute a value representing - /// these in a fashion suitable for the caller. + /// Make a best effort to determine whether and under which conditions an auto trait is + /// implemented for a type. For example, if you have + /// + /// ``` + /// struct Foo { data: Box } + /// ``` + + /// then this might return that Foo: Send if T: Send (encoded in the AutoTraitResult type). + /// The analysis attempts to account for custom impls as well as other complex cases. This + /// result is intended for use by rustdoc and other such consumers. + + /// (Note that due to the coinductive nature of Send, the full and correct result is actually + /// quite simple to generate. That is, when a type has no custom impl, it is Send iff its field + /// types are all Send. So, in our example, we might have that Foo: Send if Box: Send. + /// But this is often not the best way to present to the user.) + + /// Warning: The API should be considered highly unstable, and it may be refactored or removed + /// in the future. pub fn find_auto_trait_generics( &self, did: DefId,