Skip to main content

bynk/
explain.rs

1//! `bynk explain <code>` — the compiler's own explanation of a diagnostic code
2//! (#853), the analogue of `rustc --explain`.
3//!
4//! Every `bynk.*` diagnostic carries a stable machine code, but a code alone is
5//! a dead end for a newcomer. This subcommand prints the curated, *offline-
6//! complete* explanation behind a code — what the rule is, why it exists, and a
7//! minimal before/after example — plus a link to the relevant Book concept
8//! page. The same [`bynk_syntax::diagnostics::EXPLANATIONS`] table backs the
9//! editor's clickable diagnostic-code links (`codeDescription`), so the two
10//! surfaces never drift (DECISION A).
11//!
12//! Coverage is incremental (DECISION B): a code with no curated explanation is
13//! reported gracefully (its one-line registry summary, and a note that a longer
14//! explanation is not written yet), and a code the compiler does not recognise
15//! at all exits non-zero. Neither is an error state for the feature — a
16//! half-covered set is a designed, documented condition.
17
18use std::process::ExitCode;
19
20use bynk_syntax::diagnostics::{self, Explain};
21
22/// What `explain` found for a requested code.
23enum Lookup {
24    /// A curated explanation.
25    Explained(&'static Explain),
26    /// A real diagnostic code with no curated explanation yet; carries its
27    /// one-line registry summary.
28    KnownUnexplained(&'static str),
29    /// Not a code the compiler emits.
30    Unknown,
31}
32
33fn lookup(code: &str) -> Lookup {
34    if let Some(e) = diagnostics::explain(code) {
35        Lookup::Explained(e)
36    } else if let Some(info) = diagnostics::REGISTRY.iter().find(|d| d.code == code) {
37        Lookup::KnownUnexplained(info.summary)
38    } else {
39        Lookup::Unknown
40    }
41}
42
43/// The full explanation text for a curated code (the offline-complete answer).
44fn render_explained(e: &Explain) -> String {
45    let mut out = String::new();
46    out.push_str(e.code);
47    out.push_str("\n\n");
48    out.push_str(e.blurb);
49    out.push_str("\n\nExample:\n\n");
50    for line in e.example.lines() {
51        out.push_str("    ");
52        out.push_str(line);
53        out.push('\n');
54    }
55    out.push_str("\nLearn more: ");
56    out.push_str(&e.href());
57    out.push('\n');
58    out
59}
60
61/// The graceful message for a real code that has no curated explanation yet.
62fn render_known_unexplained(code: &str, summary: &str) -> String {
63    format!(
64        "{code}\n\n{summary}\n\n\
65         No extended explanation is written for this code yet. See the diagnostic \
66         index for every code and its summary:\n\n    {}/book/reference/diagnostics/\n",
67        diagnostics::BOOK_BASE_URL
68    )
69}
70
71/// Run `bynk explain <code>`. Exits `0` for a recognised code (explained or
72/// not) and non-zero for an unrecognised one.
73pub fn run(code: &str) -> ExitCode {
74    match lookup(code) {
75        Lookup::Explained(e) => {
76            print!("{}", render_explained(e));
77            ExitCode::SUCCESS
78        }
79        Lookup::KnownUnexplained(summary) => {
80            print!("{}", render_known_unexplained(code, summary));
81            ExitCode::SUCCESS
82        }
83        Lookup::Unknown => {
84            eprintln!(
85                "error: `{code}` is not a diagnostic code the compiler emits.\n\
86                 Diagnostic codes look like `bynk.resolve.unknown_type`; see \
87                 {}/book/reference/diagnostics/ for the full list.",
88                diagnostics::BOOK_BASE_URL
89            );
90            ExitCode::FAILURE
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn explained_code_prints_blurb_example_and_href() {
101        let e = diagnostics::explain("bynk.resolve.unknown_type").expect("curated");
102        let out = render_explained(e);
103        assert!(out.contains("bynk.resolve.unknown_type"));
104        assert!(out.contains(e.blurb));
105        assert!(out.contains("Example:"));
106        // The hosted href is the offline-visible "learn more" target.
107        assert!(out.contains("https://bynk-lang.org/book/reference/types/"));
108    }
109
110    #[test]
111    fn known_but_unexplained_code_is_graceful() {
112        // Pick a real registry code with no curated explanation. If this ever
113        // gains one, swap it — the point is a code that exists but isn't curated.
114        let code = "bynk.resolve.duplicate_type";
115        assert!(diagnostics::explain(code).is_none());
116        assert!(matches!(lookup(code), Lookup::KnownUnexplained(_)));
117        let out = render_known_unexplained(code, "Two types share a name.");
118        assert!(out.contains(code));
119        assert!(out.contains("No extended explanation"));
120    }
121
122    #[test]
123    fn unknown_code_is_not_found() {
124        assert!(matches!(lookup("bynk.not.a_real_code"), Lookup::Unknown));
125        assert!(matches!(lookup("nonsense"), Lookup::Unknown));
126    }
127}