ion/
symbol.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use std::mem::transmute;
8use std::ops::{Deref, DerefMut};
9
10use mozjs::jsapi::{
11	GetSymbolCode, GetSymbolDescription, GetSymbolFor, GetWellKnownSymbol, NewSymbol, Symbol as JSSymbol,
12	SymbolCode as JSSymbolCode,
13};
14
15use crate::conversions::{FromValue as _, ToValue as _};
16use crate::{Context, Local};
17
18/// Represents a well-known symbol code.
19///
20/// Each of these refer to a property on the `Symbol` global object.
21/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#static_properties) for more details.
22#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
23#[repr(u32)]
24pub enum WellKnownSymbolCode {
25	IsConcatSpreadable,
26	Iterator,
27	Match,
28	Replace,
29	Search,
30	Species,
31	HasInstance,
32	Split,
33	ToPrimitive,
34	ToStringTag,
35	Unscopables,
36	AsyncIterator,
37	MatchAll,
38}
39
40/// Represents the code of a [Symbol].
41/// The code can be a [WellKnownSymbolCode], a private name symbol, a symbol within the registry, or a unique symbol.
42#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
43pub enum SymbolCode {
44	WellKnown(WellKnownSymbolCode),
45	PrivateNameSymbol,
46	InSymbolRegistry,
47	UniqueSymbol,
48}
49
50impl WellKnownSymbolCode {
51	/// Converts a [WellKnownSymbolCode] into its corresponding identifier.
52	/// These identifiers refer to the property names on the `Symbol` global object.
53	pub const fn identifier(&self) -> &'static str {
54		use WellKnownSymbolCode as WKSC;
55		match self {
56			WKSC::IsConcatSpreadable => "isConcatSpreadable",
57			WKSC::Iterator => "iterator",
58			WKSC::Match => "match",
59			WKSC::Replace => "replace",
60			WKSC::Search => "search",
61			WKSC::Species => "species",
62			WKSC::HasInstance => "hasInstance",
63			WKSC::Split => "split",
64			WKSC::ToPrimitive => "toPrimitive",
65			WKSC::ToStringTag => "toStringTag",
66			WKSC::Unscopables => "unscopables",
67			WKSC::AsyncIterator => "asyncIterator",
68			WKSC::MatchAll => "matchAll",
69		}
70	}
71}
72
73impl SymbolCode {
74	/// Checks if a [SymbolCode] is a well-known symbol code.
75	pub fn well_known(&self) -> Option<WellKnownSymbolCode> {
76		if let SymbolCode::WellKnown(code) = self {
77			Some(*code)
78		} else {
79			None
80		}
81	}
82}
83
84impl From<JSSymbolCode> for SymbolCode {
85	fn from(code: JSSymbolCode) -> SymbolCode {
86		if (code as u32) < JSSymbolCode::Limit as u32 {
87			SymbolCode::WellKnown(unsafe { transmute::<JSSymbolCode, WellKnownSymbolCode>(code) })
88		} else {
89			use JSSymbolCode as JSSC;
90			match code {
91				JSSC::PrivateNameSymbol => SymbolCode::PrivateNameSymbol,
92				JSSC::InSymbolRegistry => SymbolCode::InSymbolRegistry,
93				JSSC::UniqueSymbol => SymbolCode::UniqueSymbol,
94				_ => unreachable!(),
95			}
96		}
97	}
98}
99
100impl From<WellKnownSymbolCode> for SymbolCode {
101	fn from(code: WellKnownSymbolCode) -> SymbolCode {
102		SymbolCode::WellKnown(code)
103	}
104}
105
106impl From<WellKnownSymbolCode> for JSSymbolCode {
107	fn from(code: WellKnownSymbolCode) -> Self {
108		unsafe { transmute(code) }
109	}
110}
111
112impl From<SymbolCode> for JSSymbolCode {
113	fn from(code: SymbolCode) -> JSSymbolCode {
114		use JSSymbolCode as JSSC;
115		match code {
116			SymbolCode::WellKnown(code) => code.into(),
117			SymbolCode::PrivateNameSymbol => JSSC::PrivateNameSymbol,
118			SymbolCode::InSymbolRegistry => JSSC::InSymbolRegistry,
119			SymbolCode::UniqueSymbol => JSSC::UniqueSymbol,
120		}
121	}
122}
123
124/// Represents a symbol in the JavaScript Runtime.
125/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) for more details.
126#[derive(Debug)]
127pub struct Symbol<'s> {
128	sym: Local<'s, *mut JSSymbol>,
129}
130
131impl Symbol<'_> {
132	/// Creates a new unique symbol with a given description.
133	pub fn new<'cx>(cx: &'cx Context, description: &str) -> Symbol<'cx> {
134		let description = description.as_value(cx);
135		let description = cx.root(description.handle().to_string());
136
137		let symbol = unsafe { NewSymbol(cx.as_ptr(), description.handle().into()) };
138		Symbol { sym: cx.root(symbol) }
139	}
140
141	/// Gets a [Symbol] from the symbol registry with the given key.
142	pub fn for_key<'cx>(cx: &'cx Context, key: &str) -> Symbol<'cx> {
143		let key = key.as_value(cx);
144		let key = cx.root(key.handle().to_string());
145
146		let symbol = unsafe { GetSymbolFor(cx.as_ptr(), key.handle().into()) };
147		Symbol { sym: cx.root(symbol) }
148	}
149
150	/// Creates a well-known symbol with its corresponding code.
151	pub fn well_known(cx: &Context, code: WellKnownSymbolCode) -> Symbol<'_> {
152		let symbol = unsafe { GetWellKnownSymbol(cx.as_ptr(), code.into()) };
153		Symbol { sym: cx.root(symbol) }
154	}
155
156	/// Returns the identifying code of a [Symbol].
157	pub fn code(&self) -> SymbolCode {
158		unsafe { GetSymbolCode(self.sym.handle().into()).into() }
159	}
160
161	/// Returns the description of a [Symbol].
162	/// Returns [None] for well-known symbols.
163	pub fn description(&self, cx: &Context) -> Option<String> {
164		let description = unsafe { GetSymbolDescription(self.sym.handle().into()) };
165		if !description.is_null() {
166			let description = description.as_value(cx);
167			String::from_value(cx, &description, true, ()).ok()
168		} else {
169			None
170		}
171	}
172}
173
174impl<'o> From<Local<'o, *mut JSSymbol>> for Symbol<'o> {
175	fn from(sym: Local<'o, *mut JSSymbol>) -> Symbol<'o> {
176		Symbol { sym }
177	}
178}
179
180impl<'s> Deref for Symbol<'s> {
181	type Target = Local<'s, *mut JSSymbol>;
182
183	fn deref(&self) -> &Self::Target {
184		&self.sym
185	}
186}
187
188impl DerefMut for Symbol<'_> {
189	fn deref_mut(&mut self) -> &mut Self::Target {
190		&mut self.sym
191	}
192}
193
194#[cfg(test)]
195mod tests {
196	use mozjs::jsapi::SymbolCode as JSSymbolCode;
197
198	use crate::symbol::{SymbolCode, WellKnownSymbolCode};
199
200	macro_rules! convert_codes {
201		($(($js:expr, $native:expr)$(,)?)*) => {
202			$(
203				assert_eq!($js, JSSymbolCode::from($native));
204				assert_eq!($native, SymbolCode::from($js));
205			)*
206		}
207	}
208
209	#[test]
210	fn code_conversion() {
211		use {JSSymbolCode as JSSC, SymbolCode as SC, WellKnownSymbolCode as WKSC};
212
213		// Well Known Symbol Codes
214		convert_codes! {
215			(JSSC::isConcatSpreadable, SC::WellKnown(WKSC::IsConcatSpreadable)),
216			(JSSC::iterator, SC::WellKnown(WKSC::Iterator)),
217			(JSSC::match_, SC::WellKnown(WKSC::Match)),
218			(JSSC::replace, SC::WellKnown(WKSC::Replace)),
219			(JSSC::search, SC::WellKnown(WKSC::Search)),
220			(JSSC::species, SC::WellKnown(WKSC::Species)),
221			(JSSC::hasInstance, SC::WellKnown(WKSC::HasInstance)),
222			(JSSC::toPrimitive, SC::WellKnown(WKSC::ToPrimitive)),
223			(JSSC::toStringTag, SC::WellKnown(WKSC::ToStringTag)),
224			(JSSC::unscopables, SC::WellKnown(WKSC::Unscopables)),
225			(JSSC::asyncIterator, SC::WellKnown(WKSC::AsyncIterator)),
226			(JSSC::matchAll, SC::WellKnown(WKSC::MatchAll)),
227		}
228
229		// Other Symbol Codes
230		convert_codes! {
231			(JSSC::PrivateNameSymbol, SC::PrivateNameSymbol),
232			(JSSC::InSymbolRegistry, SC::InSymbolRegistry),
233			(JSSC::UniqueSymbol, SC::UniqueSymbol),
234		}
235	}
236}