Auto merge of #45701 - cramertj:impl-trait-this-time, r=eddyb

impl Trait Lifetime Handling

This PR implements the updated strategy for handling `impl Trait` lifetimes, as described in [RFC 1951](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md) (cc #42183).

With this PR, the `impl Trait` desugaring works as follows:
```rust
fn foo<T, 'a, 'b, 'c>(...) -> impl Foo<'a, 'b> { ... }
// desugars to
exists type MyFoo<ParentT, 'parent_a, 'parent_b, 'parent_c, 'a, 'b>: Foo<'a, 'b>;
fn foo<T, 'a, 'b, 'c>(...) -> MyFoo<T, 'static, 'static, 'static, 'a, 'b> { ... }
```
All of the in-scope (parent) generics are listed as parent generics of the anonymous type, with parent regions being replaced by `'static`. Parent regions referenced in the `impl Trait` return type are duplicated into the anonymous type's generics and mapped appropriately.

One case came up that wasn't specified in the RFC: it's possible to write a return type that contains multiple regions, neither of which outlives the other. In that case, it's not clear what the required lifetime of the output type should be, so we generate an error.

There's one remaining FIXME in one of the tests: `-> impl Foo<'a, 'b> + 'c` should be able to outlive both `'a` and `'b`, but not `'c`. Currently, it can't outlive any of them. @nikomatsakis and I have discussed this, and there are some complex interactions here if we ever allow `impl<'a, 'b> SomeTrait for AnonType<'a, 'b> { ... }`, so the plan is to hold off on this until we've got a better idea of what the interactions are here.

cc #34511.
Fixes #44727.
This commit is contained in:
bors 2017-11-21 10:00:18 +00:00
commit ebda7662db
24 changed files with 884 additions and 133 deletions

View file

@ -2019,4 +2019,5 @@ register_diagnostics! {
E0628, // generators cannot have explicit arguments
E0631, // type mismatch in closure arguments
E0637, // "'_" is not a valid lifetime bound
E0657, // `impl Trait` can only capture lifetimes bound at the fn level
}

View file

@ -591,8 +591,11 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty) {
}
visitor.visit_lifetime(lifetime);
}
TyImplTraitExistential(ref bounds) => {
TyImplTraitExistential(ref existty, ref lifetimes) => {
let ExistTy { ref generics, ref bounds } = *existty;
walk_generics(visitor, generics);
walk_list!(visitor, visit_ty_param_bound, bounds);
walk_list!(visitor, visit_lifetime, lifetimes);
}
TyImplTraitUniversal(_, ref bounds) => {
walk_list!(visitor, visit_ty_param_bound, bounds);

View file

@ -42,8 +42,9 @@
use dep_graph::DepGraph;
use hir;
use hir::map::{Definitions, DefKey};
use hir::def_id::{DefIndex, DefId, CRATE_DEF_INDEX};
use hir::HirVec;
use hir::map::{Definitions, DefKey, DefPathData};
use hir::def_id::{DefIndex, DefId, CRATE_DEF_INDEX, DefIndexAddressSpace};
use hir::def::{Def, PathResolution};
use lint::builtin::PARENTHESIZED_PARAMS_IN_TYPES_AND_MODULES;
use middle::cstore::CrateStore;
@ -52,7 +53,7 @@ use session::Session;
use util::common::FN_OUTPUT_NAME;
use util::nodemap::{DefIdMap, FxHashMap, NodeMap};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashSet};
use std::fmt::Debug;
use std::iter;
use std::mem;
@ -777,7 +778,24 @@ impl<'a> LoweringContext<'a> {
t.span, GateIssue::Language,
"`impl Trait` in return position is experimental");
}
hir::TyImplTraitExistential(self.lower_bounds(bounds, itctx))
let def_index = self.resolver.definitions().opt_def_index(t.id).unwrap();
let hir_bounds = self.lower_bounds(bounds, itctx);
let (lifetimes, lifetime_defs) =
self.lifetimes_from_impl_trait_bounds(def_index, &hir_bounds);
hir::TyImplTraitExistential(hir::ExistTy {
generics: hir::Generics {
lifetimes: lifetime_defs,
// Type parameters are taken from environment:
ty_params: Vec::new().into(),
where_clause: hir::WhereClause {
id: self.next_id().node_id,
predicates: Vec::new().into(),
},
span: t.span,
},
bounds: hir_bounds,
}, lifetimes)
},
ImplTraitContext::Universal(def_id) => {
let has_feature = self.sess.features.borrow().universal_impl_trait;
@ -808,6 +826,111 @@ impl<'a> LoweringContext<'a> {
})
}
fn lifetimes_from_impl_trait_bounds(
&mut self,
parent_index: DefIndex,
bounds: &hir::TyParamBounds
) -> (HirVec<hir::Lifetime>, HirVec<hir::LifetimeDef>) {
// This visitor walks over impl trait bounds and creates defs for all lifetimes which
// appear in the bounds, excluding lifetimes that are created within the bounds.
// e.g. 'a, 'b, but not 'c in `impl for<'c> SomeTrait<'a, 'b, 'c>`
struct ImplTraitLifetimeCollector<'r, 'a: 'r> {
context: &'r mut LoweringContext<'a>,
parent: DefIndex,
currently_bound_lifetimes: Vec<Name>,
already_defined_lifetimes: HashSet<Name>,
output_lifetimes: Vec<hir::Lifetime>,
output_lifetime_defs: Vec<hir::LifetimeDef>,
}
impl<'r, 'a: 'r, 'v> hir::intravisit::Visitor<'v> for ImplTraitLifetimeCollector<'r, 'a> {
fn nested_visit_map<'this>(&'this mut self)
-> hir::intravisit::NestedVisitorMap<'this, 'v> {
hir::intravisit::NestedVisitorMap::None
}
fn visit_poly_trait_ref(&mut self,
polytr: &'v hir::PolyTraitRef,
_: hir::TraitBoundModifier) {
let old_len = self.currently_bound_lifetimes.len();
// Record the introduction of 'a in `for<'a> ...`
for lt_def in &polytr.bound_lifetimes {
// Introduce lifetimes one at a time so that we can handle
// cases like `fn foo<'d>() -> impl for<'a, 'b: 'a, 'c: 'b + 'd> ...`
if let hir::LifetimeName::Name(name) = lt_def.lifetime.name {
self.currently_bound_lifetimes.push(name);
}
// Visit the lifetime bounds
for lt_bound in &lt_def.bounds {
self.visit_lifetime(&lt_bound);
}
}
hir::intravisit::walk_trait_ref(self, &polytr.trait_ref);
self.currently_bound_lifetimes.truncate(old_len);
}
fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
// Exclude '_, 'static, and elided lifetimes (there should be no elided lifetimes)
if let hir::LifetimeName::Name(lifetime_name) = lifetime.name {
if !self.currently_bound_lifetimes.contains(&lifetime_name) &&
!self.already_defined_lifetimes.contains(&lifetime_name)
{
self.already_defined_lifetimes.insert(lifetime_name);
let name = hir::LifetimeName::Name(lifetime_name);
self.output_lifetimes.push(hir::Lifetime {
id: self.context.next_id().node_id,
span: lifetime.span,
name,
});
let def_node_id = self.context.next_id().node_id;
self.context.resolver.definitions().create_def_with_parent(
self.parent,
def_node_id,
DefPathData::LifetimeDef(lifetime_name.as_str()),
DefIndexAddressSpace::High,
Mark::root()
);
let def_lifetime = hir::Lifetime {
id: def_node_id,
span: lifetime.span,
name,
};
self.output_lifetime_defs.push(hir::LifetimeDef {
lifetime: def_lifetime,
bounds: Vec::new().into(),
pure_wrt_drop: false,
});
}
}
}
}
let mut lifetime_collector = ImplTraitLifetimeCollector {
context: self,
parent: parent_index,
currently_bound_lifetimes: Vec::new(),
already_defined_lifetimes: HashSet::new(),
output_lifetimes: Vec::new(),
output_lifetime_defs: Vec::new(),
};
for bound in bounds {
hir::intravisit::walk_ty_param_bound(&mut lifetime_collector, &bound);
}
(
lifetime_collector.output_lifetimes.into(),
lifetime_collector.output_lifetime_defs.into()
)
}
fn lower_foreign_mod(&mut self, fm: &ForeignMod) -> hir::ForeignMod {
hir::ForeignMod {
abi: fm.abi,

View file

@ -1461,6 +1461,12 @@ pub struct BareFnTy {
pub arg_names: HirVec<Spanned<Name>>,
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug)]
pub struct ExistTy {
pub generics: Generics,
pub bounds: TyParamBounds,
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug)]
/// The different kinds of types recognized by the compiler
pub enum Ty_ {
@ -1488,7 +1494,16 @@ pub enum Ty_ {
TyTraitObject(HirVec<PolyTraitRef>, Lifetime),
/// An exsitentially quantified (there exists a type satisfying) `impl
/// Bound1 + Bound2 + Bound3` type where `Bound` is a trait or a lifetime.
TyImplTraitExistential(TyParamBounds),
///
/// The `ExistTy` structure emulates an
/// `abstract type Foo<'a, 'b>: MyTrait<'a, 'b>;`.
///
/// The `HirVec<Lifetime>` is the list of lifetimes applied as parameters
/// to the `abstract type`, e.g. the `'c` and `'d` in `-> Foo<'c, 'd>`.
/// This list is only a list of lifetimes and not type parameters
/// because all in-scope type parameters are captured by `impl Trait`,
/// so they are resolved directly through the parent `Generics`.
TyImplTraitExistential(ExistTy, HirVec<Lifetime>),
/// An universally quantified (for all types satisfying) `impl
/// Bound1 + Bound2 + Bound3` type where `Bound` is a trait or a lifetime.
TyImplTraitUniversal(DefId, TyParamBounds),

View file

@ -421,8 +421,10 @@ impl<'a> State<'a> {
self.print_lifetime(lifetime)?;
}
}
hir::TyImplTraitExistential(ref bounds) |
hir::TyImplTraitUniversal(_, ref bounds) => {
hir::TyImplTraitExistential(ref existty, ref _lifetimes) => {
self.print_bounds("impl", &existty.bounds[..])?;
}
hir::TyImplTraitUniversal(_, ref bounds) => {
self.print_bounds("impl", &bounds[..])?;
}
hir::TyArray(ref ty, v) => {

View file

@ -295,6 +295,11 @@ impl_stable_hash_for!(struct hir::BareFnTy {
arg_names
});
impl_stable_hash_for!(struct hir::ExistTy {
generics,
bounds
});
impl_stable_hash_for!(enum hir::Ty_ {
TySlice(t),
TyArray(t, body_id),
@ -305,7 +310,7 @@ impl_stable_hash_for!(enum hir::Ty_ {
TyTup(ts),
TyPath(qpath),
TyTraitObject(trait_refs, lifetime),
TyImplTraitExistential(bounds),
TyImplTraitExistential(existty, lifetimes),
TyImplTraitUniversal(def_id, bounds),
TyTypeof(body_id),
TyErr,

View file

@ -84,7 +84,7 @@ impl<'a, 'gcx, 'tcx> RegionRelations<'a, 'gcx, 'tcx> {
(&ty::ReFree(_), &ty::ReEarlyBound(_)) |
(&ty::ReEarlyBound(_), &ty::ReFree(_)) |
(&ty::ReFree(_), &ty::ReFree(_)) =>
self.free_regions.relation.contains(&sub_region, &super_region),
self.free_regions.sub_free_regions(&sub_region, &super_region),
_ =>
false,
@ -158,19 +158,39 @@ impl<'tcx> FreeRegionMap<'tcx> {
}
}
// Record that `'sup:'sub`. Or, put another way, `'sub <= 'sup`.
// (with the exception that `'static: 'x` is not notable)
/// Record that `'sup:'sub`. Or, put another way, `'sub <= 'sup`.
/// (with the exception that `'static: 'x` is not notable)
pub fn relate_regions(&mut self, sub: Region<'tcx>, sup: Region<'tcx>) {
debug!("relate_regions(sub={:?}, sup={:?})", sub, sup);
if (is_free(sub) || *sub == ty::ReStatic) && is_free(sup) {
self.relation.add(sub, sup)
}
}
/// True if `r_a <= r_b` is known to hold. Both `r_a` and `r_b`
/// must be free regions from the function header.
pub fn sub_free_regions<'a, 'gcx>(&self,
r_a: Region<'tcx>,
r_b: Region<'tcx>)
-> bool {
debug!("sub_free_regions(r_a={:?}, r_b={:?})", r_a, r_b);
assert!(is_free(r_a));
assert!(is_free(r_b));
let result = r_a == r_b || self.relation.contains(&r_a, &r_b);
debug!("sub_free_regions: result={}", result);
result
}
/// Compute the least-upper-bound of two free regions. In some
/// cases, this is more conservative than necessary, in order to
/// avoid making arbitrary choices. See
/// `TransitiveRelation::postdom_upper_bound` for more details.
pub fn lub_free_regions<'a, 'gcx>(&self,
tcx: TyCtxt<'a, 'gcx, 'tcx>,
r_a: Region<'tcx>,
r_b: Region<'tcx>)
-> Region<'tcx> {
debug!("lub_free_regions(r_a={:?}, r_b={:?})", r_a, r_b);
assert!(is_free(r_a));
assert!(is_free(r_b));
let result = if r_a == r_b { r_a } else {

View file

@ -52,6 +52,7 @@ impl Region {
let i = *index;
*index += 1;
let def_id = hir_map.local_def_id(def.lifetime.id);
debug!("Region::early: index={} def_id={:?}", i, def_id);
(def.lifetime.name, Region::EarlyBound(i, def_id))
}
@ -201,6 +202,11 @@ enum Scope<'a> {
/// declaration `Binder` and the location it's referenced from.
Binder {
lifetimes: FxHashMap<hir::LifetimeName, Region>,
/// if we extend this scope with another scope, what is the next index
/// we should use for an early-bound region?
next_early_index: u32,
s: ScopeRef<'a>
},
@ -343,8 +349,10 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
let lifetimes = generics.lifetimes.iter().map(|def| {
Region::early(self.hir_map, &mut index, def)
}).collect();
let next_early_index = index + generics.ty_params.len() as u32;
let scope = Scope::Binder {
lifetimes,
next_early_index,
s: ROOT_SCOPE
};
self.with(scope, |old_scope, this| {
@ -372,12 +380,15 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
}
fn visit_ty(&mut self, ty: &'tcx hir::Ty) {
debug!("visit_ty: ty={:?}", ty);
match ty.node {
hir::TyBareFn(ref c) => {
let next_early_index = self.next_early_index();
let scope = Scope::Binder {
lifetimes: c.lifetimes.iter().map(|def| {
Region::late(self.hir_map, def)
}).collect(),
Region::late(self.hir_map, def)
}).collect(),
next_early_index,
s: self.scope
};
self.with(scope, |old_scope, this| {
@ -405,6 +416,60 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
};
self.with(scope, |_, this| this.visit_ty(&mt.ty));
}
hir::TyImplTraitExistential(ref exist_ty, ref lifetimes) => {
// Resolve the lifetimes that are applied to the existential type.
// These are resolved in the current scope.
// `fn foo<'a>() -> impl MyTrait<'a> { ... }` desugars to
// `fn foo<'a>() -> MyAnonTy<'a> { ... }`
// ^ ^this gets resolved in the current scope
for lifetime in lifetimes {
self.visit_lifetime(lifetime);
// Check for predicates like `impl for<'a> SomeTrait<impl OtherTrait<'a>>`
// and ban them. Type variables instantiated inside binders aren't
// well-supported at the moment, so this doesn't work.
// In the future, this should be fixed and this error should be removed.
let def = self.map.defs.get(&lifetime.id);
if let Some(&Region::LateBound(_, def_id)) = def {
if let Some(node_id) = self.hir_map.as_local_node_id(def_id) {
// Ensure that the parent of the def is an item, not HRTB
let parent_id = self.hir_map.get_parent_node(node_id);
let parent_impl_id = hir::ImplItemId { node_id: parent_id };
let parent_trait_id = hir::TraitItemId { node_id: parent_id };
let krate = self.hir_map.forest.krate();
if !(krate.items.contains_key(&parent_id) ||
krate.impl_items.contains_key(&parent_impl_id) ||
krate.trait_items.contains_key(&parent_trait_id))
{
span_err!(self.sess, lifetime.span, E0657,
"`impl Trait` can only capture lifetimes \
bound at the fn or impl level");
}
}
}
}
// Resolve the lifetimes in the bounds to the lifetime defs in the generics.
// `fn foo<'a>() -> impl MyTrait<'a> { ... }` desugars to
// `abstract type MyAnonTy<'b>: MyTrait<'b>;`
// ^ ^ this gets resolved in the scope of
// the exist_ty generics
let hir::ExistTy { ref generics, ref bounds } = *exist_ty;
let mut index = self.next_early_index();
debug!("visit_ty: index = {}", index);
let lifetimes = generics.lifetimes.iter()
.map(|lt_def| Region::early(self.hir_map, &mut index, lt_def))
.collect();
let next_early_index = index + generics.ty_params.len() as u32;
let scope = Scope::Binder { lifetimes, next_early_index, s: self.scope };
self.with(scope, |_old_scope, this| {
this.visit_generics(generics);
for bound in bounds {
this.visit_ty_param_bound(bound);
}
});
}
_ => {
intravisit::walk_ty(self, ty)
}
@ -477,10 +542,12 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
.. }) => {
if !bound_lifetimes.is_empty() {
self.trait_ref_hack = true;
let next_early_index = self.next_early_index();
let scope = Scope::Binder {
lifetimes: bound_lifetimes.iter().map(|def| {
Region::late(self.hir_map, def)
}).collect(),
}).collect(),
next_early_index,
s: self.scope
};
let result = self.with(scope, |old_scope, this| {
@ -524,10 +591,12 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
span_err!(self.sess, trait_ref.span, E0316,
"nested quantification of lifetimes");
}
let next_early_index = self.next_early_index();
let scope = Scope::Binder {
lifetimes: trait_ref.bound_lifetimes.iter().map(|def| {
Region::late(self.hir_map, def)
}).collect(),
}).collect(),
next_early_index,
s: self.scope
};
self.with(scope, |old_scope, this| {
@ -659,7 +728,7 @@ fn extract_labels(ctxt: &mut LifetimeContext, body: &hir::Body) {
Scope::Root => { return; }
Scope::Binder { ref lifetimes, s } => {
Scope::Binder { ref lifetimes, s, next_early_index: _ } => {
// FIXME (#24278): non-hygienic comparison
if let Some(def) = lifetimes.get(&hir::LifetimeName::Name(label)) {
let node_id = hir_map.as_local_node_id(def.id().unwrap())
@ -860,8 +929,11 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
}
}).collect();
let next_early_index = index + generics.ty_params.len() as u32;
let scope = Scope::Binder {
lifetimes,
next_early_index,
s: self.scope
};
self.with(scope, move |old_scope, this| {
@ -870,7 +942,29 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
});
}
/// Returns the next index one would use for an early-bound-region
/// if extending the current scope.
fn next_early_index(&self) -> u32 {
let mut scope = self.scope;
loop {
match *scope {
Scope::Root =>
return 0,
Scope::Binder { next_early_index, .. } =>
return next_early_index,
Scope::Body { s, .. } |
Scope::Elision { s, .. } |
Scope::ObjectLifetimeDefault { s, .. } =>
scope = s,
}
}
}
fn resolve_lifetime_ref(&mut self, lifetime_ref: &hir::Lifetime) {
debug!("resolve_lifetime_ref(lifetime_ref={:?})", lifetime_ref);
// Walk up the scope chain, tracking the number of fn scopes
// that we pass through, until we find a lifetime with the
// given name or we run out of scopes.
@ -889,7 +983,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
break None;
}
Scope::Binder { ref lifetimes, s } => {
Scope::Binder { ref lifetimes, s, next_early_index: _ } => {
if let Some(&def) = lifetimes.get(&lifetime_ref.name) {
break Some(def.shifted(late_depth));
} else {
@ -1520,7 +1614,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
return;
}
Scope::Binder { ref lifetimes, s } => {
Scope::Binder { ref lifetimes, s, next_early_index: _ } => {
if let Some(&def) = lifetimes.get(&lifetime.name) {
let node_id = self.hir_map
.as_local_node_id(def.id().unwrap())
@ -1549,7 +1643,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
probably a bug in syntax::fold");
}
debug!("{} resolved to {:?} span={:?}",
debug!("insert_lifetime: {} resolved to {:?} span={:?}",
self.hir_map.node_to_string(lifetime_ref.id),
def,
self.sess.codemap().span_to_string(lifetime_ref.span));
@ -1709,7 +1803,7 @@ fn insert_late_bound_lifetimes(map: &mut NamedRegionMap,
}
fn visit_ty(&mut self, ty: &hir::Ty) {
if let hir::TyImplTraitExistential(_) = ty.node {
if let hir::TyImplTraitExistential(..) = ty.node {
self.impl_trait = true;
}
intravisit::walk_ty(self, ty);

View file

@ -220,11 +220,11 @@ impl<'a, 'gcx, 'tcx> Substs<'tcx> {
tcx.intern_substs(&result)
}
fn fill_item<FR, FT>(substs: &mut Vec<Kind<'tcx>>,
tcx: TyCtxt<'a, 'gcx, 'tcx>,
defs: &ty::Generics,
mk_region: &mut FR,
mk_type: &mut FT)
pub fn fill_item<FR, FT>(substs: &mut Vec<Kind<'tcx>>,
tcx: TyCtxt<'a, 'gcx, 'tcx>,
defs: &ty::Generics,
mk_region: &mut FR,
mk_type: &mut FT)
where FR: FnMut(&ty::RegionParameterDef, &[Kind<'tcx>]) -> ty::Region<'tcx>,
FT: FnMut(&ty::TypeParameterDef, &[Kind<'tcx>]) -> Ty<'tcx> {

View file

@ -1015,6 +1015,10 @@ define_print! {
TyForeign(def_id) => parameterized(f, subst::Substs::empty(), def_id, &[]),
TyProjection(ref data) => data.print(f, cx),
TyAnon(def_id, substs) => {
if cx.is_verbose {
return write!(f, "TyAnon({:?}, {:?})", def_id, substs);
}
ty::tls::with(|tcx| {
// Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
// by looking up the projections associated with the def_id.