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

Handle macros in lints #263

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions bevy_lint/src/lints/insert_event_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@ use crate::{
};
use clippy_utils::{
diagnostics::span_lint_and_sugg,
source::{snippet, snippet_with_applicability},
source::{snippet, snippet_with_applicability, HasSession},
sym,
ty::match_type,
};
use rustc_errors::Applicability;
use rustc_hir::{Expr, GenericArg, GenericArgs, Path, PathSegment, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{Ty, TyKind};
use rustc_middle::{
lint::in_external_macro,
ty::{Ty, TyKind},
};
use rustc_span::Symbol;
use std::borrow::Cow;

Expand All @@ -68,6 +71,11 @@ declare_bevy_lint_pass! {

impl<'tcx> LateLintPass<'tcx> for InsertEventResource {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
// skip expressions that originate from external macros
if in_external_macro(cx.sess(), expr.span) {
return;
}

// Find a method call.
if let Some(method_call) = MethodCall::try_from(cx, expr) {
// Get the type for `src` in `src.method()`. We peel all references because the type
Expand Down
15 changes: 10 additions & 5 deletions bevy_lint/src/lints/insert_unit_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@
//! # bevy::ecs::system::assert_is_system(spawn_decal);
//! ```

use clippy_utils::{diagnostics::span_lint_hir_and_then, sym, ty::match_type};
use clippy_utils::{diagnostics::span_lint_hir_and_then, source::HasSession, sym, ty::match_type};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{Ty, TyKind};
use rustc_middle::{
lint::in_external_macro,
ty::{Ty, TyKind},
};
use rustc_span::Symbol;

use crate::{declare_bevy_lint, declare_bevy_lint_pass, utils::hir_parse::MethodCall};
Expand Down Expand Up @@ -95,9 +98,11 @@ impl<'tcx> LateLintPass<'tcx> for InsertUnitBundle {

let src_ty = cx.typeck_results().expr_ty(receiver).peel_refs();

// If the method call was not to `Commands::spawn()` we skip it.
if !(match_type(cx, src_ty, &crate::paths::COMMANDS)
&& method_path.ident.name == self.spawn)
// If the method call was not to `Commands::spawn()` or originates from an external macro,
// we skip it.
if !(in_external_macro(cx.sess(), span)
|| match_type(cx, src_ty, &crate::paths::COMMANDS)
&& method_path.ident.name == self.spawn)
{
return;
}
Expand Down
4 changes: 3 additions & 1 deletion bevy_lint/src/lints/main_return_without_appexit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@

use crate::{declare_bevy_lint, declare_bevy_lint_pass, utils::hir_parse::MethodCall};
use clippy_utils::{
diagnostics::span_lint_hir_and_then, is_entrypoint_fn, sym, ty::match_type,
diagnostics::span_lint_hir_and_then, is_entrypoint_fn, source::HasSession, sym, ty::match_type,
visitors::for_each_expr,
};
use rustc_errors::Applicability;
use rustc_hir::{def_id::LocalDefId, intravisit::FnKind, Body, FnDecl, FnRetTy, Ty, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_span::{Span, Symbol};
use std::ops::ControlFlow;

Expand Down Expand Up @@ -87,6 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for MainReturnWithoutAppExit {
..
}) = MethodCall::try_from(cx, expr)
&& method_path.ident.name == self.run
&& !in_external_macro(cx.sess(), expr.span)
{
// Get the type of `src` for `src.run()`. We peel away all references because
// both `App` and `&mut App` are allowed.
Expand Down
24 changes: 15 additions & 9 deletions bevy_lint/src/lints/missing_reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@
//! ```

use crate::{declare_bevy_lint, declare_bevy_lint_pass};
use clippy_utils::{def_path_res, diagnostics::span_lint_hir_and_then, sugg::DiagExt};
use clippy_utils::{
def_path_res, diagnostics::span_lint_hir_and_then, source::HasSession, sugg::DiagExt,
};
use rustc_errors::Applicability;
use rustc_hir::{
def::{DefKind, Res},
HirId, Item, ItemKind, Node, OwnerId, QPath, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_middle::{lint::in_external_macro, ty::TyCtxt};
use rustc_span::Span;

declare_bevy_lint! {
Expand Down Expand Up @@ -88,16 +90,18 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect {
(resources, "Resource", "a resource"),
] {
for without_reflect in checked_trait {
// Skip if a types originates from a foreign crate's macro
if in_external_macro(cx.sess(), without_reflect.item_span) {
continue;
}

span_lint_hir_and_then(
cx,
MISSING_REFLECT.lint,
// This tells `rustc` where to search for `#[allow(...)]` attributes.
without_reflect.hir_id,
without_reflect.item_span,
format!(
"defined {} without a `Reflect` implementation",
message_phrase,
),
format!("defined {message_phrase} without a `Reflect` implementation"),
|diag| {
diag.span_note(
without_reflect.impl_span,
Expand All @@ -109,9 +113,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect {
"`Reflect` can be automatically derived",
"#[derive(Reflect)]",
// This can usually be automatically applied by `rustfix` without
// issues, unless one of the fields of the struct does not implement
// `Reflect` (see #141). This suggestion may result in two consecutive
// `#[derive(...)]` attributes, but `rustfmt` merges them afterwards.
// issues, unless one of the fields of the struct does not
// implement `Reflect` (see #141).
// This suggestion may result in two consecutive
// `#[derive(...)]` attributes, but `rustfmt` merges them
// afterwards.
Applicability::MaybeIncorrect,
);
},
Expand Down
9 changes: 7 additions & 2 deletions bevy_lint/src/lints/panicking_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ use crate::{
};
use clippy_utils::{
diagnostics::span_lint_and_help,
source::{snippet, snippet_opt},
source::{snippet, snippet_opt, HasSession},
ty::match_type,
};
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_middle::ty::Ty;
use rustc_middle::{lint::in_external_macro, ty::Ty};
use rustc_span::Symbol;

declare_bevy_lint! {
Expand All @@ -108,6 +108,11 @@ declare_bevy_lint_pass! {

impl<'tcx> LateLintPass<'tcx> for PanickingMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
// skip expressions that originate from external macros
if in_external_macro(cx.sess(), expr.span) {
return;
}

// Check if `expr` is a method call
if let Some(MethodCall {
span,
Expand Down
10 changes: 9 additions & 1 deletion bevy_lint/src/lints/plugin_not_ending_in_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@
//! ```

use crate::{declare_bevy_lint, declare_bevy_lint_pass};
use clippy_utils::{diagnostics::span_lint_hir_and_then, match_def_path, path_res};
use clippy_utils::{
diagnostics::span_lint_hir_and_then, match_def_path, path_res, source::HasSession,
};
use rustc_errors::Applicability;
use rustc_hir::{def::Res, HirId, Item, ItemKind, OwnerId};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_span::symbol::Ident;

declare_bevy_lint! {
Expand Down Expand Up @@ -95,6 +98,11 @@ impl<'tcx> LateLintPass<'tcx> for PluginNotEndingInPlugin {
return;
};

// skip lint if the struct was defined in an external macro
if in_external_macro(cx.sess(), struct_span) {
return;
}

// If the type's name ends in "Plugin", exit.
if struct_name.as_str().ends_with("Plugin") {
return;
Expand Down
127 changes: 127 additions & 0 deletions bevy_lint/tests/ui/auxiliary/proc_macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Utility to test the behavior of lints when the code originates from an external macro.
//!
//! From: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui/auxiliary/proc_macros.rs
extern crate proc_macro;
use proc_macro::{
token_stream::IntoIter,
Delimiter::{self, Parenthesis},
Group, Ident, Literal, Punct,
Spacing::{self, Alone},
Span, TokenStream, TokenTree as TT,
};

type Result<T> = core::result::Result<T, TokenStream>;

/// Token used to escape the following token from the macro's span rules.
const ESCAPE_CHAR: char = '$';

/// Takes a sequence of tokens and return the tokens with the span set such that they appear to be
/// from an external macro. Tokens may be escaped with either `$ident` or `$(tokens)`.
#[proc_macro]
pub fn external(input: TokenStream) -> TokenStream {
let mut res = TokenStream::new();
if let Err(e) = write_with_span(Span::mixed_site(), input.into_iter(), &mut res) {
e
} else {
res
}
}

/// Make a `compile_error!` pointing to the given span.
fn make_error(msg: &str, span: Span) -> TokenStream {
TokenStream::from_iter([
TT::Ident(Ident::new("compile_error", span)),
TT::Punct(punct_with_span('!', Alone, span)),
TT::Group({
let mut msg = Literal::string(msg);
msg.set_span(span);
group_with_span(
Parenthesis,
TokenStream::from_iter([TT::Literal(msg)]),
span,
)
}),
])
}

/// Copies all the tokens, replacing all their spans with the given span. Tokens can be escaped
/// either by `$ident` or `$(tokens)`.
fn write_with_span(s: Span, mut input: IntoIter, out: &mut TokenStream) -> Result<()> {
while let Some(tt) = input.next() {
match tt {
TT::Punct(p) if p.as_char() == ESCAPE_CHAR => {
expect_tt(
input.next(),
|tt| match tt {
tt @ (TT::Ident(_) | TT::Literal(_)) => {
out.extend([tt]);
Some(())
}
TT::Punct(mut p) if p.as_char() == ESCAPE_CHAR => {
p.set_span(s);
out.extend([TT::Punct(p)]);
Some(())
}
TT::Group(g) if g.delimiter() == Parenthesis => {
out.extend([TT::Group(group_with_span(
Delimiter::None,
g.stream(),
g.span(),
))]);
Some(())
}
_ => None,
},
"an ident, a literal, or parenthesized tokens",
p.span(),
)?;
}
TT::Group(g) => {
let mut stream = TokenStream::new();
write_with_span(s, g.stream().into_iter(), &mut stream)?;
out.extend([TT::Group(group_with_span(g.delimiter(), stream, s))]);
}
mut tt => {
tt.set_span(s);
out.extend([tt]);
}
}
}
Ok(())
}

fn expect_tt<T>(
tt: Option<TT>,
f: impl FnOnce(TT) -> Option<T>,
expected: &str,
span: Span,
) -> Result<T> {
match tt {
None => Err(make_error(
&format!("unexpected end of input, expected {expected}"),
span,
)),
Some(tt) => {
let span = tt.span();
match f(tt) {
Some(x) => Ok(x),
None => Err(make_error(
&format!("unexpected token, expected {expected}"),
span,
)),
}
}
}
}

fn punct_with_span(c: char, spacing: Spacing, span: Span) -> Punct {
let mut p = Punct::new(c, spacing);
p.set_span(span);
p
}

fn group_with_span(delimiter: Delimiter, stream: TokenStream, span: Span) -> Group {
let mut g = Group::new(delimiter, stream);
g.set_span(span);
g
}
17 changes: 17 additions & 0 deletions bevy_lint/tests/ui/insert_event_resource/main.fixed
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
//@aux-build:../auxiliary/proc_macros.rs
#![feature(register_tool)]
#![register_tool(bevy)]
#![deny(bevy::insert_event_resource)]

use bevy::prelude::*;
extern crate proc_macros;
use proc_macros::external;

#[derive(Event)]
struct Foo;

macro_rules! local_macro {
() => {
App::new().init_resource::<Events<Foo>>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why rustfix does not fix the macro.. the span is correct but the suggestion is not applied

//~^ ERROR: called `App::init_resource::<Events<Foo>>()` instead of
// `App::add_event::<Foo>()`
};
}

fn main() {
let mut app = App::new();
App::new().add_event::<Foo>();
Expand All @@ -33,4 +44,10 @@ fn main() {
// Ensure the lint can be muted by annotating the expression.
#[allow(bevy::insert_event_resource)]
App::new().init_resource::<Events<Foo>>();

external!({
let mut app = App::new();
App::new().init_resource::<Events<Foo>>();
});
local_macro!();
}
17 changes: 17 additions & 0 deletions bevy_lint/tests/ui/insert_event_resource/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
//@aux-build:../auxiliary/proc_macros.rs
#![feature(register_tool)]
#![register_tool(bevy)]
#![deny(bevy::insert_event_resource)]

use bevy::prelude::*;
extern crate proc_macros;
use proc_macros::external;

#[derive(Event)]
struct Foo;

macro_rules! local_macro {
() => {
App::new().init_resource::<Events<Foo>>();
//~^ ERROR: called `App::init_resource::<Events<Foo>>()` instead of
// `App::add_event::<Foo>()`
};
}

fn main() {
let mut app = App::new();
App::new().init_resource::<Events<Foo>>();
Expand All @@ -33,4 +44,10 @@ fn main() {
// Ensure the lint can be muted by annotating the expression.
#[allow(bevy::insert_event_resource)]
App::new().init_resource::<Events<Foo>>();

external!({
let mut app = App::new();
App::new().init_resource::<Events<Foo>>();
});
local_macro!();
}
Loading
Loading