diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index c6bbeda36e930..996e049676a8c 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -791,10 +791,18 @@ impl<'a, 'b> MacroExpander<'a, 'b> { ) ) => { - rustc_parse::fake_token_stream_for_item(&self.cx.sess.psess, item_inner) + rustc_parse::fake_token_stream_for_item( + &self.cx.sess.psess, + item_inner, + Some(&attr), + ) } Annotatable::Item(item_inner) if item_inner.tokens.is_none() => { - rustc_parse::fake_token_stream_for_item(&self.cx.sess.psess, item_inner) + rustc_parse::fake_token_stream_for_item( + &self.cx.sess.psess, + item_inner, + None, + ) } // When a function has EII implementations attached (via `eii_impls`), // use fake tokens so the pretty-printer re-emits the EII attribute @@ -806,7 +814,11 @@ impl<'a, 'b> MacroExpander<'a, 'b> { if matches!(&item_inner.kind, ItemKind::Fn(f) if !f.eii_impls.is_empty()) => { - rustc_parse::fake_token_stream_for_item(&self.cx.sess.psess, item_inner) + rustc_parse::fake_token_stream_for_item( + &self.cx.sess.psess, + item_inner, + None, + ) } Annotatable::ForeignItem(item_inner) if item_inner.tokens.is_none() => { rustc_parse::fake_token_stream_for_foreign_item( diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 7805994b441b8..e151fa7096ec4 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use rustc_ast as ast; use rustc_ast::token; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast_pretty::pprust; use rustc_errors::{Diag, EmissionGuarantee, FatalError, PResult, pluralize}; pub use rustc_lexer::UNICODE_VERSION; @@ -251,12 +251,97 @@ pub fn parse_in<'a, T>( Ok(result) } -pub fn fake_token_stream_for_item(psess: &ParseSess, item: &ast::Item) -> TokenStream { +pub fn fake_token_stream_for_item( + psess: &ParseSess, + item: &ast::Item, + attr_to_exclude: Option<&ast::Attribute>, +) -> TokenStream { + if let Some(tokens) = fake_token_stream_for_file_mod(psess, item, attr_to_exclude) { + return tokens; + } + let source = pprust::item_to_string(item); let filename = FileName::macro_expansion_source_code(&source); unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span))) } +fn fake_token_stream_for_file_mod( + psess: &ParseSess, + item: &ast::Item, + attr_to_exclude: Option<&ast::Attribute>, +) -> Option { + let ast::ItemKind::Mod(safety, ident, ast::ModKind::Loaded(_, ast::Inline::No { .. }, spans)) = + &item.kind + else { + return None; + }; + + let attrs_hi = item + .attrs + .iter() + .filter(|attr| attr.style == ast::AttrStyle::Inner) + .map(|attr| attr.span.hi()) + .chain( + attr_to_exclude + .filter(|attr| attr.style == ast::AttrStyle::Inner) + .map(|attr| attr.span.hi()), + ) + .max() + .unwrap_or(spans.inject_use_span.lo()); + let body_span = spans.inner_span.with_lo(attrs_hi); + let body_src = psess.source_map().span_to_snippet(body_span).ok()?; + let body_stream = unwrap_or_emit_fatal(lexer::lex_token_trees( + psess, + &body_src, + body_span.lo(), + None, + StripTokens::Nothing, + )); + + let mut body_tts = Vec::new(); + for attr in item.attrs.iter().filter(|attr| attr.style == ast::AttrStyle::Inner) { + body_tts.extend(attr.token_trees()); + } + body_tts.extend(body_stream.iter().cloned()); + + // Synthesize only the `mod name { ... }` wrapper. The body tokens come from + // the loaded file so diagnostics inside the module keep their real spans. + let wrapper = ast::Item { + attrs: item + .attrs + .iter() + .filter(|attr| attr.style == ast::AttrStyle::Outer) + .cloned() + .collect(), + id: item.id, + span: item.span, + vis: item.vis.clone(), + kind: ast::ItemKind::Mod( + *safety, + *ident, + ast::ModKind::Loaded(Default::default(), ast::Inline::Yes, ast::ModSpans::default()), + ), + tokens: None, + }; + + let source = pprust::item_to_string(&wrapper); + let filename = FileName::macro_expansion_source_code(&source); + let wrapper_stream = + unwrap_or_emit_fatal(source_str_to_stream(psess, filename, source, Some(item.span))); + let mut wrapper_tts: Vec<_> = wrapper_stream.iter().cloned().collect(); + + let Some(TokenTree::Delimited(_, _, token::Delimiter::Brace, stream)) = wrapper_tts + .iter_mut() + .rev() + .find(|tt| matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _))) + else { + return None; + }; + *stream = TokenStream::new(body_tts); + + Some(TokenStream::new(wrapper_tts)) +} + pub fn fake_token_stream_for_foreign_item( psess: &ParseSess, item: &ast::ForeignItem, diff --git a/tests/ui/proc-macro/auxiliary/inner_attr_file_mod_diagnostics_child.rs b/tests/ui/proc-macro/auxiliary/inner_attr_file_mod_diagnostics_child.rs new file mode 100644 index 0000000000000..39e4dbcb92a7d --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/inner_attr_file_mod_diagnostics_child.rs @@ -0,0 +1,5 @@ +#![test_macros::identity_attr] + +pub fn f() { + g() +} diff --git a/tests/ui/proc-macro/inner-attr-file-mod-diagnostics.rs b/tests/ui/proc-macro/inner-attr-file-mod-diagnostics.rs new file mode 100644 index 0000000000000..243bcb74f3915 --- /dev/null +++ b/tests/ui/proc-macro/inner-attr-file-mod-diagnostics.rs @@ -0,0 +1,14 @@ +//@ proc-macro: test-macros.rs +//@ edition:2024 + +#![feature(custom_inner_attributes)] +#![feature(proc_macro_hygiene)] + +extern crate test_macros; + +#[path = "auxiliary/inner_attr_file_mod_diagnostics_child.rs"] +pub mod child; + +fn main() {} + +//~? ERROR cannot find function `g` in this scope diff --git a/tests/ui/proc-macro/inner-attr-file-mod-diagnostics.stderr b/tests/ui/proc-macro/inner-attr-file-mod-diagnostics.stderr new file mode 100644 index 0000000000000..a000d802ca925 --- /dev/null +++ b/tests/ui/proc-macro/inner-attr-file-mod-diagnostics.stderr @@ -0,0 +1,9 @@ +error[E0425]: cannot find function `g` in this scope + --> $DIR/auxiliary/inner_attr_file_mod_diagnostics_child.rs:4:5 + | +LL | g() + | ^ not found in this scope + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/proc-macro/inner-attr-file-mod.rs b/tests/ui/proc-macro/inner-attr-file-mod.rs index 535341957ade5..98ef30b0f97b0 100644 --- a/tests/ui/proc-macro/inner-attr-file-mod.rs +++ b/tests/ui/proc-macro/inner-attr-file-mod.rs @@ -9,9 +9,9 @@ extern crate test_macros; #[deny(unused_attributes)] mod module_with_attrs; -//~^ ERROR custom inner attributes are unstable fn main() {} +//~? ERROR custom inner attributes are unstable //~? ERROR custom inner attributes are unstable //~? ERROR inner macro attributes are unstable diff --git a/tests/ui/proc-macro/inner-attr-file-mod.stderr b/tests/ui/proc-macro/inner-attr-file-mod.stderr index a377313fc519e..e1493f64e5746 100644 --- a/tests/ui/proc-macro/inner-attr-file-mod.stderr +++ b/tests/ui/proc-macro/inner-attr-file-mod.stderr @@ -19,14 +19,15 @@ LL | #![print_attr] = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: custom inner attributes are unstable - --> $DIR/inner-attr-file-mod.rs:11:1 + --> $DIR/module_with_attrs.rs:3:4 | -LL | mod module_with_attrs; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | #![rustfmt::skip] + | ^^^^^^^^^^^^^ | = note: see issue #54726 for more information = help: add `#![feature(custom_inner_attributes)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: aborting due to 3 previous errors diff --git a/tests/ui/proc-macro/inner-attr-file-mod.stdout b/tests/ui/proc-macro/inner-attr-file-mod.stdout index 6f893875657b0..a2dfa05ee7551 100644 --- a/tests/ui/proc-macro/inner-attr-file-mod.stdout +++ b/tests/ui/proc-macro/inner-attr-file-mod.stdout @@ -40,36 +40,36 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [ Punct { ch: '#', spacing: Joint, - span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0), + span: $DIR/module_with_attrs.rs:3:1: 3:2 (#0), }, Punct { ch: '!', spacing: Alone, - span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0), + span: $DIR/module_with_attrs.rs:3:2: 3:3 (#0), }, Group { delimiter: Bracket, stream: TokenStream [ Ident { ident: "rustfmt", - span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0), + span: $DIR/module_with_attrs.rs:3:4: 3:11 (#0), }, Punct { ch: ':', spacing: Joint, - span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0), + span: $DIR/module_with_attrs.rs:3:11: 3:12 (#0), }, Punct { ch: ':', spacing: Alone, - span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0), + span: $DIR/module_with_attrs.rs:3:12: 3:13 (#0), }, Ident { ident: "skip", - span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0), + span: $DIR/module_with_attrs.rs:3:13: 3:17 (#0), }, ], - span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0), + span: $DIR/module_with_attrs.rs:3:3: 3:18 (#0), }, ], span: $DIR/inner-attr-file-mod.rs:11:1: 11:23 (#0),