1use std::{io, 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::{
7 fixpoint_encoding::{Backend, ExprEncodingCtxt, FixQueryCache, SortEncodingCtxt},
8 lean_encoding::LeanEncoder,
9};
10use flux_metadata::CStore;
11use flux_middle::{
12 Specs,
13 def_id::MaybeExternId,
14 fhir::{self, FluxItem},
15 global_env::GlobalEnv,
16 metrics::{self, Metric, TimingKind},
17 queries::{Providers, QueryResult},
18 rty::PrettyMap,
19};
20use flux_refineck as refineck;
21use itertools::Itertools;
22use rustc_borrowck::consumers::ConsumerOptions;
23use rustc_driver::{Callbacks, Compilation};
24use rustc_errors::ErrorGuaranteed;
25use rustc_hir::{
26 def::DefKind,
27 def_id::{DefId, LOCAL_CRATE, LocalDefId},
28};
29use rustc_interface::interface::Compiler;
30use rustc_middle::{query, ty::TyCtxt};
31use rustc_session::config::OutputType;
32use rustc_span::FileName;
33
34use crate::{DEFAULT_LOCALE_RESOURCES, collector::SpecCollector};
35
36#[derive(Default)]
37pub struct FluxCallbacks;
38
39impl Callbacks for FluxCallbacks {
40 fn config(&mut self, config: &mut rustc_interface::interface::Config) {
41 assert!(config.override_queries.is_none());
42
43 config.override_queries = Some(|_, local| {
44 local.mir_borrowck = mir_borrowck;
45 });
46 assert!(config.extra_symbols.is_empty());
49 config.extra_symbols = flux_syntax::symbols::PREDEFINED_FLUX_SYMBOLS.to_vec();
50 }
51
52 fn after_analysis(&mut self, compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
53 self.verify(compiler, tcx);
54 if config::full_compilation() { Compilation::Continue } else { Compilation::Stop }
55 }
56}
57
58impl FluxCallbacks {
59 fn verify(&self, compiler: &Compiler, tcx: TyCtxt<'_>) {
60 if compiler.sess.dcx().has_errors().is_some() {
61 return;
62 }
63
64 let sess = FluxSession::new(
65 &tcx.sess.opts,
66 tcx.sess.psess.clone_source_map(),
67 rustc_errors::fallback_fluent_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false),
68 );
69
70 let mut providers = Providers::default();
71 flux_desugar::provide(&mut providers);
72 flux_fhir_analysis::provide(&mut providers);
73 providers.collect_specs = collect_specs;
74
75 let cstore = CStore::load(tcx, &sess);
76 let arena = fhir::Arena::new();
77 GlobalEnv::enter(tcx, &sess, Box::new(cstore), &arena, providers, |genv| {
78 let result = metrics::time_it(TimingKind::Total, || check_crate(genv));
79 if result.is_ok() {
80 encode_and_save_metadata(genv);
81 }
82 });
83 let _ = metrics::print_and_dump_timings(tcx);
84 sess.finish_diagnostics();
85 }
86}
87
88fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
89 tracing::info_span!("check_crate").in_scope(move || {
90 tracing::info!("Callbacks::check_wf");
91 let _ = genv.qualifiers().emit(&genv)?;
93 let _ = genv.normalized_defns(LOCAL_CRATE);
94
95 let mut ck = CrateChecker::new(genv);
96 if config::lean().is_emit() {
97 ck.encode_flux_items_in_lean().unwrap_or(());
98 }
99
100 let crate_items = genv.tcx().hir_crate_items(());
101
102 let dups = crate_items.definitions().duplicates().collect_vec();
103 if !dups.is_empty() {
104 bug!("TODO: {dups:#?}");
105 }
106 let result = crate_items
107 .definitions()
108 .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
109
110 ck.cache.save().unwrap_or(());
111
112 tracing::info!("Callbacks::check_crate");
113
114 result
115 })
116}
117
118fn collect_specs(genv: GlobalEnv) -> Specs {
119 match SpecCollector::collect(genv.tcx(), genv.sess()) {
120 Ok(specs) => specs,
121 Err(err) => {
122 genv.sess().abort(err);
123 }
124 }
125}
126
127fn encode_and_save_metadata(genv: GlobalEnv) {
128 if genv.tcx().crate_name(LOCAL_CRATE) == flux_syntax::symbols::sym::core {
135 return;
136 }
137
138 let tcx = genv.tcx();
142 if tcx
143 .output_filenames(())
144 .outputs
145 .contains_key(&OutputType::Metadata)
146 {
147 let path = flux_metadata::filename_for_metadata(tcx);
148 flux_metadata::encode_metadata(genv, path.as_path());
149 }
150}
151
152struct CrateChecker<'genv, 'tcx> {
153 genv: GlobalEnv<'genv, 'tcx>,
154 cache: FixQueryCache,
155}
156
157impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
158 fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
159 Self { genv, cache: QueryCache::load() }
160 }
161
162 pub(crate) fn encode_flux_items_in_lean(&self) -> Result<(), io::Error> {
163 let mut ecx = ExprEncodingCtxt::new(self.genv, None, Backend::Lean);
164 let mut scx = SortEncodingCtxt::default();
165 let mut fun_defs = vec![];
166 let mut opaque_fun_defs = vec![];
167 for (def_id, flux_item) in self.genv.fhir_iter_flux_items() {
168 ecx.declare_fun(def_id.to_def_id());
169 match flux_item {
170 FluxItem::Func(spec_func) if spec_func.body.is_some() => {
171 if spec_func.params == 0 {
172 fun_defs.push(
173 ecx.fun_def_to_fixpoint(spec_func.def_id.to_def_id(), &mut scx)
174 .unwrap(),
175 );
176 }
177 }
178 FluxItem::Func(spec_func) => {
179 opaque_fun_defs
180 .push(ecx.fun_decl_to_fixpoint(spec_func.def_id.to_def_id(), &mut scx));
181 }
182 FluxItem::SortDecl(sort_decl) => {
183 scx.declare_opaque_sort(sort_decl.def_id.to_def_id());
184 }
185 _ => {}
186 }
187 }
188 let opaque_sorts = scx.user_sorts_to_fixpoint(self.genv);
189 let adt_defs = scx.encode_data_decls(self.genv).unwrap();
190 if !opaque_sorts.is_empty()
191 || !opaque_fun_defs.is_empty()
192 || !adt_defs.is_empty()
193 || !fun_defs.is_empty()
194 {
195 let encoder = LeanEncoder::new(self.genv, PrettyMap::new());
196 encoder
197 .encode_defs(&opaque_sorts, &opaque_fun_defs, &adt_defs, &fun_defs)
198 .unwrap();
199 }
200 Ok(())
201 }
202
203 fn matches_def(&self, def_id: MaybeExternId, def: &str) -> bool {
204 let def_path = self.genv.tcx().def_path_str(def_id.local_id());
206 def_path.contains(def)
207 }
208
209 fn matches_file_path<F>(&self, def_id: MaybeExternId, matcher: F) -> bool
210 where
211 F: Fn(&Path) -> bool,
212 {
213 let def_id = def_id.local_id();
214 let tcx = self.genv.tcx();
215 let span = tcx.def_span(def_id);
216 let sm = tcx.sess.source_map();
217 let FileName::Real(file_name) = sm.span_to_filename(span) else { return true };
218 let mut file_path = file_name.local_path_if_available();
219
220 if file_path.is_absolute() {
222 let working_dir = tcx.sess.opts.working_dir.local_path_if_available();
223 let Ok(p) = file_path.strip_prefix(working_dir) else { return true };
224 file_path = p;
225 }
226
227 matcher(file_path)
228 }
229
230 fn matches_pos(&self, def_id: MaybeExternId, line: usize, col: usize) -> bool {
231 let def_id = def_id.local_id();
232 let tcx = self.genv.tcx();
233 let hir_id = tcx.local_def_id_to_hir_id(def_id);
234 let body_span = tcx.hir_span_with_body(hir_id);
235 let source_map = tcx.sess.source_map();
236 let lo_pos = source_map.lookup_char_pos(body_span.lo());
237 let start_line = lo_pos.line;
238 let start_col = lo_pos.col_display;
239 let hi_pos = source_map.lookup_char_pos(body_span.hi());
240 let end_line = hi_pos.line;
241 let end_col = hi_pos.col_display;
242
243 if start_line < end_line {
245 start_line <= line && line <= end_line
247 } else {
248 start_line == line && start_col <= col && col <= end_col
250 }
251 }
252
253 fn is_included(&self, def_id: MaybeExternId) -> bool {
257 let Some(pattern) = config::include_pattern() else { return true };
258 if self.matches_file_path(def_id, |path| pattern.glob.is_match(path)) {
259 return true;
260 }
261 if pattern.defs.iter().any(|def| self.matches_def(def_id, def)) {
262 return true;
263 }
264 if pattern.spans.iter().any(|pos| {
265 self.matches_file_path(def_id, |path| path.ends_with(&pos.file))
266 && self.matches_pos(def_id, pos.line, pos.column)
267 }) {
268 return true;
269 }
270 false
271 }
272
273 fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
274 let mut this = std::panic::AssertUnwindSafe(self);
275 let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
276 flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
277 }
278
279 fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
280 let def_id = self.genv.maybe_extern_id(def_id);
281
282 if self.genv.is_dummy(def_id.local_id()) {
284 return Ok(());
285 }
286
287 let kind = self.genv.def_kind(def_id);
288
289 let is_fn_with_body = def_id
296 .as_local()
297 .map(|local_id| {
298 matches!(kind, DefKind::Fn | DefKind::AssocFn)
299 && self.genv.tcx().is_mir_available(local_id)
300 })
301 .unwrap_or(false);
302
303 metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
304
305 if self.genv.ignored(def_id.local_id()) {
306 metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
307 return Ok(());
308 }
309 if !self.is_included(def_id) {
310 metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
311 return Ok(());
312 }
313
314 match kind {
315 DefKind::Fn | DefKind::AssocFn => {
316 force_conv(self.genv, def_id.resolved_id()).emit(&self.genv)?;
318 let Some(local_id) = def_id.as_local() else { return Ok(()) };
319 if is_fn_with_body {
320 refineck::check_fn(self.genv, &mut self.cache, local_id)?;
321 }
322 Ok(())
323 }
324 DefKind::Enum => {
325 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
326 self.genv.variants_of(def_id).emit(&self.genv)?;
327 let adt_def = self.genv.adt_def(def_id).emit(&self.genv)?;
328 let enum_def = self
329 .genv
330 .fhir_expect_item(def_id.local_id())
331 .emit(&self.genv)?
332 .expect_enum();
333 refineck::invariants::check_invariants(
334 self.genv,
335 &mut self.cache,
336 def_id,
337 enum_def.invariants,
338 &adt_def,
339 )
340 }
341 DefKind::Struct => {
342 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
346 self.genv.adt_def(def_id).emit(&self.genv)?;
347 self.genv.variants_of(def_id).emit(&self.genv)?;
348 let _struct_def = self
349 .genv
350 .fhir_expect_item(def_id.local_id())
351 .emit(&self.genv)?
352 .expect_struct();
353 Ok(())
354 }
355 DefKind::Impl { of_trait } => {
356 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
357 if of_trait {
358 refineck::compare_impl_item::check_impl_against_trait(self.genv, def_id)
359 .emit(&self.genv)?;
360 }
361 Ok(())
362 }
363 DefKind::TyAlias => {
364 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
365 self.genv.type_of(def_id).emit(&self.genv)?;
366 Ok(())
367 }
368 DefKind::Trait => {
369 self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
370 Ok(())
371 }
372 _ => Ok(()),
373 }
374 }
375}
376
377fn force_conv(genv: GlobalEnv, def_id: DefId) -> QueryResult {
378 genv.generics_of(def_id)?;
379 genv.refinement_generics_of(def_id)?;
380 genv.predicates_of(def_id)?;
381 genv.fn_sig(def_id)?;
382 Ok(())
383}
384
385fn mir_borrowck<'tcx>(
386 tcx: TyCtxt<'tcx>,
387 def_id: LocalDefId,
388) -> query::queries::mir_borrowck::ProvidedValue<'tcx> {
389 let bodies_with_facts = rustc_borrowck::consumers::get_bodies_with_borrowck_facts(
390 tcx,
391 def_id,
392 ConsumerOptions::RegionInferenceContext,
393 );
394 for (def_id, body_with_facts) in bodies_with_facts {
395 unsafe {
398 flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
399 }
400 }
401 let mut providers = query::Providers::default();
402 rustc_borrowck::provide(&mut providers);
403 let original_mir_borrowck = providers.mir_borrowck;
404 original_mir_borrowck(tcx, def_id)
405}