Skip to content

decl_macro expansion in 2018 context cannot refer to external crates at the root #55668

Description

@jebrosen

decl_macros that are invoked in a 2018 edition crate can fail depending on how the macro's paths are defined.

Minimized example:

#![feature(decl_macro)]

macro test {
    () => {
        ::std::vec::Vec::new()
    }
}

fn main() {
    let x: Vec<i32> = test!();
}

In the 2018 edition, this fails to compile:

error[E0433]: failed to resolve. Could not find `std` in `{{root}}`
  --> src/main.rs:5:11
   |
5  |         ::std::vec::Vec::new()
   |           ^^^ Could not find `std` in `{{root}}`
...
10 |     let x: Vec<i32> = test!();
   |                       ------- in this macro invocation

error: aborting due to previous error

The edition at the expansion location is what matters in this case, not the edition in which the macro is defined. Other crates show the same error message, not just std.

The workarounds that I have found so far are to start the path with std (no leading ::), or to add extern crate std; into the macro itself. The former is not a usable option when the decl_macro is generated by a proc_macro, because the crate std could be shadowed by a module named std at the "real" decl_macro definition site.

My bisect of the more complicated example pointed to #54658 -- cc @petrochenkov

cc @SergioBenitez

Activity

  1. SergioBenitez commented on Nov 4, 2018

    @SergioBenitez
    Contributor

    I believe this is actually the expected behavior. Since decl_macro's are supposed to be fully hygienic, their expansion should not be able to refer to crates (or any item) that isn't declared in the expansion itself or a parent expansion thereof. That being said, it's long been desired that the prelude be made available to decl_macros which would resolve this case in particular.

    The bug, however, is that hygiene seems to depend on the edition. The following, for instance, compiles just fine in the 2015 edition but fails in the 2018 edition:

    #![feature(decl_macro)]
    
    macro test() {
        ::std::vec::Vec::new()
    }
    
    fn main() {
        let x: Vec<usize> = test!();
    }
  2. self-assigned this
    on Nov 4, 2018
  3. added
    A-resolveArea: Name/path resolution done by `rustc_resolve` specifically
    A-decl-macros-2-0Area: Declarative macros 2.0 (#39412)
    on Nov 4, 2018
  4. petrochenkov commented on Nov 4, 2018

    @petrochenkov
    Contributor

    @SergioBenitez

    I believe this is actually the expected behavior. Since decl_macro's are supposed to be fully hygienic, their expansion should not be able to refer to crates (or any item) that isn't declared in the expansion itself or a parent expansion thereof.

    The real scheme is a bit more complex, when resolving i-th path segment with i > 0 hygienic context is "adjusted to the module" of the prefix 0 ... i - 1, so things like

    #![feature(decl_macro)]
    
    struct S;
    
    mod m {
        macro m() {
            let s = crate::S; // OK
        }
        
        fn check() {
            m!();
        }
    }
    
    fn main() {}

    work despite S in crate::S not having the same context as S in struct S.

    So, ::my_crate should indeed work, and #54658 introduced a regression by forgetting to adjust std's context in ::std to the "crate universe module".

    That being said, it's long been desired that the prelude be made available to decl_macros

    Preludes are already kinda work with decl_macros

    #![feature(decl_macro)]
    
    macro m() {
        let v = Vec::<u8>::new(); // OK
    }
    
    fn main() {
        m!();
    }

    , but how exactly they work with non-built-in names is an open question, and their hygiene is broken in cross-crate scenarios - prelude of the use-site crate is always used.

  5. petrochenkov commented on Nov 18, 2018

    @petrochenkov
    Contributor

    Fixed in #55884

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-decl-macros-2-0Area: Declarative macros 2.0 (#39412)A-resolveArea: Name/path resolution done by `rustc_resolve` specifically

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions