Skip to content

Commit 81e20eb

Browse files
authored
Merge pull request #42 from ubolonton/symbol-global-refs
Improve ergonomics of using symbols and defining/signaling errors
2 parents a5c3367 + c867991 commit 81e20eb

File tree

15 files changed

+235
-157
lines changed

15 files changed

+235
-157
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ 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.
7+
- Added `OnceGlobalRef`, which eases the initialization of static references to long-lived Lisp values.
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.
10+
- Raised the minimum supported Rust version to 1.45.
11+
- Added `ResultExt::or_signal` to make it more convenient to convert a Rust error into a Lisp error.
12+
- Remove `ResultExt::unwrap_or_propagate`.
813

914
## [0.16.2] - 2021-03-04
1015
- Fixed compilation on `aarch64-apple-darwin` (Apple Silicon).

build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use rustc_version::{version, Version};
55

66
fn main() {
77
let version = version().unwrap();
8-
if version < Version::parse("1.38.0").unwrap() {
9-
eprintln!("emacs-module-rs requires rustc 1.38.0 or newer, got {}", version);
8+
if version < Version::parse("1.45.0").unwrap() {
9+
eprintln!("emacs-module-rs requires rustc 1.45.0 or newer, got {}", version);
1010
exit(1);
1111
}
1212
}

emacs-macros/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extern crate proc_macro2;
99
use proc_macro::TokenStream;
1010

1111
use syn::{self, AttributeArgs, ItemFn, LitInt, parse_macro_input};
12+
use quote::quote;
1213

1314
mod util;
1415
mod module;
@@ -129,3 +130,13 @@ pub fn impl_lisp_args_for_arrays(length: TokenStream) -> TokenStream {
129130
let length: LitInt = parse_macro_input!(length);
130131
lisp_args::impl_for_arrays(length.base10_parse::<usize>().unwrap()).into()
131132
}
133+
134+
/// Converts an identifier into a Lisp name, as a string literal.
135+
///
136+
/// This replaces underscores with hyphens.
137+
#[doc(hidden)]
138+
#[proc_macro]
139+
pub fn lisp_name(ident: TokenStream) -> TokenStream {
140+
let name = util::lisp_name(&parse_macro_input!(ident));
141+
quote!(#name).into()
142+
}

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: OnceCell<GlobalRef> = OnceCell::new();
47-
48-
#[emacs::module]
49-
fn init(env: &Env) -> Result<Value<'_>> {
50-
env.define_error(
51-
my_custom_error.get_or_try_init(|| env.intern("my-custom-error").map(GlobalRef::new))?,
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.get().unwrap(), ("associated", "DATA", 7))
55+
return env.signal(my_custom_error., ("associated", "DATA", 7))
6156
}
6257
Ok(())
6358
}

src/error.rs

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
#[doc(no_inline)]
2+
use std::{any::Any, fmt::Display, mem::MaybeUninit, result, thread};
3+
24
pub use anyhow::{self, Error};
3-
use std::mem::MaybeUninit;
4-
use std::result;
5-
use std::thread;
6-
use std::any::Any;
75
use thiserror::Error;
86

9-
use super::IntoLisp;
10-
use super::{Env, Value};
117
use emacs_module::*;
12-
use crate::{symbol::{self, IntoLispSymbol}, GlobalRef};
13-
use crate::call::IntoLispArgs;
8+
9+
use crate::{
10+
Env, Value, IntoLisp,
11+
GlobalRef,
12+
symbol::{self, IntoLispSymbol},
13+
call::IntoLispArgs,
14+
};
1415

1516
// We use const instead of enum, in case Emacs add more exit statuses in the future.
1617
// See https://github.com/rust-lang/rust/issues/36927
@@ -23,6 +24,38 @@ pub struct TempValue {
2324
raw: emacs_value,
2425
}
2526

