Skip to content
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

New export_tokens path strategy #8

Merged
merged 5 commits into from
Jun 12, 2023
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
59 changes: 37 additions & 22 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! This crate contains most of the internal implementation of the macros in the
//! `macro_magic_macros` crate. For the most part, the proc macros in `macro_magic_macros` just
//! call their respective `_internal` variants in this crate.

use std::sync::atomic::{AtomicUsize, Ordering};

use derive_syn_parse::Parse;
use macro_magic_core_macros::*;
use proc_macro2::{Delimiter, Group, Punct, Spacing, Span, TokenStream as TokenStream2};
Expand All @@ -15,6 +18,9 @@ use syn::{

pub const MACRO_MAGIC_ROOT: &'static str = get_macro_magic_root!();

/// A global counter, can be used to generate a relatively unique identifier.
static COUNTER: AtomicUsize = AtomicUsize::new(0);

/// Private module containing custom keywords used for parsing in this crate
mod keywords {
use syn::custom_keyword;
Expand Down Expand Up @@ -390,11 +396,36 @@ pub fn flatten_ident(ident: &Ident) -> Ident {
///
/// Used by [`export_tokens_internal`] and several other functions.
pub fn export_tokens_macro_ident(ident: &Ident) -> Ident {
let ident = flatten_ident(&ident);
let ident = flatten_ident(ident);
let ident_string = format!("__export_tokens_tt_{}", ident.to_token_stream().to_string());
Ident::new(ident_string.as_str(), Span::call_site())
}

pub fn export_tokens_macro_path(item_path: &Path) -> Path {
let Some(last_seg) = item_path.segments.last() else { unreachable!("must have at least one segment") };
let mut leading_segs = item_path
.segments
.iter()
.cloned()
.map(|seg| seg.ident)
.collect::<Vec<_>>()[0..item_path.segments.len() - 1]
.to_vec();
let last_seg = export_tokens_macro_ident(&last_seg.ident);
leading_segs.push(last_seg);
parse_quote!(#(#leading_segs)::*)
Comment on lines +405 to +415
Copy link
Contributor

@gui1117 gui1117 Jun 10, 2023

Choose a reason for hiding this comment

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

maybe this is slightly cleaner as we don't loose the path leading colon, what do you think?

Suggested change
let Some(last_seg) = item_path.segments.last() else { unreachable!("must have at least one segment") };
let mut leading_segs = item_path
.segments
.iter()
.cloned()
.map(|seg| seg.ident)
.collect::<Vec<_>>()[0..item_path.segments.len() - 1]
.to_vec();
let last_seg = export_tokens_macro_ident(&last_seg.ident);
leading_segs.push(last_seg);
parse_quote!(#(#leading_segs)::*)
let mut macro_path = item_path.clone();
let Some(syn::punctuated::Pair::End(last_seg)) = macro_path.segments.pop()
else { unreachable!("Path must have a last segment") };
let last_seg = export_tokens_macro_ident(&last_seg.ident);
macro_path.segments.push(last_seg.into());
macro_path

}

fn new_unique_export_tokens_ident(ident: &Ident) -> Ident {
let unique_id = COUNTER.fetch_add(1, Ordering::SeqCst);
let ident = flatten_ident(ident);
let ident_string = format!(
"__export_tokens_tt_{}_{}",
ident.to_token_stream().to_string(),
unique_id
);
Ident::new(ident_string.as_str(), Span::call_site())
}

/// The internal code behind the `#[export_tokens]` attribute macro.
///
/// The `attr` variable contains the tokens for the optional naming [`struct@Ident`] (necessary
Expand Down Expand Up @@ -441,6 +472,7 @@ pub fn export_tokens_internal<T: Into<TokenStream2>, E: Into<TokenStream2>>(
}
None => parse2::<Ident>(attr)?,
};
let macro_ident = new_unique_export_tokens_ident(&ident);
let ident = export_tokens_macro_ident(&ident);
let item_emit = match emit {
true => quote! {
Expand All @@ -452,7 +484,7 @@ pub fn export_tokens_internal<T: Into<TokenStream2>, E: Into<TokenStream2>>(
let output = quote! {
#[doc(hidden)]
#[macro_export]
macro_rules! #ident {
macro_rules! #macro_ident {
// arm with extra support (used by attr)
(
$(::)?$($tokens_var:ident)::*,
Expand All @@ -473,6 +505,7 @@ pub fn export_tokens_internal<T: Into<TokenStream2>, E: Into<TokenStream2>>(
}
};
}
pub use #macro_ident as #ident;
#item_emit
};
Ok(output)
Expand Down Expand Up @@ -526,16 +559,7 @@ pub fn export_tokens_alias_internal<T: Into<TokenStream2>>(
/// where `my_tokens` contains the tokens of `ExportedItem`.
pub fn import_tokens_internal<T: Into<TokenStream2>>(tokens: T) -> Result<TokenStream2> {
let args = parse2::<ImportTokensArgs>(tokens.into())?;
let Some(source_ident_seg) = args.source_path.segments.last() else { unreachable!("must have at least one segment") };
let source_ident_seg = export_tokens_macro_ident(&source_ident_seg.ident);
let source_path = if args.source_path.segments.len() > 1 {
let Some(crate_seg) = args.source_path.segments.first() else {
unreachable!("path has at least two segments, so there is a first segment");
};
quote!(#crate_seg::#source_ident_seg)
} else {
quote!(#source_ident_seg)
};
let source_path = export_tokens_macro_path(&args.source_path);
let inner_macro_path = private_path(&quote!(import_tokens_inner));
let tokens_var_ident = args.tokens_var_ident;
Ok(quote! {
Expand Down Expand Up @@ -565,16 +589,7 @@ pub fn forward_tokens_internal<T: Into<TokenStream2>>(tokens: T) -> Result<Token
Some(path) => path,
None => macro_magic_root(),
};
let Some(source_ident_seg) = args.source.segments.last() else { unreachable!("must have at least one segment") };
let source_ident_seg = export_tokens_macro_ident(&source_ident_seg.ident);
let source_path = if args.source.segments.len() > 1 {
let Some(crate_seg) = args.source.segments.first() else {
unreachable!("path has at least two segments, so there is a first segment");
};
quote!(#crate_seg::#source_ident_seg)
} else {
quote!(#source_ident_seg)
};
let source_path = export_tokens_macro_path(&args.source);
let target_path = args.target;
if let Some(extra) = args.extra {
Ok(quote! {
Expand Down
9 changes: 1 addition & 8 deletions tests/external_crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use macro_magic::*;

mod some_submodule {
pub mod some_submodule {
use macro_magic::*;

struct FooBarStruct {}
Expand Down Expand Up @@ -41,13 +41,6 @@ mod an_external_module {
}
}

fn _some_function() {
#[export_tokens]
fn some_sub_function() -> u32 {
33
}
}

macro_rules! another_macro {
() => {
let a = 2;
Expand Down
3 changes: 0 additions & 3 deletions tests/middle_crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,4 @@ pub mod export_mod {
struct ForeignItem {}

pub use test_macros::distant_re_export_attr;

pub use test_macros::distant_re_export_proc;

pub use macro_magic::{use_attr, use_proc};
23 changes: 7 additions & 16 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub mod hunter {
}
}

#[test_tokens_attr2(external_crate::AnExternalTraitImpl)]
#[test_tokens_attr2(external_crate::some_submodule::AnExternalTraitImpl)]
struct LocalItemStruct {}

#[test_tokens_attr_direct_import(external_crate::an_external_function)]
Expand All @@ -101,7 +101,7 @@ struct LionStruct {}
struct TigerStruct {}

// test proc item position
item_level_proc!(external_crate::AnExternalTraitImpl);
item_level_proc!(external_crate::some_submodule::AnExternalTraitImpl);

#[test]
fn test_import_tokens_proc_item_position() {
Expand All @@ -111,23 +111,14 @@ fn test_import_tokens_proc_item_position() {
#[test]
fn test_import_tokens_proc_statement_position() {
example_tokens_proc!(LionStruct);
example_tokens_proc!(external_crate::AnExternalTraitImpl);
example_tokens_proc!(external_crate::some_submodule::AnExternalTraitImpl);
}

#[test]
fn test_import_tokens_proc_expr_position() {
let something = example_tokens_proc!(TigerStruct);
assert_eq!(something.to_string(), "struct TigerStruct {}");
let _something_else = example_tokens_proc!(external_crate::AnExternalTraitImpl);
}

#[test]
fn test_export_tokens_inside_function() {
let something = example_tokens_proc!(external_crate::some_sub_function);
assert_eq!(
something.to_string(),
"fn some_sub_function() -> u32 { 33 }"
);
let _something_else = example_tokens_proc!(external_crate::some_submodule::AnExternalTraitImpl);
}

#[test]
Expand Down Expand Up @@ -166,7 +157,7 @@ fn import_tokens_same_mod_ident() {
#[cfg(feature = "proc_support")]
#[test]
fn import_tokens_different_mod_no_ident() {
import_tokens!(let tokens = PlusPlus);
import_tokens!(let tokens = some_module::PlusPlus);
assert_eq!(
tokens.to_string(),
"fn plus_plus < T : Into < i64 > > (n : T) -> i64 { n . into () + 1 }"
Expand All @@ -176,7 +167,7 @@ fn import_tokens_different_mod_no_ident() {
#[cfg(feature = "proc_support")]
#[test]
fn import_tokens_different_mod_ident() {
import_tokens!(let tokens = MinusMinus);
import_tokens!(let tokens = some_module::MinusMinus);
assert_eq!(
tokens.to_string(),
"fn minus_minus < T : Into < i32 > > (n : T) -> i32 { n . into () - 1 }"
Expand All @@ -199,7 +190,7 @@ fn println_inside_fn_current_file() {

#[test]
fn println_inside_fn_external_file() {
let tokens = example_tokens_proc!(external_fn_with_println);
let tokens = example_tokens_proc!(external_file::external_fn_with_println);
assert_eq!(
tokens.to_string(),
"fn external_fn_with_println() { println! (\"testing\") ; }"
Expand Down