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

proc_macro! expansion can refer to items defined in the root w/o importing them #50504

Closed
japaric opened this issue May 7, 2018 · 25 comments · Fixed by #51952
Closed

proc_macro! expansion can refer to items defined in the root w/o importing them #50504

japaric opened this issue May 7, 2018 · 25 comments · Fixed by #51952
Assignees
Labels
A-macros-1.2 Area: Declarative macros 1.2 A-macros-2.0 Area: Declarative macros 2.0 (#39412) C-bug Category: This is a bug.

Comments

@japaric
Copy link
Member

japaric commented May 7, 2018

Apologies if this has already been reported; I didn't search too hard.

STR

// bar/src/lib.rs

#![feature(proc_macro)]

extern crate proc_macro;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro]
pub fn bar(_: TokenStream) -> TokenStream {
    quote!(mod __bar {
        // NOTE `Foo` is never imported in this module
        static mut BAR: Option<Foo> = None;
    }).into()
}
// foo/src/lib.rs

#![feature(proc_macro)]

extern crate bar;

use bar::bar;

struct Foo;

bar!();

This code compiles but I would expect it not to because the expanded form doesn't compile:

// foo/src/lib.rs -- `cargo expand`-ed and cleaned up
struct Foo;

mod __bar {
    static mut BAR: Option<Foo> = None;
}
$ cargo build
error[E0412]: cannot find type `Foo` in this scope
 --> src/lib.rs:4:28
  |
4 |     static mut BAR: Option<Foo> = None;
  |                            ^^^ not found in this scope
help: possible candidate is found in another module, you can import it into scope
  |
4 |     use Foo;
  |

error: aborting due to previous error

Meta

$ rustc -V
rustc 1.27.0-nightly (428ea5f6b 2018-05-06)

cc @jseyfried

