Skip to main content

big_code_analysis/
error.rs

1//! Error type returned from the library's top-level entry points.
2//!
3//! Prior to this module, every entry point returned `Option<…>` and
4//! collapsed parse failure, empty input, and disabled-language builds
5//! into a single `None`. [`MetricsError`] distinguishes those cases so
6//! library consumers can react appropriately (e.g. report a disabled
7//! language distinctly from an empty parse).
8//!
9//! New variants may be added in future minor versions, so consumers
10//! must include a `_` arm when matching exhaustively — this is enforced
11//! by the [`#[non_exhaustive]`][non_exhaustive] attribute on the enum.
12//!
13//! [non_exhaustive]: https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute
14
15use crate::LANG;
16
17/// Error returned by the library's metric-computation entry points.
18///
19/// # Stability
20///
21/// The variant set is additive: new variants may be introduced in
22/// minor versions, so the enum is marked `#[non_exhaustive]`. Existing
23/// variants will not be removed without a major version bump. The
24/// [`std::error::Error`] and [`std::fmt::Display`] impls are part of
25/// the stable surface; the exact wording of the `Display` output is
26/// not.
27///
28/// # Examples
29///
30/// The [`MetricsError::EmptyRoot`] variant is reserved for a future
31/// walker change (see its documentation). The
32/// [`MetricsError::LanguageDisabled`] variant is the one actually
33/// produced today: every dispatch entry point emits it when the
34/// caller selects a
35/// [`LANG`] whose per-language Cargo feature is not enabled in the
36/// current build (see #252). The example exercises the happy path
37/// and demonstrates the exhaustive-with-`_` match shape that callers
38/// should adopt to stay forward-compatible with future variants.
39///
40/// ```
41/// use big_code_analysis::{analyze, MetricsError, MetricsOptions, Source, LANG};
42///
43/// let source = Source::new(LANG::Cpp, b"int a = 42;");
44/// let result = analyze(source, MetricsOptions::default());
45///
46/// // Today this call succeeds; the match below documents the shape
47/// // callers must adopt so adding a future variant is non-breaking.
48/// assert!(result.is_ok());
49///
50/// match result {
51///     Ok(_space) => {}
52///     Err(MetricsError::EmptyRoot) => {
53///         // Reserved: walker produced no top-level FuncSpace.
54///     }
55///     Err(MetricsError::LanguageDisabled(_lang)) => {
56///         // The `LANG` variant the caller asked for has no grammar
57///         // crate compiled in for this build (per-language feature
58///         // disabled — see the `[features]` table in Cargo.toml).
59///     }
60///     // `MetricsError` is `#[non_exhaustive]`; new variants may be added.
61///     Err(_) => {}
62/// }
63/// ```
64#[non_exhaustive]
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum MetricsError {
67    /// The walker produced no top-level [`FuncSpace`][crate::FuncSpace].
68    ///
69    /// Reserved — not produced today. `metrics_with_options` always
70    /// pushes a synthetic top-level [`SpaceKind::Unit`][crate::SpaceKind]
71    /// `FuncSpace` onto its state stack before walking the AST, so
72    /// every parse — including empty input, whitespace-only input,
73    /// and comment-only input — currently returns
74    /// `Ok(FuncSpace { kind: Unit, .. })`. The variant is kept for a
75    /// future walker change that lets the state stack legitimately
76    /// drain to empty (e.g. an option that suppresses the synthetic
77    /// root for sources with no parseable structure).
78    ///
79    /// [`FuncSpace`]: crate::FuncSpace
80    EmptyRoot,
81    /// The requested [`LANG`] is not enabled in this build.
82    ///
83    /// Produced by every dispatch entry point
84    /// ([`crate::analyze`], [`crate::Ast::parse`],
85    /// [`crate::Ast::from_tree_sitter`], and
86    /// [`crate::LANG::tree_sitter_language`])
87    /// when the caller selects a [`LANG`] variant whose per-language
88    /// Cargo feature is not enabled in the current build — see the
89    /// `[features]` table in the root `Cargo.toml` for the list.
90    /// The default feature set (`default = ["all-languages"]`) keeps
91    /// every grammar compiled in, matching the library's historical
92    /// behaviour; callers that opt into a narrower set with
93    /// `--no-default-features --features rust,…` are the only ones
94    /// that observe this variant.
95    LanguageDisabled(LANG),
96}
97
98impl std::fmt::Display for MetricsError {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self {
101            Self::EmptyRoot => {
102                f.write_str("no top-level FuncSpace could be produced from the source AST")
103            }
104            Self::LanguageDisabled(lang) => {
105                write!(f, "language {} is not enabled in this build", lang.name())
106            }
107        }
108    }
109}
110
111impl std::error::Error for MetricsError {}
112
113/// Error returned by [`Ast::from_path`][crate::Ast::from_path].
114///
115/// `from_path` reads, language-detects, and parses a file in one call, so it
116/// can fail in more ways than the in-memory [`Ast::parse`][crate::Ast::parse]
117/// (which only reports [`MetricsError`]). Unlike [`analyze`][crate::analyze],
118/// `from_path` does not silently skip files: every reason it cannot produce a
119/// tree surfaces as a distinct variant so the caller — who asked for *this*
120/// file's tree — learns why.
121///
122/// The enum is `#[non_exhaustive]`; match with a trailing `_` arm to stay
123/// forward-compatible.
124#[non_exhaustive]
125#[derive(Debug)]
126pub enum FromPathError {
127    /// The file could not be read (a genuine I/O fault: missing file,
128    /// permission denied, hardware error). Carries the underlying
129    /// [`std::io::Error`].
130    Io(std::io::Error),
131    /// The path is not valid UTF-8. The path doubles as the resulting
132    /// [`FuncSpace`][crate::FuncSpace] name (an identifier used as a map key
133    /// and in JSON output), so a lossy conversion is rejected rather than
134    /// silently corrupting correlation — mirroring `analyze`'s strict
135    /// default.
136    NonUtf8Path,
137    /// The file is empty, too small, binary, or encoded in an unsupported
138    /// encoding (UTF-16, invalid UTF-8) — the same files
139    /// [`analyze`][crate::analyze] skips. `from_path` reuses the library's
140    /// text reader for byte-exact metric parity with `analyze`, so these
141    /// inputs cannot yield a tree.
142    Unreadable,
143    /// No language is registered for the path (unknown extension and no
144    /// recognizable shebang / mode line).
145    UnknownLanguage,
146    /// The detected language's per-language Cargo feature is not enabled in
147    /// this build (carries the [`MetricsError::LanguageDisabled`] raised by
148    /// [`Ast::parse`][crate::Ast::parse]).
149    Parse(MetricsError),
150}
151
152impl std::fmt::Display for FromPathError {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        match self {
155            Self::Io(e) => write!(f, "could not read file: {e}"),
156            Self::NonUtf8Path => f.write_str("path is not valid UTF-8"),
157            Self::Unreadable => {
158                f.write_str("file is empty, binary, or not valid UTF-8 source text")
159            }
160            Self::UnknownLanguage => f.write_str("no language is registered for this path"),
161            Self::Parse(e) => write!(f, "{e}"),
162        }
163    }
164}
165
166impl std::error::Error for FromPathError {
167    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
168        match self {
169            Self::Io(e) => Some(e),
170            Self::Parse(e) => Some(e),
171            _ => None,
172        }
173    }
174}
175
176impl From<MetricsError> for FromPathError {
177    fn from(e: MetricsError) -> Self {
178        Self::Parse(e)
179    }
180}