Skip to content
This repository has been archived by the owner on Dec 27, 2022. It is now read-only.

Commit

Permalink
Merge pull request #66 from dtolnay/native
Browse files Browse the repository at this point in the history
Add attribute to bypass proc-macro-hack and use native macro
  • Loading branch information
dtolnay authored Oct 28, 2020
2 parents c194c0b + d686d31 commit ae11d02
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: [nightly, beta, stable, 1.42.0, 1.31.0]
rust: [nightly, beta, stable, 1.45.0, 1.42.0, 1.31.0]
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@master
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ fn main() {
macro input, use `#[proc_macro_hack(fake_call_site)]` on the re-export in your
declaration crate. *Most macros won't need this.*

- On compilers that are new enough to natively support proc macros in expression
position, proc-macro-hack does not automatically use that support, since the
hygiene can be subtly different between the two implementations. To opt in to
compiling your macro to native `#[proc_macro]` on sufficiently new compilers,
use `#[proc_macro_hack(only_hack_old_rustc)]` on the re-export in your
declaration crate.

[#10]: https://github.com/dtolnay/proc-macro-hack/issues/10
[#20]: https://github.com/dtolnay/proc-macro-hack/issues/20
[`proc-macro-nested`]: https://docs.rs/proc-macro-nested
Expand Down
31 changes: 31 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::env;
use std::process::Command;
use std::str;

// The rustc-cfg strings below are *not* public API. Please let us know by
// opening a GitHub issue if your build environment requires some way to enable
// these cfgs other than by executing our build script.
fn main() {
let minor = match rustc_minor_version() {
Some(minor) => minor,
None => return,
};

// Function-like procedural macros in expressions, patterns, and statements
// stabilized in Rust 1.45:
// https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html#stabilizing-function-like-procedural-macros-in-expressions-patterns-and-statements
if minor < 45 {
println!("cargo:rustc-cfg=need_proc_macro_hack");
}
}

fn rustc_minor_version() -> Option<u32> {
let rustc = env::var_os("RUSTC")?;
let output = Command::new(rustc).arg("--version").output().ok()?;
let version = str::from_utf8(&output.stdout).ok()?;
let mut pieces = version.split('.');
if pieces.next() != Some("rustc 1") {
return None;
}
pieces.next()?.parse().ok()
}
2 changes: 1 addition & 1 deletion demo-hack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ use proc_macro_hack::proc_macro_hack;
/// Add one to an expression.
///
/// (Documentation goes here on the re-export, not in the other crate.)
#[proc_macro_hack]
#[proc_macro_hack(only_hack_old_rustc)]
pub use demo_hack_impl::add_one;
68 changes: 60 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@
//! in the macro input, use `#[proc_macro_hack(fake_call_site)]` on the
//! re-export in your declaration crate. *Most macros won't need this.*
//!
//! - On compilers that are new enough to natively support proc macros in
//! expression position, proc-macro-hack does not automatically use that
//! support, since the hygiene can be subtly different between the two
//! implementations. To opt in to compiling your macro to native
//! `#[proc_macro]` on sufficiently new compilers, use
//! `#[proc_macro_hack(only_hack_old_rustc)]` on the re-export in your
//! declaration crate.
//!
//! [#10]: https://github.com/dtolnay/proc-macro-hack/issues/10
//! [#20]: https://github.com/dtolnay/proc-macro-hack/issues/20
//! [`proc-macro-nested`]: https://docs.rs/proc-macro-nested
Expand Down Expand Up @@ -244,9 +252,14 @@ struct ExportArgs {
support_nested: bool,
internal_macro_calls: u16,
fake_call_site: bool,
only_hack_old_rustc: bool,
}

fn expand_export(export: Export, args: ExportArgs) -> TokenStream {
if args.only_hack_old_rustc && cfg!(not(need_proc_macro_hack)) {
return expand_export_nohack(export);
}

let dummy = dummy_name_for_export(&export);

let attrs = export.attrs;
Expand All @@ -273,14 +286,14 @@ fn expand_export(export: Export, args: ExportArgs) -> TokenStream {
let mut export_call_site = TokenStream::new();
let mut macro_rules = TokenStream::new();
for Macro { name, export_as } in &export.macros {
let pub_name = pub_proc_macro_name(&name);
let hacked = hacked_proc_macro_name(&name);
let dispatch = dispatch_macro_name(&name);
let call_site = call_site_macro_name(&name);

if !actual_names.is_empty() {
actual_names.extend(quote!(,));
}
actual_names.extend(quote!(#pub_name));
actual_names.extend(quote!(#hacked));

if !export_dispatch.is_empty() {
export_dispatch.extend(quote!(,));
Expand All @@ -294,18 +307,18 @@ fn expand_export(export: Export, args: ExportArgs) -> TokenStream {

let do_derive = if !args.fake_call_site {
quote! {
#[derive(#crate_prefix #pub_name)]
#[derive(#crate_prefix #hacked)]
}
} else if crate_prefix.is_some() {
quote! {
use #crate_prefix #pub_name;
use #crate_prefix #hacked;
#[#crate_prefix #call_site ($($proc_macro)*)]
#[derive(#pub_name)]
#[derive(#hacked)]
}
} else {
quote! {
#[#call_site ($($proc_macro)*)]
#[derive(#pub_name)]
#[derive(#hacked)]
}
};

Expand Down Expand Up @@ -375,10 +388,35 @@ fn expand_export(export: Export, args: ExportArgs) -> TokenStream {
wrap_in_enum_hack(dummy, expanded)
}

fn expand_export_nohack(export: Export) -> TokenStream {
let attrs = export.attrs;
let vis = export.vis;
let from = export.from;
let mut names = TokenStream::new();

for Macro { name, export_as } in &export.macros {
let pub_name = pub_proc_macro_name(&name);
if !names.is_empty() {
names.extend(quote!(,));
}
names.extend(quote!(#pub_name as #export_as));
}

if export.macros.len() != 1 {
names = quote!({#names});
}

quote! {
#attrs
#vis use #from::#names;
}
}

fn expand_define(define: Define) -> TokenStream {
let attrs = define.attrs;
let name = define.name;
let pub_name = pub_proc_macro_name(&name);
let hacked = hacked_proc_macro_name(&name);
let body = define.body;

quote! {
Expand All @@ -388,8 +426,9 @@ fn expand_define(define: Define) -> TokenStream {
}

#attrs
#[proc_macro_derive(#pub_name)]
pub fn #pub_name(input: #pub_name::TokenStream) -> #pub_name::TokenStream {
#[doc(hidden)]
#[proc_macro_derive(#hacked)]
pub fn #hacked(input: #pub_name::TokenStream) -> #pub_name::TokenStream {
use std::iter::FromIterator;

let mut iter = input.into_iter();
Expand Down Expand Up @@ -483,6 +522,12 @@ fn expand_define(define: Define) -> TokenStream {
])
}

#attrs
#[proc_macro]
pub fn #pub_name(input: #pub_name::TokenStream) -> #pub_name::TokenStream {
#name(input)
}

fn #name #body
}
}
Expand All @@ -494,6 +539,13 @@ fn pub_proc_macro_name(conceptual: &Ident) -> Ident {
)
}

fn hacked_proc_macro_name(conceptual: &Ident) -> Ident {
Ident::new(
&format!("_proc_macro_hack_{}", conceptual),
conceptual.span(),
)
}

fn dispatch_macro_name(conceptual: &Ident) -> Ident {
Ident::new(
&format!("proc_macro_call_{}", conceptual),
Expand Down
6 changes: 5 additions & 1 deletion src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ pub(crate) fn parse_export_args(tokens: Iter) -> Result<ExportArgs, Error> {
support_nested: false,
internal_macro_calls: 0,
fake_call_site: false,
only_hack_old_rustc: false,
};

while let Some(tt) = tokens.next() {
Expand All @@ -188,10 +189,13 @@ pub(crate) fn parse_export_args(tokens: Iter) -> Result<ExportArgs, Error> {
TokenTree::Ident(ident) if ident.to_string() == "fake_call_site" => {
args.fake_call_site = true;
}
TokenTree::Ident(ident) if ident.to_string() == "only_hack_old_rustc" => {
args.only_hack_old_rustc = true;
}
_ => {
return Err(Error::new(
tt.span(),
"expected one of: `support_nested`, `internal_macro_calls`, `fake_call_site`",
"expected one of: `support_nested`, `internal_macro_calls`, `fake_call_site`, `only_hack_old_rustc`",
));
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/unknown-arg.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: expected one of: `support_nested`, `internal_macro_calls`, `fake_call_site`
error: expected one of: `support_nested`, `internal_macro_calls`, `fake_call_site`, `only_hack_old_rustc`
--> $DIR/unknown-arg.rs:3:35
|
3 | #[proc_macro_hack(fake_call_site, support_nexted)]
Expand Down

0 comments on commit ae11d02

Please sign in to comment.