Skip to content

Track lifetimes of type parameters as lifetime projections - #195

Merged
zgrannan merged 30 commits into
prusti:mainfrom
rgwohlbold:generalized-lifetime-default
Nov 5, 2026
Merged

zgrannan merged 30 commits into
prusti:mainfrom
rgwohlbold:generalized-lifetime-default

Conversation

@rgwohlbold

Copy link
Copy Markdown
Contributor

This PR adds tracking of lifetimes of type parameters as lifetime projections. Take the following example:

fn take<T>(v: &mut Vec<T>) -> T { ... }

fn f<T>(mut v: Vec<T>) -> T {
    take(&mut v)
}

Callers of f may substitute T with a type containing regions, e.g. &'a mut i32. We would like to track these (unknown) regions through the generalized lifetime RegionsIn(T) in the body of f.

The codebase already supports the right data structures with the GeneralizedLifetime type. This PR makes the following steps:

  • switch the (default) Kind in LifetimeProjection and a couple other structs/enums to Generalized and thread this change through the codebase
  • implement functions that decide when a GeneralizedLifetime outlives another one. This is the area where this PR deserves the most scrutiny

AI Usage

Claude Code was used to help with creating the PR. All the code has been manually reviewed.
Note that I am not very familiar with the codebase.

@rgwohlbold

Copy link
Copy Markdown
Contributor Author

Ran the top 50 crates and the selected_crates functions with validity checks on, against this branch and main. 118/136 pass on both, 13 fail on both, and nothing fails only on main. 5 fail only on this branch, all because type parameters now have lifetime projections and hit existing bugs:

@zgrannan zgrannan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good start and is mostly on track I think. Let me know if any of comments need claraification

Comment thread src/borrow_checker/impl.rs Resolved
Comment thread src/borrow_checker/mod.rs Resolved
Comment thread src/borrow_checker/mod.rs Resolved
Comment thread src/borrow_checker/mod.rs Resolved
Comment thread src/borrow_checker/mod.rs Resolved
Body lifetime projections and call shapes now index into the
generalized lifetime list, so places of opaque types get `RegionsIn(T)`
projections. Outlives and liveness checks involving `RegionsIn`
conservatively answer `true`.

Work in progress: test files 025, 041, 069, 163 and 174 fail (all pass
on main). Prusti does not build against this yet.
A constant argument has no place, so its post-call version cannot be
observed. With `RegionsIn` projections, call shapes could select such an
argument as an output and hit the `todo!()` in `expect_place`.
A borrow can only be stored in a value of type `X` if its region outlives
some region in `X`. Regions hidden inside an opaque type are chosen by the
caller and outlive the function body, so a region flows into `RegionsIn(X)`
only if it outlives some universal region, or a region written in `X`.

Previously such flows were always assumed, so e.g. the result of a call
taking `&mut v` kept `v` blocked although the borrow checker allows it.
The maps from call-site lifetimes to the callee's identity signature are
now keyed by generalized lifetimes, so `RegionsIn` at the call site is
translated like a region and decided in the signature (e.g. `RegionsIn(I)`
vs `RegionsIn(I::Item)` in `collect` becomes `RegionsIn(Self)` vs
`RegionsIn(B)`). This fixes 041.

Known regressions: 247 and 069 now fail. 247 hits the existing special
case for a returned type parameter, which also fails on main for
`take::<&'static u32>(&mut v)`.
A defined call's shape now requires both that the callee's signature
allows a flow and that the borrow checker allows it at the call site.
The signature alone has to account for every instantiation of the
callee's type parameters, so e.g. `take::<&'static i32>(&mut v)` used to
get an edge from the local `&mut` borrow into the `'static` result.

Fixes 069, 247 and the new test 248 (which also fails on main).
`introduce_initial_borrows` iterated over the regions of an argument's type
but created projections indexed by its generalized lifetimes. For an
associated type with a lifetime argument (`<T as Tr<'a>>::Out`), the region
`'a` is part of `RegionsIn(..)` and has no projection of its own, so the
lookup failed and the PCG panicked.
Mirrors `Place::regions` and replaces direct calls to
`extract_generalized_lifetimes` on a place's type.
Find the universal regions for 'static and the function body from the
borrow checker facts, and use them instead of a hardcoded vid.
Keep the signature check in `outlives` as on main to keep the diff small,
and add a test where the signature alone would allow cross edges.
@rgwohlbold
rgwohlbold force-pushed the generalized-lifetime-default branch from 6f3a30d to 2506577 Compare November 3, 2026 14:34
@rgwohlbold

Copy link
Copy Markdown
Contributor Author

I removed test-files/174_linked_hashmap_refresh.rs since it fails on this branch. Fixing #193 should fix this test

@rgwohlbold

Copy link
Copy Markdown
Contributor Author

I worked in the feedback, thank you very much! Most importantly, I changed the way outlives is handled for generalized lifetimes, hope it's correct now.
One incompleteness is documented in test #248 where we don't look inside opaque types, even if we could know what they resolve to

@rgwohlbold
rgwohlbold requested a review from zgrannan November 3, 2026 21:07
@rgwohlbold rgwohlbold changed the title Draft: Track lifetimes of type parameters as lifetime projections Track lifetimes of type parameters as lifetime projections Nov 4, 2026
Comment thread src/borrow_checker/impl.rs Resolved
Comment thread src/borrow_checker/impl.rs Resolved
Comment thread src/borrow_checker/impl.rs Resolved
Comment thread src/borrow_checker/impl.rs Resolved
Comment thread src/borrow_checker/mod.rs Resolved
Comment thread src/borrow_checker/mod.rs Resolved
@rgwohlbold
rgwohlbold requested a review from zgrannan November 5, 2026 14:34

@zgrannan zgrannan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I can merge it if you aren't able to for some reason

@rgwohlbold
rgwohlbold marked this pull request as ready for review November 5, 2026 17:02
@rgwohlbold

Copy link
Copy Markdown
Contributor Author

It doesn't seem like I can merge it

@zgrannan
zgrannan merged commit fa397ae into prusti:main Nov 5, 2026
24 checks passed
@rgwohlbold
rgwohlbold deleted the generalized-lifetime-default branch November 5, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants