Function register_member_constraints

Source
fn register_member_constraints<'tcx>(
    typeck: &mut TypeChecker<'_, 'tcx>,
    member_constraints: &mut MemberConstraintSet<'tcx, RegionVid>,
    opaque_type_key: OpaqueTypeKey<'tcx>,
    _: OpaqueHiddenType<'tcx>,
)
Expand description

Given the map opaque_types containing the opaque impl Trait types whose underlying, hidden types are being inferred, this method adds constraints to the regions appearing in those underlying hidden types to ensure that they at least do not refer to random scopes within the current function. These constraints are not (quite) sufficient to guarantee that the regions are actually legal values; that final condition is imposed after region inference is done.

§The Problem

Let’s work through an example to explain how it works. Assume the current function is as follows:

fn foo<'a, 'b>(..) -> (impl Bar<'a>, impl Bar<'b>)

Here, we have two impl Trait types whose values are being inferred (the impl Bar<'a> and the impl Bar<'b>). Conceptually, this is sugar for a setup where we define underlying opaque types (Foo1, Foo2) and then, in the return type of foo, we reference those definitions:

type Foo1<'x> = impl Bar<'x>;
type Foo2<'x> = impl Bar<'x>;
fn foo<'a, 'b>(..) -> (Foo1<'a>, Foo2<'b>) { .. }
                   //  ^^^^ ^^
                   //  |    |
                   //  |    args
                   //  def_id

As indicating in the comments above, each of those references is (in the compiler) basically generic parameters (args) applied to the type of a suitable def_id (which identifies Foo1 or Foo2).

Now, at this point in compilation, what we have done is to replace each of the references (Foo1<'a>, Foo2<'b>) with fresh inference variables C1 and C2. We wish to use the values of these variables to infer the underlying types of Foo1 and Foo2. That is, this gives rise to higher-order (pattern) unification constraints like:

for<'a> (Foo1<'a> = C1)
for<'b> (Foo1<'b> = C2)

For these equation to be satisfiable, the types C1 and C2 can only refer to a limited set of regions. For example, C1 can only refer to 'static and 'a, and C2 can only refer to 'static and 'b. The job of this function is to impose that constraint.

Up to this point, C1 and C2 are basically just random type inference variables, and hence they may contain arbitrary regions. In fact, it is fairly likely that they do! Consider this possible definition of foo:

fn foo<'a, 'b>(x: &'a i32, y: &'b i32) -> (impl Bar<'a>, impl Bar<'b>) {
        (&*x, &*y)
    }

Here, the values for the concrete types of the two impl traits will include inference variables:

&'0 i32
&'1 i32

Ordinarily, the subtyping rules would ensure that these are sufficiently large. But since impl Bar<'a> isn’t a specific type per se, we don’t get such constraints by default. This is where this function comes into play. It adds extra constraints to ensure that all the regions which appear in the inferred type are regions that could validly appear.

This is actually a bit of a tricky constraint in general. We want to say that each variable (e.g., '0) can only take on values that were supplied as arguments to the opaque type (e.g., 'a for Foo1<'a>) or 'static, which is always in scope. We don’t have a constraint quite of this kind in the current region checker.

§The Solution

We generally prefer to make <= constraints, since they integrate best into the region solver. To do that, we find the “minimum” of all the arguments that appear in the args: that is, some region which is less than all the others. In the case of Foo1<'a>, that would be 'a (it’s the only choice, after all). Then we apply that as a least bound to the variables (e.g., 'a <= '0).

In some cases, there is no minimum. Consider this example:

fn baz<'a, 'b>() -> impl Trait<'a, 'b> { ... }

Here we would report a more complex “in constraint”, like 'r in ['a, 'b, 'static] (where 'r is some region appearing in the hidden type).

§Constrain regions, not the hidden concrete type

Note that generating constraints on each region Rc is not the same as generating an outlives constraint on Tc itself. For example, if we had a function like this:

fn foo<'a, T>(x: &'a u32, y: T) -> impl Foo<'a> {
  (x, y)
}

// Equivalent to:
type FooReturn<'a, T> = impl Foo<'a>;
#[define_opaque(FooReturn)]
fn foo<'a, T>(x: &'a u32, y: T) -> FooReturn<'a, T> {
  (x, y)
}

then the hidden type Tc would be (&'0 u32, T) (where '0 is an inference variable). If we generated a constraint that Tc: 'a, then this would incorrectly require that T: 'a – but this is not necessary, because the opaque type we create will be allowed to reference T. So we only generate a constraint that '0: 'a.