tblgen/
error.rs

1// Copyright 2023 Daan Vanoverloop
2// See the COPYRIGHT file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! This module contains error types used by this crate and provides additional
11//! error handling utilities for dependent crates.
12//!
13//! Disclaimer: this module may change significantly in the future.
14//!
15//! All different error types are defined in the [`TableGenError`] enum.
16//! However, most functions return a [`SourceError<TableGenError>`] (has alias
17//! [`Error`]). This error type includes a [`SourceLocation`], a reference to a
18//! line in a TableGen source file.
19//!
20//! To provide information about the source code at this location (e.g. code at
21//! location, file name, line and column), [`SourceInfo`] must be provided to
22//! the error. In this case, the error message will be formatted by LLVM's
23//! `SourceMgr` class.
24//!
25//! ```rust
26//! use tblgen::{RecordKeeper, TableGenParser};
27//!
28//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
29//! let keeper: RecordKeeper = TableGenParser::new()
30//!     .add_source(
31//!         r#"
32//!         def A {
33//!             int i = 5;
34//!         }
35//!         "#,
36//!     )?
37//!     .parse()?;
38//! if let Err(e) = keeper.def("A").unwrap().string_value("i") {
39//!     println!("{}", e);
40//!     // invalid conversion from Int to alloc::string::String
41//!
42//!     println!("{}", e.add_source_info(keeper.source_info()));
43//!     // error: invalid conversion from Int to alloc::string::String
44//!     //   int a = test;
45//!     //       ^
46//! }
47//! # Ok(())
48//! # }
49//! ```
50//!
51//! Note that `add_source_info` should be called with the correct source info.
52//! This is not statically enforced, but runtime checks are implemented to check
53//! that the given [`SourceInfo`] matches the [`SourceLocation`] in the error.
54//! If it does not match, the error will be printed without information about
55//! the TableGen source file.
56//!
57//! Custom error types that implement [`std::error::Error`] also implement
58//! [`WithLocation`]. That way, a [`SourceLocation`] can be attached to any
59//! error by calling [`with_location`](`WithLocation::with_location`).
60
61use std::{
62    convert::Infallible,
63    ffi::{NulError, c_void},
64    fmt::{self, Display, Formatter},
65    str::Utf8Error,
66    string::FromUtf8Error,
67};
68
69use crate::{
70    SourceInfo, TableGenParser,
71    raw::{
72        TableGenDiagKind::TABLEGEN_DK_ERROR, TableGenSourceLocationRef, tableGenPrintError,
73        tableGenSourceLocationClone, tableGenSourceLocationFree, tableGenSourceLocationNull,
74    },
75    string_ref::StringRef,
76    util::print_string_callback,
77};
78
79/// Enum of TableGen errors.
80#[non_exhaustive]
81#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
82pub enum TableGenError {
83    #[error("invalid TableGen source")]
84    InvalidSource,
85    #[error("invalid TableGen source")]
86    InvalidSourceString(#[from] NulError),
87    #[error("invalid UTF-8 string")]
88    InvalidUtf8Str(#[from] Utf8Error),
89    #[error("invalid UTF-8 string")]
90    InvalidUtf8String(#[from] FromUtf8Error),
91    #[error("failed to parse TableGen source")]
92    Parse,
93    #[error("expected field {0} in record")]
94    MissingValue(String),
95    #[error("expected def {0}")]
96    MissingDef(String),
97    #[error("expected class {0}")]
98    MissingClass(String),
99    #[error("invalid conversion from {from} to {to}")]
100    InitConversion {
101        from: &'static str,
102        to: &'static str,
103    },
104    #[error("invalid source location")]
105    InvalidSourceLocation,
106    #[error("infallible")]
107    Infallible(#[from] Infallible),
108}
109
110/// A location in a TableGen source file.
111#[derive(Debug, PartialEq, Eq)]
112pub struct SourceLocation {
113    raw: TableGenSourceLocationRef,
114}
115
116// SourceLocation is a read-only llvm::ArrayRef, which should be thread-safe.
117unsafe impl Sync for SourceLocation {}
118unsafe impl Send for SourceLocation {}
119
120impl SourceLocation {
121    /// # Safety
122    /// The passed pointer should be a valid table gen source location.
123    pub unsafe fn from_raw(raw: TableGenSourceLocationRef) -> Self {
124        Self { raw }
125    }
126
127    /// Returns a [`SourceLocation`] for an undetermined location in the
128    /// TableGen source file.
129    pub fn none() -> Self {
130        unsafe {
131            Self {
132                raw: tableGenSourceLocationNull(),
133            }
134        }
135    }
136}
137
138impl Clone for SourceLocation {
139    fn clone(&self) -> Self {
140        unsafe { Self::from_raw(tableGenSourceLocationClone(self.raw)) }
141    }
142}
143
144impl Drop for SourceLocation {
145    fn drop(&mut self) {
146        unsafe { tableGenSourceLocationFree(self.raw) }
147    }
148}
149
150/// A wrapper around error types which includes a [`SourceLocation`].
151///
152/// This error is used to describe erros in the TableGen source file at a
153/// certain location.
154///
155/// By calling `add_source_info`, information about the TableGen source file at
156/// the [`SourceLocation`] will be included in this error.
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct SourceError<E> {
159    location: SourceLocation,
160    message: Option<String>,
161    error: E,
162}
163
164impl<E: std::error::Error> SourceError<E> {
165    /// Creates a new [`SourceError`].
166    pub fn new(location: SourceLocation, error: E) -> Self {
167        Self {
168            location,
169            error,
170            message: None,
171        }
172    }
173
174    pub fn location(&self) -> &SourceLocation {
175        &self.location
176    }
177
178    pub fn error(&self) -> &E {
179        &self.error
180    }
181
182    /// Replaces the inner error with the given error.
183    ///
184    /// Any source information that was previously attached with
185    /// [`SourceError::add_source_info`] will be removed.
186    pub fn set_error<F: std::error::Error>(self, error: F) -> SourceError<F> {
187        SourceError {
188            error,
189            message: None,
190            location: self.location,
191        }
192    }
193
194    /// Replaces the location.
195    ///
196    /// Any source information that was previously attached with
197    /// [`SourceError::add_source_info`] will be removed.
198    pub fn set_location(mut self, location: impl SourceLoc) -> Self {
199        self.location = location.source_location();
200        self
201    }
202
203    /// Adds information about the TableGen source file at the
204    /// given [`SourceLocation`] to this error.
205    ///
206    /// A new error message will be created by `SourceMgr` class of LLVM.
207    pub fn add_source_info(mut self, info: SourceInfo) -> Self {
208        self.message = Some(Self::create_message(
209            info.0,
210            &self.location,
211            &format!("{}", self.error),
212        ));
213        self
214    }
215
216    fn create_message(parser: &TableGenParser, location: &SourceLocation, message: &str) -> String {
217        let mut data: (_, Result<_, TableGenError>) = (String::new(), Ok(()));
218        let res = unsafe {
219            tableGenPrintError(
220                parser.raw,
221                location.raw,
222                TABLEGEN_DK_ERROR,
223                StringRef::from(message).to_raw(),
224                Some(print_string_callback),
225                &mut data as *mut _ as *mut c_void,
226            )
227        };
228        if res == 0 {
229            data.1 = Err(TableGenError::InvalidSourceLocation);
230        }
231        if let Err(e) = data.1 {
232            data.0 = format!("{}\nfailed to print source information: {}", message, e);
233        }
234        data.0
235    }
236}
237
238impl<E: std::error::Error> Display for SourceError<E> {
239    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
240        if let Some(message) = self.message.as_ref() {
241            write!(f, "{}", message)
242        } else {
243            write!(f, "{}", self.error)
244        }
245    }
246}
247
248impl<E: std::error::Error + 'static> std::error::Error for SourceError<E> {
249    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
250        Some(&self.error)
251    }
252}
253
254impl From<TableGenError> for SourceError<TableGenError> {
255    fn from(value: TableGenError) -> Self {
256        value.with_location(SourceLocation::none())
257    }
258}
259
260pub trait WithLocation: std::error::Error + Sized {
261    /// Creates a [`SourceError`] wrapper.
262    fn with_location<L: SourceLoc>(self, location: L) -> SourceError<Self> {
263        SourceError::new(location.source_location(), self)
264    }
265}
266
267impl<E> WithLocation for E where E: std::error::Error {}
268
269pub trait SourceLoc {
270    /// Returns the source location.
271    fn source_location(self) -> SourceLocation;
272}
273
274impl SourceLoc for SourceLocation {
275    fn source_location(self) -> SourceLocation {
276        self
277    }
278}
279
280/// Main error type.
281pub type Error = SourceError<TableGenError>;