diff --git a/tests/run-make/incr-unstable-fingerprint-def-ident-span/macros/lib.rs b/tests/run-make/incr-unstable-fingerprint-def-ident-span/macros/lib.rs new file mode 100644 index 0000000000000..5eee82170edb1 --- /dev/null +++ b/tests/run-make/incr-unstable-fingerprint-def-ident-span/macros/lib.rs @@ -0,0 +1,73 @@ +extern crate proc_macro; + +use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; + +// Re-emits each enum variant's identifier as an associated constant, reusing the +// original `Ident` tokens so the generated items keep the variants' spans. +#[proc_macro_derive(Bar)] +pub fn derive_bar(input: TokenStream) -> TokenStream { + let mut it = input.into_iter(); + let mut name: Option = None; + let mut body: Option = None; + + while let Some(tt) = it.next() { + match tt { + TokenTree::Ident(id) => { + if id.to_string() == "enum" { + if let Some(TokenTree::Ident(n)) = it.next() { + name = Some(n); + } + } + } + TokenTree::Group(g) => { + if g.delimiter() == Delimiter::Brace { + body = Some(g); + break; + } + } + _ => {} + } + } + + let name = name.expect("enum name"); + let body = body.expect("enum body"); + + // Collect variant idents (skip commas / discriminants). + let mut variants: Vec = Vec::new(); + let mut expect_ident = true; + for tt in body.stream() { + match tt { + TokenTree::Ident(id) => { + if expect_ident { + variants.push(id); + expect_ident = false; + } + } + TokenTree::Punct(p) => { + if p.as_char() == ',' { + expect_ident = true; + } + } + _ => {} + } + } + + // Build: impl Name { const V: () = (); ... } + let mut inner: Vec = Vec::new(); + for v in variants { + inner.push(TokenTree::Ident(Ident::new("const", Span::call_site()))); + inner.push(TokenTree::Ident(v)); // original ident + span + inner.push(TokenTree::Punct(Punct::new(':', Spacing::Alone))); + inner.push(TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new()))); + inner.push(TokenTree::Punct(Punct::new('=', Spacing::Alone))); + inner.push(TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::new()))); + inner.push(TokenTree::Punct(Punct::new(';', Spacing::Alone))); + } + + let mut out: Vec = Vec::new(); + out.push(TokenTree::Ident(Ident::new("impl", Span::call_site()))); + out.push(TokenTree::Ident(name)); + out.push(TokenTree::Group(Group::new(Delimiter::Brace, inner.into_iter().collect()))); + + out.into_iter().collect() +} diff --git a/tests/run-make/incr-unstable-fingerprint-def-ident-span/rmake.rs b/tests/run-make/incr-unstable-fingerprint-def-ident-span/rmake.rs new file mode 100644 index 0000000000000..8be1525ad2991 --- /dev/null +++ b/tests/run-make/incr-unstable-fingerprint-def-ident-span/rmake.rs @@ -0,0 +1,71 @@ +//@ ignore-cross-compile +//@ needs-crate-type: proc-macro + +// Regression test for . +// Recompiling incrementally after inserting an enum variant before an existing one used +// to ICE with "Found unstable fingerprints for def_ident_span". The derive re-emits the +// variant identifiers as associated constants, so their spans move while the surrounding +// generated tokens keep call-site hygiene. +// +// This cannot use the `revisions` system: the `#[cfg]`-based revisions keep both versions +// of the text in the file, so the identifier spans never move. The source has to actually +// be rewritten between the two compilations. + +use std::fs; +use std::path::PathBuf; + +use run_make_support::{rfs, rustc}; + +fn main() { + rustc().input("macros/lib.rs").crate_name("macros").crate_type("proc-macro").run(); + let macros_dylib = find_proc_macro_dylib("macros"); + + rfs::write("lib.rs", "#[derive(macros::Bar)]\npub enum FooEnum { One }\n"); + rustc() + .input("lib.rs") + .crate_type("lib") + .incremental("incr") + .arg("-Zincremental-verify-ich") + .extern_("macros", ¯os_dylib) + .run(); + + // Insert a variant *before* the existing one, moving `One`'s span. + rfs::write("lib.rs", "#[derive(macros::Bar)]\npub enum FooEnum { Zero, One }\n"); + let out = rustc() + .input("lib.rs") + .crate_type("lib") + .incremental("incr") + .arg("-Zincremental-verify-ich") + .extern_("macros", ¯os_dylib) + .run(); + + out.assert_stderr_not_contains("internal compiler error"); + out.assert_stderr_not_contains("Found unstable fingerprints"); +} + +fn find_proc_macro_dylib(name: &str) -> PathBuf { + let prefix = if cfg!(target_os = "windows") { "" } else { "lib" }; + + let ext: &str = if cfg!(target_os = "macos") { + "dylib" + } else if cfg!(target_os = "windows") { + "dll" + } else if cfg!(target_os = "aix") { + "a" + } else { + "so" + }; + + let lib_name = format!("{prefix}{name}.{ext}"); + + for entry in fs::read_dir(".").unwrap() { + let entry = entry.unwrap(); + let name = entry.file_name(); + let name = name.to_str().unwrap(); + if name == lib_name { + return entry.path(); + } + } + + panic!("could not find proc-macro dylib for `{name}`"); +}