27+
/// Defines new error signals.
28+
///
29+
/// TODO: Document this properly.
30+
///
31+
/// This macro can be used only once per Rust `mod`.
32+
#[macro_export]
33+
macro_rules! define_errors {
34+
($( $name:ident $message:literal $( ( $( $parent:ident )+ ) )? )*) => {
35+
$crate::global_refs! {__emrs_init_global_refs_to_error_symbols__(init_to_symbol) =>
36+
$( $name )*
37+
}
38+
39+
#[$crate::deps::ctor::ctor]
40+
fn __emrs_define_errors__() {
41+
$crate::init::__CUSTOM_ERRORS__.try_lock()
42+
.expect("Failed to acquire a write lock on the list of initializers for custom error signals")
43+
.push(::std::boxed::Box::new(|env| {
44+
$(
45+
env.define_error($name, $message, [
46+
$(
47+
$(
48+
env.intern($crate::deps::emacs_macros::lisp_name!($parent))?
49+
),+
50+
)?
51+
])?;
52+
)*
53+
Ok(())
54+
}));
55+
}
56+
}
57+
}
58+
2659
/// Error types generic to all Rust dynamic modules.
2760
///
2861
/// This list is intended to grow over time and it is not recommended to exhaustively match against
@@ -100,6 +133,7 @@ impl TempValue {
100133
// XXX: Technically these are unsound, but they are necessary to use the `Fail` trait. We ensure
101134
// safety by marking TempValue methods as unsafe.
102135
unsafe impl Send for TempValue {}
136+
103137
unsafe impl Sync for TempValue {}
104138

105139
impl Env {
@@ -117,16 +151,14 @@ impl Env {
117151
Err(ErrorKind::Signal {
118152
symbol: unsafe { TempValue::new(symbol.assume_init()) },
119153
data: unsafe { TempValue::new(data.assume_init()) },
120-
}
121-
.into())
154+
}.into())
122155
}
123156
(THROW, tag, value) => {
124157
self.non_local_exit_clear();
125158
Err(ErrorKind::Throw {
126159
tag: unsafe { TempValue::new(tag.assume_init()) },
127160
value: unsafe { TempValue::new(value.assume_init()) },
128-
}
129-
.into())
161+
}.into())
130162
}
131163
_ => panic!("Unexpected non local exit status {}", status),
132164
}
@@ -163,7 +195,7 @@ impl Env {
163195
if let Err(error) = m {
164196
m = match error.downcast::<ErrorKind>() {
165197
// TODO: Explain safety.
166-
Ok(err) => unsafe { return self.handle_known(&*err) },
198+
Ok(err) => unsafe { return self.handle_known(&*err); },
167199
Err(error) => Err(error),
168200
}
169201
}
@@ -175,15 +207,15 @@ 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.
181-
self.define_error(symbol::rust_panic, "Rust panic", (symbol::error,))?;
182-
self.define_error(symbol::rust_error, "Rust error", (symbol::error,))?;
213+
self.define_error(symbol::rust_panic, "Rust panic", (symbol::error, ))?;
214+
self.define_error(symbol::rust_error, "Rust error", (symbol::error, ))?;
183215
self.define_error(
184216
symbol::rust_wrong_type_user_ptr,
185217
"Wrong type user-ptr",
186-
(symbol::rust_error, self.intern("wrong-type-argument")?)
218+
(symbol::rust_error, self.intern("wrong-type-argument")?),
187219
)?;
188220
Ok(())
189221
}
@@ -255,46 +287,21 @@ impl Env {
255287
}
256288
}
257289

258-
/// Emacs-specific extension methods for [`Result`].
290+
/// Emacs-specific extension methods for the standard library's [`Result`].
259291
///
260-
/// [`Result`]: type.Result.html
292+
/// [`Result`]: result::Result
261293
pub trait ResultExt<T, E> {
262-
/// Unwraps a result, yielding the content of an [`Ok`].
263-
///
264-
/// # Panics
265-
///
266-
/// Panics if the value is an [`Err`], using a sensible panic value.
267-
///
268-
/// If the underlying error is an [`ErrorKind`], it will be used as the value of the panic,
269-
/// which makes the `#[defun]` behave as if the corresponding non-local exit was propagated.
270-
/// Otherwise, tries to use [`Display`] to get a descriptive error message.
294+
/// Converts the error into a Lisp signal if this result is an [`Err`]. The first element of the
295+
/// associated signal data will be a string formatted with [`Display::fmt`].
271296
///
272-
/// This is useful when errors cannot be propagated using [`Result`], e.g. callbacks whose types
273-
/// are dictated by 3rd-party libraries.
274-
///
275-
/// # Safety
276-
///
277-
/// The panic must not propagate across an FFI boundary, e.g. this must not be used in callbacks
278-
/// that will be called by C code. See Rust's [`issue #52652`].
279-
///
280-
/// [`Ok`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Ok
281-
/// [`Err`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err
282-
/// [`ErrorKind`]: enum.ErrorKind.html
283-
/// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
284-
/// [`Result`]: type.Result.html
285-
/// [`issue #52652`]: https://github.com/rust-lang/rust/issues/52652
286-
#[deprecated(since = "0.12.0", note = "Use Result or a variable to track error instead")]
287-
unsafe fn unwrap_or_propagate(self) -> T;
297+
/// If the result is an [`Ok`], it is returned unchanged.
298+
fn or_signal<'e, S>(self, env: &'e Env, symbol: S) -> Result<T> where S: IntoLispSymbol<'e>;
288299
}
289300

290-
impl<T> ResultExt<T, Error> for Result<T> {
291-
#[inline]
292-
unsafe fn unwrap_or_propagate(self) -> T {
293-
self.unwrap_or_else(|error| {
294-
match error.downcast::<ErrorKind>() {
295-
Ok(err) => panic!(err),
296-
Err(error) => panic!("{}", error),
297-
};
298-
})
301+
impl<T, E: Display> ResultExt<T, E> for result::Result<T, E> {
302+
fn or_signal<'e, S>(self, env: &'e Env, symbol: S) -> Result<T> where S: IntoLispSymbol<'e> {
303+
self.or_else(|err| env.signal(symbol, (
304+
format!("{}", err),
305+
)))
299306
}
300307
}

