blueprint_starlark_syntax/
error.rs

1/*
2 * Copyright 2019 The Starlark in Rust Authors.
3 * Copyright (c) Facebook, Inc. and its affiliates.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18use std::fmt;
19use std::mem;
20
21use crate::call_stack::CallStack;
22use crate::codemap::CodeMap;
23use crate::codemap::FileSpan;
24use crate::codemap::Span;
25use crate::diagnostic::WithDiagnostic;
26use crate::diagnostic::diagnostic_display;
27
28/// An error produced by starlark.
29///
30/// This error is composed of an error kind, together with some diagnostic information indicating
31/// where it occurred.
32///
33/// In order to prevent accidental conversions to `anyhow::Error`, this type intentionally does not
34/// implement `std::error::Error`. That should probably change in the future.
35pub struct Error(pub(crate) WithDiagnostic<ErrorKind>);
36
37const _: () = assert!(mem::size_of::<Error>() == mem::size_of::<usize>());
38
39impl Error {
40    /// Create a new error
41    #[cold]
42    pub fn new_kind(kind: ErrorKind) -> Self {
43        Self(WithDiagnostic::new_empty(kind))
44    }
45
46    /// Create a new error with a span
47    #[cold]
48    pub fn new_spanned(kind: ErrorKind, span: Span, codemap: &CodeMap) -> Self {
49        Self(WithDiagnostic::new_spanned(kind, span, codemap))
50    }
51
52    /// Create a new error with no diagnostic and of kind [`ErrorKind::Other`]
53    #[cold]
54    pub fn new_other(e: impl Into<anyhow::Error>) -> Self {
55        Self(WithDiagnostic::new_empty(ErrorKind::Other(e.into())))
56    }
57
58    /// Create a new error with no diagnostic and of kind [`ErrorKind::Native`]
59    #[cold]
60    pub fn new_native(e: impl Into<anyhow::Error>) -> Self {
61        Self(WithDiagnostic::new_empty(ErrorKind::Native(e.into())))
62    }
63
64    /// Create a new error with no diagnostic and of kind [`ErrorKind::Value`]
65    #[cold]
66    pub fn new_value(e: impl Into<anyhow::Error>) -> Self {
67        Self(WithDiagnostic::new_empty(ErrorKind::Value(e.into())))
68    }
69
70    /// The kind of this error
71    pub fn kind(&self) -> &ErrorKind {
72        self.0.inner()
73    }
74
75    /// Convert the error into the underlying kind
76    pub fn into_kind(self) -> ErrorKind {
77        self.0.into_inner()
78    }
79
80    pub fn has_diagnostic(&self) -> bool {
81        self.0.span().is_some() || !self.0.call_stack().is_empty()
82    }
83
84    /// Convert this error into an `anyhow::Error`
85    #[cold]
86    pub fn into_anyhow(self) -> anyhow::Error {
87        struct Wrapped(Error);
88
89        impl fmt::Display for Wrapped {
90            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91                fmt::Display::fmt(&self.0, f)
92            }
93        }
94
95        impl fmt::Debug for Wrapped {
96            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97                fmt::Debug::fmt(&self.0, f)
98            }
99        }
100
101        impl std::error::Error for Wrapped {
102            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
103                self.0.kind().source()
104            }
105        }
106
107        anyhow::Error::new(Wrapped(self))
108    }
109
110    /// Returns a value that can be used to format this error without including the diagnostic
111    /// information
112    ///
113    /// This is the same as [`kind`](crate::Error::kind), just a bit more explicit.
114    pub fn without_diagnostic<'a>(&'a self) -> impl fmt::Debug + fmt::Display + 'a {
115        self.0.inner()
116    }
117
118    pub fn span(&self) -> Option<&FileSpan> {
119        self.0.span()
120    }
121
122    pub fn call_stack(&self) -> &CallStack {
123        self.0.call_stack()
124    }
125
126    /// Set the span, unless it's already been set.
127    pub fn set_span(&mut self, span: Span, codemap: &CodeMap) {
128        self.0.set_span(span, codemap);
129    }
130
131    /// Set the `call_stack` field, unless it's already been set.
132    pub fn set_call_stack(&mut self, call_stack: impl FnOnce() -> CallStack) {
133        self.0.set_call_stack(call_stack);
134    }
135
136    /// Print an error to the stderr stream. If the error has diagnostic information it will use
137    /// color-codes when printing.
138    ///
139    /// Note that this function doesn't print any context information if the error is a diagnostic,
140    /// so you might prefer to use `eprintln!("{:#}"), err)` if you suspect there is useful context
141    /// (although you won't get pretty colors).
142    pub fn eprint(&self) {
143        if self.has_diagnostic() {
144            let mut stderr = String::new();
145            diagnostic_display(&self.0, true, &mut stderr, true).unwrap();
146            eprint!("{stderr}");
147        } else {
148            eprintln!("{self:#}")
149        }
150    }
151
152    /// Change error kind to internal error.
153    pub fn into_internal_error(self) -> Error {
154        if let ErrorKind::Internal(_) = self.kind() {
155            self
156        } else {
157            Error(self.0.map(ErrorKind::into_internal_error))
158        }
159    }
160}
161
162fn fmt_impl(this: &Error, is_debug: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163    if this.has_diagnostic() {
164        // Not showing the context trace without `{:#}` or `{:?}` is the same thing that anyhow does
165        let with_context = (f.alternate() || is_debug) && this.kind().source().is_some();
166        diagnostic_display(&this.0, false, f, with_context)
167    } else {
168        fmt::Display::fmt(&this.without_diagnostic(), f)
169    }
170}
171
172impl fmt::Display for Error {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        fmt_impl(self, false, f)
175    }
176}
177
178impl fmt::Debug for Error {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        fmt_impl(self, true, f)
181    }
182}
183
184/// The different kinds of errors that can be produced by starlark
185#[non_exhaustive]
186pub enum ErrorKind {
187    /// An explicit `fail` invocation
188    Fail(anyhow::Error),
189    /// Starlark call stack overflow.
190    StackOverflow(anyhow::Error),
191    /// An error approximately associated with a value.
192    ///
193    /// Includes unsupported operations, missing attributes, things of that sort.
194    Value(anyhow::Error),
195    /// Errors relating to the way a function is called (wrong number of args, etc.)
196    Function(anyhow::Error),
197    /// Out of scope variables and similar
198    Scope(anyhow::Error),
199    /// Syntax error.
200    Parser(anyhow::Error),
201    /// Freeze errors. Should have no metadata attached
202    Freeze(anyhow::Error),
203    /// Indicates a logic bug in starlark
204    Internal(anyhow::Error),
205    /// Error from user provided native function
206    /// (but not from native functions provided by starlark crate).
207    /// When a native function declares `anyhow::Result<_>`
208    /// return type, it is automatically converted to this variant.
209    Native(anyhow::Error),
210    /// Fallback option
211    ///
212    /// For errors produced by starlark which have not yet been assigned their own kind
213    Other(anyhow::Error),
214}
215
216impl ErrorKind {
217    /// The source of the error, akin to `[std::error::Error::source]`
218    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
219        match self {
220            Self::Fail(_) => None,
221            Self::StackOverflow(_) => None,
222            Self::Value(_) => None,
223            Self::Function(_) => None,
224            Self::Scope(_) => None,
225            Self::Freeze(_) => None,
226            Self::Parser(_) => None,
227            Self::Internal(_) => None,
228            Self::Native(e) => e.source(),
229            Self::Other(e) => e.source(),
230        }
231    }
232
233    /// Change type to `Internal`.
234    pub(crate) fn into_internal_error(self) -> ErrorKind {
235        match self {
236            ErrorKind::Internal(e)
237            | ErrorKind::Fail(e)
238            | ErrorKind::Value(e)
239            | ErrorKind::Function(e)
240            | ErrorKind::Scope(e)
241            | ErrorKind::Freeze(e)
242            | ErrorKind::Parser(e)
243            | ErrorKind::StackOverflow(e)
244            | ErrorKind::Native(e)
245            | ErrorKind::Other(e) => ErrorKind::Internal(e),
246        }
247    }
248}
249
250impl fmt::Debug for ErrorKind {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        match self {
253            Self::Fail(s) => write!(f, "fail:{s}"),
254            Self::Value(e) => fmt::Debug::fmt(e, f),
255            Self::StackOverflow(e) => fmt::Debug::fmt(e, f),
256            Self::Function(e) => fmt::Debug::fmt(e, f),
257            Self::Scope(e) => fmt::Debug::fmt(e, f),
258            Self::Freeze(e) => fmt::Debug::fmt(e, f),
259            Self::Parser(e) => fmt::Debug::fmt(e, f),
260            Self::Internal(e) => write!(f, "Internal error: {e}"),
261            Self::Native(e) => fmt::Debug::fmt(e, f),
262            Self::Other(e) => fmt::Debug::fmt(e, f),
263        }
264    }
265}
266
267impl fmt::Display for ErrorKind {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        match self {
270            Self::Fail(s) => write!(f, "fail:{s}"),
271            Self::StackOverflow(e) => fmt::Display::fmt(e, f),
272            Self::Value(e) => fmt::Display::fmt(e, f),
273            Self::Function(e) => fmt::Display::fmt(e, f),
274            Self::Scope(e) => fmt::Display::fmt(e, f),
275            Self::Freeze(e) => fmt::Display::fmt(e, f),
276            Self::Parser(e) => fmt::Display::fmt(e, f),
277            Self::Internal(e) => write!(f, "Internal error: {e}"),
278            Self::Native(e) => fmt::Display::fmt(e, f),
279            Self::Other(e) => fmt::Display::fmt(e, f),
280        }
281    }
282}
283
284impl From<anyhow::Error> for Error {
285    #[cold]
286    fn from(e: anyhow::Error) -> Self {
287        Self(WithDiagnostic::new_empty(ErrorKind::Other(e)))
288    }
289}
290
291pub trait StarlarkResultExt<T> {
292    fn into_anyhow_result(self) -> anyhow::Result<T>;
293}
294
295impl<T> StarlarkResultExt<T> for crate::Result<T> {
296    #[inline]
297    fn into_anyhow_result(self) -> anyhow::Result<T> {
298        self.map_err(Error::into_anyhow)
299    }
300}
301
302#[doc(hidden)]
303#[cold]
304pub fn internal_error_impl(args: fmt::Arguments<'_>) -> Error {
305    Error::new_kind(ErrorKind::Internal(anyhow::anyhow!("{}", args)))
306}
307
308#[doc(hidden)]
309#[cold]
310pub fn other_error_impl(args: fmt::Arguments<'_>) -> Error {
311    Error::new_kind(ErrorKind::Other(anyhow::anyhow!("{}", args)))
312}
313
314#[doc(hidden)]
315#[cold]
316pub fn value_error_impl(args: fmt::Arguments<'_>) -> Error {
317    Error::new_kind(ErrorKind::Value(anyhow::anyhow!("{}", args)))
318}
319
320#[doc(hidden)]
321#[cold]
322pub fn function_error_impl(args: fmt::Arguments<'_>) -> Error {
323    Error::new_kind(ErrorKind::Function(anyhow::anyhow!("{}", args)))
324}
325
326/// Internal error of starlark.
327#[macro_export]
328macro_rules! internal_error {
329    ($format:literal) => {
330        internal_error!($format,)
331    };
332    ($format:literal, $($args:tt)*) => {
333        $crate::error::internal_error_impl(format_args!($format, $($args)*))
334    };
335}
336
337#[macro_export]
338macro_rules! other_error {
339    ($format:literal) => {
340        other_error!($format,)
341    };
342    ($format:literal, $($args:tt)*) => {
343        $crate::error::other_error_impl(format_args!($format, $($args)*))
344    };
345}
346
347#[macro_export]
348macro_rules! value_error {
349    ($format:literal) => {
350        value_error!($format,)
351    };
352    ($format:literal, $($args:tt)*) => {
353        $crate::error::value_error_impl(format_args!($format, $($args)*))
354    };
355}
356
357#[macro_export]
358macro_rules! function_error {
359    ($format:literal) => {
360        function_error!($format,)
361    };
362    ($format:literal, $($args:tt)*) => {
363        $crate::error::function_error_impl(format_args!($format, $($args)*))
364    };
365}