1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use thiserror::Error;

use crate::report_code::ReportCode;
use crate::report::Report;
use crate::file_definition::{FileID, FileLocation};
use crate::ir::errors::IRError;

/// Error enum for CFG generation errors.
#[derive(Debug, Error)]
pub enum CFGError {
    #[error("The variable `{name}` is read before it is declared/written.")]
    UndefinedVariableError { name: String, file_id: Option<FileID>, file_location: FileLocation },
    #[error("The variable name `{name}` contains invalid characters.")]
    InvalidVariableNameError { name: String, file_id: Option<FileID>, file_location: FileLocation },
    #[error("The declaration of the variable `{name}` shadows a previous declaration.")]
    ShadowingVariableWarning {
        name: String,
        primary_file_id: Option<FileID>,
        primary_location: FileLocation,
        secondary_file_id: Option<FileID>,
        secondary_location: FileLocation,
    },
    #[error("Multiple parameters with the same name `{name}` in function or template definition.")]
    ParameterNameCollisionError {
        name: String,
        file_id: Option<FileID>,
        file_location: FileLocation,
    },
}

pub type CFGResult<T> = Result<T, CFGError>;

impl CFGError {
    pub fn into_report(self) -> Report {
        use CFGError::*;
        match self {
            UndefinedVariableError { name, file_id, file_location } => {
                let mut report = Report::error(
                    format!("The variable `{name}` is used before it is defined."),
                    ReportCode::UninitializedSymbolInExpression,
                );
                if let Some(file_id) = file_id {
                    report.add_primary(
                        file_location,
                        file_id,
                        format!("The variable `{name}` is first seen here."),
                    );
                }
                report
            }
            InvalidVariableNameError { name, file_id, file_location } => {
                let mut report = Report::error(
                    format!("Invalid variable name `{name}`."),
                    ReportCode::ParseFail,
                );
                if let Some(file_id) = file_id {
                    report.add_primary(
                        file_location,
                        file_id,
                        "This variable name contains invalid characters.".to_string(),
                    );
                }
                report
            }
            ShadowingVariableWarning {
                name,
                primary_file_id,
                primary_location,
                secondary_file_id,
                secondary_location,
            } => {
                let mut report = Report::warning(
                    format!("Declaration of variable `{name}` shadows previous declaration."),
                    ReportCode::ShadowingVariable,
                );
                if let Some(primary_file_id) = primary_file_id {
                    report.add_primary(
                        primary_location,
                        primary_file_id,
                        "Shadowing declaration here.".to_string(),
                    );
                }
                if let Some(secondary_file_id) = secondary_file_id {
                    report.add_secondary(
                        secondary_location,
                        secondary_file_id,
                        Some("Shadowed variable is declared here.".to_string()),
                    );
                }
                report.add_note(format!("Consider renaming the second occurrence of `{name}`."));
                report
            }
            ParameterNameCollisionError { name, file_id, file_location } => {
                let mut report = Report::warning(
                    format!("Parameter `{name}` declared multiple times."),
                    ReportCode::ParameterNameCollision,
                );
                if let Some(file_id) = file_id {
                    report.add_primary(
                        file_location,
                        file_id,
                        "Parameters declared here.".to_string(),
                    );
                }
                report.add_note(format!("Rename the second occurrence of `{name}`."));
                report
            }
        }
    }
}

impl From<IRError> for CFGError {
    fn from(error: IRError) -> CFGError {
        match error {
            IRError::UndefinedVariableError { name, file_id, file_location } => {
                CFGError::UndefinedVariableError { name, file_id, file_location }
            }
            IRError::InvalidVariableNameError { name, file_id, file_location } => {
                CFGError::InvalidVariableNameError { name, file_id, file_location }
            }
        }
    }
}

impl From<CFGError> for Report {
    fn from(error: CFGError) -> Report {
        error.into_report()
    }
}