flux_driver/
callbacks.rs

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    // We only save metadata when `--emit=metadata` is passed as an argument. In this case, we save
112    // the `.fluxmeta` file alongside the `.rmeta` file. This setup works for `cargo flux`, which
113    // wraps `cargo check` and always passes `--emit=metadata`. Tests also explicitly pass this flag.
114    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    /// Check whether the file where `def_id` is defined is included in the list of glob patterns.
141    /// This function will conservatively return `true` if anything unexpected happens.
142    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 the path is absolute try to normalize it to be relative to the working_dir
150        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                // Make sure we run conversion and report errors even if we skip the function
181                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                // We check invariants for `struct` in `check_constructor` (i.e. when the struct is built).
204                // However, we leave the below code in to force the queries that do the conversions that check
205                // for ill-formed annotations e.g. see tests/tests/neg/error_messages/annot_check/struct_error.rs
206                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    // SAFETY: This is safe because we are feeding in the same `tcx` that is
258    // going to be used as a witness when pulling out the data.
259    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    // grab the body-and-borrowck-facts for `def_id` and all transitively nested bodies.
269    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}