Skip to content

Commit

Permalink
Distinguish early- from late-bound lifetime parameters on the basis o…
Browse files Browse the repository at this point in the history
…f whether they appear in a trait bound. Fixes rust-lang#5121.
  • Loading branch information
nikomatsakis committed Nov 18, 2013
1 parent 37d6ca9 commit b2d11dc
Show file tree
Hide file tree
Showing 11 changed files with 446 additions and 93 deletions.
203 changes: 167 additions & 36 deletions src/librustc/middle/resolve_lifetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use driver::session;
use std::hashmap::HashMap;
use syntax::ast;
use syntax::codemap::Span;
use syntax::opt_vec;
use syntax::opt_vec::OptVec;
use syntax::parse::token::special_idents;
use syntax::print::pprust::{lifetime_to_str};
Expand All @@ -37,12 +38,14 @@ struct LifetimeContext {
}

enum ScopeChain<'self> {
ItemScope(&'self OptVec<ast::Lifetime>),
FnScope(ast::NodeId, &'self OptVec<ast::Lifetime>, &'self ScopeChain<'self>),
BlockScope(ast::NodeId, &'self ScopeChain<'self>),
EarlyScope(uint, &'self OptVec<ast::Lifetime>, Scope<'self>),
LateScope(ast::NodeId, &'self OptVec<ast::Lifetime>, Scope<'self>),
BlockScope(ast::NodeId, Scope<'self>),
RootScope
}

type Scope<'self> = &'self ScopeChain<'self>;

pub fn crate(sess: session::Session,
crate: &ast::Crate)
-> @mut NamedRegionMap {
Expand All @@ -55,10 +58,10 @@ pub fn crate(sess: session::Session,
ctxt.named_region_map
}

impl<'self> Visitor<&'self ScopeChain<'self>> for LifetimeContext {
impl<'self> Visitor<Scope<'self>> for LifetimeContext {
fn visit_item(&mut self,
item: @ast::item,
_: &'self ScopeChain<'self>) {
_: Scope<'self>) {
let scope = match item.node {
ast::item_fn(*) | // fn lifetimes get added in visit_fn below
ast::item_mod(*) |
Expand All @@ -73,7 +76,7 @@ impl<'self> Visitor<&'self ScopeChain<'self>> for LifetimeContext {
ast::item_impl(ref generics, _, _, _) |
ast::item_trait(ref generics, _, _) => {
self.check_lifetime_names(&generics.lifetimes);
ItemScope(&generics.lifetimes)
EarlyScope(0, &generics.lifetimes, &RootScope)
}
};
debug!("entering scope {:?}", scope);
Expand All @@ -87,33 +90,32 @@ impl<'self> Visitor<&'self ScopeChain<'self>> for LifetimeContext {
b: &ast::Block,
s: Span,
n: ast::NodeId,
scope: &'self ScopeChain<'self>) {
scope: Scope<'self>) {
match *fk {
visit::fk_item_fn(_, generics, _, _) |
visit::fk_method(_, 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::fk_anon(*) | visit::fk_fn_block(*) => {
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: &'self ScopeChain<'self>) {
scope: Scope<'self>) {
match ty.node {
ast::ty_closure(@ast::TyClosure { lifetimes: ref lifetimes, _ }) |
ast::ty_bare_fn(@ast::TyBareFn { lifetimes: ref lifetimes, _ }) => {
let scope1 = FnScope(ty.id, lifetimes, scope);
let scope1 = LateScope(ty.id, lifetimes, scope);
self.check_lifetime_names(lifetimes);
debug!("pushing fn scope id={} due to type", ty.id);
debug!("pushing fn ty scope id={} due to type", ty.id);
visit::walk_ty(self, ty, &scope1);
debug!("popping fn scope id={} due to type", ty.id);
debug!("popping fn ty scope id={} due to type", ty.id);
}
_ => {
visit::walk_ty(self, ty, scope);
Expand All @@ -123,17 +125,15 @@ impl<'self> Visitor<&'self ScopeChain<'self>> for LifetimeContext {

fn visit_ty_method(&mut self,
m: &ast::TypeMethod,
scope: &'self ScopeChain<'self>) {
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);
scope: Scope<'self>) {
self.visit_fn_decl(
m.id, &m.generics, scope,
|this, scope1| visit::walk_ty_method(this, m, scope1))
}

fn visit_block(&mut self,
b: &ast::Block,
scope: &'self ScopeChain<'self>) {
scope: Scope<'self>) {
let scope1 = BlockScope(b.id, scope);
debug!("pushing block scope {}", b.id);
visit::walk_block(self, b, &scope1);
Expand All @@ -142,7 +142,7 @@ impl<'self> Visitor<&'self ScopeChain<'self>> for LifetimeContext {

fn visit_lifetime_ref(&mut self,
lifetime_ref: &ast::Lifetime,
scope: &'self ScopeChain<'self>) {
scope: Scope<'self>) {
if lifetime_ref.ident == special_idents::statik {
self.insert_lifetime(lifetime_ref, ast::DefStaticRegion);
return;
Expand All @@ -151,7 +151,93 @@ impl<'self> Visitor<&'self ScopeChain<'self>> for LifetimeContext {
}
}

impl<'self> ScopeChain<'self> {
fn count_early_params(&self) -> uint {
/*!
* Counts the number of early parameters that are in scope.
* Used when checking methods so that we assign the early
* lifetime parameters declared on the method indices that
* come after those from the type (e.g., if there is something
* like `impl<'a> Foo { ... fn bar<'b>(...) }` then `'a` would
* have index 0 and `'b` would have index 1.
*/

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

impl LifetimeContext {
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 item/method \
referenced_idents={:?} \
early_count={}",
n,
referenced_idents.map(|&i| self.sess.str_of(i)),
early_count);
if referenced_idents.is_empty() {
let scope1 = LateScope(n, &generics.lifetimes, scope);

walk(self, &scope1)
} else {
let early: OptVec<ast::Lifetime> =
generics.lifetimes.iter()
.filter(|l| referenced_idents.iter().any(|i| i == &l.ident))
.map(|l| *l)
.collect();
let scope1 = EarlyScope(early_count, &early, scope);
debug!("early names = {:?}",
early.map(|l| self.sess.str_of(l.ident)));

let late: OptVec<ast::Lifetime> =
generics.lifetimes.iter()
.filter(|l| !referenced_idents.iter().any(|i| i == &l.ident))
.map(|l| *l)
.collect();
let scope2 = LateScope(n, &late, &scope1);

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

fn resolve_lifetime_ref(&self,
lifetime_ref: &ast::Lifetime,
scope: &ScopeChain) {
Expand All @@ -173,23 +259,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 @@ -227,12 +315,7 @@ 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 @@ -319,3 +402,51 @@ fn search_lifetimes(lifetimes: &OptVec<ast::Lifetime>,
}
return None;
}

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

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

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

pub fn free_lifetimes(ty_params: &OptVec<ast::TyParam>) -> OptVec<ast::Ident> {
/*!
* 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 have any binders in type parameter
* declarations, but I just to be forwards compatible for future
* extensions with my terminology. =)
*/

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::Ident>,
}

impl Visitor<()> for FreeLifetimeCollector {
fn visit_ty(&mut self, t:&ast::Ty, _:()) {
// for some weird reason visitor doesn't descend into
// types by default
visit::walk_ty(self, t, ());
}

fn visit_lifetime_ref(&mut self,
lifetime_ref: &ast::Lifetime,
_: ()) {
self.names.push(lifetime_ref.ident);
}
}
}
35 changes: 29 additions & 6 deletions src/librustc/middle/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,8 @@ pub struct Generics {
/// List of type parameters declared on the item.
type_param_defs: @~[TypeParameterDef],

/// List of region parameters declared on the item.
/// List of region parameters declared on the item. In the
/// case of a fn or method, only includes *early-bound* lifetimes.
region_param_defs: @[RegionParameterDef],
}

Expand Down Expand Up @@ -4739,6 +4740,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 @@ -4766,11 +4768,23 @@ 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.ident)})).
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.ident)}));
}
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 @@ -4792,6 +4806,15 @@ pub fn construct_parameter_environment(
}
});

debug!("construct_parameter_environment: free_id={} \
free_substs={} \
self_param_bound={} \
type_param_bounds={}",
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

0 comments on commit b2d11dc

Please sign in to comment.