litcheck_core/
diagnostics.rs

1pub use miette::{
2    bail, diagnostic, ByteOffset, Diagnostic, IntoDiagnostic, LabeledSpan, Report, Severity,
3    SourceCode, SourceOffset, SourceSpan, WrapErr,
4};
5
6pub mod reporting {
7    pub use miette::{
8        set_hook, DebugReportHandler, JSONReportHandler, NarratableReportHandler, ReportHandler,
9    };
10
11    #[cfg(feature = "fancy-diagnostics")]
12    pub use miette::{GraphicalReportHandler, GraphicalTheme};
13    #[cfg(feature = "fancy-diagnostics")]
14    pub type ReportHandlerOpts = miette::MietteHandlerOpts;
15    #[cfg(feature = "fancy-diagnostics")]
16    pub type DefaultReportHandler = miette::GraphicalReportHandler;
17    #[cfg(not(feature = "fancy-diagnostics"))]
18    pub type DefaultReportHandler = miette::DebugReportHandler;
19
20    pub struct PrintDiagnostic<D, R = DefaultReportHandler> {
21        handler: R,
22        diag: D,
23    }
24    impl<D: AsRef<dyn super::Diagnostic>> PrintDiagnostic<D> {
25        pub fn new(diag: D) -> Self {
26            Self {
27                handler: Default::default(),
28                diag,
29            }
30        }
31        #[cfg(feature = "fancy-diagnostics")]
32        pub fn new_without_color(diag: D) -> Self {
33            Self {
34                handler: DefaultReportHandler::new_themed(GraphicalTheme::none()),
35                diag,
36            }
37        }
38        #[cfg(not(feature = "fancy-diagnostics"))]
39        pub fn new_without_color(diag: D) -> Self {
40            Self::new(diag)
41        }
42    }
43    impl<D: AsRef<dyn super::Diagnostic>> PrintDiagnostic<D, NarratableReportHandler> {
44        pub fn narrated(diag: D) -> Self {
45            Self {
46                handler: NarratableReportHandler::default(),
47                diag,
48            }
49        }
50    }
51    impl<D: AsRef<dyn super::Diagnostic>> PrintDiagnostic<D, JSONReportHandler> {
52        pub fn json(diag: D) -> Self {
53            Self {
54                handler: JSONReportHandler,
55                diag,
56            }
57        }
58    }
59    impl<D: AsRef<dyn super::Diagnostic>> core::fmt::Display for PrintDiagnostic<D> {
60        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
61            self.handler.render_report(f, self.diag.as_ref())
62        }
63    }
64    impl<D: AsRef<dyn super::Diagnostic>> core::fmt::Display
65        for PrintDiagnostic<D, NarratableReportHandler>
66    {
67        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
68            self.handler.render_report(f, self.diag.as_ref())
69        }
70    }
71    impl<D: AsRef<dyn super::Diagnostic>> core::fmt::Display for PrintDiagnostic<D, JSONReportHandler> {
72        fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
73            self.handler.render_report(f, self.diag.as_ref())
74        }
75    }
76}
77
78#[cfg(feature = "fancy-diagnostics")]
79pub use miette::set_panic_hook;
80
81pub type Diag = miette::MietteDiagnostic;
82pub type DiagResult<T> = miette::Result<T>;
83
84use std::{
85    borrow::{Borrow, Cow},
86    fmt,
87    hash::{Hash, Hasher},
88    ops::{Deref, DerefMut},
89    path::Path,
90    sync::Arc,
91};
92
93use miette::{MietteError, SpanContents};
94
95use crate::{range::Range, StaticCow};
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct Label {
99    span: SourceSpan,
100    label: Option<StaticCow<str>>,
101}
102impl Label {
103    pub fn at<R>(range: R) -> Self
104    where
105        Range<usize>: From<R>,
106    {
107        let range = Range::<usize>::from(range);
108        Self {
109            span: range.into(),
110            label: None,
111        }
112    }
113
114    pub fn point<L>(at: usize, label: L) -> Self
115    where
116        StaticCow<str>: From<L>,
117    {
118        Self {
119            span: SourceSpan::from(at),
120            label: Some(Cow::from(label)),
121        }
122    }
123
124    pub fn new<R, L>(range: R, label: L) -> Self
125    where
126        Range<usize>: From<R>,
127        StaticCow<str>: From<L>,
128    {
129        let range = Range::<usize>::from(range);
130        Self {
131            span: range.into(),
132            label: Some(Cow::from(label)),
133        }
134    }
135
136    pub fn label(&self) -> Option<&str> {
137        self.label.as_deref()
138    }
139}
140impl From<Label> for SourceSpan {
141    #[inline(always)]
142    fn from(label: Label) -> SourceSpan {
143        label.span
144    }
145}
146impl From<Label> for LabeledSpan {
147    #[inline]
148    fn from(label: Label) -> LabeledSpan {
149        if let Some(message) = label.label {
150            LabeledSpan::at(label.span, message)
151        } else {
152            LabeledSpan::underline(label.span)
153        }
154    }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum FileName {
159    Stdin,
160    Path(Box<Path>),
161    Virtual(StaticCow<str>),
162}
163impl FileName {
164    pub fn from_static_str(name: &'static str) -> Self {
165        Self::Virtual(Cow::Borrowed(name))
166    }
167
168    pub fn name(&self) -> &str {
169        match self {
170            Self::Stdin => "stdin",
171            Self::Path(ref path) => path.to_str().unwrap_or("<invalid>"),
172            Self::Virtual(ref name) => name.as_ref(),
173        }
174    }
175}
176impl From<&Path> for FileName {
177    fn from(path: &Path) -> Self {
178        if path.as_os_str() == "-" {
179            Self::Stdin
180        } else {
181            Self::Path(path.to_path_buf().into_boxed_path())
182        }
183    }
184}
185impl From<Box<Path>> for FileName {
186    fn from(path: Box<Path>) -> Self {
187        if path.as_os_str() == "-" {
188            Self::Stdin
189        } else {
190            Self::Path(path)
191        }
192    }
193}
194impl From<std::path::PathBuf> for FileName {
195    fn from(path: std::path::PathBuf) -> Self {
196        if path.as_os_str() == "-" {
197            Self::Stdin
198        } else {
199            Self::Path(path.into_boxed_path())
200        }
201    }
202}
203impl From<&str> for FileName {
204    fn from(name: &str) -> Self {
205        if name == "-" {
206            Self::Stdin
207        } else {
208            Self::Virtual(Cow::Owned(name.to_string()))
209        }
210    }
211}
212impl From<String> for FileName {
213    fn from(name: String) -> Self {
214        if name == "-" {
215            Self::Stdin
216        } else {
217            Self::Virtual(Cow::Owned(name))
218        }
219    }
220}
221impl fmt::Display for FileName {
222    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223        match self {
224            Self::Stdin => f.write_str("stdin"),
225            Self::Path(ref path) => write!(f, "{}", path.display()),
226            Self::Virtual(ref name) => f.write_str(name),
227        }
228    }
229}
230
231pub trait NamedSourceFile: SourceFile {
232    fn name(&self) -> FileName {
233        FileName::Stdin
234    }
235}
236
237pub trait SourceFile {
238    fn span(&self) -> SourceSpan;
239    fn source(&self) -> &str;
240    #[inline]
241    fn source_bytes(&self) -> &[u8] {
242        self.source().as_bytes()
243    }
244}
245impl SourceFile for str {
246    fn span(&self) -> SourceSpan {
247        let len = self.as_bytes().len();
248        SourceSpan::from(0..len)
249    }
250    fn source(&self) -> &str {
251        self
252    }
253}
254impl NamedSourceFile for str {}
255impl SourceFile for String {
256    fn span(&self) -> SourceSpan {
257        let len = self.as_bytes().len();
258        SourceSpan::from(0..len)
259    }
260    fn source(&self) -> &str {
261        self.as_str()
262    }
263}
264impl NamedSourceFile for String {}
265impl SourceFile for Box<str> {
266    fn span(&self) -> SourceSpan {
267        (**self).span()
268    }
269    fn source(&self) -> &str {
270        self.as_ref()
271    }
272}
273impl NamedSourceFile for Box<str> {}
274impl<'a> SourceFile for Cow<'a, str> {
275    fn span(&self) -> SourceSpan {
276        (**self).span()
277    }
278    fn source(&self) -> &str {
279        self.as_ref()
280    }
281}
282impl<'a> NamedSourceFile for Cow<'a, str> {}
283impl SourceFile for Arc<str> {
284    fn span(&self) -> SourceSpan {
285        (**self).span()
286    }
287    fn source(&self) -> &str {
288        self.as_ref()
289    }
290}
291impl NamedSourceFile for Arc<str> {}
292
293#[derive(Debug, Clone)]
294pub struct ArcSource(Arc<Source<'static>>);
295impl From<String> for ArcSource {
296    fn from(s: String) -> Self {
297        Self::new(Source::from(s))
298    }
299}
300impl From<&'static str> for ArcSource {
301    fn from(s: &'static str) -> Self {
302        Self::new(Source::new(FileName::Stdin, Cow::Borrowed(s)))
303    }
304}
305impl From<Source<'static>> for ArcSource {
306    fn from(source: Source<'static>) -> Self {
307        Self::new(source)
308    }
309}
310impl ArcSource {
311    pub fn new(source: Source<'static>) -> Self {
312        Self(Arc::new(source))
313    }
314}
315impl Deref for ArcSource {
316    type Target = str;
317
318    fn deref(&self) -> &Self::Target {
319        self.0.source()
320    }
321}
322impl AsRef<[u8]> for ArcSource {
323    #[inline(always)]
324    fn as_ref(&self) -> &[u8] {
325        self.0.source_bytes()
326    }
327}
328impl AsRef<str> for ArcSource {
329    #[inline(always)]
330    fn as_ref(&self) -> &str {
331        self.0.source()
332    }
333}
334impl SourceFile for ArcSource {
335    fn span(&self) -> SourceSpan {
336        self.0.span()
337    }
338    fn source(&self) -> &str {
339        self.0.source()
340    }
341}
342impl NamedSourceFile for ArcSource {
343    fn name(&self) -> FileName {
344        self.0.name()
345    }
346}
347impl SourceCode for ArcSource {
348    #[inline(always)]
349    fn read_span<'a>(
350        &'a self,
351        span: &SourceSpan,
352        context_lines_before: usize,
353        context_lines_after: usize,
354    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
355        self.0
356            .read_span(span, context_lines_before, context_lines_after)
357    }
358}
359
360#[derive(Debug)]
361pub struct Source<'a> {
362    pub name: FileName,
363    pub code: Cow<'a, str>,
364}
365impl<'a> From<&'a str> for Source<'a> {
366    fn from(s: &'a str) -> Self {
367        Self {
368            name: FileName::Stdin,
369            code: Cow::Borrowed(s),
370        }
371    }
372}
373impl<'a> From<String> for Source<'a> {
374    fn from(code: String) -> Self {
375        Self {
376            name: FileName::Stdin,
377            code: Cow::Owned(code),
378        }
379    }
380}
381impl<'a> Source<'a> {
382    pub fn new<N, S>(name: N, code: S) -> Self
383    where
384        FileName: From<N>,
385        Cow<'a, str>: From<S>,
386    {
387        Self {
388            name: FileName::from(name),
389            code: Cow::from(code),
390        }
391    }
392}
393impl Source<'static> {
394    pub fn into_arc_source(self) -> ArcSource {
395        ArcSource::new(self)
396    }
397}
398impl<'a> SourceFile for Source<'a> {
399    fn span(&self) -> SourceSpan {
400        self.code.span()
401    }
402    fn source(&self) -> &str {
403        self.code.source()
404    }
405}
406impl<'a> NamedSourceFile for Source<'a> {
407    fn name(&self) -> FileName {
408        self.name.clone()
409    }
410}
411impl<'s> SourceCode for Source<'s> {
412    #[inline(always)]
413    fn read_span<'a>(
414        &'a self,
415        span: &SourceSpan,
416        context_lines_before: usize,
417        context_lines_after: usize,
418    ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
419        self.code
420            .read_span(span, context_lines_before, context_lines_after)
421    }
422}
423
424pub trait Spanned {
425    fn start(&self) -> usize {
426        self.span().start()
427    }
428    fn end(&self) -> usize {
429        self.span().end()
430    }
431    fn range(&self) -> Range<usize> {
432        self.span().range()
433    }
434    fn span(&self) -> SourceSpan;
435}
436impl<S: Spanned + ?Sized> Spanned for Box<S> {
437    fn span(&self) -> SourceSpan {
438        (**self).span()
439    }
440}
441impl<S: Spanned + ?Sized> Spanned for &S {
442    fn span(&self) -> SourceSpan {
443        (**self).span()
444    }
445}
446impl<S: Spanned + ?Sized> Spanned for &mut S {
447    fn span(&self) -> SourceSpan {
448        (**self).span()
449    }
450}
451impl Spanned for SourceSpan {
452    #[inline(always)]
453    fn start(&self) -> usize {
454        self.offset()
455    }
456    #[inline(always)]
457    fn end(&self) -> usize {
458        self.offset() + self.len()
459    }
460    #[inline]
461    fn range(&self) -> Range<usize> {
462        let offset = self.offset();
463        Range::new(offset, offset + self.len())
464    }
465    fn span(&self) -> SourceSpan {
466        *self
467    }
468}
469
470/// Represents the source span of an item of type [T]
471pub struct Span<T> {
472    span: Range<usize>,
473    spanned: T,
474}
475impl<T: Copy> Copy for Span<T> {}
476impl<T: Clone> Clone for Span<T> {
477    fn clone(&self) -> Self {
478        Self {
479            span: self.span,
480            spanned: self.spanned.clone(),
481        }
482    }
483}
484impl<T> Span<T> {
485    /// Create a span for `spanned` with `range`
486    #[inline]
487    pub fn new<R>(range: R, spanned: T) -> Self
488    where
489        Range<usize>: From<R>,
490    {
491        Self {
492            span: Range::from(range),
493            spanned,
494        }
495    }
496
497    /// Create a span for `spanned` representing a single location, `offset`
498    #[inline]
499    pub fn at(offset: usize, spanned: T) -> Self {
500        Self {
501            span: Range::new(offset, offset),
502            spanned,
503        }
504    }
505
506    #[inline]
507    pub fn map<U, F>(self, mut f: F) -> Span<U>
508    where
509        F: FnMut(T) -> U,
510    {
511        Span {
512            span: self.span,
513            spanned: f(self.spanned),
514        }
515    }
516
517    /// Shift the span right by `count` units
518    #[inline]
519    pub fn shift(&mut self, count: usize) {
520        self.span.start += count;
521        self.span.end += count;
522    }
523
524    /// Extend the end of the span by `count` units
525    #[inline]
526    pub fn extend(&mut self, count: usize) {
527        self.span.end += count;
528    }
529
530    #[inline]
531    pub fn into_parts(self) -> (SourceSpan, T) {
532        (self.span.into(), self.spanned)
533    }
534
535    #[inline]
536    pub fn into_inner(self) -> T {
537        self.spanned
538    }
539}
540impl<T: Borrow<str>, S: Borrow<T>> Borrow<T> for Span<S> {
541    fn borrow(&self) -> &T {
542        self.spanned.borrow()
543    }
544}
545impl<T> Deref for Span<T> {
546    type Target = T;
547
548    #[inline(always)]
549    fn deref(&self) -> &Self::Target {
550        &self.spanned
551    }
552}
553impl<T> DerefMut for Span<T> {
554    #[inline(always)]
555    fn deref_mut(&mut self) -> &mut Self::Target {
556        &mut self.spanned
557    }
558}
559impl<T: ?Sized, U: AsRef<T>> AsRef<T> for Span<U> {
560    fn as_ref(&self) -> &T {
561        self.spanned.as_ref()
562    }
563}
564impl<T: ?Sized, U: AsMut<T>> AsMut<T> for Span<U> {
565    fn as_mut(&mut self) -> &mut T {
566        self.spanned.as_mut()
567    }
568}
569impl<T: fmt::Debug> fmt::Debug for Span<T> {
570    #[inline]
571    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
572        fmt::Debug::fmt(&self.spanned, f)
573    }
574}
575impl<T: fmt::Display> fmt::Display for Span<T> {
576    #[inline]
577    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
578        fmt::Display::fmt(&self.spanned, f)
579    }
580}
581impl<T: Eq> Eq for Span<T> {}
582impl<T: PartialEq> PartialEq for Span<T> {
583    #[inline]
584    fn eq(&self, other: &Self) -> bool {
585        self.spanned.eq(&other.spanned)
586    }
587}
588impl<T: PartialEq> PartialEq<T> for Span<T> {
589    #[inline]
590    fn eq(&self, other: &T) -> bool {
591        self.spanned.eq(other)
592    }
593}
594impl<T: Ord> Ord for Span<T> {
595    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
596        self.spanned.cmp(&other.spanned)
597    }
598}
599impl<T: PartialOrd> PartialOrd for Span<T> {
600    #[inline]
601    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
602        self.spanned.partial_cmp(&other.spanned)
603    }
604}
605impl<T: Hash> Hash for Span<T> {
606    fn hash<H: Hasher>(&self, state: &mut H) {
607        self.spanned.hash(state);
608    }
609}
610impl<T> Spanned for Span<T> {
611    #[inline(always)]
612    fn start(&self) -> usize {
613        self.span.start
614    }
615
616    #[inline(always)]
617    fn end(&self) -> usize {
618        self.span.end
619    }
620
621    #[inline(always)]
622    fn range(&self) -> Range<usize> {
623        self.span
624    }
625
626    #[inline]
627    fn span(&self) -> SourceSpan {
628        self.span.into()
629    }
630}