bevy_lint/
callback.rs

1//! The [`BevyLintCallback`] definition and supporting code.
2
3use std::sync::atomic::{AtomicPtr, Ordering};
4
5use rustc_driver::Callbacks;
6use rustc_interface::interface::Config;
7use rustc_lint_defs::RegisteredTools;
8use rustc_middle::ty::TyCtxt;
9use rustc_session::utils::was_invoked_from_cargo;
10use rustc_span::{Ident, Symbol};
11
12/// A pointer to the original [`registered_tools()`](TyCtxt::registered_tools) query function.
13///
14/// # Safety
15///
16/// This pointer must be of the type [`fn(TyCtxt<'tcx>, ()) -> RegisteredTools`](fn).
17static ORIGINAL_REGISTERED_TOOLS: AtomicPtr<()> = {
18    fn default(_: TyCtxt<'_>, _: ()) -> RegisteredTools {
19        unreachable!("This function will be overwritten when `BevyLintCallback::config()` is run.");
20    }
21
22    AtomicPtr::new(default as *mut ())
23};
24
25/// The `rustc` [`Callbacks`] that register Bevy's lints.
26pub struct BevyLintCallback;
27
28impl Callbacks for BevyLintCallback {
29    fn config(&mut self, config: &mut Config) {
30        crate::config::load_config(config);
31
32        // Add `--cfg bevy_lint` so programs can conditionally configure lints.
33        config.crate_cfg.push("bevy_lint".to_string());
34
35        // We're overwriting `register_lints`, but we don't want to completely delete the original
36        // function. Instead, we save it so we can call it ourselves inside its replacement.
37        let previous = config.register_lints.take();
38
39        config.register_lints = Some(Box::new(move |session, store| {
40            // If there was a previous `register_lints`, call it first.
41            if let Some(previous) = &previous {
42                (previous)(session, store);
43            }
44
45            crate::lints::register(store);
46        }));
47
48        config.override_queries = Some(|_session, providers| {
49            // Save the original query so we can access it later.
50            ORIGINAL_REGISTERED_TOOLS.store(
51                providers.queries.registered_tools as *mut (),
52                Ordering::Relaxed,
53            );
54
55            // Overwrite the query with our own custom version.
56            providers.queries.registered_tools = registered_tools;
57        });
58
59        // Clone the input so we can `move` it into our custom `psess_created()`.
60        let input = config.input.clone();
61
62        config.psess_created = Some(Box::new(move |psess| {
63            if !was_invoked_from_cargo() {
64                return;
65            }
66
67            let file_depinfo = psess.file_depinfo.get_mut();
68
69            for workspace in [false, true] {
70                // Get the paths to the crate or workspace `Cargo.toml`, if they exist.
71                let manifest_path = crate::utils::cargo::locate_manifest(&input, workspace);
72
73                // If locating the manifest was successful, try to convert the path into a UTF-8
74                // string that we can intern.
75                if let Ok(path) = manifest_path
76                    && let Some(path) = path.to_str()
77                {
78                    // Insert the manifest path into `file_depinfo`. Now if the manifest is
79                    // changed, checks will re-run.
80                    file_depinfo.insert(Symbol::intern(path));
81                }
82            }
83        }));
84
85        // There shouldn't be any existing extra symbols, as we should be the only callback
86        // overriding them.
87        debug_assert!(config.extra_symbols.is_empty());
88
89        // Give the compiler a list of extra `Symbol`s to intern ahead of time. This helps us avoid
90        // calling `Symbol::intern()` while linting. See the `sym` module for a more detailed
91        // explanation.
92        config.extra_symbols = crate::sym::extra_symbols();
93    }
94}
95
96/// A custom version of the [`registered_tools()`](TyCtxt::registered_tools) query that
97/// automatically adds "bevy" as a tool.
98fn registered_tools<'tcx>(tcx: TyCtxt<'tcx>, _: ()) -> RegisteredTools {
99    // Fetch the original version of the query.
100    //
101    // SAFETY: The pointer is guaranteed to be a `fn(TyCtxt<'tcx>, ()) -> RegisteredTools` as per
102    // `ORIGINAL_REGISTERED_TOOLS`'s safety docs.
103    let original: fn(TyCtxt<'tcx>, ()) -> RegisteredTools =
104        unsafe { std::mem::transmute(ORIGINAL_REGISTERED_TOOLS.load(Ordering::Relaxed)) };
105
106    let mut tools = (original)(tcx, ());
107
108    tools.insert(Ident::from_str("bevy"));
109
110    tools
111}