Preserve SyntaxContext for invalid/dummy spans in crate metadata

Fixes #85197

We already preserved the `SyntaxContext` for invalid/dummy spans in the
incremental cache, but we weren't doing the same for crate metadata.
If an invalid (lo/hi from different files) span is written to the
incremental cache, we will decode it with a 'dummy' location, but keep
the original `SyntaxContext`. Since the crate metadata encoder was only
checking for `DUMMY_SP` (dummy location + root `SyntaxContext`),
the metadata encoder would treat it as a normal span, encoding the
`SyntaxContext`. As a result, the final span encoded to the metadata
would change across sessions, even if the crate itself was unchanged.

This PR updates our encoding of spans in the crate metadata to mirror
the encoding of spans into the incremental cache. We now always encode a
`SyntaxContext`, and encode location information for spans with a
non-dummy location.
This commit is contained in:
Aaron Hill 2021-05-12 00:10:41 -04:00
parent 70e52caed9
commit cdca3c81c1
No known key found for this signature in database
GPG key ID: B4087E510E98B164
7 changed files with 113 additions and 45 deletions

View file

@ -0,0 +1,11 @@
// revisions: rpass1 rpass2
extern crate respan;
#[macro_use]
#[path = "invalid-span-helper-mod.rs"]
mod invalid_span_helper_mod;
// Invoke a macro from a different file - this
// allows us to get tokens with spans from different files
helper!(1);

View file

@ -0,0 +1,14 @@
#[macro_export]
macro_rules! helper {
// Use `:tt` instead of `:ident` so that we don't get a `None`-delimited group
($first:tt) => {
pub fn foo<T>() {
// The span of `$first` comes from another file,
// so the expression `1 + $first` ends up with an
// 'invalid' span that starts and ends in different files.
// We use the `respan!` macro to give all tokens the same
// `SyntaxContext`, so that the parser will try to merge the spans.
respan::respan!(let a = 1 + $first;);
}
}
}

View file

@ -0,0 +1,19 @@
// force-host
// no-prefer-dynamic
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;
/// Copies the resolution information (the `SyntaxContext`) of the first
/// token to all other tokens in the stream. Does not recurse into groups.
#[proc_macro]
pub fn respan(input: TokenStream) -> TokenStream {
let first_span = input.clone().into_iter().next().unwrap().span();
input.into_iter().map(|mut tree| {
tree.set_span(tree.span().resolved_at(first_span));
tree
}).collect()
}

View file

@ -0,0 +1,24 @@
// revisions: rpass1 rpass2
// aux-build:respan.rs
// aux-build:invalid-span-helper-lib.rs
// This issue has several different parts. The high level idea is:
// 1. We create an 'invalid' span with the help of the `respan` proc-macro,
// The compiler attempts to prevent the creation of invalid spans by
// refusing to join spans with different `SyntaxContext`s. We work around
// this by applying the same `SyntaxContext` to the span of every token,
// using `Span::resolved_at`
// 2. We using this invalid span in the body of a function, causing it to get
// encoded into the `optimized_mir`
// 3. We call the function from a different crate - since the function is generic,
// monomorphization runs, causing `optimized_mir` to get called.
// 4. We re-run compilation using our populated incremental cache, but without
// making any changes. When we recompile the crate containing our generic function
// (`invalid_span_helper_lib`), we load the span from the incremental cache, and
// write it into the crate metadata.
extern crate invalid_span_helper_lib;
fn main() {
invalid_span_helper_lib::foo::<u8>();
}