Skip to main content

target_spec_miette/
imp.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use miette::{Diagnostic, LabeledSpan, SourceCode, SourceOffset, SourceSpan};
5use std::{error::Error as StdError, fmt};
6use target_spec::errors::{
7    CustomTripleCreateError, Error as TargetSpecError, ExpressionParseError, PlainStringParseError,
8    RustcVersionVerboseParseError, TripleParseError,
9};
10
11/// Extension trait that converts errors into a [`miette::Diagnostic`].
12pub trait IntoMietteDiagnostic {
13    /// The `Diagnostic` type that `self` will be converted to.
14    type IntoDiagnostic;
15
16    /// Converts the underlying error into [`Self::IntoDiagnostic`].
17    ///
18    /// This can be used to pretty-print errors returned by target-spec.
19    fn into_diagnostic(self) -> Self::IntoDiagnostic;
20}
21
22impl IntoMietteDiagnostic for TargetSpecError {
23    type IntoDiagnostic = Box<dyn Diagnostic + Send + Sync + 'static>;
24
25    fn into_diagnostic(self) -> Self::IntoDiagnostic {
26        match self {
27            Self::InvalidExpression(error) => Box::new(error.into_diagnostic()),
28            Self::InvalidTargetSpecString(error) => Box::new(error.into_diagnostic()),
29            Self::UnknownPlatformTriple(error) => Box::new(error.into_diagnostic()),
30            #[allow(deprecated)]
31            Self::CustomTripleCreate(error) => Box::new(error.into_diagnostic()),
32            Self::CustomPlatformCreate(error) => Box::new(error.into_diagnostic()),
33            Self::RustcVersionVerboseParse(error) => Box::new(error.into_diagnostic()),
34            other => Box::<dyn Diagnostic + Send + Sync + 'static>::from(other.to_string()),
35        }
36    }
37}
38
39/// A wrapper around [`ExpressionParseError`] that implements [`Diagnostic`].
40#[derive(Clone, PartialEq, Eq)]
41pub struct ExpressionParseDiagnostic(ExpressionParseError);
42
43impl ExpressionParseDiagnostic {
44    /// Creates a new `ExpressionParseDiagnostic`.
45    pub fn new(error: ExpressionParseError) -> Self {
46        Self(error)
47    }
48}
49
50impl fmt::Debug for ExpressionParseDiagnostic {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        fmt::Debug::fmt(&self.0, f)
53    }
54}
55
56impl fmt::Display for ExpressionParseDiagnostic {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        fmt::Display::fmt(&self.0, f)
59    }
60}
61
62impl StdError for ExpressionParseDiagnostic {
63    fn source(&self) -> Option<&(dyn StdError + 'static)> {
64        self.0.source()
65    }
66}
67
68impl Diagnostic for ExpressionParseDiagnostic {
69    fn source_code(&self) -> Option<&dyn SourceCode> {
70        Some(&self.0.input)
71    }
72
73    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
74        let label = LabeledSpan::new_with_span(Some(self.0.kind.to_string()), self.0.span.clone());
75        Some(Box::new(std::iter::once(label)))
76    }
77}
78
79impl IntoMietteDiagnostic for ExpressionParseError {
80    type IntoDiagnostic = ExpressionParseDiagnostic;
81
82    fn into_diagnostic(self) -> Self::IntoDiagnostic {
83        ExpressionParseDiagnostic::new(self)
84    }
85}
86
87/// A wrapper around [`TripleParseError`] that implements [`Diagnostic`].
88#[derive(Clone, PartialEq, Eq)]
89pub struct TripleParseDiagnostic {
90    error: TripleParseError,
91    // Need to store this separately because &str can't be cast to &dyn SourceCode.
92    triple_str: String,
93}
94
95impl TripleParseDiagnostic {
96    /// Creates a new `ExpressionParseDiagnostic`.
97    pub fn new(error: TripleParseError) -> Self {
98        let triple_str = error.triple_str().to_owned();
99        Self { error, triple_str }
100    }
101}
102
103impl fmt::Debug for TripleParseDiagnostic {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        fmt::Debug::fmt(&self.error, f)
106    }
107}
108
109impl fmt::Display for TripleParseDiagnostic {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        fmt::Display::fmt(&self.error, f)
112    }
113}
114
115impl StdError for TripleParseDiagnostic {
116    fn source(&self) -> Option<&(dyn StdError + 'static)> {
117        self.error.source()
118    }
119}
120
121impl Diagnostic for TripleParseDiagnostic {
122    fn source_code(&self) -> Option<&dyn SourceCode> {
123        Some(&self.triple_str as &dyn SourceCode)
124    }
125
126    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
127        let label = LabeledSpan::new_with_span(
128            Some(
129                self.error
130                    .source()
131                    .expect("TripleParseError always returns a source")
132                    .to_string(),
133            ),
134            (0, self.triple_str.len()),
135        );
136        Some(Box::new(std::iter::once(label)))
137    }
138}
139
140impl IntoMietteDiagnostic for TripleParseError {
141    type IntoDiagnostic = TripleParseDiagnostic;
142
143    fn into_diagnostic(self) -> Self::IntoDiagnostic {
144        TripleParseDiagnostic::new(self)
145    }
146}
147
148/// A wrapper around [`PlainStringParseError`] that implements [`Diagnostic`].
149#[derive(Clone, PartialEq, Eq)]
150pub struct PlainStringParseDiagnostic {
151    error: PlainStringParseError,
152    // Need to store this separately because &str can't be cast to &dyn SourceCode.
153    input: String,
154}
155
156impl PlainStringParseDiagnostic {
157    /// Creates a new `ExpressionParseDiagnostic`.
158    pub fn new(error: PlainStringParseError) -> Self {
159        let input = error.input.clone();
160        Self { error, input }
161    }
162}
163
164impl fmt::Debug for PlainStringParseDiagnostic {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        fmt::Debug::fmt(&self.error, f)
167    }
168}
169
170impl fmt::Display for PlainStringParseDiagnostic {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        // The full error message duplicates information produced by the diagnostic, so keep it
173        // short.
174        f.write_str("invalid triple identifier")
175    }
176}
177
178impl StdError for PlainStringParseDiagnostic {
179    fn source(&self) -> Option<&(dyn StdError + 'static)> {
180        self.error.source()
181    }
182}
183
184impl Diagnostic for PlainStringParseDiagnostic {
185    fn source_code(&self) -> Option<&dyn SourceCode> {
186        Some(&self.input as &dyn SourceCode)
187    }
188
189    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
190        let label = LabeledSpan::new_with_span(
191            Some("character must be alphanumeric, -, _ or .".to_owned()),
192            self.error.span(),
193        );
194        Some(Box::new(std::iter::once(label)))
195    }
196}
197
198impl IntoMietteDiagnostic for PlainStringParseError {
199    type IntoDiagnostic = PlainStringParseDiagnostic;
200
201    fn into_diagnostic(self) -> Self::IntoDiagnostic {
202        PlainStringParseDiagnostic::new(self)
203    }
204}
205
206/// A wrapper around [`RustcVersionVerboseParseError`] that implements
207/// [`Diagnostic`].
208#[derive(Clone, Debug)]
209pub struct RustcVersionVerboseParseDiagnostic(RustcVersionVerboseParseError);
210
211impl RustcVersionVerboseParseDiagnostic {
212    /// Creates a new `RustcVersionVerboseParseDiagnostic`.
213    pub fn new(error: RustcVersionVerboseParseError) -> Self {
214        Self(error)
215    }
216}
217
218impl fmt::Display for RustcVersionVerboseParseDiagnostic {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        match &self.0 {
221            // The inner Display for `MissingHostLine` embeds the entire `rustc
222            // -vV` output, which duplicates the source code that miette already
223            // renders below. Here, we keep the headline message short, and let
224            // the labeled span display the `rustc -vV` output.
225            RustcVersionVerboseParseError::MissingHostLine { .. } => {
226                f.write_str("output from `rustc -vV` did not contain a `host:` line")
227            }
228            // The inner Display for `InvalidUtf8` is already short and
229            // self-contained.
230            RustcVersionVerboseParseError::InvalidUtf8(_) => fmt::Display::fmt(&self.0, f),
231            _ => fmt::Display::fmt(&self.0, f),
232        }
233    }
234}
235
236impl StdError for RustcVersionVerboseParseDiagnostic {
237    fn source(&self) -> Option<&(dyn StdError + 'static)> {
238        self.0.source()
239    }
240}
241
242impl Diagnostic for RustcVersionVerboseParseDiagnostic {
243    fn source_code(&self) -> Option<&dyn SourceCode> {
244        match &self.0 {
245            RustcVersionVerboseParseError::MissingHostLine { output } => {
246                Some(output as &dyn SourceCode)
247            }
248            RustcVersionVerboseParseError::InvalidUtf8(_) => None,
249            _ => None,
250        }
251    }
252
253    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
254        match &self.0 {
255            RustcVersionVerboseParseError::MissingHostLine { output } => {
256                let label = LabeledSpan::new_with_span(
257                    Some("expected a `host: <triple>` line in this output".to_owned()),
258                    (0, output.len()),
259                );
260                Some(Box::new(std::iter::once(label)))
261            }
262            RustcVersionVerboseParseError::InvalidUtf8(_) => None,
263            _ => None,
264        }
265    }
266}
267
268impl IntoMietteDiagnostic for RustcVersionVerboseParseError {
269    type IntoDiagnostic = RustcVersionVerboseParseDiagnostic;
270
271    fn into_diagnostic(self) -> Self::IntoDiagnostic {
272        RustcVersionVerboseParseDiagnostic::new(self)
273    }
274}
275
276impl IntoMietteDiagnostic for CustomTripleCreateError {
277    type IntoDiagnostic = CustomTripleCreateDiagnostic;
278
279    fn into_diagnostic(self) -> Self::IntoDiagnostic {
280        CustomTripleCreateDiagnostic::new(self)
281    }
282}
283
284/// A wrapper around [`CustomTripleCreateError`] that implements [`Diagnostic`].
285pub struct CustomTripleCreateDiagnostic(CustomTripleCreateError);
286
287impl CustomTripleCreateDiagnostic {
288    /// Creates a new `CustomTripleCreateDiagnostic`.
289    pub fn new(error: CustomTripleCreateError) -> Self {
290        Self(error)
291    }
292}
293
294impl fmt::Debug for CustomTripleCreateDiagnostic {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        fmt::Debug::fmt(&self.0, f)
297    }
298}
299
300impl fmt::Display for CustomTripleCreateDiagnostic {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        fmt::Display::fmt(&self.0, f)
303    }
304}
305
306impl StdError for CustomTripleCreateDiagnostic {
307    fn source(&self) -> Option<&(dyn StdError + 'static)> {
308        // Don't show the source in case we return labels below.
309        if self.0.input().is_some() && self.0.line_and_column().is_some() {
310            None
311        } else {
312            self.0.source()
313        }
314    }
315}
316
317impl Diagnostic for CustomTripleCreateDiagnostic {
318    fn source_code(&self) -> Option<&dyn SourceCode> {
319        self.0.input_string().map(|input| input as &dyn SourceCode)
320    }
321
322    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
323        // ughhh, clippy warns about `?` here but I don't like it:
324        // https://github.com/rust-lang/rust-clippy/issues/13804
325        let input = self.0.input()?;
326        let (line, column) = self.0.line_and_column()?;
327
328        let source_offset = SourceOffset::from_location(input, line, column);
329        // serde_json doesn't return the span of the error, just a single
330        // offset.
331        let span = SourceSpan::new(source_offset, 0);
332
333        let label = LabeledSpan::new_with_span(self.0.label(), span);
334        Some(Box::new(std::iter::once(label)))
335    }
336}