Skip to content

Commit 712922b

Browse files
committed
Add define_errors! to simplify the process of defining error signals
1 parent 86154a3 commit 712922b

File tree

7 files changed

+73
-35
lines changed

7 files changed

+73
-35
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

66
## [Unreleased]
7-
- Added `Env::define_error` and `Env::signal` to simplify the process of signaling Lisp errors.
87
- Added `OnceGlobalRef`, which eases the initialization of static references to long-lived Lisp values.
9-
- Added `use_symbols!`, which enables module code to use Lisp symbols without repeatedly interning them.
8+
+ Added `use_symbols!`, which enables module code to use Lisp symbols without repeatedly interning them.
9+
+ Added `define_errors!` and `Env::signal` to simplify the process of defining and signaling custom Lisp errors.
1010
- Raised the minimum supported Rust version to 1.45.
1111

1212
## [0.16.2] - 2021-03-04

guide/src/errors.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,19 @@ This is similar to handling Lisp errors. The only difference is `ErrorKind::Thro
4040

4141
## Signaling Lisp Errors from Rust
4242

43-
The function `env.signal` allows signaling a Lisp error from Rust code. The error symbol must have been defined, e.g. by calling `env.define_error`:
43+
The function `env.signal` allows signaling a Lisp error from Rust code. The error symbol must have been defined, e.g. by the macro `define_errors!`:
4444

4545
```rust
46-
pub static my_custom_error: OnceGlobalRef = OnceGlobalRef::new();
47-
48-
#[emacs::module]
49-
fn init(env: &Env) -> Result<Value<'_>> {
50-
env.define_error(
51-
my_custom_error.init_to_symbol("my-custom-error")?,
52-
"This number should not be negative",
53-
[env.intern("error")?]
54-
)
46+
// The parentheses denote parent error signals.
47+
// If unspecified, the parent error signal is `error`.
48+
emacs::define_errors! {
49+
my_custom_error "This number should not be negative" (arith_error range_error)
5550
}
5651

5752
#[defun]
5853
fn signal_if_negative(env: &Env, x: i16) -> Result<()> {
5954
if (x < 0) {
60-
return env.signal(&my_custom_error., ("associated", "DATA", 7))
55+
return env.signal(my_custom_error., ("associated", "DATA", 7))
6156
}
6257
Ok(())
6358
}

src/error.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,38 @@ pub struct TempValue {
2323
raw: emacs_value,
2424
}
2525

26+
/// Defines new error signals.
27+
///
28+
/// TODO: Document this properly.
29+
///
30+
/// This macro can be used only once per Rust `mod`.
31+
#[macro_export]
32+
macro_rules! define_errors {
33+
($( $name:ident $message:literal $( ( $( $parent:ident )+ ) )? )*) => {
34+
$crate::global_refs! {__emrs_init_global_refs_to_error_symbols__(init_to_symbol) =>
35+
$( $name )*
36+
}
37+
38+
#[$crate::deps::ctor::ctor]
39+
fn __emrs_define_errors__() {
40+
$crate::init::__CUSTOM_ERRORS__.try_lock()
41+
.expect("Failed to acquire a write lock on the list of initializers for custom error signals")
42+
.push(::std::boxed::Box::new(|env| {
43+
$(
44+
env.define_error($name, $message, [
45+
$(
46+
$(
47+
env.intern($crate::deps::emacs_macros::lisp_name!($parent))?
48+
),+
49+
)?
50+
])?;
51+
)*
52+
Ok(())
53+
}));
54+
}
55+
}
56+
}
57+
2658
/// Error types generic to all Rust dynamic modules.
2759
///
2860
/// This list is intended to grow over time and it is not recommended to exhaustively match against
@@ -175,7 +207,7 @@ impl Env {
175207
}
176208
}
177209

