Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Ident> = None;
let mut body: Option<Group> = 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<Ident> = 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<TokenTree> = 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<TokenTree> = 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()
}
71 changes: 71 additions & 0 deletions tests/run-make/incr-unstable-fingerprint-def-ident-span/rmake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//@ ignore-cross-compile
//@ needs-crate-type: proc-macro

// Regression test for <https://github.com/rust-lang/rust/issues/95945>.
// 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", &macros_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", &macros_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}`");
}
Loading