Skip to content

macros: improve 1.0/2.0 interaction #46551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/libproc_macro/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl FromStr for TokenStream {
// notify the expansion info that it is unhygienic
let mark = Mark::fresh(mark);
mark.set_expn_info(expn_info);
let span = call_site.with_ctxt(call_site.ctxt().apply_mark(mark));
let span = call_site.with_ctxt(SyntaxContext::empty().apply_mark(mark));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let stream = parse::parse_stream_from_source_str(name, src, sess, Some(span));
Ok(__internal::token_stream_wrap(stream))
})
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_resolve/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl<'a> Resolver<'a> {

// Disallow `use $crate;`
if source.name == keywords::DollarCrate.name() && path.segments.len() == 1 {
let crate_root = self.resolve_crate_root(source.ctxt);
let crate_root = self.resolve_crate_root(source.ctxt, true);
let crate_name = match crate_root.kind {
ModuleKind::Def(_, name) => name,
ModuleKind::Block(..) => unreachable!(),
Expand Down
19 changes: 14 additions & 5 deletions src/librustc_resolve/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use rustc::hir::{Freevar, FreevarMap, TraitCandidate, TraitMap, GlobMap};
use rustc::util::nodemap::{NodeMap, NodeSet, FxHashMap, FxHashSet, DefIdMap};

use syntax::codemap::{dummy_spanned, respan};
use syntax::ext::hygiene::{Mark, SyntaxContext};
use syntax::ext::hygiene::{Mark, MarkKind, SyntaxContext};
use syntax::ast::{self, Name, NodeId, Ident, SpannedIdent, FloatTy, IntTy, UintTy};
use syntax::ext::base::SyntaxExtension;
use syntax::ext::base::Determinacy::{self, Determined, Undetermined};
Expand Down Expand Up @@ -1775,8 +1775,17 @@ impl<'a> Resolver<'a> {
result
}

fn resolve_crate_root(&mut self, mut ctxt: SyntaxContext) -> Module<'a> {
let module = match ctxt.adjust(Mark::root()) {
fn resolve_crate_root(&mut self, mut ctxt: SyntaxContext, legacy: bool) -> Module<'a> {
let mark = if legacy {
// 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.kind() != MarkKind::Modern)
} else {
ctxt = ctxt.modern();
ctxt.adjust(Mark::root())
};
let module = match mark {
Some(def) => self.macro_def_scope(def),
None => return self.graph_root,
};
Expand Down Expand Up @@ -2961,11 +2970,11 @@ impl<'a> Resolver<'a> {
(i == 1 && name == keywords::Crate.name() &&
path[0].node.name == keywords::CrateRoot.name()) {
// `::a::b` or `::crate::a::b`
module = Some(self.resolve_crate_root(ident.node.ctxt.modern()));
module = Some(self.resolve_crate_root(ident.node.ctxt, false));
continue
} else if i == 0 && name == keywords::DollarCrate.name() {
// `$crate::a::b`
module = Some(self.resolve_crate_root(ident.node.ctxt));
module = Some(self.resolve_crate_root(ident.node.ctxt, true));
continue
} else if i == 1 && self.session.features.borrow().extern_absolute_paths &&
path[0].node.name == keywords::CrateRoot.name() &&
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_resolve/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl<'a> base::Resolver for Resolver<'a> {
let ident = path.segments[0].identifier;
if ident.name == keywords::DollarCrate.name() {
path.segments[0].identifier.name = keywords::CrateRoot.name();
let module = self.0.resolve_crate_root(ident.ctxt);
let module = self.0.resolve_crate_root(ident.ctxt, true);
if !module.is_local() {
let span = path.segments[0].span;
path.segments.insert(1, match module.kind {
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_resolve/resolve_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
"crate root imports need to be explicitly named: \
`use crate as name;`".to_string()));
} else {
Some(self.resolve_crate_root(source.ctxt.modern()))
Some(self.resolve_crate_root(source.ctxt.modern(), false))
}
} else if extern_absolute_paths &&
!token::Ident(source).is_path_segment_keyword() {
Expand Down
39 changes: 39 additions & 0 deletions src/libsyntax_pos/hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,33 @@ impl SyntaxContext {

/// Extend a syntax context with a given mark
pub fn apply_mark(self, mark: Mark) -> SyntaxContext {
if mark.kind() == MarkKind::Modern {
return self.apply_mark_internal(mark);
}

let call_site_ctxt =
mark.expn_info().map_or(SyntaxContext::empty(), |info| info.call_site.ctxt()).modern();
if call_site_ctxt == SyntaxContext::empty() {
return self.apply_mark_internal(mark);
}

// Otherwise, `mark` is a macros 1.0 definition and the call site is in a
// macros 2.0 expansion, i.e. a macros 1.0 invocation is in a macros 2.0 definition.
//
// In this case, the tokens from the macros 1.0 definition inherit the hygiene
// at their invocation. That is, we pretend that the macros 1.0 definition
// was defined at its invocation (i.e. inside the macros 2.0 definition)
// so that the macros 2.0 definition remains hygienic.
//
// See the example at `test/run-pass/hygiene/legacy_interaction.rs`.
let mut ctxt = call_site_ctxt;
for mark in self.marks() {
ctxt = ctxt.apply_mark_internal(mark);
}
ctxt.apply_mark_internal(mark)
}

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;
Expand Down Expand Up @@ -215,6 +242,18 @@ impl SyntaxContext {
})
}

pub fn marks(mut self) -> Vec<Mark> {
HygieneData::with(|data| {
let mut marks = Vec::new();
while self != SyntaxContext::empty() {
marks.push(data.syntax_contexts[self.0 as usize].outer_mark);
self = data.syntax_contexts[self.0 as usize].prev_ctxt;
}
marks.reverse();
marks
})
}

/// Adjust this context for resolution in a scope created by the given expansion.
/// For example, consider the following three resolutions of `f`:
/// ```rust
Expand Down
19 changes: 19 additions & 0 deletions src/test/run-pass/hygiene/auxiliary/legacy_interaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2017 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.

// ignore-pretty pretty-printing is unhygienic

#[macro_export]
macro_rules! m {
() => {
fn f() {} // (2)
g(); // (1)
}
}
11 changes: 11 additions & 0 deletions src/test/run-pass/hygiene/auxiliary/my_crate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2017 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.

pub fn f() {}
37 changes: 37 additions & 0 deletions src/test/run-pass/hygiene/auxiliary/unhygienic_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2017 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.

#![crate_type = "lib"]

extern crate my_crate;

pub fn g() {} // (a)

#[macro_export]
macro_rules! unhygienic_macro {
() => {
// (1) unhygienic: depends on `my_crate` in the crate root at the invocation site.
::my_crate::f();

// (2) unhygienic: defines `f` at the invocation site (in addition to the above point).
use my_crate::f;
f();

g(); // (3) unhygienic: `g` needs to be in scope at use site.

$crate::g(); // (4) hygienic: this always resolves to (a)
}
}

#[allow(unused)]
fn test_unhygienic() {
unhygienic_macro!();
f(); // `f` was defined at the use site
}
50 changes: 50 additions & 0 deletions src/test/run-pass/hygiene/legacy_interaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2017 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.

// ignore-pretty pretty-printing is unhygienic

// aux-build:legacy_interaction.rs

#![feature(decl_macro)]
#[allow(unused)]

extern crate legacy_interaction;
// ^ defines
// ```rust
// macro_rules! m {
// () => {
// fn f() // (1)
// g() // (2)
// }
// }
// ```rust

mod def_site {
// Unless this macro opts out of hygiene, it should resolve the same wherever it is invoked.
pub macro m2() {
::legacy_interaction::m!();
f(); // This should resolve to (1)
fn g() {} // We want (2) resolve to this, not to (4)
}
}

mod use_site {
fn test() {
fn f() -> bool { true } // (3)
fn g() -> bool { true } // (4)

::def_site::m2!();

let _: bool = f(); // This should resolve to (3)
let _: bool = g(); // This should resolve to (4)
}
}

fn main() {}
43 changes: 43 additions & 0 deletions src/test/run-pass/hygiene/wrap_unhygienic_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2017 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.

// ignore-pretty pretty-printing is unhygienic

// aux-build:my_crate.rs
// aux-build:unhygienic_example.rs

#![feature(decl_macro)]

extern crate unhygienic_example;
extern crate my_crate; // (b)

// Hygienic version of `unhygienic_macro`.
pub macro hygienic_macro() {
fn g() {} // (c)
::unhygienic_example::unhygienic_macro!();
// ^ Even though we invoke an unhygienic macro, `hygienic_macro` remains hygienic.
// In the above expansion:
// (1) `my_crate` always resolves to (b) regardless of invocation site.
// (2) The defined function `f` is only usable inside this macro definition.
// (3) `g` always resolves to (c) regardless of invocation site.
// (4) `$crate::g` remains hygienic and continues to resolve to (a).

f();
}

#[allow(unused)]
fn test_hygienic_macro() {
hygienic_macro!();

fn f() {} // (d) no conflict
f(); // resolves to (d)
}

fn main() {}