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