From d052d28d70b31fac942765da137f794835e6536e Mon Sep 17 00:00:00 2001 From: Jeffrey Seyfried Date: Wed, 29 Nov 2017 01:05:31 -0800 Subject: [PATCH 1/2] Improve interaction between macros 2.0 and `macro_rules!`. --- src/libproc_macro/lib.rs | 2 +- src/librustc_resolve/build_reduced_graph.rs | 2 +- src/librustc_resolve/lib.rs | 19 +++++-- src/librustc_resolve/macros.rs | 2 +- src/librustc_resolve/resolve_imports.rs | 2 +- src/libsyntax_pos/hygiene.rs | 39 +++++++++++++++ .../hygiene/auxiliary/legacy_interaction.rs | 19 +++++++ .../run-pass/hygiene/legacy_interaction.rs | 50 +++++++++++++++++++ 8 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 src/test/run-pass/hygiene/auxiliary/legacy_interaction.rs create mode 100644 src/test/run-pass/hygiene/legacy_interaction.rs diff --git a/src/libproc_macro/lib.rs b/src/libproc_macro/lib.rs index 4a6841aedca..22f788e34ec 100644 --- a/src/libproc_macro/lib.rs +++ b/src/libproc_macro/lib.rs @@ -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)); let stream = parse::parse_stream_from_source_str(name, src, sess, Some(span)); Ok(__internal::token_stream_wrap(stream)) }) diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs index 8df6458b72e..21b5bb6a971 100644 --- a/src/librustc_resolve/build_reduced_graph.rs +++ b/src/librustc_resolve/build_reduced_graph.rs @@ -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!(), diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index 33e57b2d180..2a97df92092 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -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}; @@ -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, }; @@ -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() && diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index 260a0cd7cd7..fe6bbf45d9f 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -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 { diff --git a/src/librustc_resolve/resolve_imports.rs b/src/librustc_resolve/resolve_imports.rs index e249ecfca58..4fd80170785 100644 --- a/src/librustc_resolve/resolve_imports.rs +++ b/src/librustc_resolve/resolve_imports.rs @@ -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() { diff --git a/src/libsyntax_pos/hygiene.rs b/src/libsyntax_pos/hygiene.rs index ab6c3f7d62d..23c29e6c6dd 100644 --- a/src/libsyntax_pos/hygiene.rs +++ b/src/libsyntax_pos/hygiene.rs @@ -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; @@ -215,6 +242,18 @@ impl SyntaxContext { }) } + pub fn marks(mut self) -> Vec { + 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 diff --git a/src/test/run-pass/hygiene/auxiliary/legacy_interaction.rs b/src/test/run-pass/hygiene/auxiliary/legacy_interaction.rs new file mode 100644 index 00000000000..c614ee4d575 --- /dev/null +++ b/src/test/run-pass/hygiene/auxiliary/legacy_interaction.rs @@ -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 or the MIT license +// , 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) + } +} diff --git a/src/test/run-pass/hygiene/legacy_interaction.rs b/src/test/run-pass/hygiene/legacy_interaction.rs new file mode 100644 index 00000000000..683a15b99ae --- /dev/null +++ b/src/test/run-pass/hygiene/legacy_interaction.rs @@ -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 or the MIT license +// , 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() {} From b766fa887dc0e4b923a38751fe4d570e35a75710 Mon Sep 17 00:00:00 2001 From: Jeffrey Seyfried Date: Wed, 6 Dec 2017 20:30:40 -0800 Subject: [PATCH 2/2] Add example of making an unhygienic macro hygienic by wrapping it in a declarative macro. --- .../run-pass/hygiene/auxiliary/my_crate.rs | 11 +++++ .../hygiene/auxiliary/unhygienic_example.rs | 37 ++++++++++++++++ .../hygiene/wrap_unhygienic_example.rs | 43 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 src/test/run-pass/hygiene/auxiliary/my_crate.rs create mode 100644 src/test/run-pass/hygiene/auxiliary/unhygienic_example.rs create mode 100644 src/test/run-pass/hygiene/wrap_unhygienic_example.rs diff --git a/src/test/run-pass/hygiene/auxiliary/my_crate.rs b/src/test/run-pass/hygiene/auxiliary/my_crate.rs new file mode 100644 index 00000000000..e10d20b6d47 --- /dev/null +++ b/src/test/run-pass/hygiene/auxiliary/my_crate.rs @@ -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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub fn f() {} diff --git a/src/test/run-pass/hygiene/auxiliary/unhygienic_example.rs b/src/test/run-pass/hygiene/auxiliary/unhygienic_example.rs new file mode 100644 index 00000000000..298e0209a09 --- /dev/null +++ b/src/test/run-pass/hygiene/auxiliary/unhygienic_example.rs @@ -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 or the MIT license +// , 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 +} diff --git a/src/test/run-pass/hygiene/wrap_unhygienic_example.rs b/src/test/run-pass/hygiene/wrap_unhygienic_example.rs new file mode 100644 index 00000000000..55206950214 --- /dev/null +++ b/src/test/run-pass/hygiene/wrap_unhygienic_example.rs @@ -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 or the MIT license +// , 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() {}