sway_error/
diagnostic.rs

1use std::{path::PathBuf, vec};
2
3use sway_types::{SourceEngine, Span};
4
5/// Provides detailed, rich description of a compile error or warning.
6#[derive(Debug, Default)]
7pub struct Diagnostic {
8    pub reason: Option<Reason>, // TODO: Make mandatory once we remove all old-style warnings and errors.
9    pub issue: Issue,
10    pub hints: Vec<Hint>,
11    pub help: Vec<String>,
12}
13
14impl Diagnostic {
15    /// For backward compatibility purposes. True if the diagnostic
16    /// was defined before the detailed diagnostics were introduced.
17    /// An old-style diagnostic contains just the issue.
18    pub fn is_old_style(&self) -> bool {
19        self.reason.is_none() && self.hints.is_empty() && self.help.is_empty()
20    }
21
22    pub fn level(&self) -> Level {
23        match self.issue.label_type {
24            LabelType::Error => Level::Error,
25            LabelType::Warning => Level::Warning,
26            LabelType::Info => Level::Info,
27            _ => unreachable!("The diagnostic level can be only Error, Warning, or Info, and this is enforced via Diagnostics API.")
28        }
29    }
30
31    pub fn reason(&self) -> Option<&Reason> {
32        self.reason.as_ref()
33    }
34
35    pub fn issue(&self) -> &Issue {
36        &self.issue
37    }
38
39    /// All the labels, potentially in different source files.
40    pub fn labels(&self) -> Vec<&Label> {
41        let mut labels = Vec::<&Label>::new();
42
43        if self.issue.is_in_source() {
44            labels.push(&self.issue);
45        }
46
47        for hint in self.hints.iter().filter(|hint| hint.is_in_source()) {
48            labels.push(hint);
49        }
50
51        labels
52    }
53
54    /// All the labels in the source file found at `source_path`.
55    pub fn labels_in_source(&self, source_path: &SourcePath) -> Vec<&Label> {
56        self.labels()
57            .iter()
58            // Safe unwrapping because all the labels are in source.
59            .filter(|&label| label.source_path().unwrap() == source_path)
60            .copied()
61            .collect()
62    }
63
64    // All the labels that occur in the same source file where the diagnostic issue occurs.
65    pub fn labels_in_issue_source(&self) -> Vec<&Label> {
66        if !self.issue.is_in_source() {
67            return vec![];
68        }
69
70        // Safe unwrapping because the issue is in source.
71        self.labels_in_source(self.issue.source_path().unwrap())
72    }
73
74    pub fn help(&self) -> impl Iterator<Item = &String> + '_ {
75        self.help.iter().filter(|help| !help.is_empty())
76    }
77
78    /// A help text that will never be displayed. Convenient when defining help lines
79    /// that are displayed only when a condition is met.
80    pub fn help_none() -> String {
81        String::new()
82    }
83
84    /// Displays an empty line in the help footer.
85    /// Convenient when defining visual separations within suggestions.
86    pub fn help_empty_line() -> String {
87        String::from(" ")
88    }
89
90    /// All the source files that are related to the diagnostic.
91    /// This means the source file of the issue itself as well
92    /// as source files of all the hints.
93    pub fn related_sources(&self, include_issue_source: bool) -> Vec<&SourcePath> {
94        let mut source_files = vec![];
95
96        let issue_is_in_source = self.issue.is_in_source();
97
98        // All `source_path()` unwrappings are safe because we check the existence
99        // of source in case of an issue, and `self.labels()` returns
100        // only labels that are in source.
101        if issue_is_in_source && include_issue_source {
102            source_files.push(self.issue.source_path().unwrap());
103        }
104
105        for hint in self.labels() {
106            let file = hint.source_path().unwrap();
107
108            if !include_issue_source
109                && issue_is_in_source
110                && file == self.issue.source_path().unwrap()
111            {
112                continue;
113            }
114
115            if !source_files.contains(&file) {
116                source_files.push(file)
117            }
118        }
119
120        source_files
121    }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
125pub enum Level {
126    Info,
127    Warning,
128    #[default]
129    Error,
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
133pub enum LabelType {
134    #[default]
135    Info,
136    Help,
137    Warning,
138    Error,
139}
140
141/// Diagnostic message related to a span of source code in a source file.
142///
143/// If the message in a particular situation cannot be related to a span
144/// in a known source file (e.g., when importing symbols)
145/// the span must be set to [Span::dummy]. Such messages without a valid span
146/// will be ignored.
147///
148/// E.g., a note like 'The function "{name}" is defined here.'
149/// will be displayed only when we have access to the source
150/// code in which the function is defined.
151///
152/// We can also have error messages that are not related to any particular
153/// place in code.
154#[derive(Debug)]
155pub struct Label {
156    label_type: LabelType,
157    span: Span,
158    text: String,
159    source_path: Option<SourcePath>,
160}
161
162impl Label {
163    pub fn info(source_engine: &SourceEngine, span: Span, text: String) -> Label {
164        Self::new(source_engine, LabelType::Info, span, text)
165    }
166
167    pub fn help(source_engine: &SourceEngine, span: Span, text: String) -> Label {
168        Self::new(source_engine, LabelType::Help, span, text)
169    }
170
171    pub fn warning(source_engine: &SourceEngine, span: Span, text: String) -> Label {
172        Self::new(source_engine, LabelType::Warning, span, text)
173    }
174
175    pub fn error(source_engine: &SourceEngine, span: Span, text: String) -> Label {
176        Self::new(source_engine, LabelType::Error, span, text)
177    }
178
179    fn new(source_engine: &SourceEngine, label_type: LabelType, span: Span, text: String) -> Label {
180        let source_path = Self::get_source_path(source_engine, &span);
181        Label {
182            label_type,
183            span,
184            text,
185            source_path,
186        }
187    }
188
189    /// True if the `Label` is actually related to a span of source code in a source file.
190    pub fn is_in_source(&self) -> bool {
191        self.source_path.is_some() && (self.span.start() < self.span.end())
192    }
193
194    pub fn label_type(&self) -> LabelType {
195        self.label_type
196    }
197
198    pub fn span(&self) -> &Span {
199        &self.span
200    }
201
202    pub fn text(&self) -> &str {
203        self.text.as_ref()
204    }
205
206    pub fn source_path(&self) -> Option<&SourcePath> {
207        self.source_path.as_ref()
208    }
209
210    fn get_source_path(source_engine: &SourceEngine, span: &Span) -> Option<SourcePath> {
211        let path_buf = span
212            .source_id()
213            .cloned()
214            .map(|id| source_engine.get_path(&id));
215        let path_string = path_buf.as_ref().map(|p| p.to_string_lossy().to_string());
216
217        match (path_buf, path_string) {
218            (Some(path_buf), Some(path_string)) => Some(SourcePath {
219                path_buf,
220                path_string,
221            }),
222            _ => None,
223        }
224    }
225}
226
227impl Default for Label {
228    fn default() -> Self {
229        Self {
230            label_type: LabelType::Info,
231            span: Span::dummy(),
232            text: "".to_string(),
233            source_path: None,
234        }
235    }
236}
237
238#[derive(Debug)]
239pub struct Issue {
240    label: Label,
241}
242
243impl Issue {
244    pub fn warning(source_engine: &SourceEngine, span: Span, text: String) -> Self {
245        Self {
246            label: Label::warning(source_engine, span, text),
247        }
248    }
249
250    pub fn error(source_engine: &SourceEngine, span: Span, text: String) -> Self {
251        Self {
252            label: Label::error(source_engine, span, text),
253        }
254    }
255
256    pub fn info(source_engine: &SourceEngine, span: Span, text: String) -> Self {
257        Self {
258            label: Label::info(source_engine, span, text),
259        }
260    }
261}
262
263impl Default for Issue {
264    fn default() -> Self {
265        Self {
266            label: Label {
267                label_type: LabelType::Error,
268                ..Default::default()
269            },
270        }
271    }
272}
273
274impl std::ops::Deref for Issue {
275    type Target = Label;
276    fn deref(&self) -> &Self::Target {
277        &self.label
278    }
279}
280
281#[derive(Debug, Default)]
282pub struct Hint {
283    label: Label,
284}
285
286impl Hint {
287    pub fn info(source_engine: &SourceEngine, span: Span, text: String) -> Self {
288        Self {
289            label: Label::info(source_engine, span, text),
290        }
291    }
292
293    pub fn underscored_info(source_engine: &SourceEngine, span: Span) -> Self {
294        Self::info(source_engine, span, "".to_string())
295    }
296
297    pub fn multi_info(source_engine: &SourceEngine, span: &Span, hints: Vec<String>) -> Vec<Self> {
298        hints
299            .into_iter()
300            .map(|hint| Self::info(source_engine, span.clone(), hint))
301            .collect()
302    }
303
304    pub fn help(source_engine: &SourceEngine, span: Span, text: String) -> Self {
305        Self {
306            label: Label::help(source_engine, span, text),
307        }
308    }
309
310    pub fn multi_help(source_engine: &SourceEngine, span: &Span, hints: Vec<String>) -> Vec<Self> {
311        hints
312            .into_iter()
313            .map(|hint| Self::help(source_engine, span.clone(), hint))
314            .collect()
315    }
316
317    pub fn warning(source_engine: &SourceEngine, span: Span, text: String) -> Self {
318        Self {
319            label: Label::warning(source_engine, span, text),
320        }
321    }
322
323    pub fn multi_warning(
324        source_engine: &SourceEngine,
325        span: &Span,
326        hints: Vec<String>,
327    ) -> Vec<Self> {
328        hints
329            .into_iter()
330            .map(|hint| Self::warning(source_engine, span.clone(), hint))
331            .collect()
332    }
333
334    pub fn error(source_engine: &SourceEngine, span: Span, text: String) -> Self {
335        Self {
336            label: Label::error(source_engine, span, text),
337        }
338    }
339
340    pub fn multi_error(source_engine: &SourceEngine, span: &Span, hints: Vec<String>) -> Vec<Self> {
341        hints
342            .into_iter()
343            .map(|hint| Self::error(source_engine, span.clone(), hint))
344            .collect()
345    }
346
347    /// A [Hint] that will never be displayed. Convenient when defining [Hint]s that
348    /// are displayed only if a condition is met.
349    pub fn none() -> Self {
350        Self {
351            label: Label::default(),
352        }
353    }
354}
355
356impl std::ops::Deref for Hint {
357    type Target = Label;
358    fn deref(&self) -> &Self::Target {
359        &self.label
360    }
361}
362
363#[derive(Debug, Clone, PartialEq, Eq, Default)]
364pub struct SourcePath {
365    path_buf: PathBuf,
366    path_string: String,
367}
368
369impl SourcePath {
370    pub fn as_path_buf(&self) -> &PathBuf {
371        &self.path_buf
372    }
373
374    pub fn as_str(&self) -> &str {
375        self.path_string.as_ref()
376    }
377}
378
379/// Describes the different areas that we have in the
380/// sway-error crate. It allows grouping of diagnostics
381/// and ensuring that we have unique diagnostic code
382/// numbers in each of the groups.
383#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
384pub enum DiagnosticArea {
385    #[default]
386    LexicalAnalysis,
387    Parsing,
388    ParseTreeConversion,
389    TypeChecking,
390    SemanticAnalysis,
391    Warnings,
392    Migrations,
393}
394
395impl DiagnosticArea {
396    pub fn prefix(&self) -> &'static str {
397        match self {
398            Self::LexicalAnalysis => "E0",
399            Self::Parsing => "E1",
400            Self::ParseTreeConversion => "E2",
401            Self::TypeChecking => "E3",
402            Self::SemanticAnalysis => "E4",
403            Self::Warnings => "W0",
404            Self::Migrations => "M0",
405        }
406    }
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Default)]
410pub struct Code {
411    area: DiagnosticArea,
412    number: u16,
413    text: String,
414}
415
416impl Code {
417    pub fn lexical_analysis(number: u16) -> Code {
418        Self::new(DiagnosticArea::LexicalAnalysis, number)
419    }
420
421    pub fn parsing(number: u16) -> Code {
422        Self::new(DiagnosticArea::Parsing, number)
423    }
424
425    pub fn parse_tree_conversion(number: u16) -> Code {
426        Self::new(DiagnosticArea::ParseTreeConversion, number)
427    }
428
429    pub fn type_checking(number: u16) -> Code {
430        Self::new(DiagnosticArea::TypeChecking, number)
431    }
432
433    pub fn semantic_analysis(number: u16) -> Self {
434        Self::new(DiagnosticArea::SemanticAnalysis, number)
435    }
436
437    pub fn warnings(number: u16) -> Code {
438        Self::new(DiagnosticArea::Warnings, number)
439    }
440
441    pub fn migrations(number: u16) -> Code {
442        Self::new(DiagnosticArea::Migrations, number)
443    }
444
445    fn new(area: DiagnosticArea, number: u16) -> Self {
446        debug_assert!(
447            0 < number && number < 999,
448            "The diagnostic code number must be greater then zero and smaller then 999."
449        );
450        Self {
451            area,
452            number,
453            text: format!("{}{:03}", area.prefix(), number),
454        }
455    }
456
457    pub fn as_str(&self) -> &str {
458        self.text.as_ref()
459    }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Default)]
463pub struct Reason {
464    code: Code,
465    description: String,
466}
467
468impl Reason {
469    pub fn new(code: Code, description: String) -> Self {
470        Self { code, description }
471    }
472
473    pub fn code(&self) -> &str {
474        self.code.as_str()
475    }
476
477    pub fn description(&self) -> &str {
478        self.description.as_ref()
479    }
480}
481
482pub trait ToDiagnostic {
483    fn to_diagnostic(&self, source_engine: &SourceEngine) -> Diagnostic;
484}