diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index 7771bc9b1cb..640e650207e 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -1850,6 +1850,8 @@ impl<'a> Resolver<'a> { } else { ident.span.modern() } + } else { + ident = ident.modern_and_legacy(); } // Walk backwards up the ribs in scope. @@ -1987,7 +1989,7 @@ impl<'a> Resolver<'a> { // When resolving `$crate` from a `macro_rules!` invoked in a `macro`, // we don't want to pretend that the `macro_rules!` definition is in the `macro` // as described in `SyntaxContext::apply_mark`, so we ignore prepended modern marks. - ctxt.marks().into_iter().find(|&mark| mark.transparency() != Transparency::Opaque) + ctxt.marks().into_iter().rev().find(|m| m.transparency() != Transparency::Transparent) } else { ctxt = ctxt.modern(); ctxt.adjust(Mark::root()) @@ -2628,6 +2630,7 @@ impl<'a> Resolver<'a> { // must not add it if it's in the bindings map // because that breaks the assumptions later // passes make about or-patterns.) + let ident = ident.modern_and_legacy(); let mut def = Def::Local(pat_id); match bindings.get(&ident).cloned() { Some(id) if id == outer_pat_id => { @@ -3782,7 +3785,8 @@ impl<'a> Resolver<'a> { self.unused_labels.insert(id, label.ident.span); let def = Def::Label(id); self.with_label_rib(|this| { - this.label_ribs.last_mut().unwrap().bindings.insert(label.ident, def); + let ident = label.ident.modern_and_legacy(); + this.label_ribs.last_mut().unwrap().bindings.insert(ident, def); f(this); }); } else { @@ -3813,7 +3817,10 @@ impl<'a> Resolver<'a> { } ExprKind::Break(Some(label), _) | ExprKind::Continue(Some(label)) => { - match self.search_label(label.ident, |rib, id| rib.bindings.get(&id).cloned()) { + let def = self.search_label(label.ident, |rib, ident| { + rib.bindings.get(&ident.modern_and_legacy()).cloned() + }); + match def { None => { // Search again for close matches... // Picks the first label that is "close enough", which is not necessarily diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index c4a20bea685..0523765ea18 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -332,7 +332,9 @@ impl<'a> base::Resolver for Resolver<'a> { self.unused_macros.remove(&def_id); let ext = self.get_macro(def); if ext.is_modern() { - invoc.expansion_data.mark.set_transparency(Transparency::Opaque); + let transparency = + if ext.is_transparent() { Transparency::Transparent } else { Transparency::Opaque }; + invoc.expansion_data.mark.set_transparency(transparency); } else if def_id.krate == BUILTIN_MACROS_CRATE { invoc.expansion_data.mark.set_is_builtin(true); } diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 78fa3f326d6..e2424de4d14 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -649,6 +649,7 @@ pub enum SyntaxExtension { DeclMacro { expander: Box, def_info: Option<(ast::NodeId, Span)>, + is_transparent: bool, edition: Edition, } } @@ -682,6 +683,13 @@ impl SyntaxExtension { } } + pub fn is_transparent(&self) -> bool { + match *self { + SyntaxExtension::DeclMacro { is_transparent, .. } => is_transparent, + _ => false, + } + } + pub fn edition(&self) -> Edition { match *self { SyntaxExtension::NormalTT { edition, .. } | diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 38fa92f2c93..e364e145593 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -738,7 +738,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { }; let opt_expanded = match *ext { - DeclMacro { ref expander, def_info, edition } => { + DeclMacro { ref expander, def_info, edition, .. } => { if let Err(dummy_span) = validate_and_set_expn_info(self, def_info.map(|(_, s)| s), false, false, false, None, edition) { diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs index 0c81a68e999..70fc9dada42 100644 --- a/src/libsyntax/ext/tt/macro_rules.rs +++ b/src/libsyntax/ext/tt/macro_rules.rs @@ -312,9 +312,12 @@ pub fn compile(sess: &ParseSess, features: &Features, def: &ast::Item, edition: edition, } } else { + let is_transparent = attr::contains_name(&def.attrs, "rustc_transparent_macro"); + SyntaxExtension::DeclMacro { expander, def_info: Some((def.id, def.span)), + is_transparent, edition, } } diff --git a/src/libsyntax_pos/hygiene.rs b/src/libsyntax_pos/hygiene.rs index 99d8b1b172d..e7f1f31084a 100644 --- a/src/libsyntax_pos/hygiene.rs +++ b/src/libsyntax_pos/hygiene.rs @@ -33,14 +33,17 @@ pub struct SyntaxContext(pub(super) u32); pub struct SyntaxContextData { pub outer_mark: Mark, pub prev_ctxt: SyntaxContext, - pub modern: SyntaxContext, + // This context, but with all transparent and semi-transparent marks filtered away. + pub opaque: SyntaxContext, + // This context, but with all transparent marks filtered away. + pub opaque_and_semitransparent: SyntaxContext, } /// A mark is a unique id associated with a macro expansion. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)] pub struct Mark(u32); -#[derive(Debug)] +#[derive(Clone, Debug)] struct MarkData { parent: Mark, transparency: Transparency, @@ -50,7 +53,7 @@ struct MarkData { /// A property of a macro expansion that determines how identifiers /// produced by that expansion are resolved. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)] pub enum Transparency { /// Identifier produced by a transparent expansion is always resolved at call-site. /// Call-site spans in procedural macros, hygiene opt-out in `macro` should use this. @@ -69,16 +72,26 @@ pub enum Transparency { } impl Mark { + fn fresh_with_data(mark_data: MarkData, data: &mut HygieneData) -> Self { + data.marks.push(mark_data); + Mark(data.marks.len() as u32 - 1) + } + pub fn fresh(parent: Mark) -> Self { HygieneData::with(|data| { - data.marks.push(MarkData { + Mark::fresh_with_data(MarkData { parent, // By default expansions behave like `macro_rules`. transparency: Transparency::SemiTransparent, is_builtin: false, expn_info: None, - }); - Mark(data.marks.len() as u32 - 1) + }, data) + }) + } + + pub fn fresh_cloned(clone_from: Mark) -> Self { + HygieneData::with(|data| { + Mark::fresh_with_data(data.marks[clone_from.0 as usize].clone(), data) }) } @@ -207,7 +220,8 @@ impl HygieneData { syntax_contexts: vec![SyntaxContextData { outer_mark: Mark::root(), prev_ctxt: SyntaxContext(0), - modern: SyntaxContext(0), + opaque: SyntaxContext(0), + opaque_and_semitransparent: SyntaxContext(0), }], markings: HashMap::new(), default_edition: Edition::Edition2015, @@ -239,7 +253,7 @@ impl SyntaxContext { // Allocate a new SyntaxContext with the given ExpnInfo. This is used when // deserializing Spans from the incr. comp. cache. // FIXME(mw): This method does not restore MarkData::parent or - // SyntaxContextData::prev_ctxt or SyntaxContextData::modern. These things + // SyntaxContextData::prev_ctxt or SyntaxContextData::opaque. These things // don't seem to be used after HIR lowering, so everything should be fine // as long as incremental compilation does not kick in before that. pub fn allocate_directly(expansion_info: ExpnInfo) -> Self { @@ -256,7 +270,8 @@ impl SyntaxContext { data.syntax_contexts.push(SyntaxContextData { outer_mark: mark, prev_ctxt: SyntaxContext::empty(), - modern: SyntaxContext::empty(), + opaque: SyntaxContext::empty(), + opaque_and_semitransparent: SyntaxContext::empty(), }); SyntaxContext(data.syntax_contexts.len() as u32 - 1) }) @@ -269,7 +284,13 @@ impl SyntaxContext { } let call_site_ctxt = - mark.expn_info().map_or(SyntaxContext::empty(), |info| info.call_site.ctxt()).modern(); + mark.expn_info().map_or(SyntaxContext::empty(), |info| info.call_site.ctxt()); + let call_site_ctxt = if mark.transparency() == Transparency::SemiTransparent { + call_site_ctxt.modern() + } else { + call_site_ctxt.modern_and_legacy() + }; + if call_site_ctxt == SyntaxContext::empty() { return self.apply_mark_internal(mark); } @@ -293,26 +314,53 @@ impl SyntaxContext { fn apply_mark_internal(self, mark: Mark) -> SyntaxContext { HygieneData::with(|data| { let syntax_contexts = &mut data.syntax_contexts; - let mut modern = syntax_contexts[self.0 as usize].modern; - if data.marks[mark.0 as usize].transparency == Transparency::Opaque { - modern = *data.markings.entry((modern, mark)).or_insert_with(|| { - let len = syntax_contexts.len() as u32; + let transparency = data.marks[mark.0 as usize].transparency; + + let mut opaque = syntax_contexts[self.0 as usize].opaque; + let mut opaque_and_semitransparent = + syntax_contexts[self.0 as usize].opaque_and_semitransparent; + + if transparency >= Transparency::Opaque { + let prev_ctxt = opaque; + opaque = *data.markings.entry((prev_ctxt, mark)).or_insert_with(|| { + let new_opaque = SyntaxContext(syntax_contexts.len() as u32); syntax_contexts.push(SyntaxContextData { outer_mark: mark, - prev_ctxt: modern, - modern: SyntaxContext(len), + prev_ctxt, + opaque: new_opaque, + opaque_and_semitransparent: new_opaque, }); - SyntaxContext(len) + new_opaque }); } - *data.markings.entry((self, mark)).or_insert_with(|| { + if transparency >= Transparency::SemiTransparent { + let prev_ctxt = opaque_and_semitransparent; + opaque_and_semitransparent = + *data.markings.entry((prev_ctxt, mark)).or_insert_with(|| { + let new_opaque_and_semitransparent = + SyntaxContext(syntax_contexts.len() as u32); + syntax_contexts.push(SyntaxContextData { + outer_mark: mark, + prev_ctxt, + opaque, + opaque_and_semitransparent: new_opaque_and_semitransparent, + }); + new_opaque_and_semitransparent + }); + } + + let prev_ctxt = self; + *data.markings.entry((prev_ctxt, mark)).or_insert_with(|| { + let new_opaque_and_semitransparent_and_transparent = + SyntaxContext(syntax_contexts.len() as u32); syntax_contexts.push(SyntaxContextData { outer_mark: mark, - prev_ctxt: self, - modern, + prev_ctxt, + opaque, + opaque_and_semitransparent, }); - SyntaxContext(syntax_contexts.len() as u32 - 1) + new_opaque_and_semitransparent_and_transparent }) }) } @@ -452,7 +500,12 @@ impl SyntaxContext { #[inline] pub fn modern(self) -> SyntaxContext { - HygieneData::with(|data| data.syntax_contexts[self.0 as usize].modern) + HygieneData::with(|data| data.syntax_contexts[self.0 as usize].opaque) + } + + #[inline] + pub fn modern_and_legacy(self) -> SyntaxContext { + HygieneData::with(|data| data.syntax_contexts[self.0 as usize].opaque_and_semitransparent) } #[inline] diff --git a/src/libsyntax_pos/lib.rs b/src/libsyntax_pos/lib.rs index 55dec31511c..308fb118f07 100644 --- a/src/libsyntax_pos/lib.rs +++ b/src/libsyntax_pos/lib.rs @@ -491,6 +491,12 @@ impl Span { let span = self.data(); span.with_ctxt(span.ctxt.modern()) } + + #[inline] + pub fn modern_and_legacy(self) -> Span { + let span = self.data(); + span.with_ctxt(span.ctxt.modern_and_legacy()) + } } #[derive(Clone, Debug)] diff --git a/src/libsyntax_pos/symbol.rs b/src/libsyntax_pos/symbol.rs index bb64dad1208..fe0b479d161 100644 --- a/src/libsyntax_pos/symbol.rs +++ b/src/libsyntax_pos/symbol.rs @@ -68,6 +68,15 @@ impl Ident { Ident::new(self.name, self.span.modern()) } + // "Normalize" ident for use in comparisons using "local variable hygiene". + // Identifiers with same string value become same if they came from the same non-transparent + // macro (e.g. `macro` or `macro_rules!` items) and stay different if they came from different + // non-transparent macros. + // Technically, this operation strips all transparent marks from ident's syntactic context. + pub fn modern_and_legacy(self) -> Ident { + Ident::new(self.name, self.span.modern_and_legacy()) + } + pub fn gensym(self) -> Ident { Ident::new(self.name.gensymed(), self.span) } diff --git a/src/test/ui/hygiene/auxiliary/intercrate.rs b/src/test/ui/hygiene/auxiliary/intercrate.rs index aa67e5c5f4d..244a9903e31 100644 --- a/src/test/ui/hygiene/auxiliary/intercrate.rs +++ b/src/test/ui/hygiene/auxiliary/intercrate.rs @@ -19,3 +19,9 @@ pub mod foo { } } } + +pub struct SomeType; + +pub macro uses_dollar_crate() { + type Alias = $crate::SomeType; +} diff --git a/src/test/ui/hygiene/auxiliary/transparent-basic.rs b/src/test/ui/hygiene/auxiliary/transparent-basic.rs new file mode 100644 index 00000000000..ba65c5f4da8 --- /dev/null +++ b/src/test/ui/hygiene/auxiliary/transparent-basic.rs @@ -0,0 +1,16 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(decl_macro, rustc_attrs)] + +#[rustc_transparent_macro] +pub macro dollar_crate() { + let s = $crate::S; +} diff --git a/src/test/ui/hygiene/dollar-crate-modern.rs b/src/test/ui/hygiene/dollar-crate-modern.rs new file mode 100644 index 00000000000..f4b24d0c5b4 --- /dev/null +++ b/src/test/ui/hygiene/dollar-crate-modern.rs @@ -0,0 +1,22 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Make sure `$crate` works in `macro` macros. + +// compile-pass +// aux-build:intercrate.rs + +#![feature(use_extern_macros)] + +extern crate intercrate; + +intercrate::uses_dollar_crate!(); + +fn main() {} diff --git a/src/test/ui/hygiene/generate-mod.rs b/src/test/ui/hygiene/generate-mod.rs new file mode 100644 index 00000000000..90409857dea --- /dev/null +++ b/src/test/ui/hygiene/generate-mod.rs @@ -0,0 +1,24 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// This is an equivalent of issue #50504, but for declarative macros. + +#![feature(decl_macro, rustc_attrs)] + +#[rustc_transparent_macro] +macro genmod() { + mod m { + type A = S; //~ ERROR cannot find type `S` in this scope + } +} + +struct S; + +genmod!(); diff --git a/src/test/ui/hygiene/generate-mod.stderr b/src/test/ui/hygiene/generate-mod.stderr new file mode 100644 index 00000000000..e79f8528c2c --- /dev/null +++ b/src/test/ui/hygiene/generate-mod.stderr @@ -0,0 +1,17 @@ +error[E0412]: cannot find type `S` in this scope + --> $DIR/generate-mod.rs:18:18 + | +LL | type A = S; //~ ERROR cannot find type `S` in this scope + | ^ did you mean `A`? +... +LL | genmod!(); + | ---------- in this macro invocation + +error[E0601]: `main` function not found in crate `generate_mod` + | + = note: consider adding a `main` function to `$DIR/generate-mod.rs` + +error: aborting due to 2 previous errors + +Some errors occurred: E0412, E0601. +For more information about an error, try `rustc --explain E0412`. diff --git a/src/test/ui/hygiene/transparent-basic.rs b/src/test/ui/hygiene/transparent-basic.rs new file mode 100644 index 00000000000..81ece1f11bc --- /dev/null +++ b/src/test/ui/hygiene/transparent-basic.rs @@ -0,0 +1,53 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-pass +// aux-build:transparent-basic.rs + +#![feature(decl_macro, rustc_attrs)] + +extern crate transparent_basic; + +#[rustc_transparent_macro] +macro binding() { + let x = 10; +} + +#[rustc_transparent_macro] +macro label() { + break 'label +} + +macro_rules! legacy { + () => { + binding!(); + let y = x; + } +} + +fn legacy_interaction1() { + legacy!(); +} + +struct S; + +fn check_dollar_crate() { + // `$crate::S` inside the macro resolves to `S` from this crate. + transparent_basic::dollar_crate!(); +} + +fn main() { + binding!(); + let y = x; + + 'label: loop { + label!(); + } +}