I am looking to do some analysis of how certain functions cause dependencies between variable lifetimes.
For example, with Vector<String>,
let x_0: Vec<String> = vec![ "A".to_string(), "B".to_string()];
let x_1: Option<&mut String> = x_0.get_mut(0);
let x_2: &mut String = x_1.unwrap();
We know that x_0 must outlive x_1 and x_2 because the reference is to a memory location owned by the vector x_0.
However, this is not clear by looking at the type signature of Vec::get_mut. There are no obvious lifetime annotations.
pub fn get_mut<I>(
&mut self,
index: I
) -> Option<&mut <I as SliceIndex<[T]>>::Output>
where
I: SliceIndex<[T]>,
First of all, since rustc is already doing this, is there a way to extract that data for each function? If not, what is the easiest way to model this externally?
Please note that I ask for lifetime information on each function and not entire programs because the end goal for my project is to automatically assemble unit tests, thus I don't have the entire program to start out with. Lifetime information for each function in needed for semantically accurate assembly.
Have you thought about using the compiler as a library to gain access to its lifetime analysis code?
It shouldn't matter that your input is incomplete because the compiler is designed to keep analysing in the face of errors (e.g. a function at the bottom of your file can be type checked even if there's a type error somewhere higher up). You just need to make sure it doesn't bail out early like the CLI program normally would.
To help figure out how to access the information you care about, the rustc API docs are super useful. Here's the rough path I'd follow to start analysing lifetimes.
rustc_driver is the compiler's entrypoint. Pass a Callbacks to rustc_driver::run_compiler() to start the compilation process and inject your own logic
The after_analysis() callback will give you the compilation state after type-checking and lifetime resolution is done
We want to reach the type system context, which we can find by enter()ing the Queries::global_ctxt() passed to our callback
TyCtxt::fn_sig_by_hir_id() gives you a function's signature, and you can access the inputs via its decl
You can unlock the rustc_* crates with #![feature(rustc_private)].
If I was working on an automated tool I'd want to use the compiler instead of implementing it myself. That way you avoid re-implementing a lot of rustc's infrastructure and have access to a wealth of existing code that already uses the rustc internals (rustc itself).
That's an interesting idea. I forgot about rustic_driver, but that is an option.
Although our codebase is written in Java for historical reasons. We are planning to fully migrate to Rust some time this year, so we should be able to try it soon.