1use flux_common::{bug, cache::QueryCache, dbg, iter::IterExt, result::ResultExt};
2use flux_config as config;
3use flux_errors::FluxSession;
4use flux_infer::fixpoint_encoding::FixQueryCache;
5use flux_metadata::CStore;
6use flux_middle::{
7 Specs, fhir,
8 global_env::GlobalEnv,
9 queries::{Providers, QueryResult},
10 timings,
11};
12use flux_refineck as refineck;
13use itertools::Itertools;
14use rustc_borrowck::consumers::ConsumerOptions;
15use rustc_driver::{Callbacks, Compilation};
16use rustc_errors::ErrorGuaranteed;
17use rustc_hir::{
18 def::DefKind,
19 def_id::{DefId, LocalDefId},
20};
21use rustc_interface::interface::Compiler;
22use rustc_middle::{query, ty::TyCtxt};
23use rustc_session::config::OutputType;
24use rustc_span::FileName;
25
26use crate::{DEFAULT_LOCALE_RESOURCES, collector::SpecCollector};
27
28#[derive(Default)]
29pub struct FluxCallbacks;
30
31impl Callbacks for FluxCallbacks {
32 fn config(&mut self, config: &mut rustc_interface::interface::Config) {
33 assert!(config.override_queries.is_none());
34
35 config.override_queries = Some(|_, local| {
36 local.mir_borrowck = mir_borrowck;
37 });
38 }
39
40 fn after_analysis(&mut self, compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
41 self.verify(compiler, tcx);
42 if config::full_compilation() { Compilation::Continue } else { Compilation::Stop }
43 }
44}
45
46impl FluxCallbacks {
47 fn verify(&self, compiler: &Compiler, tcx: TyCtxt<'_>) {
48 if compiler.sess.dcx().has_errors().is_some() {
49 return;
50 }
51
52 let sess = FluxSession::new(
53 &tcx.sess.opts,
54 tcx.sess.psess.clone_source_map(),
55 rustc_errors::fallback_fluent_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false),
56 );
57
58 let mut providers = Providers::default();
59 flux_desugar::provide(&mut providers);
60 flux_fhir_analysis::provide(&mut providers);
61 providers.collect_specs = collect_specs;
62
63 let cstore = CStore::load(tcx, &sess);
64 let arena = fhir::Arena::new();
65 GlobalEnv::enter(tcx, &sess, Box::new(cstore), &arena, providers, |genv| {
66 let result = timings::enter(tcx, || check_crate(genv));
67 if result.is_ok() {
68 encode_and_save_metadata(genv);
69 }
70 });
71 sess.finish_diagnostics();
72 }
73}
74
75fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
76 tracing::info_span!("check_crate").in_scope(move || {
77 tracing::info!("Callbacks::check_wf");
78
79 flux_fhir_analysis::check_crate_wf(genv)?;
80
81 let mut ck = CrateChecker::new(genv);
82
83 let crate_items = genv.tcx().hir_crate_items(());
84
85 let dups = crate_items.definitions().duplicates().collect_vec();
86 if !dups.is_empty() {
87 bug!("TODO: {dups:#?}");
88 }
89 let result = crate_items
90 .definitions()
91 .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
92
93 ck.cache.save().unwrap_or(());
94
95 tracing::info!("Callbacks::check_crate");
96
97 result
98 })
99}
100
101fn collect_specs(genv: GlobalEnv) -> Specs {
102 match SpecCollector::collect(genv.tcx(), genv.sess()) {
103 Ok(specs) => specs,
104 Err(err) => {
105 genv.sess().abort(err);
106 }
107 }
108}
109
110fn encode_and_save_metadata(genv: GlobalEnv) {
111 let tcx = genv.tcx();
115 if tcx
116 .output_filenames(())
117 .outputs
118 .contains_key(&OutputType::Metadata)
119 {
120 let path = flux_metadata::filename_for_metadata(tcx);
121 flux_metadata::encode_metadata(genv, path.as_path());
122 }
123}
124
125struct CrateChecker<'genv, 'tcx> {
126 genv: GlobalEnv<'genv, 'tcx>,
127 cache: FixQueryCache,
128}
129
130impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
131 fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
132 CrateChecker { genv, cache: QueryCache::load() }
133 }
134
135 fn matches_check_def(&self, def_id: DefId) -> bool {
136 let def_path = self.genv.tcx().def_path_str(def_id);
137 def_path.contains(config::check_def())
138 }
139
140 fn file_is_included(&self, def_id: LocalDefId) -> bool {
143 let tcx = self.genv.tcx();
144 let span = tcx.def_span(def_id);
145 let sm = tcx.sess.source_map();
146 let FileName::Real(file_name) = sm.span_to_filename(span) else { return true };
147 let mut file_path = file_name.local_path_if_available();
148
149 if file_path.is_absolute() {
151 let working_dir = tcx.sess.opts.working_dir.local_path_if_available();
152 let Ok(p) = file_path.strip_prefix(working_dir) else { return true };
153 file_path = p;
154 }
155
156 config::is_checked_file(file_path)
157 }
158
159 fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
160 let mut this = std::panic::AssertUnwindSafe(self);
161 let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
162 flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
163 }
164
165 fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
166 let def_id = self.genv.maybe_extern_id(def_id);
167
168 if !self.matches_check_def(def_id.resolved_id()) {
169 return Ok(());
170 }
171 if self.genv.ignored(def_id.local_id()) || self.genv.is_dummy(def_id.local_id()) {
172 return Ok(());
173 }
174 if !self.file_is_included(def_id.local_id()) {
175 return Ok(());
176 }
177
178 match self.genv.def_kind(def_id) {
179 DefKind::Fn | DefKind::AssocFn => {
180 force_conv(self.genv, def_id.resolved_id()).emit(&self.genv)?;
182 let Some(local_id) = def_id.as_local() else { return Ok(()) };
183 refineck::check_fn(self.genv, &mut self.cache, local_id)
184 }
185 DefKind::Enum => {
186 let adt_def = self.genv.adt_def(def_id).emit(&self.genv)?;
187 let _ = self.genv.variants_of(def_id).emit(&self.genv)?;
188 let enum_def = self
189 .genv
190 .map()
191 .expect_item(def_id.local_id())
192 .emit(&self.genv)?
193 .expect_enum();
194 refineck::invariants::check_invariants(
195 self.genv,
196 &mut self.cache,
197 def_id,
198 enum_def.invariants,
199 &adt_def,
200 )
201 }
202 DefKind::Struct => {
203 let _adt_def = self.genv.adt_def(def_id).emit(&self.genv)?;
207 let _ = self.genv.variants_of(def_id).emit(&self.genv)?;
208 let _struct_def = self
209 .genv
210 .map()
211 .expect_item(def_id.local_id())
212 .emit(&self.genv)?
213 .expect_struct();
214 Ok(())
215 }
216 DefKind::Impl { of_trait } => {
217 if of_trait {
218 refineck::compare_impl_item::check_impl_against_trait(self.genv, def_id)
219 .emit(&self.genv)?;
220 }
221 Ok(())
222 }
223 DefKind::TyAlias => {
224 self.genv.type_of(def_id).emit(&self.genv)?;
225 Ok(())
226 }
227 _ => Ok(()),
228 }
229 }
230}
231
232fn force_conv(genv: GlobalEnv, def_id: DefId) -> QueryResult {
233 genv.generics_of(def_id)?;
234 genv.refinement_generics_of(def_id)?;
235 genv.predicates_of(def_id)?;
236 genv.fn_sig(def_id)?;
237 Ok(())
238}
239
240fn stash_body_with_borrowck_facts<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) {
241 let body_with_facts = rustc_borrowck::consumers::get_body_with_borrowck_facts(
242 tcx,
243 def_id,
244 ConsumerOptions::RegionInferenceContext,
245 );
246 if config::dump_mir() {
247 rustc_middle::mir::pretty::write_mir_fn(
248 tcx,
249 &body_with_facts.body,
250 &mut |_, _| Ok(()),
251 &mut dbg::writer_for_item(tcx, def_id.to_def_id(), "mir").unwrap(),
252 rustc_middle::mir::pretty::PrettyPrintMirOptions::from_cli(tcx),
253 )
254 .unwrap();
255 }
256
257 unsafe {
260 flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
261 }
262}
263
264fn mir_borrowck<'tcx>(
265 tcx: TyCtxt<'tcx>,
266 def_id: LocalDefId,
267) -> query::queries::mir_borrowck::ProvidedValue<'tcx> {
268 let mut worklist = vec![def_id];
270 while let Some(def_id) = worklist.pop() {
271 stash_body_with_borrowck_facts(tcx, def_id);
272 for nested_def_id in tcx.nested_bodies_within(def_id) {
273 worklist.push(nested_def_id);
274 }
275 }
276 let mut providers = query::Providers::default();
277 rustc_borrowck::provide(&mut providers);
278 let original_mir_borrowck = providers.mir_borrowck;
279 original_mir_borrowck(tcx, def_id)
280}