1use std::path::Path;
2
3use flux_common::{bug, cache::QueryCache, iter::IterExt, result::ResultExt};
4use flux_config::{self as config};
5use flux_errors::FluxSession;
6use flux_infer::fixpoint_encoding::FixQueryCache;
7use flux_metadata::CStore;
8use flux_middle::{
9 Specs,
10 def_id::MaybeExternId,
11 fhir,
12 global_env::GlobalEnv,
13 queries::{Providers, QueryResult},
14 timings,
15};
16use flux_refineck as refineck;
17use itertools::Itertools;
18use rustc_borrowck::consumers::ConsumerOptions;
19use rustc_driver::{Callbacks, Compilation};
20use rustc_errors::ErrorGuaranteed;
21use rustc_hir::{
22 def::DefKind,
23 def_id::{DefId, LOCAL_CRATE, LocalDefId},
24};
25use rustc_interface::interface::Compiler;
26use rustc_middle::{query, ty::TyCtxt};
27use rustc_session::config::OutputType;
28use rustc_span::FileName;
29
30use crate::{DEFAULT_LOCALE_RESOURCES, collector::SpecCollector};
31
32#[derive(Default)]
33pub struct FluxCallbacks;
34
35impl Callbacks for FluxCallbacks {
36 fn config(&mut self, config: &mut rustc_interface::interface::Config) {
37 assert!(config.override_queries.is_none());
38
39 config.override_queries = Some(|_, local| {
40 local.mir_borrowck = mir_borrowck;
41 });
42 assert!(config.extra_symbols.is_empty());
45 config.extra_symbols = flux_syntax::symbols::PREDEFINED_FLUX_SYMBOLS.to_vec();
46 }
47
48 fn after_analysis(&mut self, compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
49 self.verify(compiler, tcx);
50 if config::full_compilation() { Compilation::Continue } else { Compilation::Stop }
51 }
52}
53
54impl FluxCallbacks {
55 fn verify(&self, compiler: &Compiler, tcx: TyCtxt<'_>) {
56 if compiler.sess.dcx().has_errors().is_some() {
57 return;
58 }
59
60 let sess = FluxSession::new(
61 &tcx.sess.opts,
62 tcx.sess.psess.clone_source_map(),
63 rustc_errors::fallback_fluent_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false),
64 );
65
66 let mut providers = Providers::default();
67 flux_desugar::provide(&mut providers);
68 flux_fhir_analysis::provide(&mut providers);
69 providers.collect_specs = collect_specs;
70
71 let cstore = CStore::load(tcx, &sess);
72 let arena = fhir::Arena::new();
73 GlobalEnv::enter(tcx, &sess, Box::new(cstore), &arena, providers, |genv| {
74 let result = timings::enter(tcx, || check_crate(genv));
75 if result.is_ok() {
76 encode_and_save_metadata(genv);
77 }
78 });
79 sess.finish_diagnostics();
80 }
81}
82
83fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
84 tracing::info_span!("check_crate").in_scope(move || {
85 tracing::info!("Callbacks::check_wf");
86 let _ = genv.qualifiers().emit(&genv)?;
88 let _ = genv.normalized_defns(LOCAL_CRATE);
89
90 let mut ck = CrateChecker::new(genv);
91
92 let crate_items = genv.tcx().hir_crate_items(());
93
94 let dups = crate_items.definitions().duplicates().collect_vec();
95 if !dups.is_empty() {
96 bug!("TODO: {dups:#?}");
97 }
98 let result = crate_items
99 .definitions()
100 .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
101
102 ck.cache.save().unwrap_or(());
103
104 tracing::info!("Callbacks::check_crate");
105
106 result
107 })
108}
109
110fn collect_specs(genv: GlobalEnv) -> Specs {
111 match SpecCollector::collect(genv.tcx(), genv.sess()) {
112 Ok(specs) => specs,
113 Err(err) => {
114 genv.sess().abort(err);
115 }
116 }
117}
118
119fn encode_and_save_metadata(genv: GlobalEnv) {
120 if genv.tcx().crate_name(LOCAL_CRATE) == flux_syntax::symbols::sym::core {
127 return;
128 }
129
130 let tcx = genv.tcx();
134 if tcx
135 .output_filenames(())
136 .outputs
137 .contains_key(&OutputType::Metadata)
138 {
139 let path = flux_metadata::filename_for_metadata(tcx);
140 flux_metadata::encode_metadata(genv, path.as_path());
141 }
142}
143
144struct CrateChecker<'genv, 'tcx> {
145 genv: GlobalEnv<'genv, 'tcx>,
146 cache: FixQueryCache,
147}
148
149impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
150 fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
151 CrateChecker { genv, cache: QueryCache::load() }
152 }
153
154 fn matches_def(&self, def_id: MaybeExternId, def: &str) -> bool {
155 let def_path = self.genv.tcx().def_path_str(def_id.local_id());
157 def_path.contains(def)
158 }
159
160 fn matches_file_path<F>(&self, def_id: MaybeExternId, matcher: F) -> bool
161 where
162 F: Fn(&Path) -> bool,
163 {
164 let def_id = def_id.local_id();
165 let tcx = self.genv.tcx();
166 let span = tcx.def_span(def_id);
167 let sm = tcx.sess.source_map();
168 let FileName::Real(file_name) = sm.span_to_filename(span) else { return true };
169 let mut file_path = file_name.local_path_if_available();
170
171 if file_path.is_absolute() {
173 let working_dir = tcx.sess.opts.working_dir.local_path_if_available();
174 let Ok(p) = file_path.strip_prefix(working_dir) else { return true };
175 file_path = p;
176 }
177
178 matcher(file_path)
179 }
180
181 fn matches_pos(&self, def_id: MaybeExternId, line: usize, col: usize) -> bool {
182 let def_id = def_id.local_id();
183 let tcx = self.genv.tcx();
184 let hir_id = tcx.local_def_id_to_hir_id(def_id);
185 let body_span = tcx.hir_span_with_body(hir_id);
186 let source_map = tcx.sess.source_map();
187 let lo_pos = source_map.lookup_char_pos(body_span.lo());
188 let start_line = lo_pos.line;
189 let start_col = lo_pos.col_display;
190 let hi_pos = source_map.lookup_char_pos(body_span.hi());
191 let end_line = hi_pos.line;
192 let end_col = hi_pos.col_display;
193
194 if start_line < end_line {
196 start_line <= line && line <= end_line
198 } else {
199 start_line == line && start_col <= col && col <= end_col
201 }
202 }
203
204 fn is_included(&self, def_id: MaybeExternId) -> bool {
208 let Some(pattern) = config::include_pattern() else { return true };
209 if self.matches_file_path(def_id, |path| pattern.glob.is_match(path)) {
210 return true;
211 }
212 if pattern.defs.iter().any(|def| self.matches_def(def_id, def)) {
213 return true;
214 }
215 if pattern.spans.iter().any(|pos| {
216 self.matches_file_path(def_id, |path| path.ends_with(&pos.file))
217 && self.matches_pos(def_id, pos.line, pos.column)
218 }) {
219 return true;
220 }
221 false
222 }
223
224 fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
225 let mut this = std::panic::AssertUnwindSafe(self);
226 let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
227 flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
228 }
229
230 fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
231 let def_id = self.genv.maybe_extern_id(def_id);
232
233 if self.genv.ignored(def_id.local_id()) || self.genv.is_dummy(def_id.local_id()) {
234 return Ok(());
235 }
236 if !self.is_included(def_id) {
237 return Ok(());
238 }
239
240 match self.genv.def_kind(def_id) {
241 DefKind::Fn | DefKind::AssocFn => {
242 force_conv(self.genv, def_id.resolved_id()).emit(&self.genv)?;
244 let Some(local_id) = def_id.as_local() else { return Ok(()) };
245 refineck::check_fn(self.genv, &mut self.cache, local_id)
246 }
247 DefKind::Enum => {
248 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
249 self.genv.variants_of(def_id).emit(&self.genv)?;
250 let adt_def = self.genv.adt_def(def_id).emit(&self.genv)?;
251 let enum_def = self
252 .genv
253 .fhir_expect_item(def_id.local_id())
254 .emit(&self.genv)?
255 .expect_enum();
256 refineck::invariants::check_invariants(
257 self.genv,
258 &mut self.cache,
259 def_id,
260 enum_def.invariants,
261 &adt_def,
262 )
263 }
264 DefKind::Struct => {
265 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
269 self.genv.adt_def(def_id).emit(&self.genv)?;
270 self.genv.variants_of(def_id).emit(&self.genv)?;
271 let _struct_def = self
272 .genv
273 .fhir_expect_item(def_id.local_id())
274 .emit(&self.genv)?
275 .expect_struct();
276 Ok(())
277 }
278 DefKind::Impl { of_trait } => {
279 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
280 if of_trait {
281 refineck::compare_impl_item::check_impl_against_trait(self.genv, def_id)
282 .emit(&self.genv)?;
283 }
284 Ok(())
285 }
286 DefKind::TyAlias => {
287 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
288 self.genv.type_of(def_id).emit(&self.genv)?;
289 Ok(())
290 }
291 DefKind::Trait => {
292 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
293 Ok(())
294 }
295 _ => Ok(()),
296 }
297 }
298}
299
300fn force_conv(genv: GlobalEnv, def_id: DefId) -> QueryResult {
301 genv.generics_of(def_id)?;
302 genv.refinement_generics_of(def_id)?;
303 genv.predicates_of(def_id)?;
304 genv.fn_sig(def_id)?;
305 Ok(())
306}
307
308fn mir_borrowck<'tcx>(
309 tcx: TyCtxt<'tcx>,
310 def_id: LocalDefId,
311) -> query::queries::mir_borrowck::ProvidedValue<'tcx> {
312 let bodies_with_facts = rustc_borrowck::consumers::get_bodies_with_borrowck_facts(
313 tcx,
314 def_id,
315 ConsumerOptions::RegionInferenceContext,
316 );
317 for (def_id, body_with_facts) in bodies_with_facts {
318 unsafe {
321 flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
322 }
323 }
324 let mut providers = query::Providers::default();
325 rustc_borrowck::provide(&mut providers);
326 let original_mir_borrowck = providers.mir_borrowck;
327 original_mir_borrowck(tcx, def_id)
328}