diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs index 3921bd30df25c..49116f3f171e2 100644 --- a/src/librustc_resolve/build_reduced_graph.rs +++ b/src/librustc_resolve/build_reduced_graph.rs @@ -758,7 +758,10 @@ impl<'a> Resolver<'a> { } pub fn macro_def_scope(&mut self, expansion: Mark) -> Module<'a> { - let def_id = self.macro_defs[&expansion]; + let def_id = match self.macro_defs.get(&expansion) { + Some(def_id) => *def_id, + None => return self.graph_root, + }; if let Some(id) = self.definitions.as_local_node_id(def_id) { self.local_macro_def_scopes[&id] } else if def_id.krate == CrateNum::BuiltinMacros { diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index f26c3b8ae6afc..8f23c9813f7d9 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -17,12 +17,11 @@ use syntax::errors::DiagnosticBuilder; use syntax::ext::base::{self, Determinacy}; use syntax::ext::base::{MacroKind, SyntaxExtension}; use syntax::ext::expand::{AstFragment, Invocation, InvocationKind}; -use syntax::ext::hygiene::Mark; +use syntax::ext::hygiene::{self, Mark}; use syntax::ext::tt::macro_rules; use syntax::feature_gate::{feature_err, emit_feature_err, is_builtin_attr_name}; use syntax::feature_gate::{AttributeGate, GateIssue, Stability, BUILTIN_ATTRIBUTES}; use syntax::symbol::{Symbol, kw, sym}; -use syntax::visit::Visitor; use syntax::util::lev_distance::find_best_match_for_name; use syntax_pos::{Span, DUMMY_SP}; use errors::Applicability; @@ -146,24 +145,14 @@ impl<'a> base::Resolver for Resolver<'a> { mark } - fn resolve_dollar_crates(&mut self, fragment: &AstFragment) { - struct ResolveDollarCrates<'a, 'b> { - resolver: &'a mut Resolver<'b> - } - impl<'a> Visitor<'a> for ResolveDollarCrates<'a, '_> { - fn visit_ident(&mut self, ident: Ident) { - if ident.name == kw::DollarCrate { - let name = match self.resolver.resolve_crate_root(ident).kind { - ModuleKind::Def(.., name) if name != kw::Invalid => name, - _ => kw::Crate, - }; - ident.span.ctxt().set_dollar_crate_name(name); - } + fn resolve_dollar_crates(&mut self) { + hygiene::update_dollar_crate_names(|ctxt| { + let ident = Ident::new(kw::DollarCrate, DUMMY_SP.with_ctxt(ctxt)); + match self.resolve_crate_root(ident).kind { + ModuleKind::Def(.., name) if name != kw::Invalid => name, + _ => kw::Crate, } - fn visit_mac(&mut self, _: &ast::Mac) {} - } - - fragment.visit_with(&mut ResolveDollarCrates { resolver: self }); + }); } fn visit_ast_fragment_with_placeholders(&mut self, mark: Mark, fragment: &AstFragment, diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 15c0b6ca5aa81..c0ba41b8af405 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -701,7 +701,7 @@ pub trait Resolver { fn get_module_scope(&mut self, id: ast::NodeId) -> Mark; - fn resolve_dollar_crates(&mut self, fragment: &AstFragment); + fn resolve_dollar_crates(&mut self); fn visit_ast_fragment_with_placeholders(&mut self, mark: Mark, fragment: &AstFragment, derives: &[Mark]); fn add_builtin(&mut self, ident: ast::Ident, ext: Lrc); diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 74ef5cbe9177e..053686b8b1f27 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -429,7 +429,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { fn collect_invocations(&mut self, mut fragment: AstFragment, derives: &[Mark]) -> (AstFragment, Vec) { // Resolve `$crate`s in the fragment for pretty-printing. - self.cx.resolver.resolve_dollar_crates(&fragment); + self.cx.resolver.resolve_dollar_crates(); let invocations = { let mut collector = InvocationCollector { diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs index 67646cce69b4f..7e099bc4d5095 100644 --- a/src/libsyntax/print/pprust.rs +++ b/src/libsyntax/print/pprust.rs @@ -18,7 +18,7 @@ use crate::tokenstream::{self, TokenStream, TokenTree}; use rustc_target::spec::abi::{self, Abi}; use syntax_pos::{self, BytePos}; -use syntax_pos::{DUMMY_SP, FileName}; +use syntax_pos::{DUMMY_SP, FileName, Span}; use std::borrow::Cow; use std::io::Read; @@ -181,7 +181,46 @@ pub fn literal_to_string(lit: token::Lit) -> String { out } +fn ident_to_string(ident: ast::Ident, is_raw: bool) -> String { + ident_to_string_ext(ident.name, is_raw, Some(ident.span)) +} + +// AST pretty-printer is used as a fallback for turning AST structures into token streams for +// proc macros. Additionally, proc macros may stringify their input and expect it survive the +// stringification (especially true for proc macro derives written between Rust 1.15 and 1.30). +// So we need to somehow pretty-print `$crate` in a way preserving at least some of its +// hygiene data, most importantly name of the crate it refers to. +// As a result we print `$crate` as `crate` if it refers to the local crate +// and as `::other_crate_name` if it refers to some other crate. +// Note, that this is only done if the ident token is printed from inside of AST pretty-pringing, +// but not otherwise. Pretty-printing is the only way for proc macros to discover token contents, +// so we should not perform this lossy conversion if the top level call to the pretty-printer was +// done for a token stream or a single token. +fn ident_to_string_ext( + name: ast::Name, is_raw: bool, convert_dollar_crate: Option +) -> String { + if is_raw { + format!("r#{}", name) + } else { + if name == kw::DollarCrate { + if let Some(span) = convert_dollar_crate { + let converted = span.ctxt().dollar_crate_name(); + return if converted.is_path_segment_keyword() { + converted.to_string() + } else { + format!("::{}", converted) + } + } + } + name.to_string() + } +} + pub fn token_kind_to_string(tok: &TokenKind) -> String { + token_kind_to_string_ext(tok, None) +} + +fn token_kind_to_string_ext(tok: &TokenKind, convert_dollar_crate: Option) -> String { match *tok { token::Eq => "=".to_string(), token::Lt => "<".to_string(), @@ -227,8 +266,7 @@ pub fn token_kind_to_string(tok: &TokenKind) -> String { token::Literal(lit) => literal_to_string(lit), /* Name components */ - token::Ident(s, false) => s.to_string(), - token::Ident(s, true) => format!("r#{}", s), + token::Ident(s, is_raw) => ident_to_string_ext(s, is_raw, convert_dollar_crate), token::Lifetime(s) => s.to_string(), /* Other */ @@ -243,7 +281,12 @@ pub fn token_kind_to_string(tok: &TokenKind) -> String { } pub fn token_to_string(token: &Token) -> String { - token_kind_to_string(&token.kind) + token_to_string_ext(token, false) +} + +fn token_to_string_ext(token: &Token, convert_dollar_crate: bool) -> String { + let convert_dollar_crate = if convert_dollar_crate { Some(token.span) } else { None }; + token_kind_to_string_ext(&token.kind, convert_dollar_crate) } crate fn nonterminal_to_string(nt: &Nonterminal) -> String { @@ -256,9 +299,8 @@ crate fn nonterminal_to_string(nt: &Nonterminal) -> String { token::NtBlock(ref e) => block_to_string(e), token::NtStmt(ref e) => stmt_to_string(e), token::NtPat(ref e) => pat_to_string(e), - token::NtIdent(e, false) => ident_to_string(e), - token::NtIdent(e, true) => format!("r#{}", ident_to_string(e)), - token::NtLifetime(e) => ident_to_string(e), + token::NtIdent(e, is_raw) => ident_to_string(e, is_raw), + token::NtLifetime(e) => e.to_string(), token::NtLiteral(ref e) => expr_to_string(e), token::NtTT(ref tree) => tt_to_string(tree.clone()), token::NtImplItem(ref e) => impl_item_to_string(e), @@ -293,15 +335,15 @@ pub fn lifetime_to_string(lt: &ast::Lifetime) -> String { } pub fn tt_to_string(tt: tokenstream::TokenTree) -> String { - to_string(|s| s.print_tt(tt)) + to_string(|s| s.print_tt(tt, false)) } pub fn tts_to_string(tts: &[tokenstream::TokenTree]) -> String { - to_string(|s| s.print_tts(tts.iter().cloned().collect())) + tokens_to_string(tts.iter().cloned().collect()) } pub fn tokens_to_string(tokens: TokenStream) -> String { - to_string(|s| s.print_tts(tokens)) + to_string(|s| s.print_tts_ext(tokens, false)) } pub fn stmt_to_string(stmt: &ast::Stmt) -> String { @@ -344,10 +386,6 @@ pub fn path_segment_to_string(p: &ast::PathSegment) -> String { to_string(|s| s.print_path_segment(p, false)) } -pub fn ident_to_string(id: ast::Ident) -> String { - to_string(|s| s.print_ident(id)) -} - pub fn vis_to_string(v: &ast::Visibility) -> String { to_string(|s| s.print_visibility(v)) } @@ -629,11 +667,7 @@ pub trait PrintState<'a> { self.writer().word("::"); } if segment.ident.name != kw::PathRoot { - if segment.ident.name == kw::DollarCrate { - self.print_dollar_crate(segment.ident); - } else { - self.writer().word(segment.ident.as_str().to_string()); - } + self.writer().word(ident_to_string(segment.ident, segment.ident.is_raw_guess())); } } } @@ -707,10 +741,10 @@ pub trait PrintState<'a> { /// appropriate macro, transcribe back into the grammar we just parsed from, /// and then pretty-print the resulting AST nodes (so, e.g., we print /// expression arguments as expressions). It can be done! I think. - fn print_tt(&mut self, tt: tokenstream::TokenTree) { + fn print_tt(&mut self, tt: tokenstream::TokenTree, convert_dollar_crate: bool) { match tt { TokenTree::Token(ref token) => { - self.writer().word(token_to_string(&token)); + self.writer().word(token_to_string_ext(&token, convert_dollar_crate)); match token.kind { token::DocComment(..) => { self.writer().hardbreak() @@ -729,12 +763,16 @@ pub trait PrintState<'a> { } fn print_tts(&mut self, tts: tokenstream::TokenStream) { + self.print_tts_ext(tts, true) + } + + fn print_tts_ext(&mut self, tts: tokenstream::TokenStream, convert_dollar_crate: bool) { self.ibox(0); for (i, tt) in tts.into_trees().enumerate() { if i != 0 { self.writer().space(); } - self.print_tt(tt); + self.print_tt(tt, convert_dollar_crate); } self.end(); } @@ -744,21 +782,6 @@ pub trait PrintState<'a> { } fn nbsp(&mut self) { self.writer().word(" ") } - - // AST pretty-printer is used as a fallback for turning AST structures into token streams for - // proc macros. Additionally, proc macros may stringify their input and expect it survive the - // stringification (especially true for proc macro derives written between Rust 1.15 and 1.30). - // So we need to somehow pretty-print `$crate` in paths in a way preserving at least some of - // its hygiene data, most importantly name of the crate it refers to. - // As a result we print `$crate` as `crate` if it refers to the local crate - // and as `::other_crate_name` if it refers to some other crate. - fn print_dollar_crate(&mut self, ident: ast::Ident) { - let name = ident.span.ctxt().dollar_crate_name(); - if !ast::Ident::with_empty_ctxt(name).is_path_segment_keyword() { - self.writer().word("::"); - } - self.writer().word(name.as_str().to_string()) - } } impl<'a> PrintState<'a> for State<'a> { @@ -2287,11 +2310,7 @@ impl<'a> State<'a> { } crate fn print_ident(&mut self, ident: ast::Ident) { - if ident.is_raw_guess() { - self.s.word(format!("r#{}", ident)); - } else { - self.s.word(ident.as_str().to_string()); - } + self.s.word(ident_to_string(ident, ident.is_raw_guess())); self.ann.post(self, AnnNode::Ident(&ident)) } @@ -2322,11 +2341,7 @@ impl<'a> State<'a> { segment: &ast::PathSegment, colons_before_params: bool) { if segment.ident.name != kw::PathRoot { - if segment.ident.name == kw::DollarCrate { - self.print_dollar_crate(segment.ident); - } else { - self.print_ident(segment.ident); - } + self.print_ident(segment.ident); if let Some(ref args) = segment.args { self.print_generic_args(args, colons_before_params); } diff --git a/src/libsyntax_pos/hygiene.rs b/src/libsyntax_pos/hygiene.rs index a6c8c76cf23f7..f52952ca40274 100644 --- a/src/libsyntax_pos/hygiene.rs +++ b/src/libsyntax_pos/hygiene.rs @@ -33,7 +33,7 @@ use crate::symbol::{kw, Symbol}; use serialize::{Encodable, Decodable, Encoder, Decoder}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::sync::Lrc; -use std::{fmt, mem}; +use std::fmt; /// A SyntaxContext represents a chain of macro expansions (represented by marks). #[derive(Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash)] @@ -387,6 +387,23 @@ pub fn walk_chain(span: Span, to: SyntaxContext) -> Span { HygieneData::with(|data| data.walk_chain(span, to)) } +pub fn update_dollar_crate_names(mut get_name: impl FnMut(SyntaxContext) -> Symbol) { + // The new contexts that need updating are at the end of the list and have `$crate` as a name. + let (len, to_update) = HygieneData::with(|data| ( + data.syntax_contexts.len(), + data.syntax_contexts.iter().rev() + .take_while(|scdata| scdata.dollar_crate_name == kw::DollarCrate).count() + )); + // The callback must be called from outside of the `HygieneData` lock, + // since it will try to acquire it too. + let range_to_update = len - to_update .. len; + let names: Vec<_> = + range_to_update.clone().map(|idx| get_name(SyntaxContext::from_u32(idx as u32))).collect(); + HygieneData::with(|data| range_to_update.zip(names.into_iter()).for_each(|(idx, name)| { + data.syntax_contexts[idx].dollar_crate_name = name; + })) +} + impl SyntaxContext { #[inline] pub const fn empty() -> Self { @@ -614,17 +631,6 @@ impl SyntaxContext { pub fn dollar_crate_name(self) -> Symbol { HygieneData::with(|data| data.syntax_contexts[self.0 as usize].dollar_crate_name) } - - pub fn set_dollar_crate_name(self, dollar_crate_name: Symbol) { - HygieneData::with(|data| { - let prev_dollar_crate_name = mem::replace( - &mut data.syntax_contexts[self.0 as usize].dollar_crate_name, dollar_crate_name - ); - assert!(dollar_crate_name == prev_dollar_crate_name || - prev_dollar_crate_name == kw::DollarCrate, - "$crate name is reset for a syntax context"); - }) - } } impl fmt::Debug for SyntaxContext { diff --git a/src/test/ui/proc-macro/auxiliary/dollar-crate-external.rs b/src/test/ui/proc-macro/auxiliary/dollar-crate-external.rs index d5d393b5a6457..bdcdb7922ca57 100644 --- a/src/test/ui/proc-macro/auxiliary/dollar-crate-external.rs +++ b/src/test/ui/proc-macro/auxiliary/dollar-crate-external.rs @@ -14,3 +14,9 @@ macro_rules! external { struct D($crate::S); }; } + +#[macro_export] +macro_rules! issue_62325 { () => { + #[print_attr] + struct B(identity!($crate::S)); +}} diff --git a/src/test/ui/proc-macro/dollar-crate-issue-57089.stdout b/src/test/ui/proc-macro/dollar-crate-issue-57089.stdout index 0611fcb13f267..84821259d7b9b 100644 --- a/src/test/ui/proc-macro/dollar-crate-issue-57089.stdout +++ b/src/test/ui/proc-macro/dollar-crate-issue-57089.stdout @@ -1,4 +1,4 @@ -PRINT-BANG INPUT (DISPLAY): struct M ( $crate :: S ) ; +PRINT-BANG INPUT (DISPLAY): struct M ( crate :: S ) ; PRINT-BANG INPUT (DEBUG): TokenStream [ Ident { ident: "struct", @@ -39,7 +39,7 @@ PRINT-BANG INPUT (DEBUG): TokenStream [ }, ] PRINT-ATTR INPUT (DISPLAY): struct A(crate::S); -PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ( $crate :: S ) ; +PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ( crate :: S ) ; PRINT-ATTR INPUT (DEBUG): TokenStream [ Ident { ident: "struct", diff --git a/src/test/ui/proc-macro/dollar-crate-issue-62325.rs b/src/test/ui/proc-macro/dollar-crate-issue-62325.rs new file mode 100644 index 0000000000000..b7b152e669213 --- /dev/null +++ b/src/test/ui/proc-macro/dollar-crate-issue-62325.rs @@ -0,0 +1,27 @@ +// check-pass +// edition:2018 +// aux-build:test-macros.rs +// aux-build:dollar-crate-external.rs + +// Anonymize unstable non-dummy spans while still showing dummy spans `0..0`. +// normalize-stdout-test "bytes\([^0]\w*\.\.(\w+)\)" -> "bytes(LO..$1)" +// normalize-stdout-test "bytes\((\w+)\.\.[^0]\w*\)" -> "bytes($1..HI)" + +#![feature(proc_macro_hygiene)] + +#[macro_use] +extern crate test_macros; +extern crate dollar_crate_external; + +type S = u8; + +macro_rules! m { () => { + #[print_attr] + struct A(identity!($crate::S)); +}} + +m!(); + +dollar_crate_external::issue_62325!(); + +fn main() {} diff --git a/src/test/ui/proc-macro/dollar-crate-issue-62325.stdout b/src/test/ui/proc-macro/dollar-crate-issue-62325.stdout new file mode 100644 index 0000000000000..17ad1a7af7046 --- /dev/null +++ b/src/test/ui/proc-macro/dollar-crate-issue-62325.stdout @@ -0,0 +1,112 @@ +PRINT-ATTR INPUT (DISPLAY): struct A(identity!(crate :: S)); +PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ( identity ! ( crate :: S ) ) ; +PRINT-ATTR INPUT (DEBUG): TokenStream [ + Ident { + ident: "struct", + span: #2 bytes(LO..HI), + }, + Ident { + ident: "A", + span: #2 bytes(LO..HI), + }, + Group { + delimiter: Parenthesis, + stream: TokenStream [ + Ident { + ident: "identity", + span: #2 bytes(LO..HI), + }, + Punct { + ch: '!', + spacing: Alone, + span: #2 bytes(LO..HI), + }, + Group { + delimiter: Parenthesis, + stream: TokenStream [ + Ident { + ident: "$crate", + span: #2 bytes(LO..HI), + }, + Punct { + ch: ':', + spacing: Joint, + span: #2 bytes(LO..HI), + }, + Punct { + ch: ':', + spacing: Alone, + span: #2 bytes(LO..HI), + }, + Ident { + ident: "S", + span: #2 bytes(LO..HI), + }, + ], + span: #2 bytes(LO..HI), + }, + ], + span: #2 bytes(LO..HI), + }, + Punct { + ch: ';', + spacing: Alone, + span: #2 bytes(LO..HI), + }, +] +PRINT-ATTR INPUT (DISPLAY): struct B(identity!(::dollar_crate_external :: S)); +PRINT-ATTR RE-COLLECTED (DISPLAY): struct B ( identity ! ( ::dollar_crate_external :: S ) ) ; +PRINT-ATTR INPUT (DEBUG): TokenStream [ + Ident { + ident: "struct", + span: #7 bytes(LO..HI), + }, + Ident { + ident: "B", + span: #7 bytes(LO..HI), + }, + Group { + delimiter: Parenthesis, + stream: TokenStream [ + Ident { + ident: "identity", + span: #7 bytes(LO..HI), + }, + Punct { + ch: '!', + spacing: Alone, + span: #7 bytes(LO..HI), + }, + Group { + delimiter: Parenthesis, + stream: TokenStream [ + Ident { + ident: "$crate", + span: #7 bytes(LO..HI), + }, + Punct { + ch: ':', + spacing: Joint, + span: #7 bytes(LO..HI), + }, + Punct { + ch: ':', + spacing: Alone, + span: #7 bytes(LO..HI), + }, + Ident { + ident: "S", + span: #7 bytes(LO..HI), + }, + ], + span: #7 bytes(LO..HI), + }, + ], + span: #7 bytes(LO..HI), + }, + Punct { + ch: ';', + spacing: Alone, + span: #7 bytes(LO..HI), + }, +] diff --git a/src/test/ui/proc-macro/dollar-crate.stdout b/src/test/ui/proc-macro/dollar-crate.stdout index 3c88ee99842a2..9d9677d49e318 100644 --- a/src/test/ui/proc-macro/dollar-crate.stdout +++ b/src/test/ui/proc-macro/dollar-crate.stdout @@ -1,4 +1,4 @@ -PRINT-BANG INPUT (DISPLAY): struct M ( $crate :: S ) ; +PRINT-BANG INPUT (DISPLAY): struct M ( crate :: S ) ; PRINT-BANG INPUT (DEBUG): TokenStream [ Ident { ident: "struct", @@ -39,7 +39,7 @@ PRINT-BANG INPUT (DEBUG): TokenStream [ }, ] PRINT-ATTR INPUT (DISPLAY): struct A(crate::S); -PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ( $crate :: S ) ; +PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ( crate :: S ) ; PRINT-ATTR INPUT (DEBUG): TokenStream [ Ident { ident: "struct", @@ -80,7 +80,7 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [ }, ] PRINT-DERIVE INPUT (DISPLAY): struct D(crate::S); -PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ( $crate :: S ) ; +PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ( crate :: S ) ; PRINT-DERIVE INPUT (DEBUG): TokenStream [ Ident { ident: "struct", @@ -120,7 +120,7 @@ PRINT-DERIVE INPUT (DEBUG): TokenStream [ span: #2 bytes(LO..HI), }, ] -PRINT-BANG INPUT (DISPLAY): struct M ( $crate :: S ) ; +PRINT-BANG INPUT (DISPLAY): struct M ( ::dollar_crate_external :: S ) ; PRINT-BANG INPUT (DEBUG): TokenStream [ Ident { ident: "struct", @@ -161,7 +161,7 @@ PRINT-BANG INPUT (DEBUG): TokenStream [ }, ] PRINT-ATTR INPUT (DISPLAY): struct A(::dollar_crate_external::S); -PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ( $crate :: S ) ; +PRINT-ATTR RE-COLLECTED (DISPLAY): struct A ( ::dollar_crate_external :: S ) ; PRINT-ATTR INPUT (DEBUG): TokenStream [ Ident { ident: "struct", @@ -202,7 +202,7 @@ PRINT-ATTR INPUT (DEBUG): TokenStream [ }, ] PRINT-DERIVE INPUT (DISPLAY): struct D(::dollar_crate_external::S); -PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ( $crate :: S ) ; +PRINT-DERIVE RE-COLLECTED (DISPLAY): struct D ( ::dollar_crate_external :: S ) ; PRINT-DERIVE INPUT (DEBUG): TokenStream [ Ident { ident: "struct",