Skip to content

#[feature(nll)] produces unintelligible error message in corner case with invariance #67007

Closed
@Aaron1011

Description

@Aaron1011

While working on a diagnostics improvement for the compiler, I came across an essentially unreadable error. The following (minimized) code:

#![feature(nll)] 

// Covariant over 'a, invariant over 'tcx
struct FnCtxt<'a, 'tcx: 'a>(&'a (), *mut &'tcx ());

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
    fn use_it(&self, _: &'tcx ()) {}
}

struct Consumer<'tcx>(&'tcx ());

impl<'tcx> Consumer<'tcx> {
    fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
        let other = self.use_fcx(fcx);
        fcx.use_it(other);
    }
    
    fn use_fcx<'a>(&self, _: &FnCtxt<'a, 'tcx>) -> &'a () {
        &()
    }
}

produces this error:

error[E0521]: borrowed data escapes outside of function
  --> src/lib.rs:14:21
   |
13 |     fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
   |                       -----  --- `fcx` is a reference that is only valid in the function body
   |                       |
   |                       `self` is declared here, outside of the function body
14 |         let other = self.use_fcx(fcx);
   |                     ^^^^^^^^^^^^^^^^^ `fcx` escapes the function body here
   |
   = help: consider adding the following bound: `'a: 'tcx`

There are several problems with this error message:

  1. The span is very unhelpful. Commenting out the call to fcx.use_it(other); causes this code to compile (I think due to the fact that the lifetime of other is no longer required to outlive tcx). However, the error message points to a completely different line, which is not really the source of the problem.
  2. The 'escapes outside of function' terminology is very misleading. In general, there's nothing wrong with passing a reference to another function. The problem is that that by passing fcx to use_fcx, we (indirectly) end up constraining a.
  3. The only place where the error message refers to any lifetimes is in the suggestion to add 'a: 'tcx. Without reading the hint, you might not even realize that the named lifetimes are involved in any way.
  4. The error depends on the invariance of the 'tcx lifetime (caused by the raw pointer, which is invariant over the pointee type). I discovered this through blind luck, as I've seen variance-related errors before. If someone has never even heard of variance, it would be virtually impossible for them to discover this - the invariance of a lifetime may be caused by the lifetime being passed to a deeply nested invariant type.

Removing #![feature(nll)] makes the error marginally better:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter 'a in function call due to conflicting requirements
  --> src/lib.rs:14:26
   |
14 |         let other = self.use_fcx(fcx);
   |                          ^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the method body at 13:19...
  --> src/lib.rs:13:19
   |
13 |     fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
   |                   ^^
note: ...but the lifetime must also be valid for the lifetime `'tcx` as defined on the impl at 12:6...
  --> src/lib.rs:12:6
   |
12 | impl<'tcx> Consumer<'tcx> {
   |      ^^^^
   = note: ...so that the expression is assignable:
           expected &FnCtxt<'_, '_>
              found &FnCtxt<'a, 'tcx>

Here, we at least know that 'a and 'tcx are involved. However, the span is still bad - there's no indication that the call to fcx.use_it(other) is involved at all. The note 'so that the expression is assignable' also lacks a span entirely, making it difficult to tell what it actually means.

For some reason, removing the 'tcx: 'a requirement from the FnCtxt definition (e.g. changing it to just 'tcx) results in a better span with #![feature(nll)]:

error[E0521]: borrowed data escapes outside of function
  --> src/lib.rs:15:9
   |
13 |     fn bad_method<'a>(&self, fcx: &FnCtxt<'a, 'tcx>) {
   |                       -----  --- `fcx` is a reference that is only valid in the function body
   |                       |
   |                       `self` is declared here, outside of the function body
14 |         let other = self.use_fcx(fcx);
15 |         fcx.use_it(other);
   |         ^^^^^^^^^^^^^^^^^ `fcx` escapes the function body here
   |
   = help: consider adding the following bound: `'a: 'tcx`

error: aborting due to previous error

but the error is unchanged when #![feature(nll)] is not enabled.

cc #62953

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-NLLArea: Non-lexical lifetimes (NLL)A-diagnosticsArea: Messages for errors, warnings, and lintsC-enhancementCategory: An issue proposing an enhancement or a PR with one.D-confusingDiagnostics: Confusing error or lint that should be reworked.NLL-diagnosticsWorking towards the "diagnostic parity" goalT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions