Skip to main content

submilli_engine/
compiler_error.rs

1//! Typed failures shared by compiler phases and compilation entry points.
2
3use crate::{Diagnostic, FileId, Severity, Span};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum CompilerStage {
7    Parse,
8    Infer,
9    Codegen,
10}
11
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum CompilerFailure {
14    Limit {
15        stage: CompilerStage,
16        span: Option<Span>,
17        message: String,
18        help: Vec<String>,
19    },
20    Internal {
21        stage: CompilerStage,
22        span: Option<Span>,
23        message: String,
24    },
25}
26
27impl CompilerFailure {
28    pub(crate) fn with_span(mut self, source_span: Span) -> Self {
29        let (Self::Limit { span, .. } | Self::Internal { span, .. }) = &mut self;
30        if span.is_none() {
31            *span = Some(source_span);
32        }
33        self
34    }
35
36    pub(crate) fn with_stage(mut self, current_stage: CompilerStage) -> Self {
37        match &mut self {
38            Self::Limit { stage, .. } | Self::Internal { stage, .. } => *stage = current_stage,
39        }
40        self
41    }
42}
43
44impl std::fmt::Display for CompilerFailure {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        let (category, stage, message) = match self {
47            Self::Limit { stage, message, .. } => ("compiler limit", stage, message),
48            Self::Internal { stage, message, .. } => ("internal compiler failure", stage, message),
49        };
50        write!(f, "{category} during {stage:?}: {message}")
51    }
52}
53
54impl std::error::Error for CompilerFailure {}
55
56/// Diagnostics accumulated before a fatal phase failure are retained alongside
57/// its typed cause. Source errors have no fatal cause.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct CompileError {
60    pub diagnostics: Vec<Diagnostic>,
61    pub fatal: Option<CompilerFailure>,
62}
63
64impl CompileError {
65    pub fn with_prior_diagnostics(mut self, diagnostics: &[Diagnostic]) -> Self {
66        self.diagnostics.splice(0..0, diagnostics.iter().cloned());
67        self
68    }
69
70    /// Cuts a limit failure's span to its first line. Limits are met in large
71    /// programs, and they point at statements, such as loops and object
72    /// literals, whose source can run to thousands of lines. `text_of` returns
73    /// a file's source. The compile and typecheck entry points in `compile.rs`
74    /// apply it; the stage functions they call return uncut spans.
75    pub(crate) fn with_limit_span_cut<'a>(
76        mut self,
77        text_of: impl Fn(FileId) -> Option<&'a str>,
78    ) -> Self {
79        if let Some(CompilerFailure::Limit {
80            span: Some(span), ..
81        }) = &mut self.fatal
82            && let Some(text) = text_of(span.file)
83        {
84            *span = span.first_line_of(text);
85        }
86        self
87    }
88
89    /// Explicit compatibility adapter for APIs that historically returned only
90    /// diagnostics. Source-less failures retain a virtual compiler location.
91    pub fn into_diagnostics(mut self, _file: FileId) -> Vec<Diagnostic> {
92        if let Some(fatal) = self.fatal {
93            let span = match &fatal {
94                CompilerFailure::Limit { span, .. } | CompilerFailure::Internal { span, .. } => {
95                    *span
96                }
97            };
98            let (message, help) = match &fatal {
99                CompilerFailure::Limit { message, help, .. } => (message.clone(), help.clone()),
100                CompilerFailure::Internal { .. } => (
101                    fatal.to_string(),
102                    vec!["report this compiler error with the source program".into()],
103                ),
104            };
105            self.diagnostics.push(Diagnostic {
106                severity: Severity::Error,
107                span: span.unwrap_or(Span::at(FileId::COMPILER)),
108                message,
109                help,
110                notes: Vec::new(),
111            });
112        }
113        self.diagnostics
114    }
115}
116
117impl From<Vec<Diagnostic>> for CompileError {
118    fn from(diagnostics: Vec<Diagnostic>) -> Self {
119        Self {
120            diagnostics,
121            fatal: None,
122        }
123    }
124}
125
126impl From<CompilerFailure> for CompileError {
127    fn from(fatal: CompilerFailure) -> Self {
128        Self {
129            diagnostics: Vec::new(),
130            fatal: Some(fatal),
131        }
132    }
133}
134
135impl std::fmt::Display for CompileError {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        if let Some(fatal) = &self.fatal {
138            return fatal.fmt(f);
139        }
140        write!(
141            f,
142            "compilation failed with {} diagnostics",
143            self.diagnostics.len()
144        )
145    }
146}
147
148impl std::error::Error for CompileError {
149    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
150        self.fatal
151            .as_ref()
152            .map(|error| error as &dyn std::error::Error)
153    }
154}