Unexpected macro output

code in question:


use proc_macro::TokenStream;

mod domain_equivalent;

//domain_equivilant macro that impl From and Into to domain
pub fn domain_equivalent(input: TokenStream) -> TokenStream {


use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Item, Visibility};

pub fn domain_equivalent(input: TokenStream) -> TokenStream {
    let item: Item = parse_macro_input!(input);
    let struct_data = match item.clone() {
        Item::Struct(e) => e,
        _ => panic!("only in structs"),
    let ident = struct_data.ident;
    // panic!(
    //     "{}",
    //     check_file(ident, "./domain/src/post".into()).to_string()
    // );
    return check_file(ident, "./domain/src/post".into());

//check mod.rs
//find pub mods and structs
//check if struct is threre
//if not go to other public mod
//check if struct is there
fn check_file(name: syn::Ident, path: String) -> TokenStream {
    let file = std::fs::read_to_string(format!("{}/mod.rs", path)).expect("Error reading file");
    let parsed_file = syn::parse_file(&file).expect("Error parsing file");

    for item in parsed_file.items {
        if let Item::Struct(structure) = item {
            let struct_name = structure.ident;
            if struct_name == name.clone() {
                return retrun_macro(struct_name, structure.fields);
        } else if let Item::Mod(module) = item {
            match module.vis {
                Visibility::Public(_) => {
                    let tokenstream = check_file(
                        format!("{}/{}", path, module.ident.to_string()),
                    match tokenstream.is_empty() {
                        true => return tokenstream,
                        false => continue,
                _ => {}

fn retrun_macro(ident: syn::Ident, fields: syn::Fields) -> TokenStream {
    let fields = match fields {
        syn::Fields::Named(e) => e,
        _ => panic!("Only with named fields"),

    let mut from_fields = Vec::new();
    let mut into_fields = Vec::new();
    for field in fields.named {
        let field_ident = field.ident.expect("Failed to get Field Ident");
        if field_ident.to_string().ends_with("_thing") {
            from_fields.push(quote!(#field_ident: Some(value.#field_ident.into()),));
            into_fields.push(quote!(#field_ident: self.#field_ident.unwrap().into(),));
        } else {
            from_fields.push(quote!(#field_ident: value.#field_ident,));
            into_fields.push(quote!(#field_ident: self.#field_ident,));

    let module: syn::Ident =
        syn::parse_str(&ident.to_string().replace("Thing", "").to_case(Case::Snake))
            .expect("Failed to parse module");

    let output = quote! {
    impl From<domain::#module::#ident> for #ident {
        fn from(value: domain::#module::#ident) -> Self {
            Self {

    impl Into<domain::#module::#ident> for #ident {
        fn into(self) -> domain::#module::#ident {
            domain::#module::#ident {

When I try to expand I get nothing as derive output and it does not error;
if I uncomment the panic I get the result I want

can you help me?

More Info:
project structure
└── ./
├── domain/
│ ├── src/
│ │ ├── post/
│ │ │ └── mod.rs
│ │ └── mod.rs
│ └── Cargo.toml
├── server_macros/
│ ├── src/
│ │ ├── domain_equivalent.rs
│ │ └── lib.rs
│ ├── tests/
│ │ ├── domain_equivalent.rs
│ │ └── domain_equivalent.expanded.rs
│ └── Cargo.toml
├── Cargo.toml
└── Cargo.lock


pub mod post;


#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Default, fake::Dummy)]
pub struct PostThing {
    pub host: String,
    pub post_id: String,
    pub user_id: String,

How do you know you get nothing as the derive output? Have you tried using cargo expand to show what code the compiler sees?

VS code tells me there are no implementations

I copied your code to a new project and it works for me. Have you tried actually using the implementations in code? You may be calling cargo expand incorrectly, and rust-analyzer may show things incorrectly/show old data especially when it comes to proc macros.

This literally creates an empty token stream.

This was for the occasion that parsed_file.items had zero elemets

I tried using it from my other crate and it works fine.
The tests (from trybuild and macrotest ) are failing inside the macro crate.

replacing ./domain/src with the full path makes it work both in tests and in the other crate

do you know how can I get the root of the workspace?

You can use the CARGO_MANIFEST_DIR environment variable that Cargo sets when you run it. The environment variable will point to the crate directory and there's no equivalent for the workspace directory for some reason, but you can use it as a stable base path.

Alternatively, I think you could use include_str which uses a path relative to the current file instead of read_to_string.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.