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 receives warnings only when used within macro_rules! macros #77973

Open
taiki-e opened this issue Oct 15, 2020 · 4 comments
Open

proc-macro receives warnings only when used within macro_rules! macros #77973

taiki-e opened this issue Oct 15, 2020 · 4 comments
Labels
A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) C-bug Category: This is a bug.

Comments

@taiki-e
Copy link
Member

taiki-e commented Oct 15, 2020

A proc-macro that generates code with the partial same span as the input, receive warnings in the generated code only when used within macro_rules! macros. (does not receive warnings if used outside macro_rules! macros.)

This originally reported in taiki-e/pin-project#298 (playground).

Repro:

Cargo.toml:

[package]
name = "repro"
version = "0.0.0"
authors = ["Taiki Endo <[email protected]>"]
edition = "2018"
publish = false

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = ["full"] }

src/lib.rs:

extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;

#[proc_macro_attribute]
pub fn repro(_arg: TokenStream, input: TokenStream) -> TokenStream {
    let item: syn::ItemStruct = syn::parse_macro_input!(input);

    let name = format!("{}2", &item.ident);
    let name = Ident::new(&name, Span::call_site());
    let vis = &item.vis;

    TokenStream::from(quote! {
        #item
        // can be avoid by adding `#[allow(missing_debug_implementations)]`
        // or change `vis` to call-site span
        #vis struct #name {
            _f: ()
        }
    })
}

tests/test.rs:

#![warn(missing_debug_implementations)]

#[repro::repro] // <- no warning
#[derive(Debug)]
pub struct A {
    _f: (),
}

macro_rules! mac {
    () => {
        #[repro::repro] //~WARN type does not implement `Debug`
        #[derive(Debug)]
        pub struct B {
            _f: (),
        }
    };
}

mac!();

This example uses missing_debug_implementations, but the same problem occurred with unreachable_pub, variant_size_differences, clippy::missing_const_for_fn etc., so it doesn't seem to be specific to some lint.

Meta

It can be reproduced at least in 1.31, stable (1.47), beta (1.48), nightly-2020-10-15.

rustc --version --verbose:

rustc 1.49.0-nightly (e160e5cb8 2020-10-14)
binary: rustc
commit-hash: e160e5cb80652bc2afe74cb3affbe35b74243ea9
commit-date: 2020-10-14
host: x86_64-apple-darwin
release: 1.49.0-nightly
LLVM version: 11.0
@taiki-e

This comment has been minimized.

@rustbot rustbot added A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) labels Oct 15, 2020
@jonas-schievink
Copy link
Contributor

The intended behavior of these lints is that they don't show up when the item was generated by a proc. macro, so the second example where it goes through a macro_rules! macro should also not display the lint.

@Aaron1011
Copy link
Member

Aaron1011 commented Oct 15, 2020

Here's what seems to be happening:

  1. The missing_debug_implementations lint uses the span of the entire item (see this example.
  2. The item synthesized by the attribute macro uses a combination of Spans - the vis span is located at the visibility in the original item, while the name is located at the call site (the entire attribute invocation). These spans can't be meaningfully joined together, so the parser ends up truncating the span to just use the span of vis.
  3. When we emit a lint, we check if the span occurs in an external macro:
    if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
    // Any suggestions made here are likely to be incorrect, so anything we
    // emit shouldn't be automatically fixed by rustfix.
    err.allow_suggestions(false);
    // If this is a future incompatible lint it'll become a hard error, so
    // we have to emit *something*. Also, if this lint occurs in the
    // expansion of a macro from an external crate, allow individual lints
    // to opt-out from being reported.
    if future_incompatible.is_none() && !lint.report_in_external_macro {
    err.cancel();
    // Don't continue further, since we don't want to have
    // `diag_span_note_once` called for a diagnostic that isn't emitted.
    return;
    }
    }

Since our span has been truncated to just include vis, our span comes directly from the macro_rules! invocation - we have no idea that a proc macro was involved. Therefore, we treat this span as not coming from an external macro, and emit the lint.

@Aaron1011
Copy link
Member

Aaron1011 commented Oct 15, 2020

Unfortunately, I don't think there's too much that can be done on the rustc side. Proc-macros can concatenate tokens with completely incompatible spans (e.g. different locations in the file, or even different files entirely), and the parser has to make some decision about how to join them. When the hygiene differs (as in this example), it gets even tricker.

I think the best solution would be to perform any semantic checks (e.g. whether a span comes from an external macro) on the span of a single identifier, whenever possible. In this case, we should be checking the span of the item's identifier, rather than the span of the entire item.

This should result in the correct behavior for most proc-macros - if the item name is generated by the proc-macro, it's unlikely that we'll want to lint on it.

taiki-e added a commit to taiki-e/pin-project that referenced this issue Oct 15, 2020
bors bot added a commit to taiki-e/pin-project that referenced this issue Oct 15, 2020
298: Fix warnings when #[pin_project] used within macro_rules! macros r=taiki-e a=taiki-e

When `#[pin_project]` attribute used within `macro_rules!` macros, the generated code receives warnings.

```rust
#![warn(missing_debug_implementations)]

#[pin_project::pin_project] // <- no warning
#[derive(Debug)]
pub struct A {
    #[pin]
    _f: (),
}

macro_rules! mac {
    () => {
        #[pin_project::pin_project] //~WARN type does not implement `Debug`
        #[derive(Debug)]
        pub struct B {
            #[pin]
            _f: (),
        }
    };
}

mac!();
```

[playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fb33b572fc967f70710edb5026083266)

Refs: rust-lang/rust#77973

Co-authored-by: Taiki Endo <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) C-bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

4 participants