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, lean_encoding};
7use flux_metadata::CStore;
8use flux_middle::{
9 Specs,
10 def_id::MaybeExternId,
11 fhir::{self},
12 global_env::GlobalEnv,
13 metrics::{self, Metric, TimingKind},
14 queries::{Providers, QueryResult},
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 = metrics::time_it(TimingKind::Total, || check_crate(genv));
75 if result.is_ok() {
76 encode_and_save_metadata(genv);
77 }
78 lean_encoding::finalize(genv).unwrap_or(());
79 });
80 let _ = metrics::print_and_dump_timings(tcx);
81 sess.finish_diagnostics();
82 }
83}
84
85fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
86 tracing::info_span!("check_crate").in_scope(move || {
87 tracing::info!("Callbacks::check_wf");
88 let _ = genv.qualifiers().emit(&genv)?;
90 let _ = genv.normalized_defns(LOCAL_CRATE);
91
92 let mut ck = CrateChecker::new(genv);
93
94 let crate_items = genv.tcx().hir_crate_items(());
95
96 let dups = crate_items.definitions().duplicates().collect_vec();
97 if !dups.is_empty() {
98 bug!("TODO: {dups:#?}");
99 }
100 let result = crate_items
101 .definitions()
102 .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
103
104 if config::lean().is_emit() {
105 lean_encoding::finalize(genv)
106 .unwrap_or_else(|err| bug!("error running lean-check {err:?}"));
107 }
108
109 let lean_result = if config::lean().is_check() {
110 crate_items.definitions().try_for_each_exhaust(|def_id| {
111 if genv.proven_externally(def_id).is_some() {
112 lean_encoding::check_proof(genv, def_id.to_def_id())
113 } else {
114 Ok(())
115 }
116 })
117 } else {
118 Ok(())
119 };
120
121 ck.cache.save().unwrap_or(());
122
123 tracing::info!("Callbacks::check_crate");
124
125 result.and(lean_result)
126 })
127}
128
129fn collect_specs(genv: GlobalEnv) -> Specs {
130 match SpecCollector::collect(genv.tcx(), genv.sess()) {
131 Ok(specs) => specs,
132 Err(err) => {
133 genv.sess().abort(err);
134 }
135 }
136}
137
138fn encode_and_save_metadata(genv: GlobalEnv) {
139 if genv.tcx().crate_name(LOCAL_CRATE) == flux_syntax::symbols::sym::core {
146 return;
147 }
148
149 let tcx = genv.tcx();
153 if tcx
154 .output_filenames(())
155 .outputs
156 .contains_key(&OutputType::Metadata)
157 {
158 let path = flux_metadata::filename_for_metadata(tcx);
159 flux_metadata::encode_metadata(genv, path.as_path());
160 }
161}
162
163struct CrateChecker<'genv, 'tcx> {
164 genv: GlobalEnv<'genv, 'tcx>,
165 cache: FixQueryCache,
166}
167
168impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
169 fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
170 Self { genv, cache: QueryCache::load() }
171 }
172
173 fn matches_def(&self, def_id: MaybeExternId, def: &str) -> bool {
174 let def_path = self.genv.tcx().def_path_str(def_id.local_id());
176 def_path.contains(def)
177 }
178
179 fn matches_file_path<F>(&self, def_id: MaybeExternId, matcher: F) -> bool
180 where
181 F: Fn(&Path) -> bool,
182 {
183 let def_id = def_id.local_id();
184 let tcx = self.genv.tcx();
185 let span = tcx.def_span(def_id);
186 let sm = tcx.sess.source_map();
187 let FileName::Real(file_name) = sm.span_to_filename(span) else { return true };
188 let mut file_path = file_name.local_path_if_available();
189
190 if file_path.is_absolute() {
192 let working_dir = tcx.sess.opts.working_dir.local_path_if_available();
193 let Ok(p) = file_path.strip_prefix(working_dir) else { return true };
194 file_path = p;
195 }
196
197 matcher(file_path)
198 }
199
200 fn matches_pos(&self, def_id: MaybeExternId, line: usize, col: usize) -> bool {
201 let def_id = def_id.local_id();
202 let tcx = self.genv.tcx();
203 let hir_id = tcx.local_def_id_to_hir_id(def_id);
204 let body_span = tcx.hir_span_with_body(hir_id);
205 let source_map = tcx.sess.source_map();
206 let lo_pos = source_map.lookup_char_pos(body_span.lo());
207 let start_line = lo_pos.line;
208 let start_col = lo_pos.col_display;
209 let hi_pos = source_map.lookup_char_pos(body_span.hi());
210 let end_line = hi_pos.line;
211 let end_col = hi_pos.col_display;
212
213 if start_line < end_line {
215 start_line <= line && line <= end_line
217 } else {
218 start_line == line && start_col <= col && col <= end_col
220 }
221 }
222
223 fn is_included(&self, def_id: MaybeExternId) -> bool {
227 let Some(pattern) = config::include_pattern() else { return true };
228 if self.matches_file_path(def_id, |path| pattern.glob.is_match(path)) {
229 return true;
230 }
231 if pattern.defs.iter().any(|def| self.matches_def(def_id, def)) {
232 return true;
233 }
234 if pattern.spans.iter().any(|pos| {
235 self.matches_file_path(def_id, |path| path.ends_with(&pos.file))
236 && self.matches_pos(def_id, pos.line, pos.column)
237 }) {
238 return true;
239 }
240 false
241 }
242
243 fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
244 let mut this = std::panic::AssertUnwindSafe(self);
245 let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
246 flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
247 }
248
249 fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
250 let def_id = self.genv.maybe_extern_id(def_id);
251
252 if self.genv.is_dummy(def_id.local_id()) {
254 return Ok(());
255 }
256
257 let kind = self.genv.def_kind(def_id);
258
259 let is_fn_with_body = def_id
266 .as_local()
267 .map(|local_id| {
268 matches!(kind, DefKind::Fn | DefKind::AssocFn)
269 && self.genv.tcx().is_mir_available(local_id)
270 })
271 .unwrap_or(false);
272
273 metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
274
275 if self.genv.ignored(def_id.local_id()) {
276 metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
277 return Ok(());
278 }
279 if !self.is_included(def_id) {
280 metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
281 return Ok(());
282 }
283
284 match kind {
285 DefKind::Fn | DefKind::AssocFn => {
286 force_conv(self.genv, def_id.resolved_id()).emit(&self.genv)?;
288 let Some(local_id) = def_id.as_local() else { return Ok(()) };
289 if is_fn_with_body {
290 refineck::check_fn(self.genv, &mut self.cache, local_id)?;
291 }
292 Ok(())
293 }
294 DefKind::Enum => {
295 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
296 self.genv.variants_of(def_id).emit(&self.genv)?;
297 let adt_def = self.genv.adt_def(def_id).emit(&self.genv)?;
298 let enum_def = self
299 .genv
300 .fhir_expect_item(def_id.local_id())
301 .emit(&self.genv)?
302 .expect_enum();
303 refineck::invariants::check_invariants(
304 self.genv,
305 &mut self.cache,
306 def_id,
307 enum_def.invariants,
308 &adt_def,
309 )
310 }
311 DefKind::Struct => {
312 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
316 self.genv.adt_def(def_id).emit(&self.genv)?;
317 self.genv.variants_of(def_id).emit(&self.genv)?;
318 let _struct_def = self
319 .genv
320 .fhir_expect_item(def_id.local_id())
321 .emit(&self.genv)?
322 .expect_struct();
323 Ok(())
324 }
325 DefKind::Impl { of_trait } => {
326 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
327 if of_trait {
328 refineck::compare_impl_item::check_impl_against_trait(self.genv, def_id)
329 .emit(&self.genv)?;
330 }
331 Ok(())
332 }
333 DefKind::TyAlias => {
334 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
335 self.genv.type_of(def_id).emit(&self.genv)?;
336 Ok(())
337 }
338 DefKind::Trait => {
339 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
340 Ok(())
341 }
342 _ => Ok(()),
343 }
344 }
345}
346
347fn force_conv(genv: GlobalEnv, def_id: DefId) -> QueryResult {
348 genv.generics_of(def_id)?;
349 genv.refinement_generics_of(def_id)?;
350 genv.predicates_of(def_id)?;
351 genv.fn_sig(def_id)?;
352 Ok(())
353}
354
355fn mir_borrowck<'tcx>(
356 tcx: TyCtxt<'tcx>,
357 def_id: LocalDefId,
358) -> query::queries::mir_borrowck::ProvidedValue<'tcx> {
359 let bodies_with_facts = rustc_borrowck::consumers::get_bodies_with_borrowck_facts(
360 tcx,
361 def_id,
362 ConsumerOptions::RegionInferenceContext,
363 );
364 for (def_id, body_with_facts) in bodies_with_facts {
365 unsafe {
368 flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
369 }
370 }
371 let mut providers = query::Providers::default();
372 rustc_borrowck::provide(&mut providers);
373 let original_mir_borrowck = providers.mir_borrowck;
374 original_mir_borrowck(tcx, def_id)
375}