Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve HRTB error span when -Zno-leak-check is used #63678

Merged
merged 4 commits into from
Oct 3, 2019

Commits on Oct 1, 2019

  1. Improve HRTB error span when -Zno-leak-check is used

    As described in rust-lang#57374, NLL currently produces unhelpful higher-ranked
    trait bound (HRTB) errors when '-Zno-leak-check' is enabled.
    
    This PR tackles one half of this issue - making the error message point
    at the proper span. The error message itself is still the very generic
    "higher-ranked subtype error", but this can be improved in a follow-up
    PR.
    
    The root cause of the bad spans lies in how NLL attempts to compute the
    'blamed' region, for which it will retrieve a span for.
    Consider the following code, which (correctly) does not compile:
    
    ```rust
    let my_val: u8 = 25;
    let a: &u8 = &my_val;
    let b = a;
    let c = b;
    let d: &'static u8 = c;
    ```
    
    This will cause NLL to generate the following subtype constraints:
    
    d :< c
    c :< b
    b <: a
    
    Since normal Rust lifetimes are covariant, this results in the following
    region constraints (I'm using 'd to denote the lifetime of 'd',
    'c to denote the lifetime of 'c, etc.):
    
    'c: 'd
    'b: 'c
    'a: 'b
    
    From this, we can derive that 'a: 'd holds, which implies that 'a: 'static
    must hold. However, this is not the case, since 'a refers to 'my_val',
    which does not outlive the current function.
    
    When NLL attempts to infer regions for this code, it will see that the
    region 'a has grown 'too large' - it will be inferred to outlive
    'static, despite the fact that is not declared as outliving 'static
    We can find the region responsible, 'd, by starting at the *end* of
    the 'constraint chain' we generated above. This works because for normal
    (non-higher-ranked) lifetimes, we generally build up a 'chain' of
    lifetime constraints *away* from the original variable/lifetime.
    That is, our original lifetime 'a is required to outlive progressively
    more regions. If it ends up living for too long, we can look at the
    'end' of this chain to determine the 'most recent' usage that caused
    the lifetime to grow too large.
    
    However, this logic does not work correctly when higher-ranked trait
    bounds (HRTBs) come into play. This is because HRTBs have
    *contravariance* with respect to their bound regions. For example,
    this code snippet compiles:
    
    ```rust
    let a: for<'a> fn(&'a ()) = |_| {};
    let b: fn(&'static ()) = a;
    ```
    
    Here, we require that 'a' is a subtype of 'b'. Because of
    contravariance, we end up with the region constraint 'static: 'a,
    *not* 'a: 'static
    
    This means that our 'constraint chains' grow in the opposite direction
    of 'normal lifetime' constraint chains. As we introduce subtypes, our
    lifetime ends up being outlived by other lifetimes, rather than
    outliving other lifetimes. Therefore, starting at the end of the
    'constraint chain' will cause us to 'blame' a lifetime close to the original
    definition of a variable, instead of close to where the bad lifetime
    constraint is introduced.
    
    This PR improves how we select the region to blame for 'too large'
    universal lifetimes, when bound lifetimes are involved. If the region
    we're checking is a 'placeholder' region (e.g. the region 'a' in
    for<'a>, or the implicit region in fn(&())), we start traversing the
    constraint chain from the beginning, rather than the end.
    
    There are two (maybe more) different ways we generate region constraints for NLL:
    requirements generated from trait queries, and requirements generated
    from MIR subtype constraints. While the former always use explicit
    placeholder regions, the latter is more tricky. In order to implement
    contravariance for HRTBs, TypeRelating replaces placeholder regions with
    existential regions. This requires us to keep track of whether or not an
    existential region was originally a placeholder region. When we look for
    a region to blame, we check if our starting region is either a
    placeholder region or is an existential region created from a
    placeholder region. If so, we start iterating from the beginning of the
    constraint chain, rather than the end.
    Aaron1011 committed Oct 1, 2019
    Configuration menu
    Copy the full SHA
    1245467 View commit details
    Browse the repository at this point in the history

Commits on Oct 2, 2019

  1. Fixup tests

    Aaron1011 committed Oct 2, 2019
    Configuration menu
    Copy the full SHA
    1caa6dc View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    9c5a5c4 View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    ba54ef8 View commit details
    Browse the repository at this point in the history