Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…lyzer/issues/14772. Docs + TODO's
  • Loading branch information
peter-lyons-kehl committed May 26, 2023
1 parent 203709e commit 272afff
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 45 deletions.
17 changes: 17 additions & 0 deletions MACRO_INVOCATION_CHAIN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Macro invocations

The following is a macro (and related non-macro function) invocation chain that generates attribute
macros for prefixed (`clippy::` and `rustdoc::`) lints. Standard (prefixless, `rustc`) invocation
chain is similar.

```txt
macro_rules! allow_prefixed::prefixed_lint!
- proc_macro ::allow_internal::check_that_prefixed_lint_exists!($lint_prefix, $lint_name);
- proc_macro ::allow_internal::generate_allow_attribute_macro_prefixed!(...);
- fn ::allow_internal::generate_allow_attribute_macro_from_iter(...)
- macro_rules! (allow_prefixed::) generate_allow_attribute_macro_internal_prefixed!
- mac_rul! (allow_prefixed::) generate_allow_attribute_macro_internal_with_given_docs_prefixed
- #[doc = $doc]
#[proc_macro_attribute]
pub fn $new_macro_name(..){..}
```
2 changes: 1 addition & 1 deletion allow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! - prefixless (rustc/standard) lints are at the top level and also grouped (duplicated) under
//! `rustc::` module.
//! - `clippy` and `rustdoc` lints are grouped under clippy:: and rustdoc:: modules.
#![doc(html_no_source)]
#![forbid(unknown_lints)]
#![cfg_attr(has_rustdoc_lints, deny(rustdoc::missing_docs))]
#![cfg_attr(can_check_doc_attributes, deny(invalid_doc_attributes))]
Expand Down
87 changes: 69 additions & 18 deletions allow_internal/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! NOT for public use. Only to be used by `allow_prefixed` crate.
#![doc(html_no_source)]
#![deny(missing_docs)]

use proc_macro::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree};
Expand Down Expand Up @@ -30,13 +30,13 @@ pub fn path_to_str_literal(lint_path_input: TokenStream) -> TokenStream {
TokenStream::from(TokenTree::Literal(Literal::string(&literal)))
}

