rustc: move most of lifetime elision to resolve_lifetimes.

This commit is contained in:
Eduard-Mihai Burtescu 2017-01-13 15:09:56 +02:00
parent bbc341424c
commit ba1849daec
13 changed files with 703 additions and 602 deletions

View file

@ -327,6 +327,69 @@ struct ListNode {
This works because `Box` is a pointer, so its size is well-known.
"##,
E0106: r##"
This error indicates that a lifetime is missing from a type. If it is an error
inside a function signature, the problem may be with failing to adhere to the
lifetime elision rules (see below).
Here are some simple examples of where you'll run into this error:
```compile_fail,E0106
struct Foo { x: &bool } // error
struct Foo<'a> { x: &'a bool } // correct
enum Bar { A(u8), B(&bool), } // error
enum Bar<'a> { A(u8), B(&'a bool), } // correct
type MyStr = &str; // error
type MyStr<'a> = &'a str; // correct
```
Lifetime elision is a special, limited kind of inference for lifetimes in
function signatures which allows you to leave out lifetimes in certain cases.
For more background on lifetime elision see [the book][book-le].
The lifetime elision rules require that any function signature with an elided
output lifetime must either have
- exactly one input lifetime
- or, multiple input lifetimes, but the function must also be a method with a
`&self` or `&mut self` receiver
In the first case, the output lifetime is inferred to be the same as the unique
input lifetime. In the second case, the lifetime is instead inferred to be the
same as the lifetime on `&self` or `&mut self`.
Here are some examples of elision errors:
```compile_fail,E0106
// error, no input lifetimes
fn foo() -> &str { }
// error, `x` and `y` have distinct lifetimes inferred
fn bar(x: &str, y: &str) -> &str { }
// error, `y`'s lifetime is inferred to be distinct from `x`'s
fn baz<'a>(x: &'a str, y: &str) -> &str { }
```
Here's an example that is currently an error, but may work in a future version
of Rust:
```compile_fail,E0106
struct Foo<'a>(&'a str);
trait Quux { }
impl Quux for Foo { }
```
Lifetime elision in implementation headers was part of the lifetime elision
RFC. It is, however, [currently unimplemented][iss15872].
[book-le]: https://doc.rust-lang.org/nightly/book/lifetimes.html#lifetime-elision
[iss15872]: https://github.com/rust-lang/rust/issues/15872
"##,
E0109: r##"
You tried to give a type parameter to a type which doesn't need it. Erroneous
code example:

View file

@ -314,9 +314,10 @@ impl<'a> LoweringContext<'a> {
TyKind::Slice(ref ty) => hir::TySlice(self.lower_ty(ty)),
TyKind::Ptr(ref mt) => hir::TyPtr(self.lower_mt(mt)),
TyKind::Rptr(ref region, ref mt) => {
let span = Span { hi: t.span.lo, ..t.span };
let lifetime = match *region {
Some(ref lt) => self.lower_lifetime(lt),
None => self.elided_lifetime(t.span)
None => self.elided_lifetime(span)
};
hir::TyRptr(lifetime, self.lower_mt(mt))
}

View file

@ -22,11 +22,16 @@ use hir::def::Def;
use hir::def_id::DefId;
use middle::region;
use ty;
use std::cell::Cell;
use std::mem::replace;
use syntax::ast;
use syntax::ptr::P;
use syntax::symbol::keywords;
use syntax_pos::Span;
use errors::DiagnosticBuilder;
use util::nodemap::{NodeMap, FxHashSet, FxHashMap};
use rustc_back::slice;
use hir;
use hir::intravisit::{self, Visitor, NestedVisitorMap};
@ -36,6 +41,7 @@ pub enum Region {
Static,
EarlyBound(/* index */ u32, /* lifetime decl */ ast::NodeId),
LateBound(ty::DebruijnIndex, /* lifetime decl */ ast::NodeId),
LateBoundAnon(ty::DebruijnIndex, /* anon index */ u32),
Free(region::CallSiteScopeData, /* lifetime decl */ ast::NodeId),
}
@ -51,9 +57,18 @@ impl Region {
(def.lifetime.name, Region::LateBound(depth, def.lifetime.id))
}
fn late_anon(index: &Cell<u32>) -> Region {
let i = index.get();
index.set(i + 1);
let depth = ty::DebruijnIndex::new(1);
Region::LateBoundAnon(depth, i)
}
fn id(&self) -> Option<ast::NodeId> {
match *self {
Region::Static => None,
Region::Static |
Region::LateBoundAnon(..) => None,
Region::EarlyBound(_, id) |
Region::LateBound(_, id) |
Region::Free(_, id) => Some(id)
@ -65,6 +80,25 @@ impl Region {
Region::LateBound(depth, id) => {
Region::LateBound(depth.shifted(amount), id)
}
Region::LateBoundAnon(depth, index) => {
Region::LateBoundAnon(depth.shifted(amount), index)
}
_ => self
}
}
fn from_depth(self, depth: u32) -> Region {
match self {
Region::LateBound(debruijn, id) => {
Region::LateBound(ty::DebruijnIndex {
depth: debruijn.depth - (depth - 1)
}, id)
}
Region::LateBoundAnon(debruijn, index) => {
Region::LateBoundAnon(ty::DebruijnIndex {
depth: debruijn.depth - (depth - 1)
}, index)
}
_ => self
}
}
@ -122,14 +156,46 @@ enum Scope<'a> {
/// Lifetimes introduced by a fn are scoped to the call-site for that fn,
/// if this is a fn body, otherwise the original definitions are used.
/// Unspecified lifetimes are inferred, unless an elision scope is nested,
/// e.g. `(&T, fn(&T) -> &T);` becomes `(&'_ T, for<'a> fn(&'a T) -> &'a T)`.
Body {
id: hir::BodyId,
s: ScopeRef<'a>
},
/// A scope which either determines unspecified lifetimes or errors
/// on them (e.g. due to ambiguity). For more details, see `Elide`.
Elision {
elide: Elide,
s: ScopeRef<'a>
},
Root
}
#[derive(Clone, Debug)]
enum Elide {
/// Use a fresh anonymous late-bound lifetime each time, by
/// incrementing the counter to generate sequential indices.
FreshLateAnon(Cell<u32>),
/// Always use this one lifetime.
Exact(Region),
/// Like `Exact(Static)` but requires `#![feature(static_in_const)]`.
Static,
/// Less or more than one lifetime were found, error on unspecified.
Error(Vec<ElisionFailureInfo>)
}
#[derive(Clone, Debug)]
struct ElisionFailureInfo {
/// Where we can find the argument pattern.
parent: Option<hir::BodyId>,
/// The index of the argument in the original definition.
index: usize,
lifetime_count: usize,
have_bound_regions: bool
}
type ScopeRef<'a> = &'a Scope<'a>;
const ROOT_SCOPE: ScopeRef<'static> = &Scope::Root;
@ -189,12 +255,19 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
hir::ItemUse(..) |
hir::ItemMod(..) |
hir::ItemDefaultImpl(..) |
hir::ItemForeignMod(..) |
hir::ItemStatic(..) |
hir::ItemConst(..) => {
hir::ItemForeignMod(..) => {
// These sorts of items have no lifetime parameters at all.
intravisit::walk_item(self, item);
}
hir::ItemStatic(..) |
hir::ItemConst(..) => {
// No lifetime parameters, but implied 'static.
let scope = Scope::Elision {
elide: Elide::Static,
s: ROOT_SCOPE
};
self.with(scope, |_, this| intravisit::walk_item(this, item));
}
hir::ItemTy(_, ref generics) |
hir::ItemEnum(_, ref generics) |
hir::ItemStruct(_, ref generics) |
@ -299,6 +372,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
fn visit_lifetime(&mut self, lifetime_ref: &'tcx hir::Lifetime) {
if lifetime_ref.is_elided() {
self.resolve_elided_lifetimes(slice::ref_slice(lifetime_ref));
return;
}
if lifetime_ref.name == keywords::StaticLifetime.name() {
@ -308,6 +382,31 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
self.resolve_lifetime_ref(lifetime_ref);
}
fn visit_path_parameters(&mut self, _: Span, params: &'tcx hir::PathParameters) {
match *params {
hir::AngleBracketedParameters(ref data) => {
if data.lifetimes.iter().all(|l| l.is_elided()) {
self.resolve_elided_lifetimes(&data.lifetimes);
} else {
for l in &data.lifetimes { self.visit_lifetime(l); }
}
for ty in &data.types { self.visit_ty(ty); }
for b in &data.bindings { self.visit_assoc_type_binding(b); }
}
hir::ParenthesizedParameters(ref data) => {
self.visit_fn_like_elision(&data.inputs, data.output.as_ref());
}
}
}
fn visit_fn_decl(&mut self, fd: &'tcx hir::FnDecl) {
let output = match fd.output {
hir::DefaultReturn(_) => None,
hir::Return(ref ty) => Some(ty)
};
self.visit_fn_like_elision(&fd.inputs, output);
}
fn visit_generics(&mut self, generics: &'tcx hir::Generics) {
for ty_param in generics.ty_params.iter() {
walk_list!(self, visit_ty_param_bound, &ty_param.bounds);
@ -478,10 +577,6 @@ fn extract_labels(ctxt: &mut LifetimeContext, body: &hir::Body) {
}
intravisit::walk_expr(self, ex)
}
fn visit_item(&mut self, _: &hir::Item) {
// do not recurse into items defined in the block
}
}
fn expression_label(ex: &hir::Expr) -> Option<(ast::Name, Span)> {
@ -499,7 +594,9 @@ fn extract_labels(ctxt: &mut LifetimeContext, body: &hir::Body) {
label_span: Span) {
loop {
match *scope {
Scope::Body { s, .. } => { scope = s; }
Scope::Body { s, .. } |
Scope::Elision { s, .. } => { scope = s; }
Scope::Root => { return; }
Scope::Binder { ref lifetimes, s } => {
@ -639,6 +736,10 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
scope = s;
}
}
Scope::Elision { s, .. } => {
scope = s;
}
}
};
@ -672,6 +773,386 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
}
}
fn visit_fn_like_elision(&mut self, inputs: &'tcx [P<hir::Ty>],
output: Option<&'tcx P<hir::Ty>>) {
let mut arg_elide = Elide::FreshLateAnon(Cell::new(0));
let arg_scope = Scope::Elision {
elide: arg_elide.clone(),
s: self.scope
};
self.with(arg_scope, |_, this| {
for input in inputs {
this.visit_ty(input);
}
match *this.scope {
Scope::Elision { ref elide, .. } => {
arg_elide = elide.clone();
}
_ => bug!()
}
});
let output = match output {
Some(ty) => ty,
None => return
};
// Figure out if there's a body we can get argument names from,
// and whether there's a `self` argument (treated specially).
let mut assoc_item_kind = None;
let mut impl_self = None;
let parent = self.hir_map.get_parent_node(output.id);
let body = match self.hir_map.get(parent) {
// `fn` definitions and methods.
hir::map::NodeItem(&hir::Item {
node: hir::ItemFn(.., body), ..
}) => Some(body),
hir::map::NodeTraitItem(&hir::TraitItem {
node: hir::TraitItemKind::Method(_, ref m), ..
}) => {
match self.hir_map.expect_item(self.hir_map.get_parent(parent)).node {
hir::ItemTrait(.., ref trait_items) => {
assoc_item_kind = trait_items.iter().find(|ti| ti.id.node_id == parent)
.map(|ti| ti.kind);
}
_ => {}
}
match *m {
hir::TraitMethod::Required(_) => None,
hir::TraitMethod::Provided(body) => Some(body),
}
}
hir::map::NodeImplItem(&hir::ImplItem {
node: hir::ImplItemKind::Method(_, body), ..
}) => {
match self.hir_map.expect_item(self.hir_map.get_parent(parent)).node {
hir::ItemImpl(.., ref self_ty, ref impl_items) => {
impl_self = Some(self_ty);
assoc_item_kind = impl_items.iter().find(|ii| ii.id.node_id == parent)
.map(|ii| ii.kind);
}
_ => {}
}
Some(body)
}
// `fn(...) -> R` and `Trait(...) -> R` (both types and bounds).
hir::map::NodeTy(_) | hir::map::NodeTraitRef(_) => None,
// Foreign `fn` decls are terrible because we messed up,
// and their return types get argument type elision.
// And now too much code out there is abusing this rule.
hir::map::NodeForeignItem(_) => {
let arg_scope = Scope::Elision {
elide: arg_elide,
s: self.scope
};
self.with(arg_scope, |_, this| this.visit_ty(output));
return;
}
// Everything else (only closures?) doesn't
// actually enjoy elision in return types.
_ => {
self.visit_ty(output);
return;
}
};
let has_self = match assoc_item_kind {
Some(hir::AssociatedItemKind::Method { has_self }) => has_self,
_ => false
};
// In accordance with the rules for lifetime elision, we can determine
// what region to use for elision in the output type in two ways.
// First (determined here), if `self` is by-reference, then the
// implied output region is the region of the self parameter.
if has_self {
// Look for `self: &'a Self` - also desugared from `&'a self`,
// and if that matches, use it for elision and return early.
let is_self_ty = |def: Def| {
if let Def::SelfTy(..) = def {
return true;
}
// Can't always rely on literal (or implied) `Self` due
// to the way elision rules were originally specified.
let impl_self = impl_self.map(|ty| &ty.node);
if let Some(&hir::TyPath(hir::QPath::Resolved(None, ref path))) = impl_self {
match path.def {
// Whitelist the types that unambiguously always
// result in the same type constructor being used
// (it can't differ between `Self` and `self`).
Def::Struct(_) |
Def::Union(_) |
Def::Enum(_) |
Def::Trait(_) |
Def::PrimTy(_) => return def == path.def,
_ => {}
}
}
false
};
if let hir::TyRptr(lifetime_ref, ref mt) = inputs[0].node {
if let hir::TyPath(hir::QPath::Resolved(None, ref path)) = mt.ty.node {
if is_self_ty(path.def) {
if let Some(&lifetime) = self.map.defs.get(&lifetime_ref.id) {
let scope = Scope::Elision {
elide: Elide::Exact(lifetime),
s: self.scope
};
self.with(scope, |_, this| this.visit_ty(output));
return;
}
}
}
}
}
// 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 mut possible_implied_output_region = None;
let mut lifetime_count = 0;
let arg_lifetimes = inputs.iter().enumerate().skip(has_self as usize).map(|(i, input)| {
let mut gather = GatherLifetimes {
map: self.map,
binder_depth: 1,
have_bound_regions: false,
lifetimes: FxHashSet()
};
gather.visit_ty(input);
lifetime_count += gather.lifetimes.len();
if lifetime_count == 1 && gather.lifetimes.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 = gather.lifetimes.iter().cloned().next();
}
ElisionFailureInfo {
parent: body,
index: i,
lifetime_count: gather.lifetimes.len(),
have_bound_regions: gather.have_bound_regions
}
}).collect();
let elide = if lifetime_count == 1 {
Elide::Exact(possible_implied_output_region.unwrap())
} else {
Elide::Error(arg_lifetimes)
};
let scope = Scope::Elision {
elide: elide,
s: self.scope
};
self.with(scope, |_, this| this.visit_ty(output));
struct GatherLifetimes<'a> {
map: &'a NamedRegionMap,
binder_depth: u32,
have_bound_regions: bool,
lifetimes: FxHashSet<Region>,
}
impl<'v, 'a> Visitor<'v> for GatherLifetimes<'a> {
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> {
NestedVisitorMap::None
}
fn visit_ty(&mut self, ty: &hir::Ty) {
let delta = match ty.node {
hir::TyBareFn(_) => 1,
hir::TyPath(hir::QPath::Resolved(None, ref path)) => {
// if this path references a trait, then this will resolve to
// a trait ref, which introduces a binding scope.
match path.def {
Def::Trait(..) => 1,
_ => 0
}
}
_ => 0
};
self.binder_depth += delta;
intravisit::walk_ty(self, ty);
self.binder_depth -= delta;
}
fn visit_poly_trait_ref(&mut self,
trait_ref: &hir::PolyTraitRef,
modifier: &hir::TraitBoundModifier) {
self.binder_depth += 1;
intravisit::walk_poly_trait_ref(self, trait_ref, modifier);
self.binder_depth -= 1;
}
fn visit_lifetime_def(&mut self, lifetime_def: &hir::LifetimeDef) {
for l in &lifetime_def.bounds { self.visit_lifetime(l); }
}
fn visit_lifetime(&mut self, lifetime_ref: &hir::Lifetime) {
if let Some(&lifetime) = self.map.defs.get(&lifetime_ref.id) {
match lifetime {
Region::LateBound(debruijn, _) |
Region::LateBoundAnon(debruijn, _)
if debruijn.depth < self.binder_depth => {
self.have_bound_regions = true;
}
_ => {
self.lifetimes.insert(lifetime.from_depth(self.binder_depth));
}
}
}
}
}
}
fn resolve_elided_lifetimes(&mut self, lifetime_refs: &[hir::Lifetime]) {
if lifetime_refs.is_empty() {
return;
}
let span = lifetime_refs[0].span;
let mut late_depth = 0;
let mut scope = self.scope;
let error = loop {
match *scope {
// Do not assign any resolution, it will be inferred.
Scope::Body { .. } => return,
Scope::Root => break None,
Scope::Binder { s, .. } => {
late_depth += 1;
scope = s;
}
Scope::Elision { ref elide, .. } => {
let lifetime = match *elide {
Elide::FreshLateAnon(ref counter) => {
for lifetime_ref in lifetime_refs {
let lifetime = Region::late_anon(counter).shifted(late_depth);
self.insert_lifetime(lifetime_ref, lifetime);
}
return;
}
Elide::Exact(l) => l.shifted(late_depth),
Elide::Static => {
if !self.sess.features.borrow().static_in_const {
self.sess
.struct_span_err(span,
"this needs a `'static` lifetime or the \
`static_in_const` feature, see #35897")
.emit();
}
Region::Static
}
Elide::Error(ref e) => break Some(e)
};
for lifetime_ref in lifetime_refs {
self.insert_lifetime(lifetime_ref, lifetime);
}
return;
}
}
};
let mut err = struct_span_err!(self.sess, span, E0106,
"missing lifetime specifier{}",
if lifetime_refs.len() > 1 { "s" } else { "" });
let msg = if lifetime_refs.len() > 1 {
format!("expected {} lifetime parameters", lifetime_refs.len())
} else {
format!("expected lifetime parameter")
};
err.span_label(span, &msg);
if let Some(params) = error {
if lifetime_refs.len() == 1 {
self.report_elision_failure(&mut err, params);
}
}
err.emit();
}
fn report_elision_failure(&mut self,
db: &mut DiagnosticBuilder,
params: &[ElisionFailureInfo]) {
let mut m = String::new();
let len = params.len();
let elided_params: Vec<_> = params.iter().cloned()
.filter(|info| info.lifetime_count > 0)
.collect();
let elided_len = elided_params.len();
for (i, info) in elided_params.into_iter().enumerate() {
let ElisionFailureInfo {
parent, index, lifetime_count: n, have_bound_regions
} = info;
let help_name = if let Some(body) = parent {
let arg = &self.hir_map.body(body).arguments[index];
format!("`{}`", self.hir_map.node_to_pretty_string(arg.pat.id))
} else {
format!("argument {}", index + 1)
};
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 elided_len == 2 && i == 0 {
m.push_str(" or ");
} else if i + 2 == elided_len {
m.push_str(", or ");
} else if i != elided_len - 1 {
m.push_str(", ");
}
}
if len == 0 {
help!(db,
"this function's return type contains a borrowed value, but \
there is no value for it to be borrowed from");
help!(db,
"consider giving it a 'static lifetime");
} else if elided_len == 0 {
help!(db,
"this function's return type contains a borrowed value with \
an elided lifetime, but the lifetime cannot be derived from \
the arguments");
help!(db,
"consider giving it an explicit bounded or 'static \
lifetime");
} else if elided_len == 1 {
help!(db,
"this function's return type contains a borrowed value, but \
the signature does not say which {} it is borrowed from",
m);
} else {
help!(db,
"this function's return type contains a borrowed value, but \
the signature does not say whether it is borrowed from {}",
m);
}
}
fn check_lifetime_defs(&mut self, old_scope: ScopeRef, lifetimes: &[hir::LifetimeDef]) {
for i in 0..lifetimes.len() {
let lifetime_i = &lifetimes[i];
@ -729,7 +1210,8 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
loop {
match *old_scope {
Scope::Body { s, .. } => {
Scope::Body { s, .. } |
Scope::Elision { s, .. } => {
old_scope = s;
}