-
-
Notifications
You must be signed in to change notification settings - Fork 413
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial version of a JS <-> Rust conversion trait.
- Loading branch information
Showing
12 changed files
with
369 additions
and
5 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "boa_derive" | ||
version = "0.16.0" | ||
edition = "2021" | ||
rust-version = "1.60" | ||
authors = ["boa-dev"] | ||
description = "Ths crate adds derive macros for the main boa_engine crate." | ||
repository = "https://github.com/boa-dev/boa" | ||
license = "Unlicense/MIT" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
syn = "1.0.99" | ||
quote = "1.0.21" | ||
proc-macro2 = "1.0.43" | ||
|
||
[dev-dependencies] | ||
trybuild = "1.0.64" | ||
boa_engine = { path = "../boa_engine" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
use proc_macro::TokenStream; | ||
use quote::quote; | ||
use syn::{parse_macro_input, Data, DeriveInput, Fields, FieldsNamed}; | ||
|
||
#[proc_macro_derive(TryFromJs)] | ||
pub fn derive_try_from_js(input: TokenStream) -> TokenStream { | ||
// Parse the input tokens into a syntax tree | ||
let input = parse_macro_input!(input as DeriveInput); | ||
|
||
let data = if let Data::Struct(data) = input.data { | ||
data | ||
} else { | ||
panic!("you can only derive TryFromJs for structs"); | ||
}; | ||
|
||
let fields = if let Fields::Named(fields) = data.fields { | ||
fields | ||
} else { | ||
panic!("you can only derive TryFromJs for named-field structs") | ||
}; | ||
|
||
let conv = generate_conversion(fields); | ||
|
||
let type_name = input.ident; | ||
|
||
// Build the output, possibly using quasi-quotation | ||
let expanded = quote! { | ||
impl boa_engine::value::conversions::try_from_js::TryFromJs for #type_name { | ||
fn try_from_js(value: &boa_engine::JsValue, context: &mut boa_engine::Context) -> boa_engine::JsResult<Self> { | ||
match value { | ||
boa_engine::JsValue::Object(o) => {#conv}, | ||
_ => context.throw_type_error("cannot convert value to a #type_name"), | ||
} | ||
} | ||
} | ||
}; | ||
|
||
// Hand the output tokens back to the compiler | ||
expanded.into() | ||
} | ||
|
||
fn generate_conversion(fields: FieldsNamed) -> proc_macro2::TokenStream { | ||
let mut field_list = Vec::with_capacity(fields.named.len()); | ||
|
||
let fields = fields.named.into_iter().map(|field| { | ||
let name = field | ||
.ident | ||
.expect("you can only derive TryFromJs for named-field structs"); | ||
let name_str = format!("{name}"); | ||
field_list.push(name.clone()); | ||
|
||
let error_str = format!("cannot get property {name_str} of value"); | ||
|
||
quote! { | ||
let #name = props.get(&#name_str.into()).ok_or_else(|| { | ||
context.construct_type_error(#error_str) | ||
})?.value().ok_or_else(|| { | ||
context.construct_type_error(#error_str) | ||
})?.clone().try_js_into(context)?; | ||
} | ||
}); | ||
|
||
quote! { | ||
let o = o.borrow(); | ||
let props = o.properties(); | ||
#(#fields)* | ||
Ok(Self { | ||
#(#field_list),* | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
use boa_derive::TryFromJs; | ||
|
||
#[derive(TryFromJs)] | ||
struct TestStruct { | ||
inner: bool, | ||
} | ||
|
||
fn main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#[test] | ||
fn test() { | ||
let t = trybuild::TestCases::new(); | ||
t.pass("tests/derive/simple_struct.rs"); | ||
} |
3 changes: 3 additions & 0 deletions
3
boa_engine/src/value/conversions.rs → boa_engine/src/value/conversions/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
use crate::{Context, JsBigInt, JsResult, JsValue}; | ||
use num_bigint::BigInt; | ||
|
||
/// This trait adds a fallible and efficient conversions from a [`JsValue`] to Rust types. | ||
pub trait TryFromJs: Sized { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self>; | ||
} | ||
|
||
impl JsValue { | ||
/// This function is the inverse of [`TryFromJs`]. It tries to convert a [`JsValue`] to a given | ||
/// Rust type. | ||
pub fn try_js_into<T>(&self, context: &mut Context) -> JsResult<T> | ||
where | ||
T: TryFromJs, | ||
{ | ||
T::try_from_js(self, context) | ||
} | ||
} | ||
|
||
impl TryFromJs for bool { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Boolean(b) => Ok(*b), | ||
_ => context.throw_type_error("cannot convert value to a boolean"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for String { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::String(s) => Ok(s.as_str().to_owned()), | ||
_ => context.throw_type_error("cannot convert value to a String"), | ||
} | ||
} | ||
} | ||
|
||
impl<T> TryFromJs for Option<T> | ||
where | ||
T: TryFromJs, | ||
{ | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Null | JsValue::Undefined => Ok(None), | ||
value => Ok(Some(T::try_from_js(value, context)?)), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for JsBigInt { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::BigInt(b) => Ok(b.clone()), | ||
_ => context.throw_type_error("cannot convert value to a BigInt"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for BigInt { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::BigInt(b) => Ok(b.as_inner().clone()), | ||
_ => context.throw_type_error("cannot convert value to a BigInt"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for JsValue { | ||
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> { | ||
Ok(value.clone()) | ||
} | ||
} | ||
|
||
impl TryFromJs for f64 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => Ok((*i).into()), | ||
JsValue::Rational(r) => Ok(*r), | ||
_ => context.throw_type_error("cannot convert value to a f64"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for i8 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => (*i).try_into().map_err(|e| { | ||
context.construct_type_error(format!("cannot convert value to a i8: {e}")) | ||
}), | ||
_ => context.throw_type_error("cannot convert value to a i8"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for u8 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => (*i).try_into().map_err(|e| { | ||
context.construct_type_error(format!("cannot convert value to a u8: {e}")) | ||
}), | ||
_ => context.throw_type_error("cannot convert value to a u8"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for i16 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => (*i).try_into().map_err(|e| { | ||
context.construct_type_error(format!("cannot convert value to a i16: {e}")) | ||
}), | ||
_ => context.throw_type_error("cannot convert value to a i16"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for u16 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => (*i).try_into().map_err(|e| { | ||
context.construct_type_error(format!("cannot convert value to a iu16: {e}")) | ||
}), | ||
_ => context.throw_type_error("cannot convert value to a u16"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for i32 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => Ok(*i), | ||
_ => context.throw_type_error("cannot convert value to a i32"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for u32 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => (*i).try_into().map_err(|e| { | ||
context.construct_type_error(format!("cannot convert value to a u32: {e}")) | ||
}), | ||
_ => context.throw_type_error("cannot convert value to a u32"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for i64 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => Ok((*i).into()), | ||
_ => context.throw_type_error("cannot convert value to a i64"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for u64 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => (*i).try_into().map_err(|e| { | ||
context.construct_type_error(format!("cannot convert value to a u64: {e}")) | ||
}), | ||
_ => context.throw_type_error("cannot convert value to a u64"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for i128 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => Ok((*i).into()), | ||
_ => context.throw_type_error("cannot convert value to a i128"), | ||
} | ||
} | ||
} | ||
|
||
impl TryFromJs for u128 { | ||
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> { | ||
match value { | ||
JsValue::Integer(i) => (*i).try_into().map_err(|e| { | ||
context.construct_type_error(format!("cannot convert value to a u128: {e}")) | ||
}), | ||
_ => context.throw_type_error("cannot convert value to a u128"), | ||
} | ||
} | ||
} |
Oops, something went wrong.