Skip to content

Commit

Permalink
hygiene: Implement transparent marks
Browse files Browse the repository at this point in the history
  • Loading branch information
petrochenkov committed Jun 29, 2018
1 parent 09856c8 commit 99ecdb3
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 27 deletions.
13 changes: 10 additions & 3 deletions src/librustc_resolve/lib.rs
Expand Up @@ -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.
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/librustc_resolve/macros.rs
Expand Up @@ -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);
}
Expand Down
8 changes: 8 additions & 0 deletions src/libsyntax/ext/base.rs
Expand Up @@ -649,6 +649,7 @@ pub enum SyntaxExtension {
DeclMacro {
expander: Box<TTMacroExpander + sync::Sync + sync::Send>,
def_info: Option<(ast::NodeId, Span)>,
is_transparent: bool,
edition: Edition,
}
}
Expand Down Expand Up @@ -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, .. } |
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/ext/expand.rs
Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions src/libsyntax/ext/tt/macro_rules.rs
Expand Up @@ -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,
}
}
Expand Down
97 changes: 75 additions & 22 deletions src/libsyntax_pos/hygiene.rs
Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -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)
})
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
})
Expand All @@ -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);
}
Expand All @@ -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,
opaque: new_opaque,
opaque_and_semitransparent: new_opaque,
});
new_opaque
});
}

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: modern,
modern: SyntaxContext(len),
prev_ctxt,
opaque,
opaque_and_semitransparent: new_opaque_and_semitransparent,
});
SyntaxContext(len)
new_opaque_and_semitransparent
});
}

*data.markings.entry((self, mark)).or_insert_with(|| {
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
})
})
}
Expand Down Expand Up @@ -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]
Expand Down
6 changes: 6 additions & 0 deletions src/libsyntax_pos/lib.rs
Expand Up @@ -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)]
Expand Down
9 changes: 9 additions & 0 deletions src/libsyntax_pos/symbol.rs
Expand Up @@ -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)
}
Expand Down
6 changes: 6 additions & 0 deletions src/test/ui/hygiene/auxiliary/intercrate.rs
Expand Up @@ -19,3 +19,9 @@ pub mod foo {
}
}
}

pub struct SomeType;

pub macro uses_dollar_crate() {
type Alias = $crate::SomeType;
}
16 changes: 16 additions & 0 deletions 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;
}
22 changes: 22 additions & 0 deletions 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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() {}
24 changes: 24 additions & 0 deletions 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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!();

0 comments on commit 99ecdb3

Please sign in to comment.