Browse Source

Implement `ProxyBuilder` (#2076)

This Pull Request fixes/closes #2075.

It changes the following:

- Implements a `ProxyBuilder` struct to be able to create `Proxy` objects from native function traps.
- Documents `ProxyBuilder` and adjusts some documentation.
- Fixes a clippy lint.
pull/2084/head
jedel1043 3 years ago
parent
commit
406c642041
  1. 8
      boa_engine/src/builtins/function/mod.rs
  2. 74
      boa_engine/src/builtins/proxy/mod.rs
  3. 1
      boa_engine/src/object/internal_methods/bound_function.rs
  4. 25
      boa_engine/src/object/internal_methods/proxy.rs
  5. 477
      boa_engine/src/object/jsproxy.rs
  6. 2
      boa_engine/src/object/mod.rs
  7. 2
      boa_engine/src/string.rs

8
boa_engine/src/builtins/function/mod.rs

@ -44,6 +44,14 @@ mod tests;
///
/// Native functions need to have this signature in order to
/// be callable from Javascript.
///
/// # Arguments
///
/// - The first argument represents the `this` variable of every Javascript function.
///
/// - The second argument represents a list of all arguments passed to the function.
///
/// - The last argument is the [`Context`] of the engine.
pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
// Allows restricting closures to only `Copy` ones.

74
boa_engine/src/builtins/proxy/mod.rs

@ -12,13 +12,12 @@
use crate::{
builtins::{BuiltIn, JsArgs},
object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData},
object::{ConstructorBuilder, FunctionBuilder, JsFunction, JsObject, ObjectData},
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use tap::{Conv, Pipe};
/// Javascript `Proxy` object.
#[derive(Debug, Clone, Trace, Finalize)]
pub struct Proxy {
@ -50,7 +49,7 @@ impl BuiltIn for Proxy {
impl Proxy {
const LENGTH: usize = 2;
fn new(target: JsObject, handler: JsObject) -> Self {
pub(crate) fn new(target: JsObject, handler: JsObject) -> Self {
Self {
data: Some((target, handler)),
}
@ -82,7 +81,7 @@ impl Proxy {
}
// 2. Return ? ProxyCreate(target, handler).
Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)
Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context).map(JsValue::from)
}
// `10.5.14 ProxyCreate ( target, handler )`
@ -91,7 +90,11 @@ impl Proxy {
// - [ECMAScript reference][spec]
//
// [spec]: https://tc39.es/ecma262/#sec-proxycreate
fn create(target: &JsValue, handler: &JsValue, context: &mut Context) -> JsResult<JsValue> {
pub(crate) fn create(
target: &JsValue,
handler: &JsValue,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. If Type(target) is not Object, throw a TypeError exception.
let target = target.as_object().ok_or_else(|| {
context.construct_type_error("Proxy constructor called with non-object target")
@ -120,52 +123,49 @@ impl Proxy {
);
// 8. Return P.
Ok(p.into())
Ok(p)
}
/// `28.2.2.1 Proxy.revocable ( target, handler )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable
fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let p be ? ProxyCreate(target, handler).
let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?;
pub(crate) fn revoker(proxy: JsObject, context: &mut Context) -> JsFunction {
// 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »).
// 4. Set revoker.[[RevocableProxy]] to p.
let revoker = FunctionBuilder::closure_with_captures(
FunctionBuilder::closure_with_captures(
context,
|_, _, revocable_proxy, _| {
// a. Let F be the active function object.
// b. Let p be F.[[RevocableProxy]].
// c. If p is null, return undefined.
if revocable_proxy.is_null() {
return Ok(JsValue::undefined());
}
let p = revocable_proxy
.as_object()
.expect("[[RevocableProxy]] must be an object or null");
// e. Assert: p is a Proxy object.
// f. Set p.[[ProxyTarget]] to null.
// g. Set p.[[ProxyHandler]] to null.
p.borrow_mut()
.as_proxy_mut()
.expect("[[RevocableProxy]] must be a proxy object")
.data = None;
// d. Set F.[[RevocableProxy]] to null.
*revocable_proxy = JsValue::Null;
if let Some(p) = revocable_proxy.take() {
// e. Assert: p is a Proxy object.
// f. Set p.[[ProxyTarget]] to null.
// g. Set p.[[ProxyHandler]] to null.
p.borrow_mut()
.as_proxy_mut()
.expect("[[RevocableProxy]] must be a proxy object")
.data = None;
}
// c. If p is null, return undefined.
// h. Return undefined.
Ok(JsValue::undefined())
},
p.clone(),
Some(proxy),
)
.build();
.build()
}
/// `28.2.2.1 Proxy.revocable ( target, handler )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-proxy.revocable
fn revocable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// 1. Let p be ? ProxyCreate(target, handler).
let p = Self::create(args.get_or_undefined(0), args.get_or_undefined(1), context)?;
// Revoker creation steps on `Proxy::revoker`
let revoker = Self::revoker(p.clone(), context);
// 5. Let result be ! OrdinaryObjectCreate(%Object.prototype%).
let result = context.construct_object();

1
boa_engine/src/object/internal_methods/bound_function.rs

@ -11,7 +11,6 @@ use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(bound_function_exotic_call),
__construct__: None,
..ORDINARY_INTERNAL_METHODS
};

25
boa_engine/src/object/internal_methods/proxy.rs

@ -32,36 +32,15 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods =
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods =
InternalObjectMethods {
__get_prototype_of__: proxy_exotic_get_prototype_of,
__set_prototype_of__: proxy_exotic_set_prototype_of,
__is_extensible__: proxy_exotic_is_extensible,
__prevent_extensions__: proxy_exotic_prevent_extensions,
__get_own_property__: proxy_exotic_get_own_property,
__define_own_property__: proxy_exotic_define_own_property,
__has_property__: proxy_exotic_has_property,
__get__: proxy_exotic_get,
__set__: proxy_exotic_set,
__delete__: proxy_exotic_delete,
__own_property_keys__: proxy_exotic_own_property_keys,
__call__: Some(proxy_exotic_call),
__construct__: None,
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
};
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods =
InternalObjectMethods {
__get_prototype_of__: proxy_exotic_get_prototype_of,
__set_prototype_of__: proxy_exotic_set_prototype_of,
__is_extensible__: proxy_exotic_is_extensible,
__prevent_extensions__: proxy_exotic_prevent_extensions,
__get_own_property__: proxy_exotic_get_own_property,
__define_own_property__: proxy_exotic_define_own_property,
__has_property__: proxy_exotic_has_property,
__get__: proxy_exotic_get,
__set__: proxy_exotic_set,
__delete__: proxy_exotic_delete,
__own_property_keys__: proxy_exotic_own_property_keys,
__call__: Some(proxy_exotic_call),
__construct__: Some(proxy_exotic_construct),
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
};
/// `10.5.1 [[GetPrototypeOf]] ( )`

477
boa_engine/src/object/jsproxy.rs

@ -0,0 +1,477 @@
use boa_gc::{Finalize, Trace};
use crate::{
builtins::{function::NativeFunctionSignature, Proxy},
Context, JsResult, JsValue,
};
use super::{FunctionBuilder, JsFunction, JsObject, JsObjectType, ObjectData};
/// JavaScript [`Proxy`][proxy] rust object.
///
/// This is a wrapper type for the [`Proxy`][proxy] API that allows customizing
/// essential behaviour for an object, like [property accesses][get] or the
/// [`delete`][delete] operator.
///
/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
/// accessible from [`JsProxy::builder`].
///
/// [get]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
/// [delete]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
/// [proxy]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsProxy {
inner: JsObject,
}
impl JsProxy {
pub fn builder(target: JsObject) -> JsProxyBuilder {
JsProxyBuilder::new(target)
}
}
impl From<JsProxy> for JsObject {
#[inline]
fn from(o: JsProxy) -> Self {
o.inner.clone()
}
}
impl From<JsProxy> for JsValue {
#[inline]
fn from(o: JsProxy) -> Self {
o.inner.clone().into()
}
}
impl std::ops::Deref for JsProxy {
type Target = JsObject;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl JsObjectType for JsProxy {}
/// JavaScript [`Proxy`][proxy] rust object that can be disabled.
///
/// Safe interface for the [`Proxy.revocable`][revocable] method that creates a
/// proxy that can be disabled using the [`JsRevocableProxy::revoke`] method.
/// The internal proxy is accessible using the [`Deref`][`std::ops::Deref`] operator.
///
/// The only way to construct this type is to use the [`JsProxyBuilder`] type; also
/// accessible from [`JsProxy::builder`]; with the [`JsProxyBuilder::build_revocable`]
/// method.
///
/// [revocable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/revocable
#[derive(Debug, Trace, Finalize)]
pub struct JsRevocableProxy {
proxy: JsProxy,
revoker: JsFunction,
}
impl JsRevocableProxy {
/// Disables the traps of the internal `proxy` object, essentially
/// making it unusable and throwing `TypeError`s for all the traps.
pub fn revoke(self, context: &mut Context) -> JsResult<()> {
self.revoker.call(&JsValue::undefined(), &[], context)?;
Ok(())
}
}
impl std::ops::Deref for JsRevocableProxy {
type Target = JsProxy;
#[inline]
fn deref(&self) -> &Self::Target {
&self.proxy
}
}
/// Utility builder to create [`JsProxy`] objects from native functions.
///
/// This builder can be used when you need to create [`Proxy`] objects
/// from Rust instead of Javascript, which should generate faster
/// trap functions than its Javascript counterparts.
#[must_use]
#[derive(Clone)]
pub struct JsProxyBuilder {
target: JsObject,
apply: Option<NativeFunctionSignature>,
construct: Option<NativeFunctionSignature>,
define_property: Option<NativeFunctionSignature>,
delete_property: Option<NativeFunctionSignature>,
get: Option<NativeFunctionSignature>,
get_own_property_descriptor: Option<NativeFunctionSignature>,
get_prototype_of: Option<NativeFunctionSignature>,
has: Option<NativeFunctionSignature>,
is_extensible: Option<NativeFunctionSignature>,
own_keys: Option<NativeFunctionSignature>,
prevent_extensions: Option<NativeFunctionSignature>,
set: Option<NativeFunctionSignature>,
set_prototype_of: Option<NativeFunctionSignature>,
}
impl std::fmt::Debug for JsProxyBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[derive(Debug)]
struct NativeFunction;
f.debug_struct("ProxyBuilder")
.field("target", &self.target)
.field("apply", &self.apply.map(|_| NativeFunction))
.field("construct", &self.construct.map(|_| NativeFunction))
.field(
"define_property",
&self.define_property.map(|_| NativeFunction),
)
.field(
"delete_property",
&self.delete_property.map(|_| NativeFunction),
)
.field("get", &self.get.map(|_| NativeFunction))
.field(
"get_own_property_descriptor",
&self.get_own_property_descriptor.map(|_| NativeFunction),
)
.field(
"get_prototype_of",
&self.get_prototype_of.map(|_| NativeFunction),
)
.field("has", &self.has.map(|_| NativeFunction))
.field("is_extensible", &self.is_extensible.map(|_| NativeFunction))
.field("own_keys", &self.own_keys.map(|_| NativeFunction))
.field(
"prevent_extensions",
&self.prevent_extensions.map(|_| NativeFunction),
)
.field("set", &self.set.map(|_| NativeFunction))
.field(
"set_prototype_of",
&self.set_prototype_of.map(|_| NativeFunction),
)
.finish()
}
}
impl JsProxyBuilder {
/// Create a new `ProxyBuilder` with every trap set to `undefined`.
pub fn new(target: JsObject) -> Self {
Self {
target,
apply: None,
construct: None,
define_property: None,
delete_property: None,
get: None,
get_own_property_descriptor: None,
get_prototype_of: None,
has: None,
is_extensible: None,
own_keys: None,
prevent_extensions: None,
set: None,
set_prototype_of: None,
}
}
/// Set the `apply` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// # Note
///
/// If the `target` object is not a function, then `apply` will be ignored
/// when trying to call the proxy, which will throw a type error.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
pub fn apply(mut self, apply: NativeFunctionSignature) -> Self {
self.apply = Some(apply);
self
}
/// Set the `construct` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// # Note
///
/// If the `target` object is not a constructor, then `construct` will be ignored
/// when trying to construct an object using the proxy, which will throw a type error.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct
pub fn construct(mut self, construct: NativeFunctionSignature) -> Self {
self.construct = Some(construct);
self
}
/// Set the `defineProperty` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty
pub fn define_property(mut self, define_property: NativeFunctionSignature) -> Self {
self.define_property = Some(define_property);
self
}
/// Set the `deleteProperty` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
pub fn delete_property(mut self, delete_property: NativeFunctionSignature) -> Self {
self.delete_property = Some(delete_property);
self
}
/// Set the `get` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
pub fn get(mut self, get: NativeFunctionSignature) -> Self {
self.get = Some(get);
self
}
/// Set the `getOwnPropertyDescriptor` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor
pub fn get_own_property_descriptor(
mut self,
get_own_property_descriptor: NativeFunctionSignature,
) -> Self {
self.get_own_property_descriptor = Some(get_own_property_descriptor);
self
}
/// Set the `getPrototypeOf` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf
pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionSignature) -> Self {
self.get_prototype_of = Some(get_prototype_of);
self
}
/// Set the `has` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has
pub fn has(mut self, has: NativeFunctionSignature) -> Self {
self.has = Some(has);
self
}
/// Set the `isExtensible` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible
pub fn is_extensible(mut self, is_extensible: NativeFunctionSignature) -> Self {
self.is_extensible = Some(is_extensible);
self
}
/// Set the `ownKeys` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
pub fn own_keys(mut self, own_keys: NativeFunctionSignature) -> Self {
self.own_keys = Some(own_keys);
self
}
/// Set the `preventExtensions` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions
pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionSignature) -> Self {
self.prevent_extensions = Some(prevent_extensions);
self
}
/// Set the `set` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set
pub fn set(mut self, set: NativeFunctionSignature) -> Self {
self.set = Some(set);
self
}
/// Set the `setPrototypeOf` proxy trap to the specified native function.
///
/// More information:
///
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf
pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionSignature) -> Self {
self.set_prototype_of = Some(set_prototype_of);
self
}
/// Build a [`JsObject`] of kind [`Proxy`].
///
/// Equivalent to the `Proxy ( target, handler )` constructor, but returns a
/// [`JsObject`] in case there's a need to manipulate the returned object
/// inside Rust code.
#[must_use]
pub fn build(self, context: &mut Context) -> JsProxy {
let handler = context.construct_object();
if let Some(apply) = self.apply {
let f = FunctionBuilder::native(context, apply).length(3).build();
handler
.create_data_property_or_throw("apply", f, context)
.expect("new object should be writable");
}
if let Some(construct) = self.construct {
let f = FunctionBuilder::native(context, construct)
.length(3)
.build();
handler
.create_data_property_or_throw("construct", f, context)
.expect("new object should be writable");
}
if let Some(define_property) = self.define_property {
let f = FunctionBuilder::native(context, define_property)
.length(3)
.build();
handler
.create_data_property_or_throw("defineProperty", f, context)
.expect("new object should be writable");
}
if let Some(delete_property) = self.delete_property {
let f = FunctionBuilder::native(context, delete_property)
.length(2)
.build();
handler
.create_data_property_or_throw("deleteProperty", f, context)
.expect("new object should be writable");
}
if let Some(get) = self.get {
let f = FunctionBuilder::native(context, get).length(3).build();
handler
.create_data_property_or_throw("get", f, context)
.expect("new object should be writable");
}
if let Some(get_own_property_descriptor) = self.get_own_property_descriptor {
let f = FunctionBuilder::native(context, get_own_property_descriptor)
.length(2)
.build();
handler
.create_data_property_or_throw("getOwnPropertyDescriptor", f, context)
.expect("new object should be writable");
}
if let Some(get_prototype_of) = self.get_prototype_of {
let f = FunctionBuilder::native(context, get_prototype_of)
.length(1)
.build();
handler
.create_data_property_or_throw("getPrototypeOf", f, context)
.expect("new object should be writable");
}
if let Some(has) = self.has {
let f = FunctionBuilder::native(context, has).length(2).build();
handler
.create_data_property_or_throw("has", f, context)
.expect("new object should be writable");
}
if let Some(is_extensible) = self.is_extensible {
let f = FunctionBuilder::native(context, is_extensible)
.length(1)
.build();
handler
.create_data_property_or_throw("isExtensible", f, context)
.expect("new object should be writable");
}
if let Some(own_keys) = self.own_keys {
let f = FunctionBuilder::native(context, own_keys).length(1).build();
handler
.create_data_property_or_throw("ownKeys", f, context)
.expect("new object should be writable");
}
if let Some(prevent_extensions) = self.prevent_extensions {
let f = FunctionBuilder::native(context, prevent_extensions)
.length(1)
.build();
handler
.create_data_property_or_throw("preventExtensions", f, context)
.expect("new object should be writable");
}
if let Some(set) = self.set {
let f = FunctionBuilder::native(context, set).length(4).build();
handler
.create_data_property_or_throw("set", f, context)
.expect("new object should be writable");
}
if let Some(set_prototype_of) = self.set_prototype_of {
let f = FunctionBuilder::native(context, set_prototype_of)
.length(2)
.build();
handler
.create_data_property_or_throw("setPrototypeOf", f, context)
.expect("new object should be writable");
}
let callable = self.target.is_callable();
let constructor = self.target.is_constructor();
let proxy = JsObject::from_proto_and_data(
context.intrinsics().constructors().object().prototype(),
ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor),
);
JsProxy { inner: proxy }
}
/// Builds a [`JsObject`] of kind [`Proxy`] and a [`JsFunction`] that, when
/// called, disables the proxy of the object.
///
/// Equivalent to the `Proxy.revocable ( target, handler )` static method,
/// but returns a [`JsObject`] for the proxy and a [`JsFunction`] for the
/// revoker in case there's a need to manipulate the returned objects
/// inside Rust code.
#[must_use]
pub fn build_revocable(self, context: &mut Context) -> JsRevocableProxy {
let proxy = self.build(context);
let revoker = Proxy::revoker(proxy.inner.clone(), context);
JsRevocableProxy { proxy, revoker }
}
}

2
boa_engine/src/object/mod.rs

@ -61,12 +61,14 @@ pub(crate) mod internal_methods;
mod jsarray;
mod jsfunction;
mod jsobject;
mod jsproxy;
mod jstypedarray;
mod operations;
mod property_map;
pub use jsarray::*;
pub use jsfunction::*;
pub use jsproxy::*;
pub use jstypedarray::*;
pub(crate) trait JsObjectType:

2
boa_engine/src/string.rs

@ -626,7 +626,7 @@ impl Inner {
/// The `JsString` length and data is stored on the heap. and just an non-null
/// pointer is kept, so its size is the size of a pointer.
///
/// We define some commonly used string constants in [`CONSTANTS_ARRAY`]. For these
/// We define some commonly used string constants in an interner. For these
/// strings, we no longer allocate memory on the heap to reduce the overhead of
/// memory allocation and reference counting.
#[derive(Finalize)]

Loading…
Cancel
Save