1use std::fmt::Debug;
2
3use chumsky::error::Cheap;
4use serde::Serialize;
5
6use super::parser::perror::PError;
7use crate::span::Span;
8
9#[derive(Debug, Clone)]
11pub struct Error {
12 pub kind: MessageKind,
14 pub span: Option<Span>,
15 pub reason: Reason,
16 pub hints: Vec<String>,
17 pub code: Option<&'static str>,
19 }
21
22#[derive(Clone, Debug, Default)]
23pub enum ErrorSource {
24 Lexer(Cheap<char>),
25 Parser(PError),
26 #[default]
27 Unknown,
28 NameResolver,
29 TypeResolver,
30 SQL,
31}
32
33#[derive(Debug, Clone)]
35pub struct Errors(pub Vec<Error>);
36
37#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
39pub enum MessageKind {
40 Error,
41 Warning,
42 Lint,
43}
44
45#[derive(Debug, Clone)]
46pub enum Reason {
47 Simple(String),
48 Expected {
49 who: Option<String>,
52 expected: String,
54 found: String,
56 },
57 Unexpected {
58 found: String,
59 },
60 NotFound {
61 name: String,
62 namespace: String,
63 },
64 Bug {
65 issue: Option<i32>,
66 details: Option<String>,
67 },
68}
69
70impl Error {
71 pub fn new(reason: Reason) -> Self {
72 Error {
73 kind: MessageKind::Error,
74 span: None,
75 reason,
76 hints: Vec::new(),
77 code: None,
78 }
80 }
81
82 pub fn new_simple<S: ToString>(reason: S) -> Self {
83 Error::new(Reason::Simple(reason.to_string()))
84 }
85
86 pub fn new_bug(issue_no: i32) -> Self {
87 Error::new(Reason::Bug {
88 issue: Some(issue_no),
89 details: None,
90 })
91 }
92
93 pub fn new_assert<S: ToString>(details: S) -> Self {
95 Error::new(Reason::Bug {
96 issue: None,
97 details: Some(details.to_string()),
98 })
99 }
100}
101
102impl std::fmt::Display for Reason {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 match self {
105 Reason::Simple(text) => f.write_str(text),
106 Reason::Expected {
107 who,
108 expected,
109 found,
110 } => {
111 if let Some(who) = who {
112 write!(f, "{who} ")?;
113 }
114 write!(f, "expected {expected}, but found {found}")
115 }
116 Reason::Unexpected { found } => write!(f, "unexpected {found}"),
117 Reason::NotFound { name, namespace } => write!(f, "{namespace} `{name}` not found"),
118 Reason::Bug { issue, details } => {
119 write!(f, "internal compiler error")?;
120 if let Some(details) = details {
121 write!(f, "; {details}")?;
122 }
123 if let Some(issue_no) = issue {
124 write!(
125 f,
126 "; tracked at https://github.com/PRQL/prql/issues/{issue_no}"
127 )?;
128 }
129 Ok(())
130 }
131 }
132 }
133}
134
135impl From<Error> for Errors {
136 fn from(error: Error) -> Self {
137 Errors(vec![error])
138 }
139}
140
141impl std::error::Error for Error {}
143
144impl std::error::Error for Errors {}
146
147impl std::fmt::Display for Error {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 Debug::fmt(&self, f)
151 }
152}
153
154impl std::fmt::Display for Errors {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 Debug::fmt(&self, f)
158 }
159}
160
161pub trait WithErrorInfo: Sized {
162 fn push_hint<S: Into<String>>(self, hint: S) -> Self;
163
164 fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(self, hints: I) -> Self;
165
166 fn with_span(self, span: Option<Span>) -> Self;
167
168 fn with_span_fallback(self, span: Option<Span>) -> Self;
169
170 fn with_code(self, code: &'static str) -> Self;
171
172 fn with_source(self, source: ErrorSource) -> Self;
173}
174
175impl WithErrorInfo for Error {
176 fn push_hint<S: Into<String>>(mut self, hint: S) -> Self {
177 self.hints.push(hint.into());
178 self
179 }
180
181 fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(mut self, hints: I) -> Self {
182 self.hints = hints.into_iter().map(|x| x.into()).collect();
183 self
184 }
185
186 fn with_span(mut self, span: Option<Span>) -> Self {
187 self.span = span;
188 self
189 }
190
191 fn with_code(mut self, code: &'static str) -> Self {
192 self.code = Some(code);
193 self
194 }
195
196 fn with_span_fallback(mut self, span: Option<Span>) -> Self {
197 self.span = self.span.or(span);
198 self
199 }
200
201 fn with_source(self, _source: ErrorSource) -> Self {
202 self
204 }
205}
206
207impl<T, E: WithErrorInfo> WithErrorInfo for Result<T, E> {
208 fn push_hint<S: Into<String>>(self, hint: S) -> Self {
209 self.map_err(|e| e.push_hint(hint))
210 }
211
212 fn with_hints<S: Into<String>, I: IntoIterator<Item = S>>(self, hints: I) -> Self {
213 self.map_err(|e| e.with_hints(hints))
214 }
215
216 fn with_span(self, span: Option<Span>) -> Self {
217 self.map_err(|e| e.with_span(span))
218 }
219
220 fn with_span_fallback(self, span: Option<Span>) -> Self {
221 self.map_err(|e| e.with_span_fallback(span))
222 }
223
224 fn with_code(self, code: &'static str) -> Self {
225 self.map_err(|e| e.with_code(code))
226 }
227
228 fn with_source(self, source: ErrorSource) -> Self {
229 self.map_err(|e| e.with_source(source))
230 }
231}