Rust: proc-macro receives warnings only when used within `macro_rules!` macros

Created on 15 Oct 2020  路  4Comments  路  Source: rust-lang/rust

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 https://github.com/taiki-e/pin-project/pull/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
A-lint A-macros C-bug

All 4 comments

@rustbot modify labels: +A-macros +A-lint

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.

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: https://github.com/rust-lang/rust/blob/7f587168102498a488abf608a86c7fdfa62fb7bb/compiler/rustc_middle/src/lint.rs#L245-L260

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.

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.

Was this page helpful?
0 / 5 - 0 ratings