Skip to content

NLL should identify and respect the lifetime annotations that the user wrote #47184

Closed

Description

@shepmaster

We have made some progress on this issue.

Here's a list of everywhere within function bodies where we annotate types, with check boxes on the ones that are currently getting checked by the MIR-borrowck / NLL.

(We should update this list as we think of more.)

Original bug report follows:


This code compiles with 1.24.0-nightly (2018-01-03 0a3761e):

#![feature(nll)]

fn main() {
    let vec: Vec<&'static String> = vec![&String::new()];

    // fn only_static(_: Vec<&'static String>) {}
    // only_static(vec);
}

This is confusing as the Vec almost certainly cannot contain a reference of a 'static lifetime. If you uncomment the call to only_static, you get the expected error that the String does not live long enough.

@nikomatsakis said:

NLL currently ignores hand-written type annotations, in particular, those that appear in local variables.

Activity

  1. added
    A-NLLArea: Non-lexical lifetimes (NLL)
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Jan 4, 2018
  2. added this to the milestone on Jan 4, 2018
  3. added
    E-hardCall for participation: Hard difficulty. Experience needed to fix: A lot.
    on Jan 10, 2018
  4. nikomatsakis commented on Jan 10, 2018

    @nikomatsakis
    Contributor

    I'm tagging this with E-hard because @pnkfelix and I established that as the "have to figure out how to do this" convention. That said, I don't know that it's that hard. My rough plan was something like this:

    • Extend HAIR/MIR with some sort of extra operations, that assert equality between types.
      • The region renumbering code would know that the types in such operations come from the user, and should not be renumbered.
      • Example: ASSERT_HAS_TY<T>(_1) would assert that the variable _1 has the given type T
      • We also have to handle foo::<T>() style annotations though
    • The HIR -> HAIR lowering would insert these operations when there are explicit Ty nodes
    • The HAIR -> MIR would produce ASSERT.
    • Similar strategy could be used for casts and things.

    However, it might also be best to have some other way to indicate which types are from user -- for example, maybe instead of a Ty, MIR could embed a newtype that also carries a boolean whether it was inferred or explicit.

    But I sort of prefer to have the "explicit annotations" come from somewhere else, seems easier overall to me.

  5. modified the milestones: , on Jan 19, 2018
  6. nikomatsakis commented on Feb 1, 2018

    @nikomatsakis
    Contributor

    cc @Aaron1011 -- btw, since you're on a roll lately, this would be a good one to get done at some point. It'll require a bit of experimentation though. =) Happy to discuss it sometime.

  7. self-assigned this
    on Feb 8, 2018
  8. davidtwco commented on Feb 8, 2018

    @davidtwco
    Member

    I'll tackle this one.

  9. nikomatsakis commented on Feb 21, 2018

    @nikomatsakis
    Contributor

    @davidtwco ok, so I think my prior directions were the right path still. It probably makes sense to tackle different kinds of annotations independently. Let's start with the most basic sort of annotation, that placed on local variables:

    let x: T = ...

    To handle this, I think we want to add a new form of MIR statement, a type assertion. That would be done by editing the StorageKind enum:

    pub enum StatementKind<'tcx> {

    I would call it something like UserAssertTy. It might look like:

    /// Encodes a user's type assertion. These need to be preserved
    /// intact so that NLL can respect them. For example:
    ///
    ///     let (a, b): (T, U) = y;
    ///
    /// Here, we would insert a `UserAssertTy<(T, U)>(y)` instruction to check
    /// that the type of `y` is the right thing.
    UserAssertTy(Ty<'tcx>, Local),
    

    We would then want to special-case these statements in the renumberer, so that it skips the embedded type within:

    fn visit_statement(
    &mut self,
    block: BasicBlock,
    statement: &mut Statement<'tcx>,
    location: Location,
    ) {
    if let StatementKind::EndRegion(_) = statement.kind {
    statement.kind = StatementKind::Nop;
    }
    self.super_statement(block, statement, location);
    }

    But actually I realize there is a bit more complexity here than I initially considered. For example:

    let x: &u32 = ...;

    needs to be distinguished from

    let x: &'static u32 = ...;

    The current code doesn't have give you a good way to do that. But let's ignore that complication for a moment -- I have some ideas there for later =). For now, let's just test it with &'static u32 =)

    Anyway, once we have this MIR, we also need to generate it. This could presumably mean instrumenting the HAIR, which is the intermediate IR that we use to transition from HIR to MIR.

    I think we would want to extend the Let variant in HAIR to include the type annotation (if any):

    Let {
    /// scope for variables bound in this let; covers this and
    /// remaining statements in block
    remainder_scope: region::Scope,
    /// scope for the initialization itself; might be used as
    /// lifetime of temporaries
    init_scope: region::Scope,
    /// let <PAT> = ...
    pattern: Pattern<'tcx>,
    /// let pat = <INIT> ...
    initializer: Option<ExprRef<'tcx>>,
    /// the lint level for this let-statement
    lint_level: LintLevel,
    },

    These are converted to MIR here:

    StmtKind::Let {
    remainder_scope,
    init_scope,
    pattern,
    initializer,
    lint_level
    } => {

    Hmm. As you can see from the comment, I initially thought we would apply the type to the initializer -- this would probably mean threading the user's type to expr_into_pattern here:

    this.expr_into_pattern(block, pattern, init)

    So it can add the appropriate assertions. But I guess that wouldn't cover a case like

    let (a, b): (T, U);
    

    Maybe we want to do things slightly differently. Mostly I wanted to avoid having the need to "match up" the patterns on the LHS (e.g., the (_, _) pattern) with the type on the RHS, which sounds annoying.

  10. nikomatsakis commented on Feb 21, 2018

    @nikomatsakis
    Contributor

    Oh, I neglected to add that we will want to kill those new statements, presumably through a pass roughly like clean_end_regions. In fact, I think I would want to generalize that existing pass to something like cleanup_post_borrowck.

  11. davidtwco commented on Feb 23, 2018

    @davidtwco
    Member

    @nikomatsakis I've submitted a PR with what I've got so far: #48482.

  12. 51 remaining items

  13. added this to the Rust 2018 RC milestone on Sep 11, 2018
  14. added a commit that references this issue on Sep 11, 2018
  15. nikomatsakis commented on Sep 18, 2018

    @nikomatsakis
    Contributor

    Spun out to sub-issues.

  16. added
    metabugIssues about issues themselves ("bugs about bugs")
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    and removed
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Oct 2, 2018
  17. self-assigned this
    on Oct 2, 2018
  18. nikomatsakis commented on Oct 18, 2018

    @nikomatsakis
    Contributor

    Demoting to Rust 2018 release. However, we should go ahead and convert all remaining items to sub-issues, I think, and close this

  19. added a commit that references this issue on Oct 27, 2018
  20. nikomatsakis commented on Oct 30, 2018

    @nikomatsakis
    Contributor

    I'm going to close this bug now -- we're down to just a handful of "subbullets", each of which has their own tracking issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-NLLArea: Non-lexical lifetimes (NLL)C-enhancementCategory: An issue proposing an enhancement or a PR with one.E-hardCall for participation: Hard difficulty. Experience needed to fix: A lot.NLL-soundWorking towards the "invalid code does not compile" goalT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.metabugIssues about issues themselves ("bugs about bugs")

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions