Fix lifetime elision region accounting

This merges accumulate_regions_in_type with ty_fold::collect_regions.
Fixes #26638
This commit is contained in:
Ariel Ben-Yehuda 2015-06-30 00:32:39 +03:00
parent a1110bc3a3
commit bf164bc6e3
9 changed files with 187 additions and 179 deletions

View File

@ -359,7 +359,7 @@ fn fold_regions_in<'tcx, T, F>(tcx: &ty::ctxt<'tcx>,
where T: TypeFoldable<'tcx>,
F: FnMut(ty::Region, ty::DebruijnIndex) -> ty::Region,
{
unbound_value.fold_with(&mut ty_fold::RegionFolder::new(tcx, &mut |region, current_depth| {
ty_fold::fold_regions(tcx, unbound_value, &mut false, |region, current_depth| {
// we should only be encountering "escaping" late-bound regions here,
// because the ones at the current level should have been replaced
// with fresh variables
@ -369,7 +369,7 @@ fn fold_regions_in<'tcx, T, F>(tcx: &ty::ctxt<'tcx>,
});
fldr(region, ty::DebruijnIndex::new(current_depth))
}))
})
}
impl<'a,'tcx> InferCtxtExt for InferCtxt<'a,'tcx> {
@ -437,11 +437,10 @@ impl<'a,'tcx> InferCtxtExt for InferCtxt<'a,'tcx> {
let escaping_types =
self.type_variables.borrow().types_escaping_snapshot(&snapshot.type_snapshot);
let escaping_region_vars: FnvHashSet<_> =
escaping_types
.iter()
.flat_map(|&t| ty_fold::collect_regions(self.tcx, &t))
.collect();
let mut escaping_region_vars = FnvHashSet();
for ty in &escaping_types {
ty_fold::collect_regions(self.tcx, ty, &mut escaping_region_vars);
}
region_vars.retain(|&region_vid| {
let r = ty::ReInfer(ty::ReVar(region_vid));
@ -649,7 +648,7 @@ pub fn plug_leaks<'a,'tcx,T>(infcx: &InferCtxt<'a,'tcx>,
// binder is that we encountered in `value`. The caller is
// responsible for ensuring that (a) `value` contains at least one
// binder and (b) that binder is the one we want to use.
let result = ty_fold::fold_regions(infcx.tcx, &value, |r, current_depth| {
let result = ty_fold::fold_regions(infcx.tcx, &value, &mut false, |r, current_depth| {
match inv_skol_map.get(&r) {
None => r,
Some(br) => {

View File

@ -1713,6 +1713,16 @@ impl Region {
_ => false,
}
}
/// Returns the depth of `self` from the (1-based) binding level `depth`
pub fn from_depth(&self, depth: u32) -> Region {
match *self {
ty::ReLateBound(debruijn, r) => ty::ReLateBound(DebruijnIndex {
depth: debruijn.depth - (depth - 1)
}, r),
r => r
}
}
}
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash,
@ -6783,60 +6793,6 @@ pub enum ExplicitSelfCategory {
ByBoxExplicitSelfCategory,
}
impl<'tcx> TyS<'tcx> {
/// Pushes all the lifetimes in the given type onto the given list. A
/// "lifetime in a type" is a lifetime specified by a reference or a lifetime
/// in a list of type substitutions. This does *not* traverse into nominal
/// types, nor does it resolve fictitious types.
pub fn accumulate_lifetimes_in_type(&self, accumulator: &mut Vec<ty::Region>) {
for ty in self.walk() {
match ty.sty {
TyRef(region, _) => {
accumulator.push(*region)
}
TyTrait(ref t) => {
accumulator.push_all(t.principal.0.substs.regions().as_slice());
}
TyEnum(_, substs) |
TyStruct(_, substs) => {
accum_substs(accumulator, substs);
}
TyClosure(_, substs) => {
accum_substs(accumulator, substs);
}
TyBool |
TyChar |
TyInt(_) |
TyUint(_) |
TyFloat(_) |
TyBox(_) |
TyStr |
TyArray(_, _) |
TySlice(_) |
TyRawPtr(_) |
TyBareFn(..) |
TyTuple(_) |
TyProjection(_) |
TyParam(_) |
TyInfer(_) |
TyError => {
}
}
}
fn accum_substs(accumulator: &mut Vec<Region>, substs: &Substs) {
match substs.regions {
subst::ErasedRegions => {}
subst::NonerasedRegions(ref regions) => {
for region in regions {
accumulator.push(*region)
}
}
}
}
}
}
/// A free variable referred to in a function.
#[derive(Copy, Clone, RustcEncodable, RustcDecodable)]
pub struct Freevar {
@ -6917,7 +6873,8 @@ impl<'tcx> ctxt<'tcx> {
where T: TypeFoldable<'tcx>
{
let bound0_value = bound2_value.skip_binder().skip_binder();
let value = ty_fold::fold_regions(self, bound0_value, |region, current_depth| {
let value = ty_fold::fold_regions(self, bound0_value, &mut false,
|region, current_depth| {
match region {
ty::ReLateBound(debruijn, br) if debruijn.depth >= current_depth => {
// should be true if no escaping regions from bound2_value

View File

@ -44,7 +44,7 @@ use std::rc::Rc;
use syntax::abi;
use syntax::ast;
use syntax::owned_slice::OwnedSlice;
use util::nodemap::FnvHashMap;
use util::nodemap::{FnvHashMap, FnvHashSet};
///////////////////////////////////////////////////////////////////////////
// Two generic traits
@ -783,38 +783,51 @@ impl<'a, 'tcx, F> TypeFolder<'tcx> for BottomUpFolder<'a, 'tcx, F> where
pub struct RegionFolder<'a, 'tcx: 'a> {
tcx: &'a ty::ctxt<'tcx>,
skipped_regions: &'a mut bool,
current_depth: u32,
fld_r: &'a mut (FnMut(ty::Region, u32) -> ty::Region + 'a),
}
impl<'a, 'tcx> RegionFolder<'a, 'tcx> {
pub fn new<F>(tcx: &'a ty::ctxt<'tcx>, fld_r: &'a mut F) -> RegionFolder<'a, 'tcx>
pub fn new<F>(tcx: &'a ty::ctxt<'tcx>,
skipped_regions: &'a mut bool,
fld_r: &'a mut F) -> RegionFolder<'a, 'tcx>
where F : FnMut(ty::Region, u32) -> ty::Region
{
RegionFolder {
tcx: tcx,
skipped_regions: skipped_regions,
current_depth: 1,
fld_r: fld_r,
}
}
}
pub fn collect_regions<'tcx,T>(tcx: &ty::ctxt<'tcx>, value: &T) -> Vec<ty::Region>
/// Collects the free and escaping regions in `value` into `region_set`. Returns
/// whether any late-bound regions were skipped
pub fn collect_regions<'tcx,T>(tcx: &ty::ctxt<'tcx>,
value: &T,
region_set: &mut FnvHashSet<ty::Region>) -> bool
where T : TypeFoldable<'tcx>
{
let mut vec = Vec::new();
fold_regions(tcx, value, |r, _| { vec.push(r); r });
vec
let mut have_bound_regions = false;
fold_regions(tcx, value, &mut have_bound_regions,
|r, d| { region_set.insert(r.from_depth(d)); r });
have_bound_regions
}
/// Folds the escaping and free regions in `value` using `f`, and
/// sets `skipped_regions` to true if any late-bound region was found
/// and skipped.
pub fn fold_regions<'tcx,T,F>(tcx: &ty::ctxt<'tcx>,
value: &T,
skipped_regions: &mut bool,
mut f: F)
-> T
where F : FnMut(ty::Region, u32) -> ty::Region,
T : TypeFoldable<'tcx>,
{
value.fold_with(&mut RegionFolder::new(tcx, &mut f))
value.fold_with(&mut RegionFolder::new(tcx, skipped_regions, &mut f))
}
impl<'a, 'tcx> TypeFolder<'tcx> for RegionFolder<'a, 'tcx>
@ -834,6 +847,7 @@ impl<'a, 'tcx> TypeFolder<'tcx> for RegionFolder<'a, 'tcx>
ty::ReLateBound(debruijn, _) if debruijn.depth < self.current_depth => {
debug!("RegionFolder.fold_region({:?}) skipped bound region (current depth={})",
r, self.current_depth);
*self.skipped_regions = true;
r
}
_ => {
@ -989,7 +1003,7 @@ pub fn shift_regions<'tcx, T:TypeFoldable<'tcx>>(tcx: &ty::ctxt<'tcx>,
debug!("shift_regions(value={:?}, amount={})",
value, amount);
value.fold_with(&mut RegionFolder::new(tcx, &mut |region, _current_depth| {
value.fold_with(&mut RegionFolder::new(tcx, &mut false, &mut |region, _current_depth| {
shift_region(region, amount)
}))
}

View File

@ -9,6 +9,7 @@
// except according to those terms.
//! An iterator over the type substructure.
//! WARNING: this does not keep track of the region depth.
use middle::ty::{self, Ty};
use std::iter::Iterator;

View File

@ -59,7 +59,8 @@ use middle::traits;
use middle::ty::{self, RegionEscape, Ty, ToPredicate, HasTypeFlags};
use middle::ty_fold;
use rscope::{self, UnelidableRscope, RegionScope, ElidableRscope, ExplicitRscope,
ObjectLifetimeDefaultRscope, ShiftedRscope, BindingRscope};
ObjectLifetimeDefaultRscope, ShiftedRscope, BindingRscope,
ElisionFailureInfo, ElidedLifetime};
use util::common::{ErrorReported, FN_OUTPUT_NAME};
use util::nodemap::FnvHashSet;
@ -186,6 +187,58 @@ pub fn ast_region_to_region(tcx: &ty::ctxt, lifetime: &ast::Lifetime)
r
}
fn report_elision_failure(
tcx: &ty::ctxt,
default_span: Span,
params: Vec<ElisionFailureInfo>)
{
let mut m = String::new();
let len = params.len();
for (i, info) in params.into_iter().enumerate() {
let ElisionFailureInfo {
name, lifetime_count: n, have_bound_regions
} = info;
let help_name = if name.is_empty() {
format!("argument {}", i + 1)
} else {
format!("`{}`", name)
};
m.push_str(&(if n == 1 {
help_name
} else {
format!("one of {}'s {} elided {}lifetimes", help_name, n,
if have_bound_regions { "free " } else { "" } )
})[..]);
if len == 2 && i == 0 {
m.push_str(" or ");
} else if i + 2 == len {
m.push_str(", or ");
} else if i + 1 != len {
m.push_str(", ");
}
}
if len == 1 {
fileline_help!(tcx.sess, default_span,
"this function's return type contains a borrowed value, but \
the signature does not say which {} it is borrowed from",
m);
} else if len == 0 {
fileline_help!(tcx.sess, default_span,
"this function's return type contains a borrowed value, but \
there is no value for it to be borrowed from");
fileline_help!(tcx.sess, default_span,
"consider giving it a 'static lifetime");
} else {
fileline_help!(tcx.sess, default_span,
"this function's return type contains a borrowed value, but \
the signature does not say whether it is borrowed from {}",
m);
}
}
pub fn opt_ast_region_to_region<'tcx>(
this: &AstConv<'tcx>,
rscope: &RegionScope,
@ -197,61 +250,15 @@ pub fn opt_ast_region_to_region<'tcx>(
ast_region_to_region(this.tcx(), lifetime)
}
None => {
match rscope.anon_regions(default_span, 1) {
Err(v) => {
debug!("optional region in illegal location");
span_err!(this.tcx().sess, default_span, E0106,
"missing lifetime specifier");
match v {
Some(v) => {
let mut m = String::new();
let len = v.len();
for (i, (name, n)) in v.into_iter().enumerate() {
let help_name = if name.is_empty() {
format!("argument {}", i + 1)
} else {
format!("`{}`", name)
};
m.push_str(&(if n == 1 {
help_name
} else {
format!("one of {}'s {} elided lifetimes", help_name, n)
})[..]);
if len == 2 && i == 0 {
m.push_str(" or ");
} else if i + 2 == len {
m.push_str(", or ");
} else if i + 1 != len {
m.push_str(", ");
}
}
if len == 1 {
fileline_help!(this.tcx().sess, default_span,
"this function's return type contains a borrowed value, but \
the signature does not say which {} it is borrowed from",
m);
} else if len == 0 {
fileline_help!(this.tcx().sess, default_span,
"this function's return type contains a borrowed value, but \
there is no value for it to be borrowed from");
fileline_help!(this.tcx().sess, default_span,
"consider giving it a 'static lifetime");
} else {
fileline_help!(this.tcx().sess, default_span,
"this function's return type contains a borrowed value, but \
the signature does not say whether it is borrowed from {}",
m);
}
}
None => {},
}
ty::ReStatic
None => match rscope.anon_regions(default_span, 1) {
Ok(rs) => rs[0],
Err(params) => {
span_err!(this.tcx().sess, default_span, E0106,
"missing lifetime specifier");
if let Some(params) = params {
report_elision_failure(this.tcx(), default_span, params);
}
Ok(rs) => rs[0],
ty::ReStatic
}
}
};
@ -505,48 +512,54 @@ fn convert_angle_bracketed_parameters<'tcx>(this: &AstConv<'tcx>,
/// Returns the appropriate lifetime to use for any output lifetimes
/// (if one exists) and a vector of the (pattern, number of lifetimes)
/// corresponding to each input type/pattern.
fn find_implied_output_region(input_tys: &[Ty], input_pats: Vec<String>)
-> (Option<ty::Region>, Vec<(String, usize)>)
fn find_implied_output_region<'tcx>(tcx: &ty::ctxt<'tcx>,
input_tys: &[Ty<'tcx>],
input_pats: Vec<String>) -> ElidedLifetime
{
let mut lifetimes_for_params: Vec<(String, usize)> = Vec::new();
let mut lifetimes_for_params = Vec::new();
let mut possible_implied_output_region = None;
for (input_type, input_pat) in input_tys.iter().zip(input_pats) {
let mut accumulator = Vec::new();
input_type.accumulate_lifetimes_in_type(&mut accumulator);
let mut regions = FnvHashSet();
let have_bound_regions = ty_fold::collect_regions(tcx,
input_type,
&mut regions);
if accumulator.len() == 1 {
debug!("find_implied_output_regions: collected {:?} from {:?} \
have_bound_regions={:?}", &regions, input_type, have_bound_regions);
if regions.len() == 1 {
// there's a chance that the unique lifetime of this
// iteration will be the appropriate lifetime for output
// parameters, so lets store it.
possible_implied_output_region = Some(accumulator[0])
possible_implied_output_region = regions.iter().cloned().next();
}
lifetimes_for_params.push((input_pat, accumulator.len()));
lifetimes_for_params.push(ElisionFailureInfo {
name: input_pat,
lifetime_count: regions.len(),
have_bound_regions: have_bound_regions
});
}
let implied_output_region =
if lifetimes_for_params.iter().map(|&(_, n)| n).sum::<usize>() == 1 {
assert!(possible_implied_output_region.is_some());
possible_implied_output_region
} else {
None
};
(implied_output_region, lifetimes_for_params)
if lifetimes_for_params.iter().map(|e| e.lifetime_count).sum::<usize>() == 1 {
Ok(possible_implied_output_region.unwrap())
} else {
Err(Some(lifetimes_for_params))
}
}
fn convert_ty_with_lifetime_elision<'tcx>(this: &AstConv<'tcx>,
implied_output_region: Option<ty::Region>,
param_lifetimes: Vec<(String, usize)>,
elided_lifetime: ElidedLifetime,
ty: &ast::Ty)
-> Ty<'tcx>
{
match implied_output_region {
Some(implied_output_region) => {
match elided_lifetime {
Ok(implied_output_region) => {
let rb = ElidableRscope::new(implied_output_region);
ast_ty_to_ty(this, &rb, ty)
}
None => {
Err(param_lifetimes) => {
// All regions must be explicitly specified in the output
// if the lifetime elision rules do not apply. This saves
// the user from potentially-confusing errors.
@ -576,8 +589,7 @@ fn convert_parenthesized_parameters<'tcx>(this: &AstConv<'tcx>,
.collect::<Vec<Ty<'tcx>>>();
let input_params: Vec<_> = repeat(String::new()).take(inputs.len()).collect();
let (implied_output_region,
params_lifetimes) = find_implied_output_region(&*inputs, input_params);
let implied_output_region = find_implied_output_region(this.tcx(), &inputs, input_params);
let input_ty = this.tcx().mk_tup(inputs);
@ -585,8 +597,7 @@ fn convert_parenthesized_parameters<'tcx>(this: &AstConv<'tcx>,
Some(ref output_ty) => {
(convert_ty_with_lifetime_elision(this,
implied_output_region,
params_lifetimes,
&**output_ty),
&output_ty),
output_ty.span)
}
None => {
@ -1705,7 +1716,7 @@ fn ty_of_method_or_bare_fn<'a, 'tcx>(this: &AstConv<'tcx>,
// here), if self is by-reference, then the implied output region is the
// region of the self parameter.
let mut explicit_self_category_result = None;
let (self_ty, mut implied_output_region) = match opt_self_info {
let (self_ty, implied_output_region) = match opt_self_info {
None => (None, None),
Some(self_info) => {
// This type comes from an impl or trait; no late-bound
@ -1756,19 +1767,18 @@ fn ty_of_method_or_bare_fn<'a, 'tcx>(this: &AstConv<'tcx>,
// Second, if there was exactly one lifetime (either a substitution or a
// reference) in the arguments, then any anonymous regions in the output
// have that lifetime.
let lifetimes_for_params = if implied_output_region.is_none() {
let input_tys = if self_ty.is_some() {
// Skip the first argument if `self` is present.
&self_and_input_tys[1..]
} else {
&self_and_input_tys[..]
};
let implied_output_region = match implied_output_region {
Some(r) => Ok(r),
None => {
let input_tys = if self_ty.is_some() {
// Skip the first argument if `self` is present.
&self_and_input_tys[1..]
} else {
&self_and_input_tys[..]
};
let (ior, lfp) = find_implied_output_region(input_tys, input_pats);
implied_output_region = ior;
lfp
} else {
vec![]
find_implied_output_region(this.tcx(), input_tys, input_pats)
}
};
let output_ty = match decl.output {
@ -1777,8 +1787,7 @@ fn ty_of_method_or_bare_fn<'a, 'tcx>(this: &AstConv<'tcx>,
ast::Return(ref output) =>
ty::FnConverging(convert_ty_with_lifetime_elision(this,
implied_output_region,
lifetimes_for_params,
&**output)),
&output)),
ast::DefaultReturn(..) => ty::FnConverging(this.tcx().mk_nil()),
ast::NoReturn(..) => ty::FnDiverging
};

View File

@ -97,7 +97,7 @@ use middle::ty::{Disr, ParamTy, ParameterEnvironment};
use middle::ty::{self, HasTypeFlags, RegionEscape, ToPolyTraitRef, Ty};
use middle::ty::{MethodCall, MethodCallee};
use middle::ty_fold::{TypeFolder, TypeFoldable};
use rscope::RegionScope;
use rscope::{ElisionFailureInfo, RegionScope};
use session::Session;
use {CrateCtxt, lookup_full_def, require_same_types};
use TypeAndSubsts;
@ -1796,7 +1796,7 @@ impl<'a, 'tcx> RegionScope for FnCtxt<'a, 'tcx> {
}
fn anon_regions(&self, span: Span, count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>> {
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>> {
Ok((0..count).map(|_| {
self.infcx().next_region_var(infer::MiscVariable(span))
}).collect())

View File

@ -2248,7 +2248,7 @@ fn check_method_self_type<'a, 'tcx, RS:RegionScope>(
* before we really have a `ParameterEnvironment` to check.
*/
ty_fold::fold_regions(tcx, value, |region, _| {
ty_fold::fold_regions(tcx, value, &mut false, |region, _| {
match region {
ty::ReEarlyBound(data) => {
let def_id = local_def(data.param_id);

View File

@ -16,6 +16,15 @@ use std::cell::Cell;
use std::iter::repeat;
use syntax::codemap::Span;
#[derive(Clone)]
pub struct ElisionFailureInfo {
pub name: String,
pub lifetime_count: usize,
pub have_bound_regions: bool
}
pub type ElidedLifetime = Result<ty::Region, Option<Vec<ElisionFailureInfo>>>;
/// Defines strategies for handling regions that are omitted. For
/// example, if one writes the type `&Foo`, then the lifetime of
/// this reference has been omitted. When converting this
@ -30,7 +39,7 @@ pub trait RegionScope {
fn anon_regions(&self,
span: Span,
count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>>;
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>>;
/// If an object omits any explicit lifetime bound, and none can
/// be derived from the object traits, what should we use? If
@ -51,16 +60,16 @@ impl RegionScope for ExplicitRscope {
fn anon_regions(&self,
_span: Span,
_count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>> {
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>> {
Err(None)
}
}
// Same as `ExplicitRscope`, but provides some extra information for diagnostics
pub struct UnelidableRscope(Vec<(String, usize)>);
pub struct UnelidableRscope(Option<Vec<ElisionFailureInfo>>);
impl UnelidableRscope {
pub fn new(v: Vec<(String, usize)>) -> UnelidableRscope {
pub fn new(v: Option<Vec<ElisionFailureInfo>>) -> UnelidableRscope {
UnelidableRscope(v)
}
}
@ -73,9 +82,9 @@ impl RegionScope for UnelidableRscope {
fn anon_regions(&self,
_span: Span,
_count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>> {
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>> {
let UnelidableRscope(ref v) = *self;
Err(Some(v.clone()))
Err(v.clone())
}
}
@ -104,7 +113,7 @@ impl RegionScope for ElidableRscope {
fn anon_regions(&self,
_span: Span,
count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>>
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>>
{
Ok(repeat(self.default).take(count).collect())
}
@ -141,7 +150,7 @@ impl RegionScope for BindingRscope {
fn anon_regions(&self,
_: Span,
count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>>
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>>
{
Ok((0..count).map(|_| self.next_region()).collect())
}
@ -177,7 +186,7 @@ impl<'r> RegionScope for ObjectLifetimeDefaultRscope<'r> {
fn anon_regions(&self,
span: Span,
count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>>
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>>
{
self.base_scope.anon_regions(span, count)
}
@ -204,7 +213,7 @@ impl<'r> RegionScope for ShiftedRscope<'r> {
fn anon_regions(&self,
span: Span,
count: usize)
-> Result<Vec<ty::Region>, Option<Vec<(String, usize)>>>
-> Result<Vec<ty::Region>, Option<Vec<ElisionFailureInfo>>>
{
match self.base_scope.anon_regions(span, count) {
Ok(mut v) => {

View File

@ -0,0 +1,19 @@
// Copyright 2015 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
fn parse_type(iter: Box<Iterator<Item=&str>+'static>) -> &str { iter.next() }
//~^ ERROR missing lifetime specifier [E0106]
//~^^ HELP 2 elided lifetimes
fn parse_type_2(iter: fn(&u8)->&u8) -> &str { iter() }
//~^ ERROR missing lifetime specifier [E0106]
//~^^ HELP 0 elided free lifetimes
fn main() {}