Skip to main content

provable_contracts/query/
types_render.rs

1//! Display and Markdown rendering for query result types.
2//!
3//! Split from types.rs to keep per-function complexity under thresholds.
4
5use super::types::{
6    CallSiteInfo, DiffInfo, EquationBinding, ProjectCoverage, ProofStatusInfo, QueryOutput,
7    QueryResult, ScoreInfo, ViolationInfo,
8};
9
10impl QueryResult {
11    pub(crate) fn fmt_enrichment(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12        fmt_score(f, self.score.as_ref())?;
13        fmt_proof_status(f, self.proof_status.as_ref())?;
14        fmt_bindings(f, &self.bindings)?;
15        fmt_diff(f, self.diff.as_ref())?;
16        fmt_pagerank(f, self.pagerank)?;
17        fmt_call_sites(f, &self.call_sites)?;
18        fmt_violations(f, &self.violations)?;
19        fmt_coverage_map(f, &self.coverage_map)?;
20        Ok(())
21    }
22
23    pub(crate) fn to_markdown_item(&self) -> String {
24        use crate::schema::ContractKind;
25        let mut out = format!("### {}. {}\n\n", self.rank, self.stem);
26        out.push_str(&format!("- **Relevance:** {:.2}\n", self.relevance));
27        if self.kind != ContractKind::Kernel {
28            out.push_str(&format!("- **Kind:** {}\n", self.kind));
29        }
30        if !self.equations.is_empty() {
31            out.push_str(&format!("- **Equations:** {}\n", self.equations.join(", ")));
32        }
33        out.push_str(&format!("- **Obligations:** {}\n", self.obligation_count));
34        md_score(&mut out, self.score.as_ref());
35        md_proof_status(&mut out, self.proof_status.as_ref());
36        md_bindings(&mut out, &self.bindings);
37        md_diff(&mut out, self.diff.as_ref());
38        md_pagerank(&mut out, self.pagerank);
39        md_call_sites(&mut out, &self.call_sites);
40        md_violations(&mut out, &self.violations);
41        md_coverage_map(&mut out, &self.coverage_map);
42        if !self.references.is_empty() {
43            out.push_str(&format!("- **Papers:** {}\n", self.references.join("; ")));
44        }
45        if !self.depends_on.is_empty() {
46            out.push_str(&format!(
47                "- **Depends on:** {}\n",
48                self.depends_on.join(", ")
49            ));
50        }
51        if !self.depended_by.is_empty() {
52            out.push_str(&format!(
53                "- **Depended by:** {}\n",
54                self.depended_by.join(", ")
55            ));
56        }
57        out.push('\n');
58        out
59    }
60}
61
62impl std::fmt::Display for QueryResult {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        use crate::schema::ContractKind;
65        let kind_tag = if self.kind == ContractKind::Kernel {
66            String::new()
67        } else {
68            format!(" [{}]", self.kind)
69        };
70        writeln!(
71            f,
72            "[{}] {}{} (relevance: {:.2})",
73            self.rank, self.stem, kind_tag, self.relevance
74        )?;
75        writeln!(f, "    {}", self.description)?;
76        if !self.equations.is_empty() {
77            writeln!(f, "    Equations: {}", self.equations.join(", "))?;
78        }
79        writeln!(f, "    Obligations: {}", self.obligation_count)?;
80        if !self.references.is_empty() {
81            writeln!(f, "    Papers: {}", self.references.join("; "))?;
82        }
83        self.fmt_enrichment(f)?;
84        if !self.depends_on.is_empty() {
85            writeln!(f, "    Depends on: {}", self.depends_on.join(", "))?;
86        }
87        if !self.depended_by.is_empty() {
88            writeln!(f, "    Depended by: {}", self.depended_by.join(", "))?;
89        }
90        Ok(())
91    }
92}
93
94impl QueryOutput {
95    /// Render results as Markdown (for `--format markdown`).
96    pub fn to_markdown(&self) -> String {
97        let mut out = format!("## Query: \"{}\"\n\n", self.query);
98        for r in &self.results {
99            out.push_str(&r.to_markdown_item());
100        }
101        out.push_str(&format!("*{} matches*\n", self.total_matches));
102        out
103    }
104}
105
106impl std::fmt::Display for QueryOutput {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        for r in &self.results {
109            write!(f, "{r}")?;
110            writeln!(f, "    ---")?;
111        }
112        writeln!(f, "\n{} matches for \"{}\"", self.total_matches, self.query)
113    }
114}
115
116// --- Text format helpers ---
117
118fn fmt_score(f: &mut std::fmt::Formatter<'_>, score: Option<&ScoreInfo>) -> std::fmt::Result {
119    let Some(s) = score else { return Ok(()) };
120    writeln!(f, "    Score: {:.2} (Grade {})", s.composite, s.grade)?;
121    writeln!(
122        f,
123        "    Spec: {:.2} | Falsify: {:.2} | Kani: {:.2} | Lean: {:.2} | Bind: {:.2}",
124        s.spec_depth, s.falsification, s.kani, s.lean, s.binding
125    )
126}
127
128fn fmt_proof_status(
129    f: &mut std::fmt::Formatter<'_>,
130    ps: Option<&ProofStatusInfo>,
131) -> std::fmt::Result {
132    let Some(ps) = ps else { return Ok(()) };
133    writeln!(
134        f,
135        "    Proof Level: {} (ob:{} ft:{} kani:{} lean:{})",
136        ps.level, ps.obligations, ps.falsification_tests, ps.kani_harnesses, ps.lean_proved
137    )
138}
139
140fn fmt_bindings(f: &mut std::fmt::Formatter<'_>, bindings: &[EquationBinding]) -> std::fmt::Result {
141    if bindings.is_empty() {
142        return Ok(());
143    }
144    writeln!(f, "    Bindings:")?;
145    for b in bindings {
146        let loc = b.module_path.as_deref().unwrap_or("unbound");
147        writeln!(f, "      {}: {} ({})", b.equation, b.status, loc)?;
148    }
149    Ok(())
150}
151
152fn fmt_diff(f: &mut std::fmt::Formatter<'_>, diff: Option<&DiffInfo>) -> std::fmt::Result {
153    let Some(d) = diff else { return Ok(()) };
154    writeln!(
155        f,
156        "    Last modified: {} ({} days ago, {})",
157        d.last_modified,
158        d.days_ago,
159        &d.commit_hash[..7.min(d.commit_hash.len())]
160    )
161}
162
163fn fmt_pagerank(f: &mut std::fmt::Formatter<'_>, pr: Option<f64>) -> std::fmt::Result {
164    let Some(pr) = pr else { return Ok(()) };
165    writeln!(f, "    PageRank: {pr:.4}")
166}
167
168fn fmt_call_sites(f: &mut std::fmt::Formatter<'_>, sites: &[CallSiteInfo]) -> std::fmt::Result {
169    if sites.is_empty() {
170        return Ok(());
171    }
172    writeln!(
173        f,
174        "    Call sites ({} projects):",
175        count_unique_projects(sites)
176    )?;
177    for cs in sites {
178        let eq = cs.equation.as_deref().unwrap_or("");
179        writeln!(f, "      {}/{}:{} {eq}", cs.project, cs.file, cs.line)?;
180    }
181    Ok(())
182}
183
184fn fmt_violations(
185    f: &mut std::fmt::Formatter<'_>,
186    violations: &[ViolationInfo],
187) -> std::fmt::Result {
188    if violations.is_empty() {
189        return Ok(());
190    }
191    writeln!(f, "    Violations ({}):", violations.len())?;
192    for v in violations {
193        writeln!(f, "      {}: {} — {}", v.project, v.kind, v.detail)?;
194    }
195    Ok(())
196}
197
198fn fmt_coverage_map(f: &mut std::fmt::Formatter<'_>, map: &[ProjectCoverage]) -> std::fmt::Result {
199    if map.is_empty() {
200        return Ok(());
201    }
202    writeln!(f, "    Coverage map:")?;
203    for c in map {
204        let bar = coverage_bar(c.binding_implemented, c.binding_total);
205        writeln!(
206            f,
207            "      {:<12} {bar} ({} sites, {}/{} bound)",
208            c.project, c.call_sites, c.binding_implemented, c.binding_total
209        )?;
210    }
211    Ok(())
212}
213
214// --- Markdown format helpers ---
215
216fn md_score(out: &mut String, score: Option<&ScoreInfo>) {
217    let Some(s) = score else { return };
218    out.push_str(&format!(
219        "- **Score:** {:.2} (Grade {})\n",
220        s.composite, s.grade
221    ));
222    out.push_str(&format!(
223        "- Spec: {:.2} | Falsify: {:.2} | Kani: {:.2} | Lean: {:.2} | Bind: {:.2}\n",
224        s.spec_depth, s.falsification, s.kani, s.lean, s.binding
225    ));
226}
227
228fn md_proof_status(out: &mut String, ps: Option<&ProofStatusInfo>) {
229    let Some(ps) = ps else { return };
230    out.push_str(&format!(
231        "- **Proof Level:** {} (ob:{} ft:{} kani:{} lean:{})\n",
232        ps.level, ps.obligations, ps.falsification_tests, ps.kani_harnesses, ps.lean_proved
233    ));
234}
235
236fn md_bindings(out: &mut String, bindings: &[EquationBinding]) {
237    if bindings.is_empty() {
238        return;
239    }
240    out.push_str("- **Bindings:**\n");
241    for b in bindings {
242        let loc = b.module_path.as_deref().unwrap_or("unbound");
243        out.push_str(&format!("  - `{}`: {} (`{}`)\n", b.equation, b.status, loc));
244    }
245}
246
247fn md_diff(out: &mut String, diff: Option<&DiffInfo>) {
248    let Some(d) = diff else { return };
249    out.push_str(&format!(
250        "- **Last modified:** {} ({} days ago)\n",
251        d.last_modified, d.days_ago
252    ));
253}
254
255fn md_pagerank(out: &mut String, pr: Option<f64>) {
256    let Some(pr) = pr else { return };
257    out.push_str(&format!("- **PageRank:** {pr:.4}\n"));
258}
259
260fn md_call_sites(out: &mut String, sites: &[CallSiteInfo]) {
261    if sites.is_empty() {
262        return;
263    }
264    out.push_str(&format!(
265        "- **Call sites** ({} projects):\n",
266        count_unique_projects(sites)
267    ));
268    for cs in sites {
269        let eq = cs
270            .equation
271            .as_deref()
272            .map_or(String::new(), |e| format!(" eq={e}"));
273        out.push_str(&format!(
274            "  - `{}/{}:{}`{eq}\n",
275            cs.project, cs.file, cs.line
276        ));
277    }
278}
279
280fn md_violations(out: &mut String, violations: &[ViolationInfo]) {
281    if violations.is_empty() {
282        return;
283    }
284    out.push_str(&format!("- **Violations ({}):**\n", violations.len()));
285    for v in violations {
286        out.push_str(&format!("  - `{}`: {} — {}\n", v.project, v.kind, v.detail));
287    }
288}
289
290fn md_coverage_map(out: &mut String, map: &[ProjectCoverage]) {
291    if map.is_empty() {
292        return;
293    }
294    out.push_str("- **Coverage map:**\n");
295    for c in map {
296        let pct = if c.binding_total > 0 {
297            #[allow(clippy::cast_precision_loss)]
298            {
299                c.binding_implemented as f64 / c.binding_total as f64 * 100.0
300            }
301        } else {
302            0.0
303        };
304        out.push_str(&format!(
305            "  - `{}`: {:.0}% ({}/{} bound, {} sites)\n",
306            c.project, pct, c.binding_implemented, c.binding_total, c.call_sites
307        ));
308    }
309}
310
311// --- Shared helpers ---
312
313pub(crate) fn coverage_bar(implemented: usize, total: usize) -> String {
314    if total == 0 {
315        return "--".to_string();
316    }
317    let filled = (implemented * 6 + total / 2) / total.max(1);
318    let filled = filled.min(6);
319    let empty = 6 - filled;
320    format!("{}{}", "█".repeat(filled), "░".repeat(empty))
321}
322
323fn count_unique_projects(sites: &[CallSiteInfo]) -> usize {
324    let mut seen = std::collections::HashSet::new();
325    for s in sites {
326        seen.insert(&s.project);
327    }
328    seen.len()
329}