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}