1use flux_common::{bug, cache::QueryCache, iter::IterExt, result::ResultExt};
2use flux_config::{self as config};
3use flux_errors::FluxSession;
4use flux_infer::{
5 fixpoint_encoding::{FixQueryCache, LeanStatus, lean_task_key},
6 lean_encoding,
7};
8use flux_metadata::CStore;
9use flux_middle::{
10 Specs,
11 def_id::MaybeExternId,
12 fhir::{self},
13 global_env::GlobalEnv,
14 metrics::{self, Metric, TimingKind},
15 queries::{Providers, QueryResult},
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::{queries, ty::TyCtxt, util};
27use rustc_session::config::OutputType;
28
29use crate::collector::SpecCollector;
30
31#[derive(Default)]
32pub struct FluxCallbacks;
33
34impl Callbacks for FluxCallbacks {
35 fn config(&mut self, config: &mut rustc_interface::interface::Config) {
36 assert!(config.override_queries.is_none());
37
38 config.override_queries = Some(|_, local| {
39 local.queries.mir_borrowck = mir_borrowck;
40 });
41 assert!(config.extra_symbols.is_empty());
44 config.extra_symbols = flux_syntax::symbols::PREDEFINED_FLUX_SYMBOLS.to_vec();
45
46 if flux_config::std_extern_specs() {
47 inject_std_extern_specs(config);
48 }
49 }
50
51 fn after_analysis(&mut self, compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
52 self.verify(compiler, tcx);
53 if config::full_compilation() { Compilation::Continue } else { Compilation::Stop }
54 }
55}
56
57impl FluxCallbacks {
58 fn verify(&self, compiler: &Compiler, tcx: TyCtxt<'_>) {
59 if compiler.sess.dcx().has_errors().is_some() {
60 return;
61 }
62
63 let sess = FluxSession::new(&tcx.sess.opts, tcx.sess.psess.clone_source_map());
64
65 let mut providers = Providers::default();
66 flux_opt::provide(&mut providers);
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 load_extern_specs() -> Vec<(String, std::path::PathBuf)> {
86 use flux_sysroot::SysrootManifest;
87 let Some(sysroot) = config::sysroot() else { return vec![] };
88 SysrootManifest::extern_specs(&sysroot)
89}
90
91fn inject_std_extern_specs(config: &mut rustc_interface::interface::Config) {
92 use std::collections::{BTreeMap, BTreeSet};
93
94 use rustc_session::{
95 config::{ExternEntry, ExternLocation, Externs},
96 utils::CanonicalizedPath,
97 };
98
99 let specs = load_extern_specs();
100 if specs.is_empty() {
101 return;
102 }
103
104 let mut map: BTreeMap<String, ExternEntry> = config
105 .opts
106 .externs
107 .iter()
108 .map(|(k, v)| (k.clone(), v.clone()))
109 .collect();
110
111 for (crate_name, rmeta_path) in specs {
112 let entry = ExternEntry {
113 location: ExternLocation::ExactPaths(BTreeSet::from([CanonicalizedPath::new(
114 rmeta_path,
115 )])),
116 is_private_dep: false,
117 add_prelude: true,
118 nounused_dep: true,
119 force: true,
120 };
121 map.insert(crate_name, entry);
122 }
123
124 config.opts.externs = Externs::new(map);
125}
126
127fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
128 tracing::info_span!("check_crate").in_scope(move || {
129 tracing::info!("Callbacks::check_wf");
130 let _ = genv.qualifiers().emit(&genv)?;
132 let _ = genv.spec_funcs(LOCAL_CRATE);
133
134 let mut ck = CrateChecker::new(genv);
135
136 let result = genv
138 .tcx()
139 .iter_local_def_id()
140 .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
141
142 if config::lean().is_check() || config::lean().is_emit() {
143 lean_encoding::finalize(genv)
144 .unwrap_or_else(|err| bug!("error running lean-check {err:?}"));
145 }
146
147 let lean_result = if config::lean().is_check() {
148 let digest = if config::is_cache_enabled() {
150 lean_encoding::project_digest(genv).ok()
151 } else {
152 None
153 };
154 let pending = genv
155 .iter_local_def_id()
156 .filter(|def_id| {
157 genv.included(genv.maybe_extern_id(*def_id))
159 && genv.proven_externally(*def_id).is_some()
160 })
161 .map(|def_id| def_id.to_def_id())
162 .filter(|def_id| {
163 let key = lean_task_key(genv.tcx(), *def_id);
165 let cached = ck.cache.lookup_by_key(&key).map(|r| &r.lean_status);
166 !matches!((cached, digest), (Some(LeanStatus::Valid(d1)), Some(d2)) if *d1 == d2)
167 })
168 .collect::<Vec<_>>();
169 let results = lean_encoding::check_proofs(genv, &pending);
171 pending
172 .iter()
173 .zip(results)
174 .map(|(def_id, result)| {
175 if result.is_ok()
176 && let Some(digest) = digest
177 {
178 let key = lean_task_key(genv.tcx(), *def_id);
180 ck.cache.update_result_by_key(&key, |r| {
181 r.lean_status = LeanStatus::Valid(digest);
182 });
183 }
184 result
185 })
186 .try_for_each_exhaust(|result| result)
187 } else {
188 Ok(())
189 };
190
191 ck.cache.save().unwrap_or(());
192
193 tracing::info!("Callbacks::check_crate");
194
195 result.and(lean_result)
196 })
197}
198
199fn collect_specs(genv: GlobalEnv) -> Specs {
200 match SpecCollector::collect(genv.tcx(), genv.sess()) {
201 Ok(specs) => specs,
202 Err(err) => {
203 genv.sess().abort(err);
204 }
205 }
206}
207
208fn encode_and_save_metadata(genv: GlobalEnv) {
209 let tcx = genv.tcx();
213 if tcx
214 .output_filenames(())
215 .outputs
216 .contains_key(&OutputType::Metadata)
217 {
218 let path = flux_metadata::filename_for_metadata(tcx);
219 flux_metadata::encode_metadata(genv, path.as_path());
220 }
221}
222
223struct CrateChecker<'genv, 'tcx> {
224 genv: GlobalEnv<'genv, 'tcx>,
225 cache: FixQueryCache,
226}
227
228impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
229 fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
230 Self { genv, cache: QueryCache::load() }
231 }
232
233 fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
234 let mut this = std::panic::AssertUnwindSafe(self);
235 let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
236 flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
237 }
238
239 fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
240 let genv = self.genv;
241 let def_id = genv.maybe_extern_id(def_id);
242
243 if genv.is_dummy(def_id.local_id()) {
245 return Ok(());
246 }
247
248 let kind = genv.def_kind(def_id);
249
250 let is_fn_with_body = def_id
257 .as_local()
258 .map(|local_id| {
259 matches!(kind, DefKind::Fn | DefKind::AssocFn)
260 && genv.tcx().is_mir_available(local_id)
261 })
262 .unwrap_or(false);
263
264 metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
265
266 if genv.ignored(def_id.local_id()) {
267 metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
268 return Ok(());
269 }
270 if !self.genv.included(def_id) {
271 metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
272 return Ok(());
273 }
274
275 trigger_queries(genv, def_id).emit(&genv)?;
276
277 match kind {
278 DefKind::Fn | DefKind::AssocFn => {
279 let Some(local_id) = def_id.as_local() else { return Ok(()) };
280 if is_fn_with_body {
281 refineck::check_fn(genv, &mut self.cache, local_id)?;
282 }
283 }
284 DefKind::Enum => {
285 let adt_def = genv.adt_def(def_id).emit(&genv)?;
286 let enum_def = genv
287 .fhir_expect_item(def_id.local_id())
288 .emit(&genv)?
289 .expect_enum();
290 refineck::invariants::check_invariants(
291 genv,
292 &mut self.cache,
293 def_id,
294 enum_def.invariants,
295 &adt_def,
296 )?;
297 }
298 DefKind::Struct => {
299 }
302 DefKind::Impl { of_trait } => {
303 if of_trait {
304 refineck::compare_impl_item::check_impl_against_trait(genv, def_id)
305 .emit(&genv)?;
306 }
307 }
308 DefKind::TyAlias => {}
309 DefKind::Trait => {}
310 DefKind::Static { .. } => {
311 if let Some(local_id) = def_id.as_local()
313 && genv.tcx().hir_node_by_def_id(local_id).body_id().is_some()
314 {
315 let ty = genv.static_info(def_id).emit(&genv)?;
318 refineck::check_static(genv, &mut self.cache, local_id, ty)?;
319 }
320 }
321 _ => (),
322 }
323 Ok(())
324 }
325}
326
327fn trigger_queries(genv: GlobalEnv, def_id: MaybeExternId) -> QueryResult {
334 match genv.def_kind(def_id) {
335 DefKind::Trait => {
336 genv.generics_of(def_id)?;
337 genv.predicates_of(def_id)?;
338 genv.refinement_generics_of(def_id)?;
339 }
340 DefKind::Impl { .. } => {
341 genv.generics_of(def_id)?;
342 genv.predicates_of(def_id)?;
343 genv.refinement_generics_of(def_id)?;
344 }
345 DefKind::Fn | DefKind::AssocFn => {
346 genv.generics_of(def_id)?;
347 genv.refinement_generics_of(def_id)?;
348 genv.predicates_of(def_id)?;
349 genv.fn_sig(def_id)?;
350 }
351 DefKind::Ctor(_, CtorKind::Fn) => {
352 genv.generics_of(def_id)?;
353 genv.refinement_generics_of(def_id)?;
354 let _ = genv.fn_sig(def_id);
357 }
358 DefKind::Enum | DefKind::Struct => {
359 genv.generics_of(def_id)?;
360 genv.predicates_of(def_id)?;
361 genv.refinement_generics_of(def_id)?;
362 genv.adt_def(def_id)?;
363 genv.invariants_of(def_id);
364 genv.adt_sort_def_of(def_id)?;
365 genv.variants_of(def_id)?;
366 genv.type_of(def_id)?;
367 }
368 DefKind::TyAlias => {
369 genv.generics_of(def_id)?;
370 genv.predicates_of(def_id)?;
371 genv.refinement_generics_of(def_id)?;
372 genv.type_of(def_id)?;
373 }
374 DefKind::OpaqueTy => {
375 genv.generics_of(def_id)?;
376 genv.predicates_of(def_id)?;
377 genv.item_bounds(def_id)?;
378 genv.refinement_generics_of(def_id)?;
379 }
380 _ => {}
381 }
382 Ok(())
383}
384
385fn mir_borrowck<'tcx>(
386 tcx: TyCtxt<'tcx>,
387 def_id: LocalDefId,
388) -> 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 = util::Providers::default();
402 rustc_borrowck::provide(&mut providers.queries);
403 let original_mir_borrowck = providers.queries.mir_borrowck;
404 original_mir_borrowck(tcx, def_id)
405}