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 rty::StaticInfo,
16};
17use flux_refineck as refineck;
18use rustc_borrowck::consumers::ConsumerOptions;
19use rustc_driver::{Callbacks, Compilation};
20use rustc_errors::ErrorGuaranteed;
21use rustc_hir::{
22 def::{CtorKind, DefKind},
23 def_id::{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 result = genv
96 .tcx()
97 .iter_local_def_id()
98 .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
99
100 if config::lean().is_check() || config::lean().is_emit() {
101 lean_encoding::finalize(genv)
102 .unwrap_or_else(|err| bug!("error running lean-check {err:?}"));
103 }
104
105 let lean_result = if config::lean().is_check() {
106 genv.iter_local_def_id().try_for_each_exhaust(|def_id| {
107 if genv.proven_externally(def_id).is_some() {
108 lean_encoding::check_proof(genv, def_id.to_def_id())
109 } else {
110 Ok(())
111 }
112 })
113 } else {
114 Ok(())
115 };
116
117 ck.cache.save().unwrap_or(());
118
119 tracing::info!("Callbacks::check_crate");
120
121 result.and(lean_result)
122 })
123}
124
125fn collect_specs(genv: GlobalEnv) -> Specs {
126 match SpecCollector::collect(genv.tcx(), genv.sess()) {
127 Ok(specs) => specs,
128 Err(err) => {
129 genv.sess().abort(err);
130 }
131 }
132}
133
134fn encode_and_save_metadata(genv: GlobalEnv) {
135 let tcx = genv.tcx();
139 if tcx
140 .output_filenames(())
141 .outputs
142 .contains_key(&OutputType::Metadata)
143 {
144 let path = flux_metadata::filename_for_metadata(tcx);
145 flux_metadata::encode_metadata(genv, path.as_path());
146 }
147}
148
149struct CrateChecker<'genv, 'tcx> {
150 genv: GlobalEnv<'genv, 'tcx>,
151 cache: FixQueryCache,
152}
153
154impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
155 fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
156 Self { genv, cache: QueryCache::load() }
157 }
158
159 fn matches_def(&self, def_id: MaybeExternId, def: &str) -> bool {
160 let def_path = self.genv.tcx().def_path_str(def_id.local_id());
162 def_path.contains(def)
163 }
164
165 fn matches_file_path<F>(&self, def_id: MaybeExternId, matcher: F) -> bool
166 where
167 F: Fn(&Path) -> bool,
168 {
169 let def_id = def_id.local_id();
170 let tcx = self.genv.tcx();
171 let span = tcx.def_span(def_id);
172 let sm = tcx.sess.source_map();
173 let FileName::Real(file_name) = sm.span_to_filename(span) else { return true };
174 let mut file_path = file_name.local_path_if_available();
175
176 if file_path.is_absolute() {
178 let working_dir = tcx.sess.opts.working_dir.local_path_if_available();
179 let Ok(p) = file_path.strip_prefix(working_dir) else { return true };
180 file_path = p;
181 }
182
183 matcher(file_path)
184 }
185
186 fn matches_pos(&self, def_id: MaybeExternId, line: usize, col: usize) -> bool {
187 let def_id = def_id.local_id();
188 let tcx = self.genv.tcx();
189 let hir_id = tcx.local_def_id_to_hir_id(def_id);
190 let body_span = tcx.hir_span_with_body(hir_id);
191 let source_map = tcx.sess.source_map();
192 let lo_pos = source_map.lookup_char_pos(body_span.lo());
193 let start_line = lo_pos.line;
194 let start_col = lo_pos.col_display;
195 let hi_pos = source_map.lookup_char_pos(body_span.hi());
196 let end_line = hi_pos.line;
197 let end_col = hi_pos.col_display;
198
199 if start_line < end_line {
201 start_line <= line && line <= end_line
203 } else {
204 start_line == line && start_col <= col && col <= end_col
206 }
207 }
208
209 fn is_included(&self, def_id: MaybeExternId) -> bool {
213 let Some(pattern) = config::include_pattern() else { return true };
214 if self.matches_file_path(def_id, |path| pattern.glob.is_match(path)) {
215 return true;
216 }
217 if pattern.defs.iter().any(|def| self.matches_def(def_id, def)) {
218 return true;
219 }
220 if pattern.spans.iter().any(|pos| {
221 self.matches_file_path(def_id, |path| path.ends_with(&pos.file))
222 && self.matches_pos(def_id, pos.line, pos.column)
223 }) {
224 return true;
225 }
226 false
227 }
228
229 fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
230 let mut this = std::panic::AssertUnwindSafe(self);
231 let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
232 flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
233 }
234
235 fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
236 let genv = self.genv;
237 let def_id = genv.maybe_extern_id(def_id);
238
239 if genv.is_dummy(def_id.local_id()) {
241 return Ok(());
242 }
243
244 let kind = genv.def_kind(def_id);
245
246 let is_fn_with_body = def_id
253 .as_local()
254 .map(|local_id| {
255 matches!(kind, DefKind::Fn | DefKind::AssocFn)
256 && genv.tcx().is_mir_available(local_id)
257 })
258 .unwrap_or(false);
259
260 metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
261
262 if genv.ignored(def_id.local_id()) {
263 metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
264 return Ok(());
265 }
266 if !self.is_included(def_id) {
267 metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
268 return Ok(());
269 }
270
271 trigger_queries(genv, def_id).emit(&genv)?;
272
273 match kind {
274 DefKind::Fn | DefKind::AssocFn => {
275 let Some(local_id) = def_id.as_local() else { return Ok(()) };
276 if is_fn_with_body {
277 refineck::check_fn(genv, &mut self.cache, local_id)?;
278 }
279 }
280 DefKind::Enum => {
281 let adt_def = genv.adt_def(def_id).emit(&genv)?;
282 let enum_def = genv
283 .fhir_expect_item(def_id.local_id())
284 .emit(&genv)?
285 .expect_enum();
286 refineck::invariants::check_invariants(
287 genv,
288 &mut self.cache,
289 def_id,
290 enum_def.invariants,
291 &adt_def,
292 )?;
293 }
294 DefKind::Struct => {
295 }
298 DefKind::Impl { of_trait } => {
299 if of_trait {
300 refineck::compare_impl_item::check_impl_against_trait(genv, def_id)
301 .emit(&genv)?;
302 }
303 }
304 DefKind::TyAlias => {}
305 DefKind::Trait => {}
306 DefKind::Static { .. } => {
307 if let StaticInfo::Known(ty) = genv.static_info(def_id).emit(&genv)?
308 && let Some(local_id) = def_id.as_local()
309 {
310 refineck::check_static(genv, &mut self.cache, local_id, ty)?;
311 }
312 }
313 _ => (),
314 }
315 Ok(())
316 }
317}
318
319fn trigger_queries(genv: GlobalEnv, def_id: MaybeExternId) -> QueryResult {
326 match genv.def_kind(def_id) {
327 DefKind::Trait => {
328 genv.generics_of(def_id)?;
329 genv.predicates_of(def_id)?;
330 genv.refinement_generics_of(def_id)?;
331 }
332 DefKind::Impl { .. } => {
333 genv.generics_of(def_id)?;
334 genv.predicates_of(def_id)?;
335 genv.refinement_generics_of(def_id)?;
336 }
337 DefKind::Fn | DefKind::AssocFn => {
338 genv.generics_of(def_id)?;
339 genv.refinement_generics_of(def_id)?;
340 genv.predicates_of(def_id)?;
341 genv.fn_sig(def_id)?;
342 }
343 DefKind::Ctor(_, CtorKind::Fn) => {
344 genv.generics_of(def_id)?;
345 genv.refinement_generics_of(def_id)?;
346 let _ = genv.fn_sig(def_id);
349 }
350 DefKind::Enum | DefKind::Struct => {
351 genv.generics_of(def_id)?;
352 genv.predicates_of(def_id)?;
353 genv.refinement_generics_of(def_id)?;
354 genv.adt_def(def_id)?;
355 genv.adt_sort_def_of(def_id)?;
356 genv.variants_of(def_id)?;
357 genv.type_of(def_id)?;
358 }
359 DefKind::TyAlias => {
360 genv.generics_of(def_id)?;
361 genv.predicates_of(def_id)?;
362 genv.refinement_generics_of(def_id)?;
363 genv.type_of(def_id)?;
364 }
365 DefKind::OpaqueTy => {
366 genv.generics_of(def_id)?;
367 genv.predicates_of(def_id)?;
368 genv.item_bounds(def_id)?;
369 genv.refinement_generics_of(def_id)?;
370 }
371 _ => {}
372 }
373 Ok(())
374}
375
376fn mir_borrowck<'tcx>(
377 tcx: TyCtxt<'tcx>,
378 def_id: LocalDefId,
379) -> query::queries::mir_borrowck::ProvidedValue<'tcx> {
380 let bodies_with_facts = rustc_borrowck::consumers::get_bodies_with_borrowck_facts(
381 tcx,
382 def_id,
383 ConsumerOptions::RegionInferenceContext,
384 );
385 for (def_id, body_with_facts) in bodies_with_facts {
386 unsafe {
389 flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
390 }
391 }
392 let mut providers = query::Providers::default();
393 rustc_borrowck::provide(&mut providers);
394 let original_mir_borrowck = providers.mir_borrowck;
395 original_mir_borrowck(tcx, def_id)
396}