178-
pub(crate) fn define_errors(&self) -> Result<()> {
210+
pub(crate) fn define_core_errors(&self) -> Result<()> {
179211
// FIX: Make panics louder than errors, by somehow make sure that 'rust-panic is
180212
// not a sub-type of 'error.
181213
self.define_error(symbol::rust_panic, "Rust panic", (symbol::error,))?;

src/global.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ macro_rules! global_refs {
130130

131131
#[$crate::deps::ctor::ctor]
132132
fn $registrator_name() {
133-
$crate::init::__PRE_INIT__.try_lock()
134-
.expect("Failed to acquire a write lock on the list of initializers")
133+
$crate::init::__GLOBAL_REFS__.try_lock()
134+
.expect("Failed to acquire a write lock on the list of initializers for global refs")
135135
.push(::std::boxed::Box::new(|env| {
136136
$(
137137
#[allow(unused_variables)]

src/init.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,17 @@ type FnMap = HashMap<String, InitFn>;
5151
/// [`emacs_module_init`].
5252
///
5353
/// [`emacs_module_init`]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html
54-
pub static __PRE_INIT__: Lazy<Mutex<Vec<InitFn>>> = Lazy::new(|| Mutex::new(vec![]));
54+
pub static __GLOBAL_REFS__: Lazy<Mutex<Vec<InitFn>>> = Lazy::new(|| Mutex::new(vec![]));
55+
56+
/// Functions that will be called by [`emacs_module_init`] to define custom error signals.
57+
///
58+
/// They are called before loading module metadata, e.g. module name, function prefix.
59+
///
60+
/// This list is populated when the OS loads the dynamic library, before Emacs calls
61+
/// [`emacs_module_init`].
62+
///
63+
/// [`emacs_module_init`]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Modules.html
64+
pub static __CUSTOM_ERRORS__: Lazy<Mutex<Vec<InitFn>>> = Lazy::new(|| Mutex::new(vec![]));
5565

5666
/// Functions that will be called by [`emacs_module_init`] to define the module functions.
5767
///
@@ -94,11 +104,16 @@ pub fn initialize<F>(env: &Env, init: F) -> os::raw::c_int
94104
{
95105
let env = panic::AssertUnwindSafe(env);
96106
let result = panic::catch_unwind(|| match (|| {
97-
for pre_init in __PRE_INIT__.try_lock().expect("Failed to acquire a read lock on the list of initializers").iter() {
98-
pre_init(&env)?;
107+
for init_global_ref in __GLOBAL_REFS__.try_lock()
108+
.expect("Failed to acquire a read lock on the list of initializers for global-refs").iter() {
109+
init_global_ref(&env)?;
99110
}
100-
env.define_errors()?;
111+
env.define_core_errors()?;
101112
check_gc_bug_31238(&env)?;
113+
for define_error in __CUSTOM_ERRORS__.try_lock()
114+
.expect("Failed to acquire a read lock on the list of initializers for custom error signals").iter() {
115+
define_error(&env)?;
116+
}
102117
init(&env)
103118
})() {
104119
Ok(_) => 0,

test-module/src/test_error.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Testing error reporting and handling.
22
3-
use emacs::{defun, CallEnv, Env, Result, Value, OnceGlobalRef};
3+
use emacs::{defun, CallEnv, Env, Result, Value};
44
use emacs::ErrorKind::{self, Signal, Throw};
55
use emacs::ResultExt;
66

@@ -87,29 +87,22 @@ fn parse_arg(env: &CallEnv) -> Result<String> {
8787
Ok(s)
8888
}
8989

90+
emacs::define_errors! {
91+
emacs_module_rs_test_error "Hello" (rust_error)
92+
error_defined_without_parent "No error message"
93+
}
94+
9095
pub fn init(env: &Env) -> Result<()> {
9196
emacs::__export_functions! {
9297
env, format!("{}error:", *MODULE_PREFIX), {
9398
"parse-arg" => (parse_arg , 2..5),
9499
}
95100
}
96101

97-
#[allow(non_upper_case_globals)]
98-
static custom_error: &'static OnceGlobalRef = {
99-
static custom_error: OnceGlobalRef = OnceGlobalRef::new();
100-
&custom_error
101-
};
102-
103102
#[defun(mod_in_name = false, name = "error:signal-custom")]
104103
fn signal_custom(env: &Env) -> Result<()> {
105-
env.signal(custom_error, [])
104+
env.signal(emacs_module_rs_test_error, [])
106105
}
107106

108-
env.define_error(
109-
custom_error.init_to_symbol(env, "emacs-module-rs-test-error")?,
110-
"Hello",
111-
[env.intern("rust-error")?]
112-
)?;
113-
114107
Ok(())
115108
}

test-module/tests/main.el

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,12 @@
168168
(t/error:signal 'rust-error "abc")
169169
(rust-error (should (equal err '(rust-error . ("abc"))))))
170170
(should-error (t/error:signal-custom) :type 'emacs-module-rs-test-error)
171+
(should-error (t/error:signal-custom) :type 'rust-error)
172+
(should-error (t/error:signal-custom) :type 'error)
171173
(condition-case err
172174
(t/error:signal 'emacs-module-rs-test-error "abc")
173-
(rust-error (should (equal err '(emacs-module-rs-test-error . ("abc")))))))
175+
(rust-error (should (equal err '(emacs-module-rs-test-error . ("abc"))))))
176+
(should-error (signal 'error-defined-without-parent nil) :type 'error))
174177

175178
;;; ----------------------------------------------------------------------------
176179
;;; Functions.

0 commit comments

Comments
 (0)