/// Generate the code that invokes `generate_allow_attribute_macro_definition_internal` macro, and
/// Generate the code that invokes `generate_allow_attribute_macro_internal` macro, and
/// as a result it defines an attribute macro for the given lint.
///
/// Param `pass_through` indicates whether the result attribute macro should just pass through its
/// input without injecting `#[allow(lint-name-here)]`. Used for removed/deprecated lints - for
/// backwards compatibility.
fn generate_allow_attribute_macro_definition_from_iter(
fn generate_allow_attribute_macro_from_iter(
lint_prefix: Option<Ident>,
mut lint_name_input: impl Iterator<Item = TokenTree>,
pass_through: bool,
Expand Down Expand Up @@ -73,34 +73,40 @@ fn generate_allow_attribute_macro_definition_from_iter(

// Note: Do NOT prefix the generated Rust invocation (from `allow_prefixed` itself) in the
// following with `crate::` like:
// `crate::generate_allow_attribute_macro_definition_internal!(...);` That fails!
// `crate::generate_allow_attribute_macro_internal_prefixed!(...);` That fails!
let generate_internal = TokenTree::Ident(Ident::new(
"generate_allow_attribute_macro_definition_internal",
if lint_prefix.is_some() {
"generate_allow_attribute_macro_internal_prefixed"
} else {
"generate_allow_attribute_macro_internal_standard"
},
Span::call_site(),
));
let exclamation = proc_builder::get_punct_joint('!');

let mut generate_internal_params = Vec::with_capacity(6);
let mut generate_internal_params = Vec::with_capacity(6); //@TODO capacity
// @TODO
// 1. change type of lint_prefix to Option<TokenTree>
// 2. clone() it and run a check that it contains exactly one Ident
// 3. extract the Ident in the 2nd if {...} below.
// 3. change the 1st if {...} below to use that TokenTree.
// 4. extract the Ident in the 2nd if {...} below - use the TokenTree from the Option param
// instead.
if let Some(lint_prefix) = &lint_prefix {
generate_internal_params.push(TokenTree::Ident(lint_prefix.clone()));
generate_internal_params.push(proc_builder::get_colon_joint());
generate_internal_params.push(proc_builder::get_colon_alone());
generate_internal_params.push(proc_builder::get_punct_alone(','));
}
generate_internal_params.push(lint_name.clone());
generate_internal_params.push(proc_builder::get_punct_alone(','));

if let Some(lint_prefix) = lint_prefix.clone() {
let mut generated_proc_macro_name = lint_name;
if let Some(lint_prefix) = lint_prefix {
//@TODO remove .clone()
let mut lint_prefix = lint_prefix.to_string();
lint_prefix.push('_');
lint_prefix.extend(lint_name.to_string().chars());
lint_name = TokenTree::Ident(Ident::new(&lint_prefix, Span::call_site()));
lint_prefix.extend(generated_proc_macro_name.to_string().chars());
generated_proc_macro_name = TokenTree::Ident(Ident::new(&lint_prefix, Span::call_site()));
}
generate_internal_params.push(lint_name);
generate_internal_params.push(generated_proc_macro_name);
generate_internal_params.push(proc_builder::get_punct_alone(','));
generate_internal_params.push(TokenTree::Ident(Ident::new(
if pass_through { "true" } else { "false" },
Expand All @@ -123,13 +129,13 @@ fn generate_allow_attribute_macro_definition_from_iter(
//TokenStream::from_iter(tokens) // use if we upgrade Rust min. version, or edition to 2021
}

/// Like [`generate_allow_attribute_macro_definition_prefixed!`], but generate a macro for a given
/// Like [`generate_allow_attribute_macro_prefixed!`], but generate a macro for a given
/// standard (prefixless) `rustc` lint. The macro name itself will be the same as the lint name.
#[proc_macro]
pub fn generate_allow_attribute_macro_definition_standard(
pub fn generate_allow_attribute_macro_standard(
lint_name_input: TokenStream,
) -> TokenStream {
generate_allow_attribute_macro_definition_from_iter(None, lint_name_input.into_iter(), false)
generate_allow_attribute_macro_from_iter(None, lint_name_input.into_iter(), false)
}

/// Input: prefix, lint_name. Output: Attribute macro code that (when applied) injects
Expand All @@ -140,7 +146,7 @@ pub fn generate_allow_attribute_macro_definition_standard(
/// The macro name will be based on the given prefix and lint name, concatenated with an underscore
/// in between.
#[proc_macro]
pub fn generate_allow_attribute_macro_definition_prefixed(
pub fn generate_allow_attribute_macro_prefixed(
prefix_and_lint_name: TokenStream,
) -> TokenStream {
let mut prefix_and_lint_name = prefix_and_lint_name.into_iter();
Expand Down Expand Up @@ -188,7 +194,7 @@ pub fn generate_allow_attribute_macro_definition_prefixed(
prefix_and_lint_name.collect::<Vec<_>>()
);*/

generate_allow_attribute_macro_definition_from_iter(Some(prefix), prefix_and_lint_name, false)
generate_allow_attribute_macro_from_iter(Some(prefix), prefix_and_lint_name, false)
}

/// Generate code like: `#[allow(prefix::lint_name)] const _: () = ();`. Use it together with
Expand All @@ -200,6 +206,8 @@ pub fn generate_allow_attribute_macro_definition_prefixed(
///
/// When calling this from a `macro_rules!`, you want to capture the prefix and lint name as `tt`
/// (and NOT as `ident`) metavariable. Otherwise use it with `defile` crate.
///
/// Workaround & See https://github.com/rust-lang/rust/issues/109881.
#[proc_macro]
pub fn check_that_prefixed_lint_exists(
prefix_and_lint_name_without_double_colon: TokenStream,
Expand Down Expand Up @@ -267,3 +275,46 @@ pub fn check_that_prefixed_lint_exists(
token_streams.push(TokenStream::from(proc_builder::get_punct_alone(';')));
auxiliary::token_streams_to_stream(&token_streams)
}

macro_rules! empty_proc_macro_gen {
($macro_name:tt, $subdoc_literal:tt) => {
// Generated macro $macro_name and its documentation
//#[doc = stringify!(Generated macro: $macro_name based on ...)]
//#[doc = "generated proc_mac with a #[doc = ...]-based documentation. This documentation DOES show up in rust-analyzer."]
#[doc = "Alias to #[allow(clippy::"]
//#[doc = $macro_name] // -- this would be missing enclosing quotes "..."
/// to $macro_name
#[doc = ")]"]
#[proc_macro]
pub fn $macro_name(
_input: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
::proc_macro::TokenStream::new()
}
};
}

empty_proc_macro_gen!(generated_proc_macro, "prefix:lint");

// Can't use stringify! here:
//
//empty_proc_macro_generator!(generated_proc_macro, stringify!(prefix:lint));

// https://github.com/rust-lang/rust-analyzer/issues/8092
//
// https://github.com/rust-lang/rust-analyzer/issues/14772

/// Helper.
/// TODO if we parse: Requires `use proc_macro::{TokenStream ETC.}` at the caller scope.
#[proc_macro]
pub fn generate_proc_mac_with_doc_attrib(
_input: TokenStream,
) -> TokenStream {
"#[proc_macro]#[doc = \"Documented by a `#[doc = \\\"...\\\" ]` attribute.\" ]
pub fn generated_proc_mac_with_doc_attrib(
_input: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
::proc_macro::TokenStream::new()
}
".parse().unwrap()
}
102 changes: 81 additions & 21 deletions allow_prefixed/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// mistake in specifying Rust version ranges for specific lint macros in `allow_prefixed`.
//
// Instead of `#[forbid(unknown_lints)]` here, we have it in tests.
#![doc(html_no_source)]
#![deny(unknown_lints, missing_docs)]
#![cfg_attr(can_check_doc_attributes, deny(invalid_doc_attributes))]
#![deny(unused_doc_comments)]
Expand Down Expand Up @@ -52,27 +53,20 @@ mod restrict_floating_toolchain_pof {
}
}*/

/// NOT for public use. See [generate_allow_attribute_macro_definition_internal].
/// NOT for public use. See [generate_allow_attribute_macro_internal].
///
/// $doc is used for rustdoc of the generated proc macro; it must be an `&str`-like literal or
/// expression - for example, a result of `stringify!`
#[allow(unused_macros)]
macro_rules! generate_allow_attribute_macro_definition_internal_with_given_docs {
// The following accepts both:
// - $lint_path:tt or
// - $lint_path:path
( $lint_path:tt, $new_macro_name:ident, $pass_through:expr, $doc:expr ) => {
macro_rules! generate_allow_attribute_macro_internal_with_given_docs_standard {
// @TODO rename $lint_path to $lint_name
( $lint_path:tt, $new_macro_name:tt, $pass_through:expr, $doc:expr ) => {
#[doc = $doc]
//@TODO: concatenate several docs?
//#[doc = "Buf buf."]
#[proc_macro_attribute]
pub fn $new_macro_name(
given_attrs: ::proc_macro::TokenStream,
item_to_be_linted: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
// Clippy lints that have configuration (few of them) don't accept the config values as
// any attribute parameters. See
// https://doc.rust-lang.org/nightly/clippy/configuration.html.
assert!(
given_attrs.is_empty(),
"Do not pass any attribute parameters."
Expand All @@ -85,6 +79,7 @@ macro_rules! generate_allow_attribute_macro_definition_internal_with_given_docs
let streams = [
$crate::proc_builder::get_hash(),
$crate::proc_builder::brackets_allow_lint_path(
// @TODO consider: stringify!()
::allow_internal::path_to_str_literal!($lint_path),
),
item_to_be_linted,
Expand All @@ -100,9 +95,51 @@ macro_rules! generate_allow_attribute_macro_definition_internal_with_given_docs
};
}

/// Prefixed
#[allow(unused_macros)]
macro_rules! generate_allow_attribute_macro_internal_with_given_docs_prefixed {
( $lint_prefix:tt, $lint_name:tt, $new_macro_name:tt, $pass_through:expr, $doc:expr ) => {
#[doc = $doc]
#[proc_macro_attribute]
pub fn $new_macro_name(
given_attrs: ::proc_macro::TokenStream,
item_to_be_linted: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
assert!(
given_attrs.is_empty(),
"Do not pass any attribute parameters."
);
// The following if..else branching is optimized out in compile time.
if $pass_through {
item_to_be_linted
} else {
// TODO replace with the below if we upgrade Rust min. version, or edition to 2021
let streams = [
$crate::proc_builder::get_hash(),
$crate::proc_builder::brackets_allow_lint_path(
// stringify!(...).trim() is NOT enough, because Rust injects spaces around
// :: path separator.
&stringify!($lint_prefix::$lint_name)
.chars()
.filter(|c| !c.is_ascii_whitespace())
.collect::<String>(), //::allow_internal::path_to_str_literal!($lint_prefix::$lint_name),
),
item_to_be_linted,
];
auxiliary::token_streams_to_stream(&streams)
/*::proc_macro::TokenStream::from_iter([
$crate::proc_builder::get_hash(),
$crate::proc_builder::brackets_allow_lint(::allow_internal::path_to_str_literal!($lint_prefix::$lint_name)),
item,
])*/
}
}
};
}

/// NOT for public use. "Used" only by
/// [`allow_internal::generate_allow_attribute_macro_definition_standard`] and
/// [`allow_internal::generate_allow_attribute_macro_definition_prefixed`] macros. Those macros
/// [`allow_internal::generate_allow_attribute_macro_standard`] and
/// [`allow_internal::generate_allow_attribute_macro_prefixed`] macros. Those macros
/// don't invoke this one, but instead they generate code that invokes it.
///
/// This macro generates a definition of a `proc` attribute macro to allow (suppress a warning for)
Expand All @@ -115,20 +152,39 @@ macro_rules! generate_allow_attribute_macro_definition_internal_with_given_docs
/// unmodified. Used for backwards or future compatibility, where the lint doesn't exist anymore,
/// or doesn't exist yet, for the given Rust version.
#[cfg(attributes_can_invoke_macros)]
macro_rules! generate_allow_attribute_macro_definition_internal {
macro_rules! generate_allow_attribute_macro_internal_standard {
// The following refuses $lint_path:tt. It accepts $lint_path:path only.
( $lint_path:path, $new_macro_name:ident, $pass_through:expr ) => {
generate_allow_attribute_macro_definition_internal_with_given_docs!(
$lint_path,
( $lint_name:tt, $new_macro_name:tt, $pass_through:expr ) => {
generate_allow_attribute_macro_internal_with_given_docs_standard!(
$lint_name,
$new_macro_name,
$pass_through,
stringify!(Alias to #[allow($lint_path)].));
stringify!(Alias to #[allow($lint_name)].));
};
}
//@TODO FIX (for old Rust) LATER:
//
//#[cfg(not(attributes_can_invoke_macros))]
//
//macro_rules! generate_allow_attribute_macro_internal_standard {...}

#[cfg(attributes_can_invoke_macros)]
macro_rules! generate_allow_attribute_macro_internal_prefixed {
// The following refuses $lint_path:tt. It accepts $lint_path:path only.
( $lint_prefix:tt, $lint_name:tt, $new_macro_name:tt, $pass_through:expr ) => {
generate_allow_attribute_macro_internal_with_given_docs_prefixed!(
$lint_prefix,
$lint_name,
$new_macro_name,
$pass_through,
stringify!(Alias to #[allow($lint_prefix::$lint_name)].));
};
}
//@TODO FIX (for old Rust) LATER:
#[cfg(not(attributes_can_invoke_macros))]
macro_rules! generate_allow_attribute_macro_definition_internal {
( $lint_path:tt, $new_macro_name:ident, $pass_through:expr ) => {
generate_allow_attribute_macro_definition_internal_with_given_docs!(
macro_rules! generate_allow_attribute_macro_internal {
( $lint_path:tt, $new_macro_name:tt, $pass_through:expr ) => {
generate_allow_attribute_macro_internal_with_given_docs!(
$lint_path,
$new_macro_name,
$pass_through,
Expand Down Expand Up @@ -1052,3 +1108,7 @@ prefixed_lint!(clippy, zst_offset);
// ::allow_internal::check_that_prefixed_lint_exists!(clippy, bufo);
//
// standard_lint!(non_existing_std_lint);

allow_internal::generated_proc_macro!();

allow_internal::generate_proc_mac_with_doc_attrib!();
10 changes: 5 additions & 5 deletions allow_prefixed/src/wrapper_macros.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
macro_rules! standard_lint {
// the `const _` is to check that the lint name is valid. It gets checked with `cargo check`.
($lint_name:ident) => {
($lint_name:tt) => {
#[allow($lint_name)]
const _: () = ();
::allow_internal::generate_allow_attribute_macro_definition_standard!($lint_name);
::allow_internal::generate_allow_attribute_macro_standard!($lint_name);
};
}
macro_rules! prefixed_lint {
Expand All @@ -13,7 +13,7 @@ macro_rules! prefixed_lint {
($lint_prefix:tt, $lint_name:tt) => {
// Workaround https://github.com/rust-lang/rust/issues/109881:
::allow_internal::check_that_prefixed_lint_exists!($lint_prefix, $lint_name);
::allow_internal::generate_allow_attribute_macro_definition_prefixed!(
::allow_internal::generate_allow_attribute_macro_prefixed!(
$lint_prefix,
$lint_name
);
Expand All @@ -23,14 +23,14 @@ macro_rules! prefixed_lint {
macro_rules! standard_lint_versioned {
// We can't match major.minor.patch in macro_rules. So far all lints started at patch version
// being 0, so we omit it as a parameter.
($major_minor:tt, $lint_name:ident) => {
($major_minor:tt, $lint_name:tt) => {
#[rustversion::since($major_minor.0)]
standard_lint!($lint_name);
}; // @TODO initial version - deprecated (or removed?) version
}

macro_rules! standard_lint_nightly {
($lint_name:ident) => {
($lint_name:tt) => {
#[rustversion::nightly]
standard_lint!($lint_name);
};
Expand Down
2 changes: 2 additions & 0 deletions allow_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ pub fn unused() {
#[allow_unused_variables]
//#[allow_prefixed::sing_le]
fn _unused() {}

allow_prefixed::generated_proc_mac_with_doc_attrib!();

0 comments on commit 272afff

Please sign in to comment.