hygiene: Implement transparent marks

This commit is contained in:
Vadim Petrochenkov 2018-06-24 19:54:23 +03:00
parent 09856c85b7
commit 99ecdb3f5f
14 changed files with 253 additions and 27 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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,
}
}
@ -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, .. } |

View File

@ -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) {

View File

@ -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,
}
}

View File

@ -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]

View File

@ -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)]

View File

@ -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)
}

View File

@ -19,3 +19,9 @@ pub mod foo {
}
}
}
pub struct SomeType;
pub macro uses_dollar_crate() {
type Alias = $crate::SomeType;
}

View File

@ -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;
}

View File

@ -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() {}

View File

@ -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!();

View File

@ -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`.

View File

@ -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 <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.
// 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!();
}
}