Skip to content

Commit

Permalink
Add proper support for early/late distinction for lifetime bindings.
Browse files Browse the repository at this point in the history
Uses newly added Vec::partition method to simplify resolve_lifetime.
  • Loading branch information
pnkfelix committed Mar 12, 2014
1 parent 586b619 commit 742e458
Show file tree
Hide file tree
Showing 20 changed files with 566 additions and 200 deletions.
208 changes: 164 additions & 44 deletions src/librustc/middle/resolve_lifetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use std::vec_ng::Vec;
use util::nodemap::NodeMap;
use syntax::ast;
use syntax::codemap::Span;
use syntax::opt_vec;
use syntax::opt_vec::OptVec;
use syntax::parse::token::special_idents;
use syntax::parse::token;
use syntax::print::pprust::{lifetime_to_str};
Expand All @@ -33,14 +35,25 @@ use syntax::visit::Visitor;
// that it corresponds to
pub type NamedRegionMap = NodeMap<ast::DefRegion>;

// Returns an instance of some type that implements std::fmt::Show
fn lifetime_show(lt_name: &ast::Name) -> token::InternedString {
token::get_name(*lt_name)
}

struct LifetimeContext {
sess: session::Session,
named_region_map: @RefCell<NamedRegionMap>,
}

enum ScopeChain<'a> {
ItemScope(&'a Vec<ast::Lifetime>),
FnScope(ast::NodeId, &'a Vec<ast::Lifetime>, Scope<'a>),
/// EarlyScope(i, ['a, 'b, ...], s) extends s with early-bound
/// lifetimes, assigning indexes 'a => i, 'b => i+1, ... etc.
EarlyScope(uint, &'a Vec<ast::Lifetime>, Scope<'a>),
/// LateScope(binder_id, ['a, 'b, ...], s) extends s with late-bound
/// lifetimes introduced by the declaration binder_id.
LateScope(ast::NodeId, &'a Vec<ast::Lifetime>, Scope<'a>),
/// lifetimes introduced by items within a code block are scoped
/// to that block.
BlockScope(ast::NodeId, Scope<'a>),
RootScope
}
Expand All @@ -62,6 +75,7 @@ impl<'a> Visitor<Scope<'a>> for LifetimeContext {
fn visit_item(&mut self,
item: &ast::Item,
_: Scope<'a>) {
let root = RootScope;
let scope = match item.node {
ast::ItemFn(..) | // fn lifetimes get added in visit_fn below
ast::ItemMod(..) |
Expand All @@ -76,7 +90,7 @@ impl<'a> Visitor<Scope<'a>> for LifetimeContext {
ast::ItemImpl(ref generics, _, _, _) |
ast::ItemTrait(ref generics, _, _) => {
self.check_lifetime_names(&generics.lifetimes);
ItemScope(&generics.lifetimes)
EarlyScope(0, &generics.lifetimes, &root)
}
};
debug!("entering scope {:?}", scope);
Expand All @@ -90,49 +104,41 @@ impl<'a> Visitor<Scope<'a>> for LifetimeContext {
match *fk {
visit::FkItemFn(_, generics, _, _) |
visit::FkMethod(_, generics, _) => {
let scope1 = FnScope(n, &generics.lifetimes, scope);
self.check_lifetime_names(&generics.lifetimes);
debug!("pushing fn scope id={} due to item/method", n);
visit::walk_fn(self, fk, fd, b, s, n, &scope1);
debug!("popping fn scope id={} due to item/method", n);
self.visit_fn_decl(
n, generics, scope,
|this, scope1| visit::walk_fn(this, fk, fd, b, s, n, scope1))
}
visit::FkFnBlock(..) => {
visit::walk_fn(self, fk, fd, b, s, n, scope);
visit::walk_fn(self, fk, fd, b, s, n, scope)
}
}
}

fn visit_ty(&mut self, ty: &ast::Ty,
scope: Scope<'a>) {
fn visit_ty(&mut self, ty: &ast::Ty, scope: Scope<'a>) {
match ty.node {
ast::TyClosure(closure) => {
let scope1 = FnScope(ty.id, &closure.lifetimes, scope);
self.check_lifetime_names(&closure.lifetimes);
debug!("pushing fn scope id={} due to type", ty.id);
visit::walk_ty(self, ty, &scope1);
debug!("popping fn scope id={} due to type", ty.id);
}
ast::TyBareFn(bare_fn) => {
let scope1 = FnScope(ty.id, &bare_fn.lifetimes, scope);
self.check_lifetime_names(&bare_fn.lifetimes);
debug!("pushing fn scope id={} due to type", ty.id);
visit::walk_ty(self, ty, &scope1);
debug!("popping fn scope id={} due to type", ty.id);
}
_ => {
visit::walk_ty(self, ty, scope);
}
ast::TyClosure(c) => push_fn_scope(self, ty, scope, &c.lifetimes),
ast::TyBareFn(c) => push_fn_scope(self, ty, scope, &c.lifetimes),
_ => visit::walk_ty(self, ty, scope),
}

fn push_fn_scope(this: &mut LifetimeContext,
ty: &ast::Ty,
scope: Scope,
lifetimes: &Vec<ast::Lifetime>) {
let scope1 = LateScope(ty.id, lifetimes, scope);
this.check_lifetime_names(lifetimes);
debug!("pushing fn scope id={} due to type", ty.id);
visit::walk_ty(this, ty, &scope1);
debug!("popping fn scope id={} due to type", ty.id);
}
}

fn visit_ty_method(&mut self,
m: &ast::TypeMethod,
scope: Scope<'a>) {
let scope1 = FnScope(m.id, &m.generics.lifetimes, scope);
self.check_lifetime_names(&m.generics.lifetimes);
debug!("pushing fn scope id={} due to ty_method", m.id);
visit::walk_ty_method(self, m, &scope1);
debug!("popping fn scope id={} due to ty_method", m.id);
self.visit_fn_decl(
m.id, &m.generics, scope,
|this, scope1| visit::walk_ty_method(this, m, scope1))
}

fn visit_block(&mut self,
Expand All @@ -155,7 +161,82 @@ impl<'a> Visitor<Scope<'a>> for LifetimeContext {
}
}

impl<'a> ScopeChain<'a> {
fn count_early_params(&self) -> uint {
/*!
* Counts the number of early-bound parameters that are in
* scope. Used when checking methods: the early-bound
* lifetime parameters declared on the method are assigned
* indices that come after the indices from the type. Given
* something like `impl<'a> Foo { ... fn bar<'b>(...) }`
* then `'a` gets index 0 and `'b` gets index 1.
*/

match *self {
RootScope => 0,
EarlyScope(base, lifetimes, _) => base + lifetimes.len(),
LateScope(_, _, s) => s.count_early_params(),
BlockScope(_, _) => 0,
}
}
}

impl LifetimeContext {
/// Visits self by adding a scope and handling recursive walk over the contents with `walk`.
fn visit_fn_decl(&mut self,
n: ast::NodeId,
generics: &ast::Generics,
scope: Scope,
walk: |&mut LifetimeContext, Scope|) {
/*!
* Handles visiting fns and methods. These are a bit
* complicated because we must distinguish early- vs late-bound
* lifetime parameters. We do this by checking which lifetimes
* appear within type bounds; those are early bound lifetimes,
* and the rest are late bound.
*
* For example:
*
* fn foo<'a,'b,'c,T:Trait<'b>>(...)
*
* Here `'a` and `'c` are late bound but `'b` is early
* bound. Note that early- and late-bound lifetimes may be
* interspersed together.
*
* If early bound lifetimes are present, we separate them into
* their own list (and likewise for late bound). They will be
* numbered sequentially, starting from the lowest index that
* is already in scope (for a fn item, that will be 0, but for
* a method it might not be). Late bound lifetimes are
* resolved by name and associated with a binder id (`n`), so
* the ordering is not important there.
*/

self.check_lifetime_names(&generics.lifetimes);

let early_count = scope.count_early_params();
let referenced_idents = free_lifetimes(&generics.ty_params);
debug!("pushing fn scope id={} due to fn item/method\
referenced_idents={:?} \
early_count={}",
n,
referenced_idents.map(lifetime_show),
early_count);
if referenced_idents.is_empty() {
let scope1 = LateScope(n, &generics.lifetimes, scope);
walk(self, &scope1)
} else {
let (early, late) = generics.lifetimes.clone().partition(
|l| referenced_idents.iter().any(|&i| i == l.name));

let scope1 = EarlyScope(early_count, &early, scope);
let scope2 = LateScope(n, &late, &scope1);

walk(self, &scope2);
}
debug!("popping fn scope id={} due to fn item/method", n);
}

fn resolve_lifetime_ref(&self,
lifetime_ref: &ast::Lifetime,
scope: Scope) {
Expand All @@ -177,23 +258,25 @@ impl LifetimeContext {
break;
}

ItemScope(lifetimes) => {
EarlyScope(base, lifetimes, s) => {
match search_lifetimes(lifetimes, lifetime_ref) {
Some((index, decl_id)) => {
Some((offset, decl_id)) => {
let index = base + offset;
let def = ast::DefEarlyBoundRegion(index, decl_id);
self.insert_lifetime(lifetime_ref, def);
return;
}
None => {
break;
depth += 1;
scope = s;
}
}
}

FnScope(id, lifetimes, s) => {
LateScope(binder_id, lifetimes, s) => {
match search_lifetimes(lifetimes, lifetime_ref) {
Some((_index, decl_id)) => {
let def = ast::DefLateBoundRegion(id, depth, decl_id);
let def = ast::DefLateBoundRegion(binder_id, depth, decl_id);
self.insert_lifetime(lifetime_ref, def);
return;
}
Expand Down Expand Up @@ -231,12 +314,8 @@ impl LifetimeContext {
break;
}

ItemScope(lifetimes) => {
search_result = search_lifetimes(lifetimes, lifetime_ref);
break;
}

FnScope(_, lifetimes, s) => {
EarlyScope(_, lifetimes, s) |
LateScope(_, lifetimes, s) => {
search_result = search_lifetimes(lifetimes, lifetime_ref);
if search_result.is_some() {
break;
Expand Down Expand Up @@ -323,3 +402,44 @@ fn search_lifetimes(lifetimes: &Vec<ast::Lifetime>,
}
return None;
}

///////////////////////////////////////////////////////////////////////////

pub fn early_bound_lifetimes<'a>(generics: &'a ast::Generics) -> Vec<ast::Lifetime> {
let referenced_idents = free_lifetimes(&generics.ty_params);
if referenced_idents.is_empty() {
return Vec::new();
}

generics.lifetimes.iter()
.filter(|l| referenced_idents.iter().any(|&i| i == l.name))
.map(|l| *l)
.collect()
}

pub fn free_lifetimes(ty_params: &OptVec<ast::TyParam>) -> OptVec<ast::Name> {
/*!
* Gathers up and returns the names of any lifetimes that appear
* free in `ty_params`. Of course, right now, all lifetimes appear
* free, since we don't currently have any binders in type parameter
* declarations; just being forwards compatible with future extensions.
*/

let mut collector = FreeLifetimeCollector { names: opt_vec::Empty };
for ty_param in ty_params.iter() {
visit::walk_ty_param_bounds(&mut collector, &ty_param.bounds, ());
}
return collector.names;

struct FreeLifetimeCollector {
names: OptVec<ast::Name>,
}

impl Visitor<()> for FreeLifetimeCollector {
fn visit_lifetime_ref(&mut self,
lifetime_ref: &ast::Lifetime,
_: ()) {
self.names.push(lifetime_ref.name);
}
}
}
2 changes: 1 addition & 1 deletion src/librustc/middle/subst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ impl Subst for ty::Region {
// bound in *fn types*. Region substitution of the bound
// regions that appear in a function signature is done using
// the specialized routine
// `middle::typeck::check::regionmanip::replace_bound_regions_in_fn_sig()`.
// `middle::typeck::check::regionmanip::replace_late_regions_in_fn_sig()`.
match self {
&ty::ReEarlyBound(_, i, _) => {
match substs.regions {
Expand Down
34 changes: 29 additions & 5 deletions src/librustc/middle/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,7 @@ pub struct Generics {
type_param_defs: Rc<Vec<TypeParameterDef> >,

/// List of region parameters declared on the item.
/// For a fn or method, only includes *early-bound* lifetimes.
region_param_defs: Rc<Vec<RegionParameterDef> >,
}

Expand Down Expand Up @@ -5077,6 +5078,7 @@ pub fn construct_parameter_environment(
item_type_params: &[TypeParameterDef],
method_type_params: &[TypeParameterDef],
item_region_params: &[RegionParameterDef],
method_region_params: &[RegionParameterDef],
free_id: ast::NodeId)
-> ParameterEnvironment
{
Expand Down Expand Up @@ -5104,11 +5106,24 @@ pub fn construct_parameter_environment(
});

// map bound 'a => free 'a
let region_params = item_region_params.iter().
map(|r| ty::ReFree(ty::FreeRegion {
scope_id: free_id,
bound_region: ty::BrNamed(r.def_id, r.name)})).
collect();
let region_params = {
fn push_region_params(accum: OptVec<ty::Region>,
free_id: ast::NodeId,
region_params: &[RegionParameterDef])
-> OptVec<ty::Region> {
let mut accum = accum;
for r in region_params.iter() {
accum.push(
ty::ReFree(ty::FreeRegion {
scope_id: free_id,
bound_region: ty::BrNamed(r.def_id, r.name)}));
}
accum
}

let t = push_region_params(opt_vec::Empty, free_id, item_region_params);
push_region_params(t, free_id, method_region_params)
};

let free_substs = substs {
self_ty: self_ty,
Expand All @@ -5130,6 +5145,15 @@ pub fn construct_parameter_environment(
}
});

debug!("construct_parameter_environment: free_id={} \
free_subst={} \
self_param_bound={} \
type_param_bound={}",
free_id,
free_substs.repr(tcx),
self_bound_substd.repr(tcx),
type_param_bounds_substd.repr(tcx));

ty::ParameterEnvironment {
free_substs: free_substs,
self_param_bound: self_bound_substd,
Expand Down
Loading

2 comments on commit 742e458

@pnkfelix
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r=pnkfelix

(original author was nikomatsakis; pnkfelix did various review/refactoring/rebasing).

@pnkfelix
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bors: retry

Please sign in to comment.