src/global.rs

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,6 @@ impl<'e> IntoLisp<'e> for &'e GlobalRef {
9595
}
9696
}
9797

98-
impl<'e> IntoLisp<'e> for &'e OnceGlobalRef {
99-
#[inline(always)]
100-
fn into_lisp(self, env: &'e Env) -> Result<Value<'e>> {
101-
Ok(self.bind(env))
102-
}
103-
}
10498

10599
impl<'e> Value<'e> {
106100
/// Creates a new [`GlobalRef`] for this value.
@@ -112,45 +106,99 @@ impl<'e> Value<'e> {
112106
}
113107
}
114108

109+
/// Declares global references. These will be initialized when the module is loaded.
110+
#[doc(hidden)]
111+
#[macro_export]
112+
macro_rules! global_refs {
113+
($($name:ident)*) => {
114+
$(
115+
#[allow(non_upper_case_globals)]
116+
pub static $name: &'static $crate::OnceGlobalRef = {
117+
static X: $crate::OnceGlobalRef = $crate::OnceGlobalRef::new();
118+
&X
119+
};
120+
)*
121+
};
122+
($registrator_name:ident ($init_method:ident) =>
123+
$(
124+
$name:ident $( => $lisp_name:expr )?
125+
)*
126+
) => {
127+
$crate::global_refs! {
128+
$($name)*
129+
}
130+
131+
#[$crate::deps::ctor::ctor]
132+
fn $registrator_name() {
133+
$crate::init::__GLOBAL_REFS__.try_lock()
134+
.expect("Failed to acquire a write lock on the list of initializers for global refs")
135+
.push(::std::boxed::Box::new(|env| {
136+
$(
137+
#[allow(unused_variables)]
138+
let name = $crate::deps::emacs_macros::lisp_name!($name);
139+
$( let name = $lisp_name; )?
140+
$crate::OnceGlobalRef::$init_method(&$name, env, name)?;
141+
)*
142+
Ok(())
143+
}));
144+
}
145+
};
146+
}
147+
115148
/// A [`GlobalRef`] that can be initialized once. This is useful for long-lived values that should
116-
/// be initialized when the module is loaded, such as frequently-used symbols.
149+
/// be initialized when the dynamic module is loaded. A typical use case is specifying
150+
/// frequently-used symbols, which can be done with the help of the macro [`use_symbols!`].
117151
///
118-
/// [`GlobalRef`]: struct.GlobalRef.html
152+
/// [`use_symbols`]: crate::use_symbols
119153
#[derive(Debug)]
120154
#[repr(transparent)]
121155
pub struct OnceGlobalRef {
122156
inner: OnceCell<GlobalRef>
123157
}
124158

125159
impl OnceGlobalRef {
126-
pub(crate) const fn new() -> Self {
160+
pub const fn new() -> Self {
127161
Self { inner: OnceCell::new() }
128162
}
129163

130164
/// Initializes this global reference with the given function.
131165
#[doc(hidden)]
132-
pub fn init<F: FnOnce(&Env) -> Result<Value>>(&self, env: &Env, f: F) -> Result<()> {
166+
pub fn init<F: FnOnce(&Env) -> Result<Value>>(&self, env: &Env, f: F) -> Result<&GlobalRef> {
133167
let g = f(env)?.make_global_ref();
134168
self.inner.set(g).expect("Cannot initialize a global reference more than once");
135-
Ok(())
169+
Ok(self.inner.get().expect("Failed to get an initialized OnceGlobalRef"))
136170
}
137171

138172
/// Points this global reference to an interned Lisp symbol with the given name.
173+
///
174+
/// This should be called once, during module initialization.
139175
#[doc(hidden)]
140-
pub fn init_to_symbol(&self, env: &Env, name: &str) -> Result<()> {
176+
pub fn init_to_symbol(&self, env: &Env, name: &str) -> Result<&GlobalRef> {
141177
self.init(env, |env| env.intern(name))
142178
}
143179

144180
/// Points this global reference to the function bound to the Lisp symbol with the given name.
181+
///
182+
/// This should be called once, during module initialization.
183+
///
184+
/// If the symbol is later bound to another function, this global reference will still point to
185+
/// the old function. Therefore, this is best used for built-in and primitive functions.
145186
#[doc(hidden)]
146-
pub fn init_to_function(&self, env: &Env, name: &str) -> Result<()> {
187+
pub fn init_to_function(&self, env: &Env, name: &str) -> Result<&GlobalRef> {
147188
self.init(env, |env| {
148189
let symbol = env.intern(name)?;
149190
env.call("indirect-function", [symbol])
150191
})
151192
}
152193
}
153194

195+
impl<'e> IntoLisp<'e> for &'e OnceGlobalRef {
196+
#[inline(always)]
197+
fn into_lisp(self, env: &'e Env) -> Result<Value<'e>> {
198+
Ok(self.bind(env))
199+
}
200+
}
201+
154202
impl Deref for OnceGlobalRef {
155203
type Target = GlobalRef;
156204

0 commit comments

Comments
 (0)