fe_analyzer/
errors.rs

1//! Semantic errors.
2
3use crate::context::{DiagnosticVoucher, NamedThing};
4use fe_common::diagnostics::{Diagnostic, Label, Severity};
5use fe_common::Span;
6use std::fmt::Display;
7
8/// Error indicating that a type is invalid.
9///
10/// Note that the "type" of a thing (eg the type of a `FunctionParam`)
11/// in [`crate::namespace::types`] is sometimes represented as a
12/// `Result<Type, TypeError>`.
13///
14/// If, for example, a function parameter has an undefined type, we emit a [`Diagnostic`] message,
15/// give that parameter a "type" of `Err(TypeError)`, and carry on. If/when that parameter is
16/// used in the function body, we assume that a diagnostic message about the undefined type
17/// has already been emitted, and halt the analysis of the function body.
18///
19/// To ensure that that assumption is sound, a diagnostic *must* be emitted before creating
20/// a `TypeError`. So that the rust compiler can help us enforce this rule, a `TypeError`
21/// cannot be constructed without providing a [`DiagnosticVoucher`]. A voucher can be obtained
22/// by calling an error function on an [`AnalyzerContext`](crate::context::AnalyzerContext).
23/// Please don't try to work around this restriction.
24///
25/// Example: `TypeError::new(context.error("something is wrong", some_span, "this thing"))`
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub struct TypeError(DiagnosticVoucher);
28impl TypeError {
29    // `Clone` is required because these are stored in a salsa db.
30    // Please don't clone these manually.
31    pub fn new(voucher: DiagnosticVoucher) -> Self {
32        Self(voucher)
33    }
34}
35
36impl From<FatalError> for TypeError {
37    fn from(err: FatalError) -> Self {
38        Self(err.0)
39    }
40}
41impl From<ConstEvalError> for TypeError {
42    fn from(err: ConstEvalError) -> Self {
43        Self(err.0)
44    }
45}
46
47/// Error to be returned when otherwise no meaningful information can be returned.
48/// Can't be created unless a diagnostic has been emitted, and thus a [`DiagnosticVoucher`]
49/// has been obtained. (See comment on [`TypeError`])
50#[derive(Debug)]
51pub struct FatalError(DiagnosticVoucher);
52
53impl FatalError {
54    /// Create a `FatalError` instance, given a "voucher"
55    /// obtained by emitting an error via an [`AnalyzerContext`](crate::context::AnalyzerContext).
56    pub fn new(voucher: DiagnosticVoucher) -> Self {
57        Self(voucher)
58    }
59}
60
61impl From<ConstEvalError> for FatalError {
62    fn from(err: ConstEvalError) -> Self {
63        Self(err.0)
64    }
65}
66
67impl From<AlreadyDefined> for FatalError {
68    fn from(err: AlreadyDefined) -> Self {
69        Self(err.0)
70    }
71}
72
73/// Error indicating constant evaluation failed.
74///
75/// This error emitted when
76/// 1. an expression can't be evaluated in compilation time
77/// 2. arithmetic overflow occurred during evaluation
78/// 3. zero division is detected during evaluation
79///
80/// Can't be created unless a diagnostic has been emitted, and thus a [`DiagnosticVoucher`]
81/// has been obtained. (See comment on [`TypeError`])
82///
83/// NOTE: `Clone` is required because these are stored in a salsa db.
84/// Please don't clone these manually.
85#[derive(Debug, Clone, PartialEq, Eq, Hash)]
86pub struct ConstEvalError(DiagnosticVoucher);
87
88impl ConstEvalError {
89    pub fn new(voucher: DiagnosticVoucher) -> Self {
90        Self(voucher)
91    }
92}
93
94impl From<TypeError> for ConstEvalError {
95    fn from(err: TypeError) -> Self {
96        Self(err.0)
97    }
98}
99
100impl From<FatalError> for ConstEvalError {
101    fn from(err: FatalError) -> Self {
102        Self(err.0)
103    }
104}
105
106impl From<IncompleteItem> for ConstEvalError {
107    fn from(err: IncompleteItem) -> Self {
108        Self(err.0)
109    }
110}
111
112/// Error returned by `ModuleId::resolve_name` if the name is not found, and parsing of the module
113/// failed. In this case, emitting an error message about failure to resolve the name might be misleading,
114/// because the file may in fact contain an item with the given name, somewhere after the syntax error that caused
115/// parsing to fail.
116#[derive(Debug)]
117pub struct IncompleteItem(DiagnosticVoucher);
118impl IncompleteItem {
119    #[allow(clippy::new_without_default)]
120    pub fn new() -> Self {
121        Self(DiagnosticVoucher::assume_the_parser_handled_it())
122    }
123}
124
125/// Error to be returned from APIs that should reject duplicate definitions
126#[derive(Debug)]
127pub struct AlreadyDefined(DiagnosticVoucher);
128impl AlreadyDefined {
129    #[allow(clippy::new_without_default)]
130    pub fn new(voucher: DiagnosticVoucher) -> Self {
131        Self(voucher)
132    }
133}
134
135/// Errors that can result from indexing
136#[derive(Debug, PartialEq, Eq)]
137pub enum IndexingError {
138    WrongIndexType,
139    NotSubscriptable,
140}
141
142/// Errors that can result from a binary operation
143#[derive(Debug, PartialEq, Eq)]
144pub enum BinaryOperationError {
145    TypesNotCompatible,
146    TypesNotNumeric,
147    RightTooLarge,
148    RightIsSigned,
149    NotEqualAndUnsigned,
150}
151
152/// Errors that can result from an implicit type coercion
153#[derive(Debug, PartialEq, Eq)]
154pub enum TypeCoercionError {
155    /// Value is in storage and must be explicitly moved with .to_mem()
156    RequiresToMem,
157    /// Value type cannot be coerced to the expected type
158    Incompatible,
159    /// `self` contract used where an external contract value is expected
160    SelfContractType,
161}
162
163impl From<TypeError> for FatalError {
164    fn from(err: TypeError) -> Self {
165        Self::new(err.0)
166    }
167}
168
169impl From<IncompleteItem> for FatalError {
170    fn from(err: IncompleteItem) -> Self {
171        Self::new(err.0)
172    }
173}
174
175impl From<IncompleteItem> for TypeError {
176    fn from(err: IncompleteItem) -> Self {
177        Self::new(err.0)
178    }
179}
180
181pub fn error(message: impl Into<String>, label_span: Span, label: impl Into<String>) -> Diagnostic {
182    fancy_error(message, vec![Label::primary(label_span, label)], vec![])
183}
184
185pub fn fancy_error(
186    message: impl Into<String>,
187    labels: Vec<Label>,
188    notes: Vec<String>,
189) -> Diagnostic {
190    Diagnostic {
191        severity: Severity::Error,
192        message: message.into(),
193        labels,
194        notes,
195    }
196}
197
198pub fn type_error(
199    message: impl Into<String>,
200    span: Span,
201    expected: impl Display,
202    actual: impl Display,
203) -> Diagnostic {
204    error(
205        message,
206        span,
207        format!("this has type `{actual}`; expected type `{expected}`"),
208    )
209}
210
211pub fn not_yet_implemented(feature: impl Display, span: Span) -> Diagnostic {
212    error(
213        format!("feature not yet implemented: {feature}"),
214        span,
215        "not yet implemented",
216    )
217}
218
219pub fn duplicate_name_error(
220    message: &str,
221    name: &str,
222    original: Span,
223    duplicate: Span,
224) -> Diagnostic {
225    fancy_error(
226        message,
227        vec![
228            Label::primary(original, format!("`{name}` first defined here")),
229            Label::secondary(duplicate, format!("`{name}` redefined here")),
230        ],
231        vec![],
232    )
233}
234
235pub fn name_conflict_error(
236    name_kind: &str, // Eg "function parameter" or "variable name"
237    name: &str,
238    original: &NamedThing,
239    original_span: Option<Span>,
240    duplicate_span: Span,
241) -> Diagnostic {
242    if let Some(original_span) = original_span {
243        fancy_error(
244            format!(
245                "{} name `{}` conflicts with previously defined {}",
246                name_kind,
247                name,
248                original.item_kind_display_name()
249            ),
250            vec![
251                Label::primary(original_span, format!("`{name}` first defined here")),
252                Label::secondary(duplicate_span, format!("`{name}` redefined here")),
253            ],
254            vec![],
255        )
256    } else {
257        fancy_error(
258            format!(
259                "{} name `{}` conflicts with built-in {}",
260                name_kind,
261                name,
262                original.item_kind_display_name()
263            ),
264            vec![Label::primary(
265                duplicate_span,
266                format!(
267                    "`{}` is a built-in {}",
268                    name,
269                    original.item_kind_display_name()
270                ),
271            )],
272            vec![],
273        )
274    }
275}
276
277pub fn to_mem_error(span: Span) -> Diagnostic {
278    fancy_error(
279        "value must be copied to memory",
280        vec![Label::primary(span, "this value is in storage")],
281        vec![
282            "Hint: values located in storage can be copied to memory using the `to_mem` function."
283                .into(),
284            "Example: `self.my_array.to_mem()`".into(),
285        ],
286    )
287}
288pub fn self_contract_type_error(span: Span, typ: &dyn Display) -> Diagnostic {
289    fancy_error(
290        format!("`self` can't be used where a contract of type `{typ}` is expected",),
291        vec![Label::primary(span, "cannot use `self` here")],
292        vec![format!(
293            "Hint: Values of type `{typ}` represent external contracts.\n\
294             To treat `self` as an external contract, use `{typ}(ctx.self_address())`."
295        )],
296    )
297}