flux_refineck/
lib.rs

1//! Refinement type checking
2
3#![feature(
4    associated_type_defaults,
5    box_patterns,
6    if_let_guard,
7    min_specialization,
8    never_type,
9    rustc_private,
10    unwrap_infallible
11)]
12
13extern crate rustc_abi;
14extern crate rustc_data_structures;
15extern crate rustc_errors;
16extern crate rustc_hir;
17extern crate rustc_index;
18extern crate rustc_infer;
19extern crate rustc_middle;
20extern crate rustc_mir_dataflow;
21extern crate rustc_span;
22extern crate rustc_type_ir;
23
24mod checker;
25pub mod compare_impl_item;
26mod ghost_statements;
27pub mod invariants;
28mod primops;
29mod queue;
30mod type_env;
31
32use checker::{Checker, trait_impl_subtyping};
33use flux_common::{dbg, dbg::SpanTrace, result::ResultExt as _};
34use flux_config as config;
35use flux_infer::{
36    fixpoint_encoding::{FixQueryCache, SolutionTrace},
37    infer::{ConstrReason, SubtypeReason, Tag},
38};
39use flux_macros::fluent_messages;
40use flux_middle::{
41    FixpointQueryKind,
42    def_id::MaybeExternId,
43    global_env::GlobalEnv,
44    metrics::{self, Metric, TimingKind},
45    rty::{self, ESpan},
46};
47use rustc_data_structures::unord::UnordMap;
48use rustc_errors::ErrorGuaranteed;
49use rustc_hir::def_id::LocalDefId;
50use rustc_span::Span;
51
52use crate::{checker::errors::ResultExt as _, ghost_statements::compute_ghost_statements};
53
54fluent_messages! { "../locales/en-US.ftl" }
55
56fn report_fixpoint_errors(
57    genv: GlobalEnv,
58    local_id: LocalDefId,
59    errors: Vec<Tag>,
60) -> Result<(), ErrorGuaranteed> {
61    #[expect(clippy::collapsible_else_if, reason = "it looks better")]
62    if genv.should_fail(local_id) {
63        if errors.is_empty() { report_expected_neg(genv, local_id) } else { Ok(()) }
64    } else {
65        if errors.is_empty() { Ok(()) } else { report_errors(genv, errors) }
66    }
67}
68
69fn check_body(
70    genv: GlobalEnv,
71    cache: &mut FixQueryCache,
72    def_id: LocalDefId,
73    poly_sig: &rty::PolyFnSig,
74) -> Result<(), ErrorGuaranteed> {
75    let span = genv.tcx().def_span(def_id);
76    let opts = genv.infer_opts(def_id);
77
78    let ghost_stmts = compute_ghost_statements(genv, def_id)
79        .with_span(span)
80        .map_err(|err| err.emit(genv, def_id))?;
81    let mut closures = UnordMap::default();
82
83    // PHASE 1: infer shape of `TypeEnv` at the entry of join points
84    let shape_result =
85        Checker::run_in_shape_mode(genv, def_id, &ghost_stmts, &mut closures, opts, poly_sig)
86            .map_err(|err| err.emit(genv, def_id))?;
87
88    // PHASE 2: generate refinement tree constraint
89    let infcx_root = Checker::run_in_refine_mode(
90        genv,
91        def_id,
92        &ghost_stmts,
93        &mut closures,
94        shape_result,
95        opts,
96        poly_sig,
97    )
98    .map_err(|err| err.emit(genv, def_id))?;
99
100    // PHASE 3: invoke fixpoint on the constraint
101    if (genv.proven_externally(def_id).is_some() && flux_config::lean().is_check())
102        || flux_config::lean().is_emit()
103    {
104        infcx_root
105            .execute_lean_query(cache, MaybeExternId::Local(def_id))
106            .emit(&genv)
107    } else {
108        let answer = infcx_root
109            .execute_fixpoint_query(cache, MaybeExternId::Local(def_id), FixpointQueryKind::Body)
110            .emit(&genv)?;
111
112        let tcx = genv.tcx();
113        let hir_id = tcx.local_def_id_to_hir_id(def_id);
114        let body_span = tcx.hir_span_with_body(hir_id);
115        dbg::solution!(genv, &answer, body_span);
116
117        let errors = answer.errors;
118        report_fixpoint_errors(genv, def_id, errors)
119    }
120}
121
122pub fn check_static(
123    genv: GlobalEnv,
124    cache: &mut FixQueryCache,
125    def_id: LocalDefId,
126    ty: rty::Ty,
127) -> Result<(), ErrorGuaranteed> {
128    // Build a PolyFnSig with no inputs and `ty` as the output
129    let output = rty::Binder::dummy(rty::FnOutput::new(ty, vec![]));
130    let fn_sig = rty::FnSig::new(
131        rustc_hir::Safety::Safe,
132        rustc_abi::ExternAbi::Rust,
133        rty::List::empty(),
134        rty::List::empty(),
135        output,
136        rty::Expr::ff(),
137        false,
138    );
139    let poly_sig = rty::PolyFnSig::dummy(fn_sig);
140
141    metrics::incr_metric(Metric::FnChecked, 1);
142    metrics::time_it(TimingKind::CheckBody(def_id), || check_body(genv, cache, def_id, &poly_sig))
143}
144
145pub fn check_fn(
146    genv: GlobalEnv,
147    cache: &mut FixQueryCache,
148    def_id: LocalDefId,
149) -> Result<(), ErrorGuaranteed> {
150    let span = genv.tcx().def_span(def_id);
151
152    // HACK(nilehmann) this will ignore any code generated by a macro. This is a temporary
153    // workaround to deal with a `#[derive(..)]` that generates code that flux cannot handle.
154    // Note that this is required because code generated by a `#[derive(..)]` cannot be
155    // manually trusted or ignored.
156    if !genv.tcx().def_span(def_id).ctxt().is_root() {
157        metrics::incr_metric(Metric::FnTrusted, 1);
158        return Ok(());
159    }
160
161    let opts = genv.infer_opts(def_id);
162
163    // FIXME(nilehmann) we should move this check to `compare_impl_item`
164    if let Some(infcx_root) = trait_impl_subtyping(genv, def_id, opts, span)
165        .with_span(span)
166        .map_err(|err| err.emit(genv, def_id))?
167    {
168        tracing::info!("check_fn::refine-subtyping");
169        let answer = infcx_root
170            .execute_fixpoint_query(cache, MaybeExternId::Local(def_id), FixpointQueryKind::Impl)
171            .emit(&genv)?;
172        tracing::info!("check_fn::fixpoint-subtyping");
173        let errors = answer.errors;
174        report_fixpoint_errors(genv, def_id, errors)?;
175    }
176
177    // Skip trusted functions
178    if genv.trusted(def_id) {
179        metrics::incr_metric(Metric::FnTrusted, 1);
180        return Ok(());
181    }
182
183    metrics::incr_metric(Metric::FnChecked, 1);
184    metrics::time_it(TimingKind::CheckBody(def_id), || -> Result<(), ErrorGuaranteed> {
185        let poly_sig = genv
186            .fn_sig(def_id)
187            .with_span(span)
188            .map_err(|err| err.emit(genv, def_id))?
189            .instantiate_identity();
190        let poly_sig = rty::auto_strong(genv, def_id, poly_sig);
191
192        check_body(genv, cache, def_id, &poly_sig)
193    })?;
194
195    dbg::check_fn_span!(genv.tcx(), def_id).in_scope(|| Ok(()))
196}
197
198fn call_error(genv: GlobalEnv, span: Span, dst_span: Option<ESpan>) -> ErrorGuaranteed {
199    genv.sess()
200        .emit_err(errors::RefineError::call(span, dst_span))
201}
202
203fn ret_error(genv: GlobalEnv, span: Span, dst_span: Option<ESpan>) -> ErrorGuaranteed {
204    genv.sess()
205        .emit_err(errors::RefineError::ret(span, dst_span))
206}
207
208fn report_errors(genv: GlobalEnv, errors: Vec<Tag>) -> Result<(), ErrorGuaranteed> {
209    let mut e = None;
210    for err in errors {
211        let span = err.src_span;
212        e = Some(match err.reason {
213            ConstrReason::Call
214            | ConstrReason::Subtype(SubtypeReason::Input)
215            | ConstrReason::Subtype(SubtypeReason::Requires)
216            | ConstrReason::Predicate => call_error(genv, span, err.dst_span),
217            ConstrReason::Assign => genv.sess().emit_err(errors::AssignError { span }),
218            ConstrReason::Ret
219            | ConstrReason::Subtype(SubtypeReason::Output)
220            | ConstrReason::Subtype(SubtypeReason::Ensures) => ret_error(genv, span, err.dst_span),
221            ConstrReason::Div => genv.sess().emit_err(errors::DivError { span }),
222            ConstrReason::Rem => genv.sess().emit_err(errors::RemError { span }),
223            ConstrReason::Goto(_) => genv.sess().emit_err(errors::GotoError { span }),
224            ConstrReason::Assert(msg) => genv.sess().emit_err(errors::AssertError { span, msg }),
225            ConstrReason::Fold | ConstrReason::FoldLocal => {
226                genv.sess().emit_err(errors::FoldError { span })
227            }
228            ConstrReason::Overflow => genv.sess().emit_err(errors::OverflowError { span }),
229            ConstrReason::Underflow => genv.sess().emit_err(errors::UnderflowError { span }),
230            ConstrReason::Other => genv.sess().emit_err(errors::UnknownError { span }),
231            ConstrReason::NoPanic(callee) => {
232                genv.sess().emit_err(errors::PanicError {
233                    span,
234                    callee: genv.tcx().def_path_debug_str(callee),
235                })
236            }
237        });
238    }
239
240    if let Some(e) = e { Err(e) } else { Ok(()) }
241}
242
243fn report_expected_neg(genv: GlobalEnv, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
244    Err(genv.sess().emit_err(errors::ExpectedNeg {
245        span: genv.tcx().def_span(def_id),
246        def_descr: genv.tcx().def_descr(def_id.to_def_id()),
247    }))
248}
249
250mod errors {
251    use flux_errors::E0999;
252    use flux_macros::{Diagnostic, Subdiagnostic};
253    use flux_middle::rty::ESpan;
254    use rustc_span::Span;
255
256    #[derive(Diagnostic)]
257    #[diag(refineck_goto_error, code = E0999)]
258    pub struct GotoError {
259        #[primary_span]
260        pub span: Span,
261    }
262
263    #[derive(Diagnostic)]
264    #[diag(refineck_assign_error, code = E0999)]
265    pub struct AssignError {
266        #[primary_span]
267        pub span: Span,
268    }
269
270    #[derive(Subdiagnostic)]
271    #[note(refineck_condition_span_note)]
272    pub(crate) struct ConditionSpanNote {
273        #[primary_span]
274        pub span: Span,
275    }
276
277    #[derive(Subdiagnostic)]
278    #[note(refineck_call_span_note)]
279    pub(crate) struct CallSpanNote {
280        #[primary_span]
281        pub span: Span,
282    }
283
284    #[derive(Diagnostic)]
285    #[diag(refineck_refine_error, code = E0999)]
286    pub struct RefineError {
287        #[primary_span]
288        #[label]
289        pub span: Span,
290        cond: &'static str,
291        #[subdiagnostic]
292        span_note: Option<ConditionSpanNote>,
293        #[subdiagnostic]
294        call_span_note: Option<CallSpanNote>,
295    }
296
297    impl RefineError {
298        pub fn call(span: Span, espan: Option<ESpan>) -> Self {
299            RefineError::new("precondition", span, espan)
300        }
301
302        pub fn ret(span: Span, espan: Option<ESpan>) -> Self {
303            RefineError::new("postcondition", span, espan)
304        }
305
306        fn new(cond: &'static str, span: Span, espan: Option<ESpan>) -> RefineError {
307            match espan {
308                Some(dst_span) => {
309                    let span_note = Some(ConditionSpanNote { span: dst_span.span });
310                    let call_span_note = dst_span.base.map(|span| CallSpanNote { span });
311                    RefineError { span, cond, span_note, call_span_note }
312                }
313                None => RefineError { span, cond, span_note: None, call_span_note: None },
314            }
315        }
316    }
317
318    #[derive(Diagnostic)]
319    #[diag(refineck_div_error, code = E0999)]
320    pub struct DivError {
321        #[primary_span]
322        pub span: Span,
323    }
324
325    #[derive(Diagnostic)]
326    #[diag(refineck_rem_error, code = E0999)]
327    pub struct RemError {
328        #[primary_span]
329        pub span: Span,
330    }
331
332    #[derive(Diagnostic)]
333    #[diag(refineck_assert_error, code = E0999)]
334    pub struct AssertError {
335        #[primary_span]
336        pub span: Span,
337        pub msg: &'static str,
338    }
339
340    #[derive(Diagnostic)]
341    #[diag(refineck_fold_error, code = E0999)]
342    pub struct FoldError {
343        #[primary_span]
344        pub span: Span,
345    }
346
347    #[derive(Diagnostic)]
348    #[diag(refineck_overflow_error, code = E0999)]
349    pub struct OverflowError {
350        #[primary_span]
351        pub span: Span,
352    }
353
354    #[derive(Diagnostic)]
355    #[diag(refineck_underflow_error, code = E0999)]
356    pub struct UnderflowError {
357        #[primary_span]
358        pub span: Span,
359    }
360
361    #[derive(Diagnostic)]
362    #[diag(refineck_unknown_error, code = E0999)]
363    pub struct UnknownError {
364        #[primary_span]
365        pub span: Span,
366    }
367
368    #[derive(Diagnostic)]
369    #[diag(refineck_expected_neg, code = E0999)]
370    pub struct ExpectedNeg {
371        #[primary_span]
372        pub span: Span,
373        pub def_descr: &'static str,
374    }
375
376    #[derive(Diagnostic)]
377    #[diag(refineck_panic_error, code = E0999)]
378    pub(super) struct PanicError {
379        #[primary_span]
380        pub(super) span: Span,
381        pub(super) callee: String,
382    }
383}