generic_lang_api/host.rs
1//! Safe wrapper around the host vtable for Rust plugin authors.
2
3use crate::abi::{FfiReturn, FfiStatus, FfiStr, GenericValue, HostApi};
4use crate::{PluginError, ValueKind};
5
6/// Generates the typed error constructors on [`Host`]: one per builtin
7/// exception class, with the class name spelled exactly once - a plugin
8/// author cannot typo a builtin class name by going through these.
9macro_rules! host_error_constructors {
10 ($($(#[$doc:meta])* $name:ident => $class_name:literal),* $(,)?) => {
11 $(
12 $(#[$doc])*
13 #[must_use]
14 pub fn $name(&self, message: &str) -> PluginError {
15 self.error($class_name, message)
16 }
17 )*
18 };
19}
20
21/// Safe access to the host VM for the duration of one plugin call.
22///
23/// Methods that run generic bytecode (and may therefore trigger garbage
24/// collection) take `&mut self`: the borrow checker then guarantees the
25/// rooting contract's string rule - any [`&str`](str) obtained from
26/// the host borrows `self` and cannot be held across a re-entering call.
27/// Values held across a re-entering call must be rooted, e.g.
28/// via [`Host::rooted`].
29pub struct Host<'a> {
30 api: &'a HostApi,
31}
32
33/// A decoded view of a [`GenericValue`], obtained via [`Host::decode`].
34#[derive(Debug, Clone, Copy)]
35pub enum ArgValue<'h> {
36 /// The `nil` value.
37 Nil,
38 /// A boolean.
39 Bool(bool),
40 /// An integer that fits in an `i64`.
41 Int(i64),
42 /// An integer that does not fit in an `i64`; convert via
43 /// [`Host::display`] if a textual form suffices.
44 BigInt(GenericValue),
45 /// A float.
46 Float(f64),
47 /// A rational number; inspect via [`Host::display`].
48 Rational(GenericValue),
49 /// A string, borrowed from the host (see the re-fetch rule).
50 Str(&'h str),
51 /// A list ([`Host::list_len`], [`Host::list_get`]).
52 List(GenericValue),
53 /// A tuple ([`Host::tuple_len`], [`Host::tuple_get`]).
54 Tuple(GenericValue),
55 /// A dict ([`Host::dict_get`], [`Host::dict_set`]).
56 Dict(GenericValue),
57 /// A set ([`Host::set_add`], [`Host::set_contains`]).
58 Set(GenericValue),
59 /// A range; inspect via [`Host::display`] or drive its iterator.
60 Range(GenericValue),
61 /// The exhausted-iterator sentinel.
62 StopIteration,
63 /// A plain class instance ([`Host::attr_get`], [`Host::invoke`]).
64 Instance(GenericValue),
65 /// A class ([`Host::call`] instantiates).
66 Class(GenericValue),
67 /// A callable ([`Host::call`]).
68 Function(GenericValue),
69 /// A module.
70 Module(GenericValue),
71 /// An exception instance.
72 Exception(GenericValue),
73 /// A generator; drive via [`Host::invoke`] with `__next__`.
74 Generator(GenericValue),
75 /// An iterator (drive via [`Host::invoke`] with `__next__`).
76 Iterator(GenericValue),
77 /// VM-internal values a plugin should never meaningfully receive.
78 Other(GenericValue),
79}
80
81impl<'a> Host<'a> {
82 /// Wrap a host vtable. Called by the `export_module!` glue.
83 #[doc(hidden)]
84 #[must_use]
85 pub const fn new(api: &'a HostApi) -> Self {
86 Self { api }
87 }
88
89 // --- inspect ---
90
91 /// The [`ValueKind`] of a value.
92 #[must_use]
93 pub fn kind(&self, value: GenericValue) -> ValueKind {
94 ValueKind::from_u32((self.api.value_kind)(self.api.ctx, value))
95 }
96
97 /// Decode a value into a borrowed view.
98 #[must_use]
99 pub fn decode(&self, value: GenericValue) -> ArgValue<'_> {
100 match self.kind(value) {
101 ValueKind::Nil => ArgValue::Nil,
102 ValueKind::Bool => ArgValue::Bool(self.as_bool(value).unwrap_or_default()),
103 ValueKind::Int => ArgValue::Int(self.as_int(value).unwrap_or_default()),
104 ValueKind::BigInt => ArgValue::BigInt(value),
105 ValueKind::Float => ArgValue::Float(self.as_float(value).unwrap_or_default()),
106 ValueKind::Rational => ArgValue::Rational(value),
107 ValueKind::String => ArgValue::Str(self.as_str(value).unwrap_or_default()),
108 ValueKind::List => ArgValue::List(value),
109 ValueKind::Tuple => ArgValue::Tuple(value),
110 ValueKind::Dict => ArgValue::Dict(value),
111 ValueKind::Set => ArgValue::Set(value),
112 ValueKind::Range => ArgValue::Range(value),
113 ValueKind::StopIteration => ArgValue::StopIteration,
114 ValueKind::Instance => ArgValue::Instance(value),
115 ValueKind::Class => ArgValue::Class(value),
116 ValueKind::Function => ArgValue::Function(value),
117 ValueKind::Module => ArgValue::Module(value),
118 ValueKind::Exception => ArgValue::Exception(value),
119 ValueKind::Generator => ArgValue::Generator(value),
120 ValueKind::Iterator => ArgValue::Iterator(value),
121 ValueKind::Other => ArgValue::Other(value),
122 }
123 }
124
125 /// `None` if the value is not a bool.
126 #[must_use]
127 pub fn as_bool(&self, value: GenericValue) -> Option<bool> {
128 let mut out = false;
129 (self.api.bool_get)(self.api.ctx, value, &raw mut out).then_some(out)
130 }
131
132 /// The value as an `i64`; `None` if it is not an integer or does not
133 /// fit in an `i64` (big integers - fall back to `display`).
134 #[must_use]
135 pub fn as_int(&self, value: GenericValue) -> Option<i64> {
136 let mut out = 0i64;
137 (self.api.int_get)(self.api.ctx, value, &raw mut out).then_some(out)
138 }
139
140 /// `None` if the value is not a float.
141 #[must_use]
142 pub fn as_float(&self, value: GenericValue) -> Option<f64> {
143 let mut out = 0f64;
144 (self.api.float_get)(self.api.ctx, value, &raw mut out).then_some(out)
145 }
146
147 /// The contents of a string value; `None` if the value is not a string
148 /// (or the host answered with malformed string data - a null pointer or
149 /// invalid UTF-8, both protocol violations).
150 ///
151 /// The returned string borrows the host and therefore cannot be held
152 /// across a re-entering call (`&mut self` methods); the compiler
153 /// rejects it:
154 ///
155 /// ```compile_fail,E0502
156 /// use generic_lang_api::{GenericValue, Host, PluginError};
157 ///
158 /// fn plugin_fn(host: &mut Host, args: &[GenericValue]) -> Result<GenericValue, PluginError> {
159 /// let name = host.as_str(args[0]).unwrap(); // borrows `host`
160 /// host.call(args[1], &[])?; // re-enters: needs `&mut host`
161 /// Ok(host.make_str(name)) // ERROR: `name` still borrowed
162 /// }
163 /// ```
164 ///
165 /// Copy the string out (`.to_owned()`) before re-entering if it is
166 /// needed afterwards.
167 #[must_use]
168 pub fn as_str(&self, value: GenericValue) -> Option<&str> {
169 let mut out = FfiStr::null();
170 if !(self.api.string_get)(self.api.ctx, value, &raw mut out) {
171 return None;
172 }
173 // A null pointer is not a valid `FfiStr` (see `abi::FfiStr`): a host
174 // that wrote one - or answered `true` without writing at all -
175 // violated the protocol. Report "not a string" rather than fabricate
176 // a value from it.
177 if out.ptr.is_null() {
178 return None;
179 }
180 // SAFETY: on success the host wrote a non-null pointer (checked
181 // above, per the `FfiStr` contract) to `len` initialized bytes of
182 // interned string data, stable until the next re-entering callback,
183 // which `&mut self` methods make unreachable while the returned
184 // borrow lives.
185 let bytes = unsafe { core::slice::from_raw_parts(out.ptr, out.len) };
186 core::str::from_utf8(bytes).ok()
187 }
188
189 /// `None` if the value is not a list.
190 #[must_use]
191 pub fn list_len(&self, value: GenericValue) -> Option<usize> {
192 let mut out = 0usize;
193 (self.api.list_len)(self.api.ctx, value, &raw mut out).then_some(out)
194 }
195
196 /// The list element at `index`.
197 ///
198 /// # Errors
199 ///
200 /// `TypeError` if the value is not a list; `IndexError` if the index
201 /// is out of bounds.
202 pub fn list_get(&self, value: GenericValue, index: usize) -> Result<GenericValue, PluginError> {
203 self.ffi_result((self.api.list_get)(self.api.ctx, value, index))
204 }
205
206 /// `None` if the value is not a tuple.
207 #[must_use]
208 pub fn tuple_len(&self, value: GenericValue) -> Option<usize> {
209 let mut out = 0usize;
210 (self.api.tuple_len)(self.api.ctx, value, &raw mut out).then_some(out)
211 }
212
213 /// The tuple element at `index`.
214 ///
215 /// # Errors
216 ///
217 /// `TypeError` if the value is not a tuple; `IndexError` if the index
218 /// is out of bounds.
219 pub fn tuple_get(
220 &self,
221 value: GenericValue,
222 index: usize,
223 ) -> Result<GenericValue, PluginError> {
224 self.ffi_result((self.api.tuple_get)(self.api.ctx, value, index))
225 }
226
227 /// `None` if the value is not a dict.
228 #[must_use]
229 pub fn dict_len(&self, value: GenericValue) -> Option<usize> {
230 let mut out = 0usize;
231 (self.api.dict_len)(self.api.ctx, value, &raw mut out).then_some(out)
232 }
233
234 /// `None` if the value is not a set.
235 #[must_use]
236 pub fn set_len(&self, value: GenericValue) -> Option<usize> {
237 let mut out = 0usize;
238 (self.api.set_len)(self.api.ctx, value, &raw mut out).then_some(out)
239 }
240
241 /// Look up a builtin global by name - exception classes like
242 /// `"TypeError"`, native classes, builtin functions.
243 ///
244 /// # Errors
245 ///
246 /// `NameError` if absent.
247 pub fn builtin(&self, name: &str) -> Result<GenericValue, PluginError> {
248 self.ffi_result((self.api.builtin_get)(self.api.ctx, Self::ffi_str(name)))
249 }
250
251 /// Whether `value` is an instance of `class` or of a subclass of it -
252 /// the exact semantics of the `isinstance` builtin, value-type proxy
253 /// classes included.
254 ///
255 /// # Errors
256 ///
257 /// `TypeError` if `class` is not a class.
258 pub fn is_instance(
259 &self,
260 value: GenericValue,
261 class: GenericValue,
262 ) -> Result<bool, PluginError> {
263 let result = self.ffi_result((self.api.is_instance)(self.api.ctx, value, class))?;
264 Ok(self.as_bool(result).unwrap_or_default())
265 }
266
267 /// The class of an instance, as a class value (the analogue of
268 /// `type(self)`). Call it to construct another instance of the same class,
269 /// or pass it to [`Host::is_instance`] to type-check another argument
270 /// before reading its opaque state.
271 ///
272 /// # Errors
273 ///
274 /// `TypeError` if `value` is not an instance.
275 pub fn class_of(&self, value: GenericValue) -> Result<GenericValue, PluginError> {
276 self.ffi_result((self.api.class_of)(self.api.ctx, value))
277 }
278
279 // --- attributes (plain field access; never re-enters) ---
280
281 /// A field of an instance.
282 ///
283 /// # Errors
284 ///
285 /// `AttributeError` if the field is absent, `TypeError` if the receiver
286 /// is not an instance.
287 pub fn attr_get(
288 &self,
289 receiver: GenericValue,
290 name: &str,
291 ) -> Result<GenericValue, PluginError> {
292 self.ffi_result((self.api.attr_get)(
293 self.api.ctx,
294 receiver,
295 Self::ffi_str(name),
296 ))
297 }
298
299 /// Set a field on an instance.
300 ///
301 /// # Errors
302 ///
303 /// `TypeError` if the receiver is not an instance.
304 pub fn attr_set(
305 &self,
306 receiver: GenericValue,
307 name: &str,
308 value: GenericValue,
309 ) -> Result<(), PluginError> {
310 self.ffi_result((self.api.attr_set)(
311 self.api.ctx,
312 receiver,
313 Self::ffi_str(name),
314 value,
315 ))
316 .map(|_| ())
317 }
318
319 /// Whether an instance has a field.
320 ///
321 /// # Errors
322 ///
323 /// `TypeError` if the receiver is not an instance.
324 pub fn attr_has(&self, receiver: GenericValue, name: &str) -> Result<bool, PluginError> {
325 let result = self.ffi_result((self.api.attr_has)(
326 self.api.ctx,
327 receiver,
328 Self::ffi_str(name),
329 ))?;
330 Ok(self.as_bool(result).unwrap_or_default())
331 }
332
333 // --- construct ---
334
335 /// A new `nil` value.
336 #[must_use]
337 pub fn make_nil(&self) -> GenericValue {
338 (self.api.nil_new)(self.api.ctx)
339 }
340
341 /// A new boolean value.
342 #[must_use]
343 pub fn make_bool(&self, value: bool) -> GenericValue {
344 (self.api.bool_new)(self.api.ctx, value)
345 }
346
347 /// A new integer value.
348 #[must_use]
349 pub fn make_int(&self, value: i64) -> GenericValue {
350 (self.api.int_new)(self.api.ctx, value)
351 }
352
353 /// A new float value.
354 #[must_use]
355 pub fn make_float(&self, value: f64) -> GenericValue {
356 (self.api.float_new)(self.api.ctx, value)
357 }
358
359 /// Intern a string value.
360 ///
361 /// # Panics
362 ///
363 /// Panics if the host rejects the string, which cannot happen for Rust
364 /// strings (they are always valid UTF-8).
365 #[must_use]
366 pub fn make_str(&self, value: &str) -> GenericValue {
367 let ffi = FfiStr {
368 ptr: value.as_ptr(),
369 len: value.len(),
370 };
371 self.ffi_result((self.api.string_new)(self.api.ctx, ffi))
372 .expect("host rejected a valid UTF-8 string")
373 }
374
375 /// A new, empty list.
376 #[must_use]
377 pub fn make_list(&self) -> GenericValue {
378 (self.api.list_new)(self.api.ctx)
379 }
380
381 /// Append to a list value.
382 ///
383 /// # Errors
384 ///
385 /// `TypeError` if the target is not a list.
386 pub fn list_push(&self, list: GenericValue, item: GenericValue) -> Result<(), PluginError> {
387 self.ffi_result((self.api.list_push)(self.api.ctx, list, item))
388 .map(|_| ())
389 }
390
391 /// Replace the element at an index.
392 ///
393 /// # Errors
394 ///
395 /// `TypeError` if the target is not a list; `IndexError` if the index
396 /// is out of bounds.
397 pub fn list_set(
398 &self,
399 list: GenericValue,
400 index: usize,
401 value: GenericValue,
402 ) -> Result<(), PluginError> {
403 self.ffi_result((self.api.list_set)(self.api.ctx, list, index, value))
404 .map(|_| ())
405 }
406
407 /// A new exception instance of `class` (any class deriving from
408 /// `Exception` - builtin or user-defined), ready to be thrown
409 /// (returned inside [`PluginError::Exception`]) or passed to generic
410 /// code. Sets the message directly, bypassing `__init__`. Prefer the
411 /// typed constructors below for the common builtin-class case.
412 ///
413 /// # Errors
414 ///
415 /// `TypeError` if `class` is not a class deriving from `Exception`.
416 pub fn make_exception(
417 &self,
418 class: GenericValue,
419 message: &str,
420 ) -> Result<GenericValue, PluginError> {
421 self.ffi_result((self.api.exception_new)(
422 self.api.ctx,
423 class,
424 Self::ffi_str(message),
425 ))
426 }
427
428 /// A [`PluginError`] carrying a fresh instance of the builtin
429 /// exception class `class_name`. Unknown names fall back to the base
430 /// `Exception` (unreachable through the typed constructors below). A
431 /// fatal host error during construction stays [`PluginError::Fatal`] -
432 /// it must never be downgraded to something catchable.
433 fn error(&self, class_name: &str, message: &str) -> PluginError {
434 let result = self
435 .builtin(class_name)
436 .and_then(|class| self.make_exception(class, message))
437 .or_else(|error| {
438 // Only fall back for catchable failures (an unknown class
439 // name); a fatal host error must propagate as-is.
440 if matches!(error, PluginError::Fatal) {
441 return Err(error);
442 }
443 let class = self.builtin("Exception")?;
444 self.make_exception(class, message)
445 });
446 match result {
447 Ok(exception) => PluginError::Exception(exception),
448 Err(PluginError::Fatal) => PluginError::Fatal,
449 // Unreachable with the real host (the base `Exception` always
450 // exists and `builtin_get`/`exception_new` fail catchably at
451 // worst), but a real nil keeps this a valid `Value`: if it ever
452 // escaped, the host would reject the non-exception gracefully
453 // rather than transmute an invalid blob.
454 Err(_) => PluginError::Exception(self.make_nil()),
455 }
456 }
457
458 host_error_constructors!(
459 /// A [`PluginError`] carrying a fresh base `Exception` instance.
460 exception => "Exception",
461 /// A [`PluginError`] carrying a fresh `TypeError` instance.
462 type_error => "TypeError",
463 /// A [`PluginError`] carrying a fresh `ValueError` instance.
464 value_error => "ValueError",
465 /// A [`PluginError`] carrying a fresh `NameError` instance.
466 name_error => "NameError",
467 /// A [`PluginError`] carrying a fresh `ConstReassignmentError` instance.
468 const_reassignment_error => "ConstReassignmentError",
469 /// A [`PluginError`] carrying a fresh `AttributeError` instance.
470 attribute_error => "AttributeError",
471 /// A [`PluginError`] carrying a fresh `ImportError` instance.
472 import_error => "ImportError",
473 /// A [`PluginError`] carrying a fresh `AssertionError` instance.
474 assertion_error => "AssertionError",
475 /// A [`PluginError`] carrying a fresh `IoError` instance.
476 io_error => "IoError",
477 /// A [`PluginError`] carrying a fresh `KeyError` instance.
478 key_error => "KeyError",
479 /// A [`PluginError`] carrying a fresh `IndexError` instance.
480 index_error => "IndexError",
481 );
482
483 // --- display ---
484
485 /// The raw string representation of any value, as a string value.
486 /// Does NOT honor a user class's `__str__` - see [`Host::to_str`].
487 #[must_use]
488 pub fn display(&self, value: GenericValue) -> GenericValue {
489 (self.api.value_display)(self.api.ctx, value)
490 }
491
492 /// [`Host::display`], copied out as an owned Rust `String`.
493 #[must_use]
494 pub fn display_string(&self, value: GenericValue) -> String {
495 let displayed = self.display(value);
496 self.as_str(displayed).unwrap_or_default().to_owned()
497 }
498
499 // --- re-entering (run generic bytecode; GC may occur) ---
500
501 /// Call a callable value with the given arguments.
502 ///
503 /// # Errors
504 ///
505 /// Returns the generic exception raised by the callee, if any.
506 pub fn call(
507 &mut self,
508 callee: GenericValue,
509 args: &[GenericValue],
510 ) -> Result<GenericValue, PluginError> {
511 self.ffi_result((self.api.call_value)(
512 self.api.ctx,
513 callee,
514 args.as_ptr(),
515 args.len(),
516 ))
517 }
518
519 /// Invoke a named method on a receiver.
520 ///
521 /// # Errors
522 ///
523 /// Returns the generic exception raised by the method, if any.
524 pub fn invoke(
525 &mut self,
526 receiver: GenericValue,
527 name: &str,
528 args: &[GenericValue],
529 ) -> Result<GenericValue, PluginError> {
530 let name = FfiStr {
531 ptr: name.as_ptr(),
532 len: name.len(),
533 };
534 self.ffi_result((self.api.invoke_method)(
535 self.api.ctx,
536 receiver,
537 name,
538 args.as_ptr(),
539 args.len(),
540 ))
541 }
542
543 /// String conversion honoring a user class's `__str__`.
544 ///
545 /// # Errors
546 ///
547 /// Returns the generic exception raised by `__str__`, if any.
548 pub fn to_str(&mut self, value: GenericValue) -> Result<GenericValue, PluginError> {
549 self.ffi_result((self.api.value_str)(self.api.ctx, value))
550 }
551
552 /// Look up a key in a dict.
553 ///
554 /// # Errors
555 ///
556 /// `KeyError` if absent, `TypeError` for unusable targets/keys, or any
557 /// exception raised by `__hash__`/`__eq__`.
558 pub fn dict_get(
559 &mut self,
560 dict: GenericValue,
561 key: GenericValue,
562 ) -> Result<GenericValue, PluginError> {
563 self.ffi_result((self.api.dict_get)(self.api.ctx, dict, key))
564 }
565
566 /// Insert or replace a key in a dict.
567 ///
568 /// # Errors
569 ///
570 /// `TypeError` for unusable targets/keys, or any exception raised by
571 /// `__hash__`/`__eq__`.
572 pub fn dict_set(
573 &mut self,
574 dict: GenericValue,
575 key: GenericValue,
576 value: GenericValue,
577 ) -> Result<(), PluginError> {
578 self.ffi_result((self.api.dict_set)(self.api.ctx, dict, key, value))
579 .map(|_| ())
580 }
581
582 /// Whether a dict contains a key.
583 ///
584 /// # Errors
585 ///
586 /// `TypeError` for unusable targets/keys, or any exception raised by
587 /// `__hash__`/`__eq__`.
588 pub fn dict_contains(
589 &mut self,
590 dict: GenericValue,
591 key: GenericValue,
592 ) -> Result<bool, PluginError> {
593 let value = self.ffi_result((self.api.dict_contains)(self.api.ctx, dict, key))?;
594 Ok(self.as_bool(value).unwrap_or_default())
595 }
596
597 /// Add an item to a set.
598 ///
599 /// # Errors
600 ///
601 /// `TypeError` for unusable targets/items, or any exception raised by
602 /// `__hash__`/`__eq__`.
603 pub fn set_add(&mut self, set: GenericValue, item: GenericValue) -> Result<(), PluginError> {
604 self.ffi_result((self.api.set_add)(self.api.ctx, set, item))
605 .map(|_| ())
606 }
607
608 /// Whether a set contains an item.
609 ///
610 /// # Errors
611 ///
612 /// `TypeError` for unusable targets/items, or any exception raised by
613 /// `__hash__`/`__eq__`.
614 pub fn set_contains(
615 &mut self,
616 set: GenericValue,
617 item: GenericValue,
618 ) -> Result<bool, PluginError> {
619 let value = self.ffi_result((self.api.set_contains)(self.api.ctx, set, item))?;
620 Ok(self.as_bool(value).unwrap_or_default())
621 }
622
623 /// Truthiness honoring `__bool__`.
624 ///
625 /// # Errors
626 ///
627 /// Returns the generic exception raised by `__bool__`, if any.
628 pub fn truthy(&mut self, value: GenericValue) -> Result<bool, PluginError> {
629 let result = self.ffi_result((self.api.value_truthy)(self.api.ctx, value))?;
630 Ok(self.as_bool(result).unwrap_or_default())
631 }
632
633 /// Equality honoring `__eq__`.
634 ///
635 /// # Errors
636 ///
637 /// Returns the generic exception raised by `__eq__`, if any.
638 pub fn equals(&mut self, a: GenericValue, b: GenericValue) -> Result<bool, PluginError> {
639 let result = self.ffi_result((self.api.value_equals)(self.api.ctx, a, b))?;
640 Ok(self.as_bool(result).unwrap_or_default())
641 }
642
643 /// Hash honoring `__hash__`.
644 ///
645 /// # Errors
646 ///
647 /// Returns the generic exception raised by `__hash__`, if any.
648 pub fn hash(&mut self, value: GenericValue) -> Result<i64, PluginError> {
649 let result = self.ffi_result((self.api.value_hash)(self.api.ctx, value))?;
650 Ok(self.as_int(result).unwrap_or_default())
651 }
652
653 // --- rooting ---
654
655 /// Keep a value alive across re-entering calls for the rest of this
656 /// plugin call (the host releases all roots automatically on return).
657 /// Prefer the RAII form, [`Host::rooted`].
658 pub fn root(&self, value: GenericValue) {
659 (self.api.root)(self.api.ctx, value);
660 }
661
662 /// Release the `n` most recent roots early. Releasing more roots than
663 /// were pushed corrupts interpreter state; prefer the RAII form,
664 /// [`Host::rooted`].
665 pub fn unroot(&self, n: usize) {
666 (self.api.unroot)(self.api.ctx, n);
667 }
668
669 /// Root a value for the lifetime of the returned guard.
670 ///
671 /// Guards release in LIFO order - drop them in reverse order of
672 /// creation (scopes do this naturally).
673 #[must_use]
674 pub fn rooted(&self, value: GenericValue) -> Rooted<'a> {
675 (self.api.root)(self.api.ctx, value);
676 Rooted {
677 api: self.api,
678 value,
679 }
680 }
681
682 // --- plugin instance state ---
683
684 /// Install the plugin's opaque pointer on a plugin-backed instance.
685 ///
686 /// Typically called from `__init__` with `args[0]` (the receiver) and a
687 /// `Box::into_raw(state)` pointer. The class's `drop` callback is called
688 /// with this pointer when the instance is garbage-collected.
689 ///
690 /// Overwriting an already-installed pointer leaks the previous one: the
691 /// host does not run `drop` on it, since it cannot know whether the plugin
692 /// still holds a copy elsewhere. If a plugin means to replace state, it must
693 /// [`Host::get_opaque`] and free the old pointer itself first.
694 ///
695 /// # Errors
696 ///
697 /// `TypeError` if `receiver` is not a plugin-backed instance.
698 pub fn set_opaque(
699 &self,
700 receiver: GenericValue,
701 ptr: *mut core::ffi::c_void,
702 ) -> Result<(), PluginError> {
703 self.ffi_result((self.api.instance_set_opaque)(self.api.ctx, receiver, ptr))
704 .map(|_| ())
705 }
706
707 /// Recover the pointer installed by [`Host::set_opaque`], or null if none
708 /// was installed (e.g. before `__init__` ran) or `receiver` is not a
709 /// plugin-backed instance. Never raises.
710 #[must_use]
711 pub fn get_opaque(&self, receiver: GenericValue) -> *mut core::ffi::c_void {
712 (self.api.instance_get_opaque)(self.api.ctx, receiver)
713 }
714
715 /// Typed mutable view of the opaque pointer, or `None` if it is null or
716 /// `receiver` is not a plugin-backed instance.
717 ///
718 /// # Safety
719 ///
720 /// The caller must ensure `T` is the correct type for this instance's
721 /// opaque state. The reference is valid while the instance is alive (the
722 /// GC will not collect it while the plugin holds the instance value).
723 // The `&mut T` derives from the opaque `*mut` the plugin installed, not
724 // from `&self`; the shared borrow only scopes the call, so the plugin can
725 // mutate its own per-instance state through a `&Host`.
726 #[allow(clippy::mut_from_ref)]
727 #[must_use]
728 pub unsafe fn opaque_ref<T>(&self, receiver: GenericValue) -> Option<&mut T> {
729 let ptr = self.get_opaque(receiver).cast::<T>();
730 // SAFETY: guaranteed by the caller (see the `# Safety` section).
731 unsafe { ptr.as_mut() }
732 }
733
734 const fn ffi_str(s: &str) -> FfiStr {
735 FfiStr {
736 ptr: s.as_ptr(),
737 len: s.len(),
738 }
739 }
740
741 fn ffi_result(&self, ret: FfiReturn) -> Result<GenericValue, PluginError> {
742 match FfiStatus::from_u32(ret.status) {
743 Some(FfiStatus::Ok) => Ok(ret.value),
744 Some(FfiStatus::Exception) => Err(PluginError::Exception(ret.value)),
745 Some(FfiStatus::Fatal) => Err(PluginError::Fatal),
746 // A status outside the enum is a protocol violation.
747 None => Err(self.protocol_violation(&format!(
748 "host callback returned unknown status {}",
749 ret.status
750 ))),
751 }
752 }
753
754 /// Builds the protocol-violation exception without going through
755 /// [`Self::ffi_result`]: a host broken enough to answer `builtin_get`/
756 /// `exception_new` with unknown statuses too would otherwise recurse
757 /// through error construction forever. Any failure here falls back to
758 /// a nil-carrying exception rather than another decode attempt.
759 fn protocol_violation(&self, message: &str) -> PluginError {
760 let class = (self.api.builtin_get)(self.api.ctx, Self::ffi_str("Exception"));
761 if FfiStatus::from_u32(class.status) == Some(FfiStatus::Ok) {
762 let exception =
763 (self.api.exception_new)(self.api.ctx, class.value, Self::ffi_str(message));
764 if FfiStatus::from_u32(exception.status) == Some(FfiStatus::Ok) {
765 return PluginError::Exception(exception.value);
766 }
767 }
768 PluginError::Exception(self.make_nil())
769 }
770}
771
772/// RAII guard for a rooted value; see [`Host::rooted`].
773///
774/// Holds the vtable (not the [`Host`] borrow), so re-entering `&mut Host`
775/// methods remain callable while guards are alive.
776pub struct Rooted<'a> {
777 api: &'a HostApi,
778 value: GenericValue,
779}
780
781impl Rooted<'_> {
782 /// The rooted value.
783 #[must_use]
784 pub const fn get(&self) -> GenericValue {
785 self.value
786 }
787}
788
789impl Drop for Rooted<'_> {
790 fn drop(&mut self) {
791 (self.api.unroot)(self.api.ctx, 1);
792 }
793}
794
795/// Signature of a Rust plugin function used with `export_module!`.
796pub type RustPluginFn = fn(&mut Host, &[GenericValue]) -> Result<GenericValue, PluginError>;
797
798/// Implementation detail of `export_module!`: runs a Rust plugin function
799/// behind `catch_unwind` and maps the outcome to an [`FfiReturn`].
800///
801/// # Safety
802///
803/// `host` must point to a valid [`HostApi`] and `args` to `nargs`
804/// contiguous values, both valid for the duration of the call - which is
805/// what the interpreter guarantees when calling an exported plugin function.
806#[doc(hidden)]
807pub unsafe fn __invoke_plugin_fn(
808 fun: RustPluginFn,
809 host: *const HostApi,
810 args: *const GenericValue,
811 nargs: usize,
812) -> FfiReturn {
813 // SAFETY: guaranteed by the caller, see above.
814 let api = unsafe { &*host };
815 let args: &[GenericValue] = if nargs == 0 {
816 &[]
817 } else {
818 // SAFETY: guaranteed by the caller, see above.
819 unsafe { core::slice::from_raw_parts(args, nargs) }
820 };
821
822 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
823 let mut host = Host::new(api);
824 fun(&mut host, args)
825 }));
826
827 finish_plugin_invoke(api, result)
828}
829
830/// Signature of a Rust plugin method used with `export_module!`. The receiver
831/// (`self`) is a separate parameter; `args` are the remaining arguments only.
832pub type RustPluginMethodFn =
833 fn(&mut Host, GenericValue, &[GenericValue]) -> Result<GenericValue, PluginError>;
834
835/// Implementation detail of `export_module!`: the method counterpart of
836/// [`__invoke_plugin_fn`], threading the receiver through as a separate value.
837///
838/// # Safety
839///
840/// As [`__invoke_plugin_fn`]: `host` and `args`/`nargs` must be valid for the
841/// call. `receiver` is a bit-copy of the receiver value.
842#[doc(hidden)]
843pub unsafe fn __invoke_plugin_method_fn(
844 fun: RustPluginMethodFn,
845 host: *const HostApi,
846 receiver: GenericValue,
847 args: *const GenericValue,
848 nargs: usize,
849) -> FfiReturn {
850 // SAFETY: guaranteed by the caller, see above.
851 let api = unsafe { &*host };
852 let args: &[GenericValue] = if nargs == 0 {
853 &[]
854 } else {
855 // SAFETY: guaranteed by the caller, see above.
856 unsafe { core::slice::from_raw_parts(args, nargs) }
857 };
858
859 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
860 let mut host = Host::new(api);
861 fun(&mut host, receiver, args)
862 }));
863
864 finish_plugin_invoke(api, result)
865}
866
867/// Signature of a Rust plugin value creator used with `export_module!`:
868/// builds one module constant at import time.
869pub type RustPluginValueFn = fn(&mut Host) -> Result<GenericValue, PluginError>;
870
871/// Implementation detail of `export_module!`: the value-creator counterpart
872/// of [`__invoke_plugin_fn`].
873///
874/// # Safety
875///
876/// `host` must point to a valid [`HostApi`], valid for the duration of the
877/// call - which is what the interpreter guarantees when importing the
878/// plugin module.
879#[doc(hidden)]
880pub unsafe fn __invoke_plugin_value_fn(fun: RustPluginValueFn, host: *const HostApi) -> FfiReturn {
881 // SAFETY: guaranteed by the caller, see above.
882 let api = unsafe { &*host };
883
884 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
885 let mut host = Host::new(api);
886 fun(&mut host)
887 }));
888
889 finish_plugin_invoke(api, result)
890}
891
892/// Map a caught plugin invocation outcome to an [`FfiReturn`], turning a panic
893/// into a catchable base `Exception` so nothing unwinds across the C ABI.
894fn finish_plugin_invoke(
895 api: &HostApi,
896 result: std::thread::Result<Result<GenericValue, PluginError>>,
897) -> FfiReturn {
898 let host = Host::new(api);
899 match result {
900 Ok(Ok(value)) => FfiReturn {
901 status: FfiStatus::Ok as u32,
902 value,
903 },
904 Ok(Err(error)) => error_return(&host, error),
905 Err(panic) => {
906 let message = panic
907 .downcast_ref::<&str>()
908 .map(ToString::to_string)
909 .or_else(|| panic.downcast_ref::<String>().cloned())
910 .unwrap_or_else(|| "plugin function panicked".to_owned());
911 error_return(&host, host.exception(&format!("panic: {message}")))
912 }
913 }
914}
915
916fn error_return(host: &Host, error: PluginError) -> FfiReturn {
917 match error {
918 PluginError::Exception(value) => FfiReturn {
919 status: FfiStatus::Exception as u32,
920 value,
921 },
922 // The value is never read for a fatal status; a real nil keeps it a
923 // valid `Value` rather than a zeroed blob that is not one.
924 PluginError::Fatal => FfiReturn {
925 status: FfiStatus::Fatal as u32,
926 value: host.make_nil(),
927 },
928 }
929}