@alexcrichton alexcrichton added the A-macros-2.0 Area: Declarative macros 2.0 (#39412) label May 7, 2018
@alexcrichton
Copy link
Member

@petrochenkov do you know if this might be related to #50050? The hygiene here is indeed quite suspicious

@petrochenkov
Copy link
Contributor

petrochenkov commented May 7, 2018

This looks like a different issue to me at a first glance (but we need to check on older releases whether this is a regression or not).
Or even two issues?
First, isn't quote supposed to have def-site hygiene? Here it exhibits something call-site-like.
Second, the bar's call-site location (in module system), in which Foo is in scope, is somehow mixed up with its call-site hygienic context.
At least quote! is not stabilized.

@alexcrichton
Copy link
Member

@petrochenkov oh this is using the quote crate, not the one in proc_macro

@alexcrichton
Copy link
Member

Looks like this is a bug on stable Rust --

// foo.rs
#![crate_type = "proc-macro"]

extern crate proc_macro;

use proc_macro::*;

#[proc_macro_derive(Foo)]
pub fn foo1(a: TokenStream) -> TokenStream {
    drop(a);
    "mod __bar { static mut BAR: Option<Something> = None; }".parse().unwrap()
}
// bar.rs
#[macro_use]
extern crate foo;

struct Something;

#[derive(Foo)]
struct Another;

fn main() {}
$ rustc +stable foo.rs && rustc +stable bar.rs -L .
... only warnings printed

@alexcrichton
Copy link
Member

cc @nrc, you may want to take a look at this as well, especially because it's a bug on stable Rust which fixing may cause a lot of regressions

@nrc
Copy link
Member

nrc commented May 9, 2018

This looks correct to me - aiui we use call-site hygiene for proc macros, and so we're using the hygiene context from where bar! is written to resolve the name Foo, and it does exist there. Long term, we should be using def-site hygiene here and so it wouldn't compile because there is no Foo where bar is defined.

@alexcrichton
Copy link
Member

@nrc hm but we've been advocating that "hygiene" in expanded macros for 1.1/1.2 is basically "copy/paste" hygiene, but in this case it's not?

@nrc
Copy link
Member

nrc commented May 9, 2018

No, this is very much a limitation of the hygiene algorithm here - in the proper hygiene setup when you create a new scope in a macro, you get a combination of the hygiene info for the new scope plus the 'base scope'. The intuition you want is that the base scope is the call-site (that gives 'copy and paste' hygiene). However, the macros 1.1/1.2 algorithm is essentially the base scope everywhere (i.e., we ignore any scopes introduced by the macro) and the base scope is the call site.

@petrochenkov
Copy link
Contributor

petrochenkov commented May 9, 2018

@nrc
How do you plan to move to the "proper hygiene setup" from the current scheme (which seems incorrect/underimplemented to me) if proc_macro/proc_macro_attribute are stabilized now?

@petrochenkov
Copy link
Contributor

petrochenkov commented May 9, 2018

Even more alarming example:

// foo.rs
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::*;

#[proc_macro_derive(Foo)]
pub fn foo1(_: TokenStream) -> TokenStream {
    "
    struct Outer;
    mod inner {
        type Inner = Outer; // `Outer` shouldn't be available from here
    }
    ".parse().unwrap()
}
// bar.rs
#![allow(unused)]

#[macro_use]
extern crate foo;

#[derive(Foo)] // OK?!
struct Dummy;

fn main() {}

I.e. the current implementation of call-site hygiene effectively flattens any module structures existing inside the macro.

@petrochenkov
Copy link
Contributor

Perhaps we need to add some extra feature gate for macros generating modules?
(Possibly retroactively affecting proc_macro_derive).
Non-module scoping is not affected as far as I can conclude from some quick experiments.

@alexcrichton
Copy link
Member

Yeah if this is the current state of hygiene then I'd want to definitely gate modules from being in the output of procedural macros and procedural attributes. I'd ideally prefer to aggressviely start warning against modules in the output of custom derive as well.

@alexcrichton
Copy link
Member

I've posted #50587 to help evaluate the impact of forbidding modules from being generated by procedural derive macros

@alexcrichton
Copy link
Member

Given the crater results it seems clear that we cannot retroactively prevent custom derive from generating modules. Current "offenders" are:

  • pest_derive - #[derive(Parser)]
  • vulkano-shader-derive - #[derive(VulkanoShader)]
  • diesel - #[derive(QueryId)]
  • soa_derive - #[derive(StructOfArray)]
  • enumflags_derive - #[derive(EnumFlags)]
  • robin-derives - #[derive(Builder)]
  • juniper_codegen - #[derive(GraphQLEnum)]
  • prost-derive - #[derive(Message)]
  • ethabi-derive - ??

My PR purely disallowed mod items being generated and did not fix hygiene or anything like that. It's unknown what the fallout would be if we were to fix issues like mentioned above.

I'd still like to gate such features by default for maros 1.2 (attributes and bang macros) as I'd like to avoid worsening this problem further

@alexcrichton
Copy link
Member

I've opened #50820 to separately gate procedural bang and attribute macros, and will leave custom derive alone for now.

alexcrichton added a commit to alexcrichton/rust that referenced this issue May 18, 2018
This commit feature gates generating modules and macro definitions in procedural
macro expansions. Custom derive is exempt from this check as it would be a large
retroactive breaking change (rust-lang#50587). It's hoped that we can hopefully stem the
bleeding to figure out a better solution here before opening up the floodgates.

The restriction here is specifically targeted at surprising hygiene results [1]
that result in non-"copy/paste" behavior. Hygiene and procedural macros is
intended to be avoided as much as possible for Macros 1.2 by saying everything
is "as if you copy/pasted the code", but modules and macros are sort of weird
exceptions to this rule that aren't fully fleshed out.

[1]: rust-lang#50504 (comment)

cc rust-lang#50504
bors added a commit that referenced this issue May 20, 2018
rustc: Disallow modules and macros in expansions

This commit feature gates generating modules and macro definitions in procedural
macro expansions. Custom derive is exempt from this check as it would be a large
retroactive breaking change (#50587). It's hoped that we can hopefully stem the
bleeding to figure out a better solution here before opening up the floodgates.

The restriction here is specifically targeted at surprising hygiene results [1]
that result in non-"copy/paste" behavior. Hygiene and procedural macros is
intended to be avoided as much as possible for Macros 1.2 by saying everything
is "as if you copy/pasted the code", but modules and macros are sort of weird
exceptions to this rule that aren't fully fleshed out.

[1]: #50504 (comment)

cc #50504
@alexcrichton alexcrichton added the C-bug Category: This is a bug. label May 22, 2018
@petrochenkov
Copy link
Contributor

Looks like this issue is going to be fixed as well as a part of #50050.

@petrochenkov
Copy link
Contributor

petrochenkov commented Jun 24, 2018

This is not #50050, but a separate issue after all.

This is what happens:

  • During expansion InvocationCollector::collect creates a fresh expansion ID (aka Mark) for each macro invocation (like bar!() in the example above).
  • Later expansion algorithm associate this ID with its position in the module system in various side tables like macro_defs/invocations in Resolver.
  • Proc macros use the ID mentioned above for def-site hygiene, but they have to create an absolutely fresh expansion ID for call-site hygiene (currently in TokenStream::from_str) to assign the new transparency to it.
  • The new ID remains unassociated with positions in the module system and due to various fallbacks ends up associated with the root module.

Possible solution: put the newly created ID into the same resolver tables with same values as the original ID.

Probably better solution: decouple expansion ID from transparency. Each token produced by an expansion can have an independent transparency, so we won't have to create fresh IDs to change transparency.

EDIT: Expansion IDs remaining unassociated with positions in module system are indeed an issue (fixed in #51952), but the issue was caused by other reason, namely this fallback in name resolution

let mut module_expansion = module.expansion.modern(); // for backward compatibility
while let Some(parent) = module.parent {
let parent_expansion = parent.expansion.modern();
if module_expansion.is_descendant_of(parent_expansion) &&
parent_expansion != module_expansion {
return if parent_expansion.is_descendant_of(span.ctxt().outer()) {
Some(parent)
} else {
None
};
}
module = parent;
module_expansion = parent_expansion;
}

that worked for all proc macros 2.0 and declarative macros 2.0.
#51952 removes this fallback from all macros except for proc macro derives, in which it's reported as a compatibility warning.

@petrochenkov petrochenkov self-assigned this Jun 25, 2018
bors added a commit that referenced this issue Jun 30, 2018
 hygiene: Decouple transparencies from expansion IDs

Fixes #50504 in accordance with [this comment](#50504 (comment)).
@alexcrichton alexcrichton added the A-macros-1.2 Area: Declarative macros 1.2 label Jul 10, 2018
Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this issue Jul 11, 2018
 hygiene: Decouple transparencies from expansion IDs

And remove fallback to parent modules during resolution of names in scope.

This is a breaking change for users of unstable macros 2.0 (both procedural and declarative), code like this:
```rust
#![feature(decl_macro)]

macro m($S: ident) {
    struct $S;
    mod m {
        type A = $S;
    }
}

fn main() {
    m!(S);
}
```
or equivalent
```rust
#![feature(decl_macro)]

macro m($S: ident) {
    mod m {
        type A = $S;
    }
}

fn main() {
    struct S;
    m!(S);
}
```
stops working due to module boundaries being properly enforced.

For proc macro derives this is still reported as a compatibility warning to give `actix_derive`, `diesel_derives` and `palette_derive` time to fix their issues.

Fixes rust-lang#50504 in accordance with [this comment](rust-lang#50504 (comment)).
jebrosen added a commit to jebrosen/Rocket that referenced this issue Jul 13, 2018
SergioBenitez pushed a commit to rwf2/Rocket that referenced this issue Jul 25, 2018
This resolves a warning introduced in rust-lang/rust#51952 that will
eventually become a hard error, the latter of which is being tracked
in rust-lang/rust#50504.
@iddm
Copy link

iddm commented Aug 1, 2018

How can I fix these errors now?

warning: cannot find type `users` in this scope
  --> src/models.rs:45:37
   |
45 | #[derive(Debug, Clone, AsChangeset, Insertable, Serialize, Deserialize)]
   |                                     ^^^^^^^^^^ names from parent modules are not accessible without an explicit import
   |
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #50504 <https://github.com/rust-lang/rust/issues/50504>

@alexcrichton
Copy link
Member

@vityafx unfortunately as a user you can't do anything to fix that error, the fix would need to be in diesel's Insertable custom derive. In the meantime you can allow the lint though.

@lukedupin
Copy link

lukedupin commented Aug 5, 2018

Here is an example of how to suppress the warning:
export RUSTFLAGS="-Aproc-macro-derive-resolution-fallback"

@apiraino
Copy link
Contributor

apiraino commented Aug 12, 2018

Nitpicking on @lukedupin solution; maybe better adding:

#![allow(proc_macro_derive_resolution_fallback)]

at the top of your files that trigger the warning.

Setting an env variable somehow invalidates the compiler cache, therefore:

$ cargo check

and:

$ RUSTFLAGS="-Aproc-macro-derive-resolution-fallback" cargo check

are not the same and running one or the another will trigger a full (possible long) rebuild; f.e. your IDE may use cargo check for linting without setting that env var.

Mark-Simulacrum added a commit to Mark-Simulacrum/rust that referenced this issue Aug 22, 2018
Stabilize a few secondary macro features

- `tool_attributes` - closes rust-lang#44690
- `proc_macro_path_invoc` - this feature was created due to issues with tool attributes (rust-lang#51277), those issues are now fixed (rust-lang#52841)
- partially `proc_macro_gen` - this feature was created due to issue rust-lang#50504, the issue is now fixed (rust-lang#51952), so proc macros can generate modules. They still can't generate `macro_rules` items though due to unclear hygiene interactions.
bors added a commit that referenced this issue Aug 23, 2018
Stabilize a few secondary macro features

- `tool_attributes` - closes #44690
- `proc_macro_path_invoc` - this feature was created due to issues with tool attributes (#51277), those issues are now fixed (#52841)
- partially `proc_macro_gen` - this feature was created due to issue #50504, the issue is now fixed (#51952), so proc macros can generate modules. They still can't generate `macro_rules` items though due to unclear hygiene interactions.
pgab pushed a commit to GiGainfosystems/diesel-oci that referenced this issue Sep 5, 2018
* removed some (useless?) output
* also ran clippy, note there is a warning about:
```
warning: cannot find type `As` in this scope
  --> src/oracle/query_builder/alias.rs:17:24
   |
17 | #[derive(Debug, Clone, QueryId)]
   |                        ^^^^^^^ names from parent modules are not accessible without an explicit import
   |
   = note: #[warn(proc_macro_derive_resolution_fallback)] on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #50504 <rust-lang/rust#50504>
```
which seems to be fixed with diesel-rs/diesel#1787 which has not been released yet
@xpe
Copy link

xpe commented Sep 9, 2018

@apiraino I'd like to add a small but important correction to what you wrote, which suggested using an outer attribute, prefixed with #. Here is the proper syntax to use at the top of a file to disable the warning:

#![allow(proc_macro_derive_resolution_fallback)]

This uses an inner attribute, prefixed with #!, which includes the bang.

@Boscop
Copy link

Boscop commented Sep 24, 2018

Btw, I'm getting these kind of warnings also for diesel's AsChangeset, Insertable, Associations, Identifiable, Queryable and QueryableByName.

@kotauskas
Copy link

I feel like there's a bug with the lint itself. My proc macro expands to the following (example taken from its unit test, slightly modified to demonstrate the problem):

// Not a part of actual macro output, but is instead present at call site. I'm parsing the input struct's type
// specificially (the macro is a derive macro), so it's fine to avoid fully qualifying it, since use super::*;
// will take care of everything.
use std::time::SystemTime; 
mod entries {
    use super::*;
    #[doc = "The entry identifier type for the `field` field in the `MyConfigTable` config table."]
    pub enum Field {}
    impl ::snec::Entry for Field {
        type Data = SystemTime; // This is not in the prelude 
        const NAME: &'static str = "field";
    }
};
...

The use super::*; there is what is expected to make the name resolution work correctly instead of failing because of the module. The macro's output compiles just fine, but produces the lint.

Even though I'm completely not familiar with the implementation of the lint itself, I suspect that the culprit is that it simply checks for relative names which would are valid outside the module. I don't know whether it's implemented like this though, that's just a wild guess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros-1.2 Area: Declarative macros 1.2 A-macros-2.0 Area: Declarative macros 2.0 (#39412) C-bug Category: This is a bug.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants