Lifetimes-and they lived happily ever after

The story of designing E0623, the error case where both the regions corresponding to a function or trait are anonymous.

Previously, the aim was to infer the variable with the missing lifetime, given a named and anonymous region. Now, we look at cases where both the regions are anonymous. Let’s look at a relevant code sample.

struct Foo<'a> { x:  &'a u8 }
fn foo(x: &mut Vec<Foo>, y: Foo) {
       ---      --- these structs must have the same lifetime
   x.push(y);
   ^^^^^^^^^
}

Notice that Vec<Foo> and Foo both do not have lifetime declarations.

After considering various output messages, we finalized only on one.

error[E0621]: lifetime mismatch
--> ../../my_repos/rust/self1.rs:14:12
|
12 |   fn foo(x: &mut Vec<&u8>, y:&u8) {
|                      ---     --- these references must have the same lifetime
13 |
14 |     x.push(y);
|            ^ data from `y` flows into `x` here

How do we go on with the code for this?

  1. find both the anonymous regions of concern(Vec<&u8> and &u8). The underlines for x must be for Vec<&u8> and not for &u8 .
  2. find the 2 arguments, corresponding to the among which the dataflow occurs.
  3. generates wording of the error message.

Some of the errors made by me

The challenge here was to extract the inner type which is Vec<Foo> instead of &mut Vec<Foo> . To find this region, you need to walk the type.

Attempt 1

We walk the fndecl arguments, look for a LateBoundAnon region and find the corresponding argument using the below snippet. The index corresponds to the BrAnon region.

if debuijn_index.depth ==1 && anon_index == index{
Some(&**arg)}

We shall discuss debruijn indices in more detail later.

fndecl.inputs.iter().filter_map(
|arg| match arg.node {
hir::TyRptr(ref lifetime, _) => {
match self.tcx.named_region_map.defs.get(&lifetime.id) {
Some(&rl::Region::LateBoundAnon(debuijn_index, anon_index))
match *br{
ty::BrAnon(index) => {
if debuijn_index.depth ==1 && anon_index == index{
Some(&**arg)}
else{
None}}
_=> None,
}

}

This ended up finding &mut Vec<Foo> instead of Vec<Foo> .

Attempt 2

Using TypeFolders.

hir::TyRptr(ref lifetime, _) => {

match self.tcx.named_region_map.defs.get(&lifetime.id) {
Some(&rl::Region::LateBoundAnon(debuijn_index, anon_index)) => {

if debuijn_index.depth ==1 && anon_index == br_index {
let mut found_anon_region = false;
if let anon_type = self.tcx.fold_regions(&**arg, &mut false, |r, _|
if r == region { found_anon_region = true; r } else { r })
if found_anon_region{Some(&anon_type)}else{None}
}

}

fold_regions() only handles the internal type Ty<'tcx> but what we need is hir::Ty .

Attempt 3

And it worked!!!

Since we need the hir::Ty, we decided to go ahead with using a HIR Visitor.

struct FindNestedTypeVisitor<'a, 'gcx: 'a + 'tcx, 'tcx: 'a> {
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
hir_map: &'a hir::map::Map<'gcx>,
bound_region: ty::BoundRegion,
found_type: Option<&'gcx hir::Ty>,
}

The visit_ty function is overridden as follows

let br_index = match self.bound_region {
ty::BrAnon(index) => index,
_ => return,
};

match arg.node {
hir::TyRptr(ref lifetime, _) => {
match self.infcx.tcx.named_region_map.defs.get(&lifetime.id) {
// the lifetime of the TyRptr!
Some(&rl::Region::LateBoundAnon(debuijn_index, anon_index)) => {
if debuijn_index.depth == 1 && anon_index == br_index {
self.found_type = Some(arg);
return; // we can stop visiting now
}
}
Some(&rl::Region::Static) |
Some(&rl::Region::EarlyBound(_, _)) |
Some(&rl::Region::LateBound(_, _)) |
Some(&rl::Region::Free(_, _)) |
None => {
debug!("no arg found");
}
}
}
_ => {}
}
// walk the embedded contents: e.g., if we are visiting `Vec<&Foo>`,
// go on to visit `&Foo`
intravisit::walk_ty(self, arg);
}

So now that we have our hands on both the anonymous regions, the next step is a cakewalk :P. More on this in the later articles. Thanks for reading.

Show your support

Clapping shows how much you appreciated GeekyTwoShoes11’s story.