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

Types do not expand hygienically in macros that create modules #28072

Closed
geofft opened this issue Aug 28, 2015 · 7 comments
Closed

Types do not expand hygienically in macros that create modules #28072

geofft opened this issue Aug 28, 2015 · 7 comments
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) P-low Low priority

Comments

@geofft
Copy link
Contributor

geofft commented Aug 28, 2015

If I take a macro parameter of type ty, and in the macro I create a module and expand ty within that module, the name of the type is parsed within the module, not within the context of the macro, which subtly breaks hygiene. For instance (playpen):

macro_rules! f {
    (fn $name:ident ( $($v:ident: $t:ty),* );) => {
        mod $name {
            fn one($($v: $t),*) -> i32 { 1 }
        }
    }
}

f! { fn foo(x: &str, y: Option<i32>); }
f! { fn bar(z: &std::io::Read); }

The first call works fine because str and Option are in the prelude, but the second call to f! produces

<anon>:12:17: 12:30 error: failed to resolve. Use of undeclared type or module `std::io` [E0433]
<anon>:12 f! { fn bar(z: &std::io::Read); }
[...]
<anon>:12:17: 12:30 error: use of undeclared type name `std::io::Read` [E0412]
<anon>:12 f! { fn bar(z: &std::io::Read); }

because there's no std::io::Read from the point of view of mod bar {...}, there's just ::std::io::Read or super::std::io::Read or whatever.

There's not even a workaround for this, because the type parameter isn't a path. If I try $v: super::$t, I get z: super::&std::io::Read, which isn't really helpful.

To be fair, my use case here involves using modules as a workaround for the lack of gensym. I've filed rust-lang/rfcs#1266 requesting the addition of gensym; at that point, my use case would no longer involve expanding types within a module. But I think this would still be a valid bug.

@Ryman
Copy link
Contributor

Ryman commented Aug 29, 2015

I'm unsure about hygiene being broken here, if it leaked imports from the calling context into a generated module, I'd consider that to be really broken.

This might help you get what you need done: http://is.gd/Q9tXFx

@steveklabnik steveklabnik added the A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) label Sep 3, 2015
@nrc
Copy link
Member

nrc commented Nov 8, 2015

As far as I understand it, Rust macros are not hygienic with respect to types at all. Since it would be a breaking change to fix this, it would need an RFC. Propose closing this issue or moving to RFC issues.

@nejucomo
Copy link

Hi. I've just run across the same issue, and it took me a while to understand. My use is slightly different, although an interesting similarity is that I first wanted to generate multiple functions in a macro expansion using gensym (or ident concatenation, or name mangling), and when I couldn't determine how to do that, I decided to introduce a module within the expansion.

Here's the failing minimized example:

const C: usize = 42;

macro_rules! mac {
    ( $e:expr ) => {
        mod a_test_mod {
            #[test]
            fn funky() {
                println!("inside funky(), e is: {}", $e);
            }
        }
    }
}

mac!(C);

The result is:

$ cargo test
   Compiling macro-scoping-bug v0.1.0 (file:///home/n/sandbox/rust/macro-scoping-bug)
src/main.rs:14:6: 14:7 error: unresolved name `C`
src/main.rs:14 mac!(C);
                    ^
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
src/main.rs:8:17: 8:58 note: expansion site
src/main.rs:3:1: 12:2 note: in expansion of mac!
src/main.rs:14:1: 14:9 note: expansion site
error: aborting due to previous error
Could not compile `macro-scoping-bug`.

To learn more, run the command again with --verbose.

Here is a "fix" which seems counter-intuitive without realizing that items and/or paths aren't hygienic:

const C: usize = 42;

macro_rules! mac {
    ( $e:expr ) => {
        mod a_test_mod {
            use super::C;

            #[test]
            fn funky() {
                println!("inside funky(), e is: {}", $e);
            }
        }
    }
}

mac!(C);

Here's a second fix which is similarly counter-intuitive (for me at least):

const C: usize = 42;

macro_rules! mac {
    ( $e:expr ) => {
        mod a_test_mod {
            #[test]
            fn funky() {
                println!("inside funky(), e is: {}", $e);
            }
        }
    }
}

mac!(super::C);

Note that this macro defines items which should "leak out" so I think I want hygiene in one case, but not the other. Perhaps I want to eat my cake and have it, too. (See #28335 for an example of someone expecting some kind of "exported item definition hygiene".)

One final note to motivate my use case, I have something like:

const C: usize = 42;

macro_rules! define_tests {
    ( $name:ident $e:expr ) => {
        mod $name {
            #[test]
            fn test_one_way() {
                ...
            }

            #[test]
            fn test_another_way() {
                ...
            }
        }
    }
}

/* Run both kinds of tests on various boundary conditions: */
define_tests!(one_c, super::C);
define_tests!(one_less_than_c, super::C - 1);
define_tests!(one_more_than_c, super::C + 1);
define_tests!(two_c, super::C + super::C);

As I said above, another approach is to remove the intermediate module and then use name mangling to ensure all of the test functions have unique names. I haven't tried that yet, but from #12249 I'm lead to believe that approach won't work. (I find this hierarchical mod approach to be "cleaner" anyway.)

Phew, ok... so for the purpose of this ticket, given the current accepted design of "partial hygiene" for macros, maybe the best improvement is to have some of these examples in the chapter on macros in the book.

@brson
Copy link
Contributor

brson commented May 4, 2017

Current error



rustc 1.17.0 (56124baa9 2017-04-24)
error[E0433]: failed to resolve. Use of undeclared type or module `std`
  --> <anon>:10:17
   |
10 | f! { fn bar(z: &std::io::Read); }
   | ----------------^^^^^^^^^^^^^----
   | |               |
   | |               Use of undeclared type or module `std`
   | in this macro invocation

error: aborting due to previous error

@brson
Copy link
Contributor

brson commented May 4, 2017

cc @jseyfried just seeing if this is interesting to you, or you know how to fix it.

@brson brson added P-low Low priority I-wrong labels May 4, 2017
@jseyfried jseyfried removed the I-wrong label May 4, 2017
@jseyfried
Copy link
Contributor

jseyfried commented May 4, 2017

This is a known limitation of macro_rules! -- only local variables (and labels) are hygienic, not items (including paths). As @nrc mentioned, this would be a breaking change to fix with macro_rules!.

This is fixed in declarative macros 2.0 (it will "just work"), see this test case.

@Mark-Simulacrum
Copy link
Member

As a known limitation of macro_rules that we don't plan on fixing, I'm going to close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) P-low Low priority
Projects
None yet
Development

No branches or pull requests

8 participants