Rust: decl_macro: imports don't work in cross-crate invocations

Created on 31 May 2017  ·  3Comments  ·  Source: rust-lang/rust

Minimal reproduction:

// src/lib.rs
#![feature(decl_macro)]

pub macro use_fmt {
    () => {
        use std::fmt;
    }
}

// examples/test.rs
#![feature(use_extern_macros)]

extern crate macro_test;

use macro_test::use_fmt;

use_fmt!();

fn main() {}
The error:
$ cargo build --example test
   Compiling macro-test v0.1.0 (file:///.../macro-test)
error[E0432]: unresolved import `std::fmt`
 --> examples/test.rs:7:1
  |
7 | use_fmt!();
  | ^^^^^^^^^^^ Could not find `std` in `{{root}}`
  |
  = note: this error originates in a macro outside of the current crate

error: aborting due to previous error(s)

error: Could not compile `macro-test`.

Invoking the macro in the same crate as it's defined works as expected.

cc @jseyfried

A-macros-2.0 C-bug

Most helpful comment

Sorry for the delay getting to this. Fixed in #46419.

All 3 comments

Sorry for the delay getting to this. Fixed in #46419.

I don't know if it is the same issue (and if the PR fixed it), but I get the same error with proc_macro.
Here's a minimal example to reproduce this issue.
Is it the same issue?
When I run cargo build, I get the following errors:

error[E0433]: failed to resolve. Could not find `postgres` in `{{root}}`
 --> src/lib.rs:9:17
  |
9 | #[derive(Debug, SqlTable)]
  |                 ^^^^^^^^ Could not find `postgres` in `{{root}}`

error[E0433]: failed to resolve. Could not find `std` in `{{root}}`
 --> src/lib.rs:9:17
  |
9 | #[derive(Debug, SqlTable)]
  |                 ^^^^^^^^ Could not find `std` in `{{root}}`

error[E0412]: cannot find type `Table` in this scope
 --> src/lib.rs:9:17
  |
9 | #[derive(Debug, SqlTable)]
  |                 ^^^^^^^^ not found in this scope
help: possible candidate is found in another module, you can import it into scope
  |
3 | use Table;
  |

Is there any workaround for this issue?
Thank you.

@antoyo This is a consequence of hygiene.

Since ::postgres::types::Type is at the proc-macro definition, it resolve in the scope of the macro definition. In particular, it will resolve the same no matter where the macro is invoked.

There is no extern crate postgres; at the proc-macro crate root, so you get a resolution error. Just adding extern crate postgres; to the proc-macro crate root isn't sufficient either, however, since proc-macro dependencies are compiled for the host platform (phase 0), not the target platform (phase 1) and so the expansion can't see them.

The solution here is to add target dependencies to proc-macro crates as described in https://github.com/rust-lang/rust/issues/45934#issuecomment-344497531.

For now, you need to put an extern crate postgres; in the macro expansion. Due to hygiene, this won't conflict with anything named postgres at the invocation site, won't effect the resolution of the macro's arguments, etc. For example:

            quote! {
                extern crate std;
                extern crate postgres;
                use std::io::Write;
                use postgres::types::{IsNull, ToSql, Type};

                impl ToSql for #table_ident {
                    fn to_sql<W: Write + ?Sized>(&self, ty: &Type, out: &mut W) -> postgres::Result<IsNull> {
                        self.#primary_key_ident.to_sql(ty, out)
                    }

                    fn accepts(ty: &Type) -> bool {
                        match *ty {
                            Type::Int4 => true,
                            _ => false,
                        }
                    }

                    fn to_sql_checked(&self, ty: &Type, out: &mut Write) -> postgres::Result<IsNull> {
                        if !<Self as ToSql>::accepts(ty) {
                            return Err(postgres::error::Error::WrongType(ty.clone()));
                        }
                        self.to_sql(ty, out)
                    }
                }
            }

Alternatively, you can make ::postgres::types::Type resolve at the call site (unhygienically) by giving it Span::call_site() (tokens from quote! have Span::def_site() by default). Soon, spanned_quote!(span, tokens...) will make this easier.

Was this page helpful?
0 / 5 - 0 ratings