From 147fd07dec85018473d8298d7f09e3f2030f848c Mon Sep 17 00:00:00 2001 From: crowlkats Date: Mon, 16 Dec 2024 17:56:21 +0100 Subject: [PATCH 01/33] feat: WebIDL derive macro --- core/lib.rs | 4 +- core/webidl.rs | 154 ++++++++++++++++++++++++++ ops/lib.rs | 9 ++ ops/op2/dispatch_fast.rs | 6 +- ops/op2/dispatch_slow.rs | 55 +++++++--- ops/op2/signature.rs | 9 +- ops/webidl/mod.rs | 229 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 448 insertions(+), 18 deletions(-) create mode 100644 core/webidl.rs create mode 100644 ops/webidl/mod.rs diff --git a/core/lib.rs b/core/lib.rs index 68e02d6d1..eb5048956 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -32,9 +32,11 @@ mod runtime; mod source_map; mod tasks; mod web_timeout; +pub mod webidl; // Re-exports pub use anyhow; +pub use deno_ops::op2; pub use deno_unsync as unsync; pub use futures; pub use parking_lot; @@ -51,8 +53,6 @@ pub use sourcemap; pub use url; pub use v8; -pub use deno_ops::op2; - pub use crate::async_cancel::CancelFuture; pub use crate::async_cancel::CancelHandle; pub use crate::async_cancel::CancelTryFuture; diff --git a/core/webidl.rs b/core/webidl.rs new file mode 100644 index 000000000..b23596475 --- /dev/null +++ b/core/webidl.rs @@ -0,0 +1,154 @@ +use std::borrow::Cow; +use v8::HandleScope; +use v8::Local; +use v8::Value; + +#[derive(Debug)] +pub struct WebIdlError { + pub prefix: Cow<'static, str>, + pub context: Cow<'static, str>, + pub kind: WebIdlErrorKind, +} + +impl WebIdlError { + pub fn new( + prefix: Cow<'static, str>, + context: Cow<'static, str>, + kind: WebIdlErrorKind, + ) -> Self { + Self { + prefix, + context, + kind, + } + } +} + +impl std::fmt::Display for WebIdlError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.prefix, self.context)?; + + match &self.kind { + WebIdlErrorKind::ConvertToConverterType(kind) => { + write!(f, "can not be converted to a {kind}") + } + WebIdlErrorKind::DictionaryCannotConvertKey { converter, key } => { + write!( + f, + "can not be converted to '{converter}' because '{key}' is required in '{converter}'", + ) + } + WebIdlErrorKind::Other(other) => std::fmt::Display::fmt(other, f), + } + } +} + +impl std::error::Error for WebIdlError {} + +#[derive(Debug)] +pub enum WebIdlErrorKind { + ConvertToConverterType(&'static str), + DictionaryCannotConvertKey { + converter: &'static str, + key: &'static str, + }, + Other(Box), +} + +pub trait WebIdlConverter<'a>: Sized { + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: Cow<'static, str>, + ) -> Result; +} + +impl<'a, T: crate::FromV8<'a>> WebIdlConverter<'a> for T { + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: Cow<'static, str>, + ) -> Result { + Self::from_v8(scope, value).map_err(|e| { + WebIdlError::new(prefix, context, WebIdlErrorKind::Other(Box::new(e))) + }) + } +} + +impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: Cow<'static, str>, + ) -> Result { + let Some(obj) = value.to_object(scope) else { + return Err(WebIdlError::new( + prefix, + context, + WebIdlErrorKind::ConvertToConverterType("sequence"), + )); + }; + + let iter_key = v8::Symbol::get_iterator(scope); + let Some(iter) = obj + .get(scope, iter_key.into()) + .and_then(|iter| iter.try_cast::().ok()) + .and_then(|iter| iter.call(scope, obj.cast(), &[])) + .and_then(|iter| iter.to_object(scope)) + else { + return Err(WebIdlError::new( + prefix, + context, + WebIdlErrorKind::ConvertToConverterType("sequence"), + )); + }; + + let mut out = vec![]; + + let next_key = v8::String::new(scope, "next").unwrap(); + let done_key = v8::String::new(scope, "done").unwrap(); + let value_key = v8::String::new(scope, "value").unwrap(); + + loop { + let Some(res) = iter + .get(scope, next_key.into()) + .and_then(|next| next.try_cast::().ok()) + .and_then(|next| next.call(scope, iter.cast(), &[])) + .and_then(|res| res.to_object(scope)) + else { + return Err(WebIdlError::new( + prefix, + context, + WebIdlErrorKind::ConvertToConverterType("sequence"), + )); + }; + + if res + .get(scope, done_key.into()) + .is_some_and(|val| val.is_true()) + { + break; + } + + let Some(iter_val) = res.get(scope, value_key.into()) else { + return Err(WebIdlError::new( + prefix, + context, + WebIdlErrorKind::ConvertToConverterType("sequence"), + )); + }; + + out.push(WebIdlConverter::convert( + scope, + iter_val, + prefix.clone(), + format!("{context}, index {}", out.len()).into(), + )?); + } + + Ok(out) + } +} diff --git a/ops/lib.rs b/ops/lib.rs index 2b7d4c9cb..de652c1ce 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -5,6 +5,7 @@ use proc_macro::TokenStream; use std::error::Error; mod op2; +mod webidl; /// A macro designed to provide an extremely fast V8->Rust interface layer. #[doc = include_str!("op2/README.md")] @@ -31,3 +32,11 @@ fn op2_macro(attr: TokenStream, item: TokenStream) -> TokenStream { } } } + +#[proc_macro_derive(WebIDL, attributes(webidl))] +pub fn webidl(item: TokenStream) -> TokenStream { + match webidl::webidl(item.into()) { + Ok(output) => output.into(), + Err(err) => err.into_compile_error().into(), + } +} diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs index c8fb19ede..02dfa7c18 100644 --- a/ops/op2/dispatch_fast.rs +++ b/ops/op2/dispatch_fast.rs @@ -903,6 +903,7 @@ fn map_arg_to_v8_fastcall_type( | Arg::OptionBuffer(..) | Arg::SerdeV8(_) | Arg::FromV8(_) + | Arg::WebIDL(_) | Arg::Ref(..) => return Ok(None), // We don't support v8 global arguments Arg::V8Global(_) | Arg::OptionV8Global(_) => return Ok(None), @@ -953,7 +954,10 @@ fn map_retval_to_v8_fastcall_type( arg: &Arg, ) -> Result, V8MappingError> { let rv = match arg { - Arg::OptionNumeric(..) | Arg::SerdeV8(_) | Arg::ToV8(_) => return Ok(None), + Arg::OptionNumeric(..) + | Arg::SerdeV8(_) + | Arg::ToV8(_) + | Arg::WebIDL(_) => return Ok(None), Arg::Void => V8FastCallType::Void, Arg::Numeric(NumericArg::bool, _) => V8FastCallType::Bool, Arg::Numeric(NumericArg::u32, _) diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs index ab2375601..c5330c44b 100644 --- a/ops/op2/dispatch_slow.rs +++ b/ops/op2/dispatch_slow.rs @@ -235,6 +235,22 @@ pub(crate) fn with_fn_args( ) } +pub(crate) fn get_prefix(generator_state: &mut GeneratorState) -> String { + if generator_state.needs_self { + format!( + "Failed to execute '{}' on '{}'", + generator_state.name, generator_state.self_ty + ) + } else if generator_state.use_this_cppgc { + format!("Failed to construct '{}'", generator_state.self_ty) + } else { + format!( + "Failed to execute '{}.{}'", + generator_state.self_ty, generator_state.name + ) + } +} + pub(crate) fn with_required_check( generator_state: &mut GeneratorState, required: u8, @@ -246,24 +262,12 @@ pub(crate) fn with_required_check( "argument" }; - let prefix = if generator_state.needs_self { - format!( - "Failed to execute '{}' on '{}': ", - generator_state.name, generator_state.self_ty - ) - } else if generator_state.use_this_cppgc { - format!("Failed to construct '{}': ", generator_state.self_ty) - } else { - format!( - "Failed to execute '{}.{}': ", - generator_state.self_ty, generator_state.name - ) - }; + let prefix = get_prefix(generator_state); gs_quote!(generator_state(fn_args, scope) => (if #fn_args.length() < #required as i32 { let msg = format!( - "{}{} {} required, but only {} present", + "{}: {} {} required, but only {} present", #prefix, #required, #arguments_lit, @@ -647,6 +651,29 @@ pub fn from_arg( }; } } + Arg::WebIDL(ty) => { + *needs_scope = true; + let ty = + syn::parse_str::(ty).expect("Failed to reparse state type"); + let scope = scope.clone(); + let err = format_ident!("{}_err", arg_ident); + let throw_exception = throw_type_error_string(generator_state, &err)?; + let prefix = get_prefix(generator_state); + let context = format!("Argument {}", index + 1); + quote! { + let #arg_ident = match <#ty as deno_core::webidl::WebIdlConverter>::convert( + &mut #scope, + #arg_ident, + #prefix.into(), + #context.into() + ) { + Ok(t) => t, + Err(#err) => { + #throw_exception; + } + }; + } + } Arg::CppGcResource(ty) => { *needs_scope = true; let scope = scope.clone(); diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs index 51caeb5e7..49dcfbfd4 100644 --- a/ops/op2/signature.rs +++ b/ops/op2/signature.rs @@ -278,6 +278,7 @@ pub enum Arg { OptionCppGcResource(String), FromV8(String), ToV8(String), + WebIDL(String), VarArgs, } @@ -740,6 +741,8 @@ pub enum AttributeModifier { ToV8, /// #[from_v8] for types that impl `FromV8` FromV8, + /// #[webidl], for types that impl `WebIdlConverter` + WebIDL, /// #[smi], for non-integral ID types representing small integers (-2³¹ and 2³¹-1 on 64-bit platforms, /// see https://medium.com/fhinkel/v8-internals-how-small-is-a-small-integer-e0badc18b6da). Smi, @@ -773,6 +776,7 @@ impl AttributeModifier { AttributeModifier::Buffer(..) => "buffer", AttributeModifier::Smi => "smi", AttributeModifier::Serde => "serde", + AttributeModifier::WebIDL => "webidl", AttributeModifier::String(_) => "string", AttributeModifier::State => "state", AttributeModifier::Global => "global", @@ -1231,6 +1235,7 @@ fn parse_attribute( (#[bigint]) => Some(AttributeModifier::Bigint), (#[number]) => Some(AttributeModifier::Number), (#[serde]) => Some(AttributeModifier::Serde), + (#[webidl]) => Some(AttributeModifier::WebIDL), (#[smi]) => Some(AttributeModifier::Smi), (#[string]) => Some(AttributeModifier::String(StringMode::Default)), (#[string(onebyte)]) => Some(AttributeModifier::String(StringMode::OneByte)), @@ -1556,11 +1561,13 @@ pub(crate) fn parse_type( } AttributeModifier::Serde | AttributeModifier::FromV8 - | AttributeModifier::ToV8 => { + | AttributeModifier::ToV8 + | AttributeModifier::WebIDL => { let make_arg = match primary { AttributeModifier::Serde => Arg::SerdeV8, AttributeModifier::FromV8 => Arg::FromV8, AttributeModifier::ToV8 => Arg::ToV8, + AttributeModifier::WebIDL => Arg::WebIDL, _ => unreachable!(), }; match ty { diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs new file mode 100644 index 000000000..082679eea --- /dev/null +++ b/ops/webidl/mod.rs @@ -0,0 +1,229 @@ +use proc_macro2::Ident; +use proc_macro2::TokenStream; +use quote::quote; +use quote::ToTokens; +use syn::parse::Parse; +use syn::parse::ParseStream; +use syn::parse2; +use syn::spanned::Spanned; +use syn::Attribute; +use syn::Data; +use syn::DeriveInput; +use syn::Error; +use syn::Expr; +use syn::Field; +use syn::Fields; +use syn::Type; + +pub fn webidl(item: TokenStream) -> Result { + let input = parse2::(item)?; + let span = input.span(); + let ident = input.ident; + let ident_string = ident.to_string(); + let converter = input + .attrs + .into_iter() + .find_map(|attr| ConverterType::from_attribute(attr).transpose()) + .ok_or_else(|| Error::new(span, "missing #[webidl] attribute"))??; + + let out = match input.data { + Data::Struct(data) => match converter { + ConverterType::Dictionary => { + let fields = match data.fields { + Fields::Named(fields) => fields, + Fields::Unnamed(_) => { + return Err(Error::new( + span, + "Unnamed fields are currently not supported", + )) + } + Fields::Unit => { + return Err(Error::new( + span, + "Unit fields are currently not supported", + )) + } + }; + + let mut fields = fields + .named + .into_iter() + .map(TryInto::try_into) + .collect::, Error>>()?; + fields.sort_by(|a, b| a.name.cmp(&b.name)); + + let names = fields + .iter() + .map(|field| field.name.clone()) + .collect::>(); + + let fields = fields.into_iter().map(|field| { + let string_name = field.name.to_string(); + let name = field.name; + + let required_or_default = if field.required && field.default_value.is_none() { + quote! { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + __context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: #ident_string, + key: #string_name, + }, + )); + } + } else if let Some(default) = field.default_value { + default.to_token_stream() + } else { + quote! { None } + }; + + let val = if field.required { + quote!(val) + } else { + quote!(Some(val)) + }; + + quote! { + let #name = { + if let Some(__value) = __obj.as_ref().and_then(|__obj| { + let __key = v8::String::new(__scope, #string_name).unwrap(); + __obj.get(__scope, __key.into()) + }) { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + format!("'{}' of '{}' ({__context})", #string_name, #ident_string).into(), + )?; + #val + } else { + #required_or_default + } + }; + } + }).collect::>(); + + quote! { + impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident { + fn convert( + __scope: &mut v8::HandleScope<'a>, + __value: v8::Local<'a, v8::Value>, + __prefix: std::borrow::Cow<'static, str>, + __context: std::borrow::Cow<'static, str> + ) -> Result { + let __obj = if __value.is_undefined() || __value.is_null() { + None + } else { + Some(__value.to_object(__scope).ok_or_else(|| { + ::deno_core::webidl::WebIdlError::new( + __prefix.clone(), + __context.clone(), + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("dictionary") + ) + })?) + }; + + #(#fields)* + + Ok(Self { #(#names),* }) + } + } + } + } + }, + Data::Enum(_) => { + return Err(Error::new(span, "Enums are currently not supported")); + } + Data::Union(_) => { + return Err(Error::new(span, "Unions are currently not supported")) + } + }; + + Ok(out) +} + +mod kw { + syn::custom_keyword!(dictionary); +} + +enum ConverterType { + Dictionary, +} + +impl ConverterType { + fn from_attribute(attr: Attribute) -> Result, Error> { + if attr.path().is_ident("webidl") { + let list = attr.meta.require_list()?; + let value = list.parse_args::()?; + Ok(Some(value)) + } else { + Ok(None) + } + } +} + +impl Parse for ConverterType { + fn parse(input: ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(kw::dictionary) { + input.parse::()?; + Ok(Self::Dictionary) + } else { + Err(lookahead.error()) + } + } +} + +struct DictionaryField { + name: Ident, + default_value: Option, + required: bool, +} + +impl TryFrom for DictionaryField { + type Error = Error; + fn try_from(value: Field) -> Result { + let is_optional = if let Type::Path(path) = value.ty { + if let Some(last) = path.path.segments.last() { + last.ident.to_string() == "Option" + } else { + false + } + } else { + false + }; + + let default_value = value + .attrs + .into_iter() + .find_map(|attr| { + if attr.path().is_ident("webidl") { + let list = match attr.meta.require_list() { + Ok(list) => list, + Err(e) => return Some(Err(e)), + }; + let name_value = match list.parse_args::() { + Ok(name_value) => name_value, + Err(e) => return Some(Err(e)), + }; + + if name_value.path.is_ident("default") { + Some(Ok(name_value.value)) + } else { + None + } + } else { + None + } + }) + .transpose()?; + + Ok(Self { + name: value.ident.unwrap(), + default_value, + required: !is_optional, + }) + } +} From fd612c154866963ce73a9bf38064818ac842239f Mon Sep 17 00:00:00 2001 From: crowlkats Date: Mon, 16 Dec 2024 19:11:17 +0100 Subject: [PATCH 02/33] address comments --- core/lib.rs | 1 + core/runtime/mod.rs | 2 +- core/runtime/v8_static_strings.rs | 3 ++- core/webidl.rs | 45 ++++++++++++++++++++++--------- ops/webidl/mod.rs | 31 ++++++++++++++++----- 5 files changed, 61 insertions(+), 21 deletions(-) diff --git a/core/lib.rs b/core/lib.rs index eb5048956..088dcaaf6 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -37,6 +37,7 @@ pub mod webidl; // Re-exports pub use anyhow; pub use deno_ops::op2; +pub use deno_ops::WebIDL; pub use deno_unsync as unsync; pub use futures; pub use parking_lot; diff --git a/core/runtime/mod.rs b/core/runtime/mod.rs index c01bdc809..f6616569e 100644 --- a/core/runtime/mod.rs +++ b/core/runtime/mod.rs @@ -10,7 +10,7 @@ pub mod ops_rust_to_v8; mod setup; mod snapshot; pub mod stats; -pub(crate) mod v8_static_strings; +pub mod v8_static_strings; #[cfg(all(test, not(miri)))] mod tests; diff --git a/core/runtime/v8_static_strings.rs b/core/runtime/v8_static_strings.rs index 1bdc0a750..e448283f2 100644 --- a/core/runtime/v8_static_strings.rs +++ b/core/runtime/v8_static_strings.rs @@ -1,4 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +#[macro_export] macro_rules! v8_static_strings { ($($ident:ident = $str:literal),* $(,)?) => { $( @@ -7,7 +8,7 @@ macro_rules! v8_static_strings { }; } -pub(crate) use v8_static_strings; +pub use v8_static_strings; v8_static_strings!( BUILD_CUSTOM_ERROR = "buildCustomError", diff --git a/core/webidl.rs b/core/webidl.rs index b23596475..1773f84b3 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -1,3 +1,5 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + use std::borrow::Cow; use v8::HandleScope; use v8::Local; @@ -22,6 +24,14 @@ impl WebIdlError { kind, } } + + pub fn other( + prefix: Cow<'static, str>, + context: Cow<'static, str>, + other: T, + ) -> Self { + Self::new(prefix, context, WebIdlErrorKind::Other(Box::new(other))) + } } impl std::fmt::Display for WebIdlError { @@ -71,9 +81,8 @@ impl<'a, T: crate::FromV8<'a>> WebIdlConverter<'a> for T { prefix: Cow<'static, str>, context: Cow<'static, str>, ) -> Result { - Self::from_v8(scope, value).map_err(|e| { - WebIdlError::new(prefix, context, WebIdlErrorKind::Other(Box::new(e))) - }) + Self::from_v8(scope, value) + .map_err(|e| WebIdlError::other(prefix, context, e)) } } @@ -108,13 +117,22 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { let mut out = vec![]; - let next_key = v8::String::new(scope, "next").unwrap(); - let done_key = v8::String::new(scope, "done").unwrap(); - let value_key = v8::String::new(scope, "value").unwrap(); + let next_key = NEXT + .v8_string(scope) + .map_err(|e| WebIdlError::other(prefix.clone(), context.clone(), e))? + .into(); + let done_key = DONE + .v8_string(scope) + .map_err(|e| WebIdlError::other(prefix.clone(), context.clone(), e))? + .into(); + let value_key = VALUE + .v8_string(scope) + .map_err(|e| WebIdlError::other(prefix.clone(), context.clone(), e))? + .into(); loop { let Some(res) = iter - .get(scope, next_key.into()) + .get(scope, next_key) .and_then(|next| next.try_cast::().ok()) .and_then(|next| next.call(scope, iter.cast(), &[])) .and_then(|res| res.to_object(scope)) @@ -126,14 +144,11 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { )); }; - if res - .get(scope, done_key.into()) - .is_some_and(|val| val.is_true()) - { + if res.get(scope, done_key).is_some_and(|val| val.is_true()) { break; } - let Some(iter_val) = res.get(scope, value_key.into()) else { + let Some(iter_val) = res.get(scope, value_key) else { return Err(WebIdlError::new( prefix, context, @@ -152,3 +167,9 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { Ok(out) } } + +crate::v8_static_strings! { + NEXT = "next", + DONE = "done", + VALUE = "value", +} diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 082679eea..eae4008f6 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -1,5 +1,8 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + use proc_macro2::Ident; use proc_macro2::TokenStream; +use quote::format_ident; use quote::quote; use quote::ToTokens; use syn::parse::Parse; @@ -56,10 +59,19 @@ pub fn webidl(item: TokenStream) -> Result { .iter() .map(|field| field.name.clone()) .collect::>(); + let v8_static_strings = names + .iter() + .map(|name| { + let value = name.to_string(); + let new_ident = format_ident!("__v8_static_{name}"); + quote!(#new_ident = #value) + }) + .collect::>(); let fields = fields.into_iter().map(|field| { let string_name = field.name.to_string(); let name = field.name; + let v8_static_name = format_ident!("__v8_static_{name}"); let required_or_default = if field.required && field.default_value.is_none() { quote! { @@ -77,7 +89,7 @@ pub fn webidl(item: TokenStream) -> Result { } else { quote! { None } }; - + let val = if field.required { quote!(val) } else { @@ -86,10 +98,11 @@ pub fn webidl(item: TokenStream) -> Result { quote! { let #name = { - if let Some(__value) = __obj.as_ref().and_then(|__obj| { - let __key = v8::String::new(__scope, #string_name).unwrap(); - __obj.get(__scope, __key.into()) - }) { + let __key = #v8_static_name + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), __context.clone(), e))? + .into(); + if let Some(__value) = __obj.as_ref().and_then(|__obj| __obj.get(__scope, __key)) { let val = ::deno_core::webidl::WebIdlConverter::convert( __scope, __value, @@ -105,10 +118,14 @@ pub fn webidl(item: TokenStream) -> Result { }).collect::>(); quote! { + ::deno_core::v8_static_strings! { + #(#v8_static_strings)*, + } + impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident { fn convert( - __scope: &mut v8::HandleScope<'a>, - __value: v8::Local<'a, v8::Value>, + __scope: &mut::deno_core:: v8::HandleScope<'a>, + __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, __prefix: std::borrow::Cow<'static, str>, __context: std::borrow::Cow<'static, str> ) -> Result { From fdfe35dfdae832664538c0f1865cda87cbdbb296 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Mon, 16 Dec 2024 19:24:34 +0100 Subject: [PATCH 03/33] fix --- ops/webidl/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index eae4008f6..86dd8a2b4 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -204,7 +204,7 @@ impl TryFrom for DictionaryField { fn try_from(value: Field) -> Result { let is_optional = if let Type::Path(path) = value.ty { if let Some(last) = path.path.segments.last() { - last.ident.to_string() == "Option" + last.ident == "Option" } else { false } From 23505c11ccbecc0ce43bd667f083f6ff81f9a972 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Tue, 17 Dec 2024 15:00:10 +0100 Subject: [PATCH 04/33] implement integers --- core/webidl.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 5 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index 1773f84b3..50497d0ec 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -74,15 +74,101 @@ pub trait WebIdlConverter<'a>: Sized { ) -> Result; } -impl<'a, T: crate::FromV8<'a>> WebIdlConverter<'a> for T { +/* +todo: +If bitLength is 64, then: + + Let upperBound be 253 − 1. + + If signedness is "unsigned", then let lowerBound be 0. + + Otherwise let lowerBound be −253 + 1. + + */ +// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint +macro_rules! impl_ints { + ($($t:ty: $unsigned:tt = $name: literal ),*) => { + $( + impl<'a> WebIdlConverter<'a> for $t { + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: Cow<'static, str>, + ) -> Result { + let Some(mut n) = value.number_value(scope) else { + return Err(WebIdlError::new(prefix, context, WebIdlErrorKind::ConvertToConverterType($name))); + }; + if n == -0.0 { + n = 0.0; + } + + // TODO: enforceRange + // TODO: clamp + + if !n.is_finite() || n == 0.0 { + return Ok(0); + } + + n = n.trunc(); + if n == -0.0 { + n = 0.0; + } + + if n >= Self::MIN as f64 && n <= Self::MAX as f64 { + return Ok(n as Self); + } + + let bit_len_num = 2.0f64.powi(Self::BITS as i32); + + n = { + let sign_might_not_match = n % bit_len_num; + if n.is_sign_positive() != bit_len_num.is_sign_positive() { + sign_might_not_match + bit_len_num + } else { + sign_might_not_match + } + }; + + impl_ints!(@handle_unsigned $unsigned n bit_len_num); + + Ok(n as Self) + } + } + )* + }; + + (@handle_unsigned false $n:ident $bit_len_num:ident) => { + if $n >= Self::MAX as f64 { + return Ok(($n - $bit_len_num) as Self); + } + }; + + (@handle_unsigned true $n:ident $bit_len_num:ident) => {}; +} + +// https://webidl.spec.whatwg.org/#js-integer-types +impl_ints!( + i8: false = "byte", + u8: true = "octet", + i16: false = "short", + u16: true = "unsigned short", + i32: false = "long", + u32: true = "unsigned long", + i64: false = "long long", + u64: true = "unsigned long long" +); + +// TODO: float, unrestricted float, double, unrestricted double + +impl<'a> WebIdlConverter<'a> for bool { fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, - prefix: Cow<'static, str>, - context: Cow<'static, str>, + _prefix: Cow<'static, str>, + _context: Cow<'static, str>, ) -> Result { - Self::from_v8(scope, value) - .map_err(|e| WebIdlError::other(prefix, context, e)) + Ok(value.to_boolean(scope).is_true()) } } From 2f7dc35d74b6e0c967a12f325190d4dc80cdc2aa Mon Sep 17 00:00:00 2001 From: crowlkats Date: Tue, 17 Dec 2024 15:00:49 +0100 Subject: [PATCH 05/33] fmt --- core/webidl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/webidl.rs b/core/webidl.rs index 50497d0ec..c58ad7f6f 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -140,7 +140,7 @@ macro_rules! impl_ints { (@handle_unsigned false $n:ident $bit_len_num:ident) => { if $n >= Self::MAX as f64 { - return Ok(($n - $bit_len_num) as Self); + return Ok(($n - $bit_len_num) as Self); } }; From ec09ee6dd6d1284648884f30fb7a6ce11f7121cb Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 18 Dec 2024 00:20:15 +0100 Subject: [PATCH 06/33] handle options and fix various issues --- core/webidl.rs | 54 ++++++++- dcore/Cargo.toml | 1 + ops/lib.rs | 2 +- ops/op2/dispatch_fast.rs | 4 +- ops/op2/dispatch_slow.rs | 24 +++- ops/op2/signature.rs | 80 +++++++++---- ops/webidl/mod.rs | 170 +++++++++++++++++++++------ testing/checkin/runner/extensions.rs | 1 + testing/checkin/runner/ops.rs | 14 +++ 9 files changed, 281 insertions(+), 69 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index c58ad7f6f..eecd089c0 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -48,6 +48,8 @@ impl std::fmt::Display for WebIdlError { "can not be converted to '{converter}' because '{key}' is required in '{converter}'", ) } + WebIdlErrorKind::IntNotFinite => write!(f, "is not a finite number"), + WebIdlErrorKind::IntRange { lower_bound, upper_bound } => write!(f, "is outside the accepted range of ${lower_bound} to ${upper_bound}, inclusive"), WebIdlErrorKind::Other(other) => std::fmt::Display::fmt(other, f), } } @@ -62,18 +64,32 @@ pub enum WebIdlErrorKind { converter: &'static str, key: &'static str, }, + IntNotFinite, + IntRange { + lower_bound: f64, + upper_bound: f64, + }, Other(Box), } pub trait WebIdlConverter<'a>: Sized { + type Options: Default; + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, context: Cow<'static, str>, + options: &Self::Options, ) -> Result; } +#[derive(Debug, Default)] +pub struct IntOptions { + pub clamp: bool, + pub enforce_range: bool, +} + /* todo: If bitLength is 64, then: @@ -90,11 +106,14 @@ macro_rules! impl_ints { ($($t:ty: $unsigned:tt = $name: literal ),*) => { $( impl<'a> WebIdlConverter<'a> for $t { + type Options = IntOptions; + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, context: Cow<'static, str>, + options: &Self::Options, ) -> Result { let Some(mut n) = value.number_value(scope) else { return Err(WebIdlError::new(prefix, context, WebIdlErrorKind::ConvertToConverterType($name))); @@ -103,8 +122,32 @@ macro_rules! impl_ints { n = 0.0; } - // TODO: enforceRange - // TODO: clamp + if options.enforce_range { + if !n.is_finite() { + return Err(WebIdlError::new(prefix, context, WebIdlErrorKind::IntNotFinite)); + } + + n = n.trunc(); + if n == -0.0 { + n = 0.0; + } + + if n < Self::MIN as f64 || n > Self::MAX as f64 { + return Err(WebIdlError::new(prefix, context, WebIdlErrorKind::IntRange { + lower_bound: Self::MIN as f64, + upper_bound: Self::MAX as f64, + })); + } + + return Ok(n as Self); + } + + if !n.is_nan() && options.clamp { + return Ok( + n.clamp(Self::MIN as f64, Self::MAX as f64) + .round_ties_even() as Self + ); + } if !n.is_finite() || n == 0.0 { return Ok(0); @@ -162,22 +205,28 @@ impl_ints!( // TODO: float, unrestricted float, double, unrestricted double impl<'a> WebIdlConverter<'a> for bool { + type Options = (); + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, _prefix: Cow<'static, str>, _context: Cow<'static, str>, + _options: &Self::Options, ) -> Result { Ok(value.to_boolean(scope).is_true()) } } impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { + type Options = T::Options; + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, context: Cow<'static, str>, + options: &Self::Options, ) -> Result { let Some(obj) = value.to_object(scope) else { return Err(WebIdlError::new( @@ -247,6 +296,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { iter_val, prefix.clone(), format!("{context}, index {}", out.len()).into(), + options, )?); } diff --git a/dcore/Cargo.toml b/dcore/Cargo.toml index bf02f8fb6..ed531ed3f 100644 --- a/dcore/Cargo.toml +++ b/dcore/Cargo.toml @@ -10,6 +10,7 @@ publish = false readme = "README.md" repository.workspace = true description = "A simple binary that builds on deno_core" +default-run = "dcore" [[bin]] name = "dcore" diff --git a/ops/lib.rs b/ops/lib.rs index de652c1ce..938a8e4c2 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -33,7 +33,7 @@ fn op2_macro(attr: TokenStream, item: TokenStream) -> TokenStream { } } -#[proc_macro_derive(WebIDL, attributes(webidl))] +#[proc_macro_derive(WebIDL, attributes(webidl, options))] pub fn webidl(item: TokenStream) -> TokenStream { match webidl::webidl(item.into()) { Ok(output) => output.into(), diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs index 02dfa7c18..3c952113d 100644 --- a/ops/op2/dispatch_fast.rs +++ b/ops/op2/dispatch_fast.rs @@ -903,7 +903,7 @@ fn map_arg_to_v8_fastcall_type( | Arg::OptionBuffer(..) | Arg::SerdeV8(_) | Arg::FromV8(_) - | Arg::WebIDL(_) + | Arg::WebIDL(_, _) | Arg::Ref(..) => return Ok(None), // We don't support v8 global arguments Arg::V8Global(_) | Arg::OptionV8Global(_) => return Ok(None), @@ -957,7 +957,7 @@ fn map_retval_to_v8_fastcall_type( Arg::OptionNumeric(..) | Arg::SerdeV8(_) | Arg::ToV8(_) - | Arg::WebIDL(_) => return Ok(None), + | Arg::WebIDL(_, _) => return Ok(None), Arg::Void => V8FastCallType::Void, Arg::Numeric(NumericArg::bool, _) => V8FastCallType::Bool, Arg::Numeric(NumericArg::u32, _) diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs index c5330c44b..c2ab9748e 100644 --- a/ops/op2/dispatch_slow.rs +++ b/ops/op2/dispatch_slow.rs @@ -7,7 +7,6 @@ use super::dispatch_shared::v8slice_to_buffer; use super::generator_state::gs_extract; use super::generator_state::gs_quote; use super::generator_state::GeneratorState; -use super::signature::Arg; use super::signature::ArgMarker; use super::signature::ArgSlowRetval; use super::signature::BufferMode; @@ -21,6 +20,7 @@ use super::signature::RefType; use super::signature::RetVal; use super::signature::Special; use super::signature::Strings; +use super::signature::{Arg, WebIDLPairs}; use super::V8MappingError; use super::V8SignatureMappingError; use proc_macro2::Ident; @@ -651,7 +651,7 @@ pub fn from_arg( }; } } - Arg::WebIDL(ty) => { + Arg::WebIDL(ty, options) => { *needs_scope = true; let ty = syn::parse_str::(ty).expect("Failed to reparse state type"); @@ -660,12 +660,30 @@ pub fn from_arg( let throw_exception = throw_type_error_string(generator_state, &err)?; let prefix = get_prefix(generator_state); let context = format!("Argument {}", index + 1); + + let options = if options.is_empty() { + quote!(Default::default()) + } else { + let inner = options + .iter() + .map(|WebIDLPairs(k, v)| quote!(#k: #v)) + .collect::>(); + + quote! { + <#ty as deno_core::webidl::WebIdlConverter>::Options { + #(#inner),* + ..Default::default() + } + } + }; + quote! { let #arg_ident = match <#ty as deno_core::webidl::WebIdlConverter>::convert( &mut #scope, #arg_ident, #prefix.into(), - #context.into() + #context.into(), + &#options, ) { Ok(t) => t, Err(#err) => { diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs index 49dcfbfd4..72300af73 100644 --- a/ops/op2/signature.rs +++ b/ops/op2/signature.rs @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use proc_macro2::Ident; use proc_macro2::Span; use proc_macro2::TokenStream; +use proc_macro2::{Ident, Literal}; use proc_macro_rules::rules; use quote::format_ident; use quote::quote; @@ -247,6 +247,16 @@ pub enum NumericFlag { Number, } +// its own struct to facility Eq & PartialEq on other structs +#[derive(Clone, Debug)] +pub struct WebIDLPairs(pub Ident, pub Literal); +impl PartialEq for WebIDLPairs { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 && self.1.to_string() == other.1.to_string() + } +} +impl Eq for WebIDLPairs {} + /// Args are not a 1:1 mapping with Rust types, rather they represent broad classes of types that /// tend to have similar argument handling characteristics. This may need one more level of indirection /// given how many of these types have option variants, however. @@ -278,7 +288,7 @@ pub enum Arg { OptionCppGcResource(String), FromV8(String), ToV8(String), - WebIDL(String), + WebIDL(String, Vec), VarArgs, } @@ -733,7 +743,7 @@ pub enum BufferSource { Any, } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum AttributeModifier { /// #[serde], for serde_v8 types. Serde, @@ -742,7 +752,7 @@ pub enum AttributeModifier { /// #[from_v8] for types that impl `FromV8` FromV8, /// #[webidl], for types that impl `WebIdlConverter` - WebIDL, + WebIDL(Vec), /// #[smi], for non-integral ID types representing small integers (-2³¹ and 2³¹-1 on 64-bit platforms, /// see https://medium.com/fhinkel/v8-internals-how-small-is-a-small-integer-e0badc18b6da). Smi, @@ -776,7 +786,7 @@ impl AttributeModifier { AttributeModifier::Buffer(..) => "buffer", AttributeModifier::Smi => "smi", AttributeModifier::Serde => "serde", - AttributeModifier::WebIDL => "webidl", + AttributeModifier::WebIDL(_) => "webidl", AttributeModifier::String(_) => "string", AttributeModifier::State => "state", AttributeModifier::Global => "global", @@ -877,7 +887,7 @@ pub enum RetError { AttributeError(#[from] AttributeError), } -#[derive(Copy, Clone, Default)] +#[derive(Clone, Default)] pub(crate) struct Attributes { primary: Option, } @@ -1202,7 +1212,7 @@ fn parse_attributes( return Ok(Attributes::default()); } Ok(Attributes { - primary: Some(*attrs.first().unwrap()), + primary: Some((*attrs.first().unwrap()).clone()), }) } @@ -1235,7 +1245,8 @@ fn parse_attribute( (#[bigint]) => Some(AttributeModifier::Bigint), (#[number]) => Some(AttributeModifier::Number), (#[serde]) => Some(AttributeModifier::Serde), - (#[webidl]) => Some(AttributeModifier::WebIDL), + (#[webidl]) => Some(AttributeModifier::WebIDL(vec![])), + (#[webidl($($key: ident = $value: literal),*)]) => Some(AttributeModifier::WebIDL(key.into_iter().zip(value.into_iter()).map(|v| WebIDLPairs(v.0, v.1)).collect())), (#[smi]) => Some(AttributeModifier::Smi), (#[string]) => Some(AttributeModifier::String(StringMode::Default)), (#[string(onebyte)]) => Some(AttributeModifier::String(StringMode::OneByte)), @@ -1360,10 +1371,10 @@ fn parse_type_path( ( v8 :: Local < $( $_scope:lifetime , )? v8 :: $v8:ident $(,)? >) => Ok(CV8Local(TV8(parse_v8_type(&v8)?))), ( v8 :: Global < $( $_scope:lifetime , )? v8 :: $v8:ident $(,)? >) => Ok(CV8Global(TV8(parse_v8_type(&v8)?))), ( v8 :: $v8:ident ) => Ok(CBare(TV8(parse_v8_type(&v8)?))), - ( $( std :: rc :: )? Rc < RefCell < $ty:ty $(,)? > $(,)? > ) => Ok(CRcRefCell(TSpecial(parse_type_special(position, attrs, &ty)?))), - ( $( std :: rc :: )? Rc < $ty:ty $(,)? > ) => Ok(CRc(TSpecial(parse_type_special(position, attrs, &ty)?))), + ( $( std :: rc :: )? Rc < RefCell < $ty:ty $(,)? > $(,)? > ) => Ok(CRcRefCell(TSpecial(parse_type_special(position, attrs.clone(), &ty)?))), + ( $( std :: rc :: )? Rc < $ty:ty $(,)? > ) => Ok(CRc(TSpecial(parse_type_special(position, attrs.clone(), &ty)?))), ( Option < $ty:ty $(,)? > ) => { - match parse_type(position, attrs, &ty)? { + match parse_type(position, attrs.clone(), &ty)? { Arg::Special(special) => Ok(COption(TSpecial(special))), Arg::String(string) => Ok(COption(TString(string))), Arg::Numeric(numeric, _) => Ok(COption(TNumeric(numeric))), @@ -1531,7 +1542,7 @@ pub(crate) fn parse_type( use ParsedType::*; use ParsedTypeContainer::*; - if let Some(primary) = attrs.primary { + if let Some(primary) = attrs.clone().primary { match primary { AttributeModifier::Ignore => { unreachable!(); @@ -1562,12 +1573,14 @@ pub(crate) fn parse_type( AttributeModifier::Serde | AttributeModifier::FromV8 | AttributeModifier::ToV8 - | AttributeModifier::WebIDL => { - let make_arg = match primary { - AttributeModifier::Serde => Arg::SerdeV8, - AttributeModifier::FromV8 => Arg::FromV8, - AttributeModifier::ToV8 => Arg::ToV8, - AttributeModifier::WebIDL => Arg::WebIDL, + | AttributeModifier::WebIDL(_) => { + let make_arg: Box Arg> = match primary { + AttributeModifier::Serde => Box::new(|s| Arg::SerdeV8(s)), + AttributeModifier::FromV8 => Box::new(|s| Arg::FromV8(s)), + AttributeModifier::ToV8 => Box::new(|s| Arg::ToV8(s)), + AttributeModifier::WebIDL(ref options) => { + Box::new(move |s| Arg::WebIDL(s, options.clone())) + } _ => unreachable!(), }; match ty { @@ -1622,7 +1635,12 @@ pub(crate) fn parse_type( } AttributeModifier::Number => match ty { Type::Path(of) => { - match parse_type_path(position, attrs, TypePathContext::None, of)? { + match parse_type_path( + position, + attrs.clone(), + TypePathContext::None, + of, + )? { COption(TNumeric( n @ (NumericArg::u64 | NumericArg::usize @@ -1690,8 +1708,8 @@ pub(crate) fn parse_type( } numeric => { let res = CBare(TBuffer(BufferType::Slice(mut_type, numeric))); - res.validate_attributes(position, attrs, &of)?; - Arg::from_parsed(res, attrs).map_err(|_| { + res.validate_attributes(position, attrs.clone(), &of)?; + Arg::from_parsed(res, attrs.clone()).map_err(|_| { ArgError::InvalidType(stringify_token(ty), "for slice") }) } @@ -1701,7 +1719,12 @@ pub(crate) fn parse_type( } } Type::Path(of) => { - match parse_type_path(position, attrs, TypePathContext::Ref, of)? { + match parse_type_path( + position, + attrs.clone(), + TypePathContext::Ref, + of, + )? { CBare(TString(Strings::RefStr)) => Ok(Arg::String(Strings::RefStr)), COption(TString(Strings::RefStr)) => { Ok(Arg::OptionString(Strings::RefStr)) @@ -1725,14 +1748,19 @@ pub(crate) fn parse_type( }; match &*of.elem { Type::Path(of) => { - match parse_type_path(position, attrs, TypePathContext::Ptr, of)? { + match parse_type_path( + position, + attrs.clone(), + TypePathContext::Ptr, + of, + )? { CBare(TNumeric(NumericArg::__VOID__)) => { Ok(Arg::External(External::Ptr(mut_type))) } CBare(TNumeric(numeric)) => { let res = CBare(TBuffer(BufferType::Ptr(mut_type, numeric))); - res.validate_attributes(position, attrs, &of)?; - Arg::from_parsed(res, attrs).map_err(|_| { + res.validate_attributes(position, attrs.clone(), &of)?; + Arg::from_parsed(res, attrs.clone()).map_err(|_| { ArgError::InvalidType( stringify_token(ty), "for numeric pointer", @@ -1752,7 +1780,7 @@ pub(crate) fn parse_type( } } Type::Path(of) => Arg::from_parsed( - parse_type_path(position, attrs, TypePathContext::None, of)?, + parse_type_path(position, attrs.clone(), TypePathContext::None, of)?, attrs, ) .map_err(|_| ArgError::InvalidType(stringify_token(ty), "for path")), diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 86dd8a2b4..88bfe236a 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -7,9 +7,8 @@ use quote::quote; use quote::ToTokens; use syn::parse::Parse; use syn::parse::ParseStream; -use syn::parse2; +use syn::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::Attribute; use syn::Data; use syn::DeriveInput; use syn::Error; @@ -17,6 +16,8 @@ use syn::Expr; use syn::Field; use syn::Fields; use syn::Type; +use syn::{parse2, LitStr, Token}; +use syn::{Attribute, MetaNameValue}; pub fn webidl(item: TokenStream) -> Result { let input = parse2::(item)?; @@ -59,11 +60,15 @@ pub fn webidl(item: TokenStream) -> Result { .iter() .map(|field| field.name.clone()) .collect::>(); - let v8_static_strings = names + let v8_static_strings = fields .iter() - .map(|name| { - let value = name.to_string(); + .map(|field| { + let name = &field.name; let new_ident = format_ident!("__v8_static_{name}"); + let value = field + .rename + .clone() + .unwrap_or_else(|| stringcase::camel_case(&name.to_string())); quote!(#new_ident = #value) }) .collect::>(); @@ -96,18 +101,47 @@ pub fn webidl(item: TokenStream) -> Result { quote!(Some(val)) }; + let options = if field.converter_options.is_empty() { + quote!(Default::default()) + } else { + let inner = field.converter_options + .into_iter() + .map(|(k, v)| quote!(#k: #v)) + .collect::>(); + + let ty = field.ty; + + quote! { + <#ty as ::deno_core::webidl::WebIdlConverter>::Options { + #(#inner),*, + ..Default::default() + } + } + }; + + println!("{:?}", options.to_string()); + quote! { let #name = { let __key = #v8_static_name .v8_string(__scope) .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), __context.clone(), e))? .into(); - if let Some(__value) = __obj.as_ref().and_then(|__obj| __obj.get(__scope, __key)) { + if let Some(__value) = __obj.as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { + None + } else { + Some(__value) + } + }) { let val = ::deno_core::webidl::WebIdlConverter::convert( __scope, __value, __prefix.clone(), format!("'{}' of '{}' ({__context})", #string_name, #ident_string).into(), + &#options, )?; #val } else { @@ -119,15 +153,18 @@ pub fn webidl(item: TokenStream) -> Result { quote! { ::deno_core::v8_static_strings! { - #(#v8_static_strings)*, + #(#v8_static_strings),* } impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident { + type Options = (); + fn convert( __scope: &mut::deno_core:: v8::HandleScope<'a>, __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, __prefix: std::borrow::Cow<'static, str>, - __context: std::borrow::Cow<'static, str> + __context: std::borrow::Cow<'static, str>, + __options: &Self::Options, ) -> Result { let __obj = if __value.is_undefined() || __value.is_null() { None @@ -162,6 +199,9 @@ pub fn webidl(item: TokenStream) -> Result { mod kw { syn::custom_keyword!(dictionary); + syn::custom_keyword!(default); + syn::custom_keyword!(rename); + syn::custom_keyword!(required); } enum ConverterType { @@ -195,14 +235,54 @@ impl Parse for ConverterType { struct DictionaryField { name: Ident, + rename: Option, default_value: Option, required: bool, + converter_options: std::collections::HashMap, + ty: Type, } impl TryFrom for DictionaryField { type Error = Error; fn try_from(value: Field) -> Result { - let is_optional = if let Type::Path(path) = value.ty { + let mut default_value: Option = None; + let mut rename: Option = None; + let mut required = false; + let mut converter_options = std::collections::HashMap::new(); + + for attr in value.attrs { + if attr.path().is_ident("webidl") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + for argument in args { + match argument { + FieldArgument::Default { value, .. } => default_value = Some(value), + FieldArgument::Rename { value, .. } => rename = Some(value.value()), + FieldArgument::Required { .. } => required = true, + } + } + } else if attr.path().is_ident("options") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + let args = args + .into_iter() + .map(|kv| { + let ident = kv.path.require_ident()?; + Ok((ident.clone(), kv.value)) + }) + .collect::, Error>>()?; + + converter_options.extend(args); + } + } + + let is_option = if let Type::Path(path) = &value.ty { if let Some(last) = path.path.segments.last() { last.ident == "Option" } else { @@ -212,35 +292,55 @@ impl TryFrom for DictionaryField { false }; - let default_value = value - .attrs - .into_iter() - .find_map(|attr| { - if attr.path().is_ident("webidl") { - let list = match attr.meta.require_list() { - Ok(list) => list, - Err(e) => return Some(Err(e)), - }; - let name_value = match list.parse_args::() { - Ok(name_value) => name_value, - Err(e) => return Some(Err(e)), - }; - - if name_value.path.is_ident("default") { - Some(Ok(name_value.value)) - } else { - None - } - } else { - None - } - }) - .transpose()?; - Ok(Self { name: value.ident.unwrap(), + rename, default_value, - required: !is_optional, + required: required || !is_option, + converter_options, + ty: value.ty, }) } } + +#[allow(dead_code)] +enum FieldArgument { + Default { + name_token: kw::default, + eq_token: Token![=], + value: Expr, + }, + Rename { + name_token: kw::rename, + eq_token: Token![=], + value: LitStr, + }, + Required { + name_token: kw::rename, + }, +} + +impl Parse for FieldArgument { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::default) { + Ok(FieldArgument::Default { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else if lookahead.peek(kw::rename) { + Ok(FieldArgument::Rename { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else if lookahead.peek(kw::required) { + Ok(FieldArgument::Required { + name_token: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} diff --git a/testing/checkin/runner/extensions.rs b/testing/checkin/runner/extensions.rs index a54008242..ccfcfd7ae 100644 --- a/testing/checkin/runner/extensions.rs +++ b/testing/checkin/runner/extensions.rs @@ -23,6 +23,7 @@ deno_core::extension!( ops::op_stats_dump, ops::op_stats_delete, ops::op_nop_generic

, + ops::op_webidl, ops_io::op_pipe_create, ops_io::op_file_open, ops_io::op_path_to_url, diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs index 095aba4ad..b2b94368f 100644 --- a/testing/checkin/runner/ops.rs +++ b/testing/checkin/runner/ops.rs @@ -180,3 +180,17 @@ impl DOMPoint { pub fn op_nop_generic(state: &mut OpState) { state.take::(); } + +#[derive(deno_core::WebIDL, Debug)] +#[webidl(dictionary)] +pub struct Foo { + #[options(clamp = true)] + test: Vec, + #[webidl(default = Some(3))] + my_number: Option, +} + +#[op2] +pub fn op_webidl(#[webidl] arg: Foo) { + dbg!(arg); +} From bdf6b39dc3455c2a88f83c568d504db582bf18d5 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 26 Dec 2024 16:54:07 +0530 Subject: [PATCH 07/33] Fixes --- dcore/Cargo.toml | 2 +- ops/op2/dispatch_slow.rs | 3 ++- ops/op2/signature.rs | 3 ++- ops/webidl/mod.rs | 23 +++++++++++++++-------- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/dcore/Cargo.toml b/dcore/Cargo.toml index ed531ed3f..cd6cbbdb5 100644 --- a/dcore/Cargo.toml +++ b/dcore/Cargo.toml @@ -4,13 +4,13 @@ name = "dcore" version = "0.1.0" authors.workspace = true +default-run = "dcore" edition.workspace = true license.workspace = true publish = false readme = "README.md" repository.workspace = true description = "A simple binary that builds on deno_core" -default-run = "dcore" [[bin]] name = "dcore" diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs index c2ab9748e..cd69dc7ee 100644 --- a/ops/op2/dispatch_slow.rs +++ b/ops/op2/dispatch_slow.rs @@ -7,6 +7,7 @@ use super::dispatch_shared::v8slice_to_buffer; use super::generator_state::gs_extract; use super::generator_state::gs_quote; use super::generator_state::GeneratorState; +use super::signature::Arg; use super::signature::ArgMarker; use super::signature::ArgSlowRetval; use super::signature::BufferMode; @@ -20,7 +21,7 @@ use super::signature::RefType; use super::signature::RetVal; use super::signature::Special; use super::signature::Strings; -use super::signature::{Arg, WebIDLPairs}; +use super::signature::WebIDLPairs; use super::V8MappingError; use super::V8SignatureMappingError; use proc_macro2::Ident; diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs index 72300af73..6d6b43952 100644 --- a/ops/op2/signature.rs +++ b/ops/op2/signature.rs @@ -1,7 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use proc_macro2::Ident; +use proc_macro2::Literal; use proc_macro2::Span; use proc_macro2::TokenStream; -use proc_macro2::{Ident, Literal}; use proc_macro_rules::rules; use quote::format_ident; use quote::quote; diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 88bfe236a..ee59a8e84 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -7,17 +7,20 @@ use quote::quote; use quote::ToTokens; use syn::parse::Parse; use syn::parse::ParseStream; +use syn::parse2; use syn::punctuated::Punctuated; use syn::spanned::Spanned; +use syn::Attribute; use syn::Data; use syn::DeriveInput; use syn::Error; use syn::Expr; use syn::Field; use syn::Fields; +use syn::LitStr; +use syn::MetaNameValue; +use syn::Token; use syn::Type; -use syn::{parse2, LitStr, Token}; -use syn::{Attribute, MetaNameValue}; pub fn webidl(item: TokenStream) -> Result { let input = parse2::(item)?; @@ -108,19 +111,23 @@ pub fn webidl(item: TokenStream) -> Result { .into_iter() .map(|(k, v)| quote!(#k: #v)) .collect::>(); - + let ty = field.ty; + // Type-alias to workaround https://github.com/rust-lang/rust/issues/86935 quote! { - <#ty as ::deno_core::webidl::WebIdlConverter>::Options { - #(#inner),*, - ..Default::default() + { + type Alias<'a> = <#ty as ::deno_core::webidl::WebIdlConverter<'a>>::Options; + Alias { + #(#inner),*, + ..Default::default() + } } } }; - + println!("{:?}", options.to_string()); - + quote! { let #name = { let __key = #v8_static_name From 4081ace8e83e8eb42b72d0e090c96c602248272c Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 26 Dec 2024 17:00:16 +0530 Subject: [PATCH 08/33] Lint --- ops/op2/signature.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs index 6d6b43952..b9f6e3ceb 100644 --- a/ops/op2/signature.rs +++ b/ops/op2/signature.rs @@ -1576,9 +1576,9 @@ pub(crate) fn parse_type( | AttributeModifier::ToV8 | AttributeModifier::WebIDL(_) => { let make_arg: Box Arg> = match primary { - AttributeModifier::Serde => Box::new(|s| Arg::SerdeV8(s)), - AttributeModifier::FromV8 => Box::new(|s| Arg::FromV8(s)), - AttributeModifier::ToV8 => Box::new(|s| Arg::ToV8(s)), + AttributeModifier::Serde => Box::new(Arg::SerdeV8), + AttributeModifier::FromV8 => Box::new(Arg::FromV8), + AttributeModifier::ToV8 => Box::new(Arg::ToV8), AttributeModifier::WebIDL(ref options) => { Box::new(move |s| Arg::WebIDL(s, options.clone())) } From edb414a7af22de648894a37e823ac6f1cb9970b1 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 26 Dec 2024 17:05:00 +0530 Subject: [PATCH 09/33] Use it --- testing/checkin/runner/ops.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs index b2b94368f..6e8ec0159 100644 --- a/testing/checkin/runner/ops.rs +++ b/testing/checkin/runner/ops.rs @@ -181,7 +181,7 @@ pub fn op_nop_generic(state: &mut OpState) { state.take::(); } -#[derive(deno_core::WebIDL, Debug)] +#[derive(deno_core::WebIDL)] #[webidl(dictionary)] pub struct Foo { #[options(clamp = true)] @@ -192,5 +192,6 @@ pub struct Foo { #[op2] pub fn op_webidl(#[webidl] arg: Foo) { - dbg!(arg); + println!("{}", arg.test.len()); + println!("{:#?}", arg.my_number); } From e6ef3388481b606882ec97eb91c30811d4dc929b Mon Sep 17 00:00:00 2001 From: crowlkats Date: Mon, 30 Dec 2024 12:43:53 +0100 Subject: [PATCH 10/33] perf --- core/webidl.rs | 106 ++++++++++++++++++++++++++++----------- ops/op2/dispatch_slow.rs | 2 +- ops/webidl/mod.rs | 37 ++++++++------ 3 files changed, 100 insertions(+), 45 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index eecd089c0..bdad4e904 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -15,19 +15,19 @@ pub struct WebIdlError { impl WebIdlError { pub fn new( prefix: Cow<'static, str>, - context: Cow<'static, str>, + context: &impl Fn() -> Cow<'static, str>, kind: WebIdlErrorKind, ) -> Self { Self { prefix, - context, + context: context(), kind, } } pub fn other( prefix: Cow<'static, str>, - context: Cow<'static, str>, + context: &impl Fn() -> Cow<'static, str>, other: T, ) -> Self { Self::new(prefix, context, WebIdlErrorKind::Other(Box::new(other))) @@ -72,16 +72,20 @@ pub enum WebIdlErrorKind { Other(Box), } +pub type WebIdlContext = Box Cow<'static, str>>; + pub trait WebIdlConverter<'a>: Sized { type Options: Default; - fn convert( + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, - context: Cow<'static, str>, + context: C, options: &Self::Options, - ) -> Result; + ) -> Result + where + C: Fn() -> Cow<'static, str>; } #[derive(Debug, Default)] @@ -94,11 +98,11 @@ pub struct IntOptions { todo: If bitLength is 64, then: - Let upperBound be 253 − 1. + Let upperBound be 2^53 − 1. If signedness is "unsigned", then let lowerBound be 0. - Otherwise let lowerBound be −253 + 1. + Otherwise let lowerBound be −2^53 + 1. */ // https://webidl.spec.whatwg.org/#abstract-opdef-converttoint @@ -108,15 +112,22 @@ macro_rules! impl_ints { impl<'a> WebIdlConverter<'a> for $t { type Options = IntOptions; - fn convert( + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, - context: Cow<'static, str>, + context: C, options: &Self::Options, - ) -> Result { + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + if value.is_big_int() { + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name))); + } + let Some(mut n) = value.number_value(scope) else { - return Err(WebIdlError::new(prefix, context, WebIdlErrorKind::ConvertToConverterType($name))); + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name))); }; if n == -0.0 { n = 0.0; @@ -124,7 +135,7 @@ macro_rules! impl_ints { if options.enforce_range { if !n.is_finite() { - return Err(WebIdlError::new(prefix, context, WebIdlErrorKind::IntNotFinite)); + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntNotFinite)); } n = n.trunc(); @@ -133,7 +144,7 @@ macro_rules! impl_ints { } if n < Self::MIN as f64 || n > Self::MAX as f64 { - return Err(WebIdlError::new(prefix, context, WebIdlErrorKind::IntRange { + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntRange { lower_bound: Self::MIN as f64, upper_bound: Self::MAX as f64, })); @@ -204,16 +215,52 @@ impl_ints!( // TODO: float, unrestricted float, double, unrestricted double +#[derive(Debug)] +pub struct BigInt { + pub sign: bool, + pub words: Vec, +} + +impl<'a> WebIdlConverter<'a> for BigInt { + type Options = (); + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + _options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let Some(bigint) = value.to_big_int(scope) else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("bigint"), + )); + }; + + let mut words = vec![]; + let (sign, _) = bigint.to_words_array(&mut words); + Ok(Self { sign, words }) + } +} + impl<'a> WebIdlConverter<'a> for bool { type Options = (); - fn convert( + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, _prefix: Cow<'static, str>, - _context: Cow<'static, str>, + _context: C, _options: &Self::Options, - ) -> Result { + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { Ok(value.to_boolean(scope).is_true()) } } @@ -221,17 +268,20 @@ impl<'a> WebIdlConverter<'a> for bool { impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { type Options = T::Options; - fn convert( + fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, prefix: Cow<'static, str>, - context: Cow<'static, str>, + context: C, options: &Self::Options, - ) -> Result { + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { let Some(obj) = value.to_object(scope) else { return Err(WebIdlError::new( prefix, - context, + &context, WebIdlErrorKind::ConvertToConverterType("sequence"), )); }; @@ -245,7 +295,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { else { return Err(WebIdlError::new( prefix, - context, + &context, WebIdlErrorKind::ConvertToConverterType("sequence"), )); }; @@ -254,15 +304,15 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { let next_key = NEXT .v8_string(scope) - .map_err(|e| WebIdlError::other(prefix.clone(), context.clone(), e))? + .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))? .into(); let done_key = DONE .v8_string(scope) - .map_err(|e| WebIdlError::other(prefix.clone(), context.clone(), e))? + .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))? .into(); let value_key = VALUE .v8_string(scope) - .map_err(|e| WebIdlError::other(prefix.clone(), context.clone(), e))? + .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))? .into(); loop { @@ -274,7 +324,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { else { return Err(WebIdlError::new( prefix, - context, + &context, WebIdlErrorKind::ConvertToConverterType("sequence"), )); }; @@ -286,7 +336,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { let Some(iter_val) = res.get(scope, value_key) else { return Err(WebIdlError::new( prefix, - context, + &context, WebIdlErrorKind::ConvertToConverterType("sequence"), )); }; @@ -295,7 +345,7 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { scope, iter_val, prefix.clone(), - format!("{context}, index {}", out.len()).into(), + || format!("{}, index {}", context(), out.len()).into(), options, )?); } diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs index cd69dc7ee..da5a9b84e 100644 --- a/ops/op2/dispatch_slow.rs +++ b/ops/op2/dispatch_slow.rs @@ -683,7 +683,7 @@ pub fn from_arg( &mut #scope, #arg_ident, #prefix.into(), - #context.into(), + || std::borrow::Cow::Borrowed(#context), &#options, ) { Ok(t) => t, diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index ee59a8e84..5bdffa656 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -85,7 +85,7 @@ pub fn webidl(item: TokenStream) -> Result { quote! { return Err(::deno_core::webidl::WebIdlError::new( __prefix, - __context, + &__context, ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { converter: #ident_string, key: #string_name, @@ -125,14 +125,14 @@ pub fn webidl(item: TokenStream) -> Result { } } }; - - println!("{:?}", options.to_string()); + + let new_context = format!("'{string_name}' of '{ident_string}'"); quote! { let #name = { let __key = #v8_static_name .v8_string(__scope) - .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), __context.clone(), e))? + .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))? .into(); if let Some(__value) = __obj.as_ref() .and_then(|__obj| __obj.get(__scope, __key)) @@ -147,7 +147,7 @@ pub fn webidl(item: TokenStream) -> Result { __scope, __value, __prefix.clone(), - format!("'{}' of '{}' ({__context})", #string_name, #ident_string).into(), + || format!("{} ({})", #new_context, __context()).into(), &#options, )?; #val @@ -166,23 +166,28 @@ pub fn webidl(item: TokenStream) -> Result { impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident { type Options = (); - fn convert( - __scope: &mut::deno_core:: v8::HandleScope<'a>, + fn convert( + __scope: &mut ::deno_core::v8::HandleScope<'a>, __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, __prefix: std::borrow::Cow<'static, str>, - __context: std::borrow::Cow<'static, str>, + __context: C, __options: &Self::Options, - ) -> Result { - let __obj = if __value.is_undefined() || __value.is_null() { + ) -> Result + where + C: Fn() -> std::borrow::Cow<'static, str>, + { + let __obj: Option<::deno_core::v8::Local<::deno_core::v8::Object>> = if __value.is_undefined() || __value.is_null() { None } else { - Some(__value.to_object(__scope).ok_or_else(|| { - ::deno_core::webidl::WebIdlError::new( - __prefix.clone(), - __context.clone(), + if let Ok(obj) = __value.try_into() { + Some(obj) + } else { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("dictionary") - ) - })?) + )); + } }; #(#fields)* From faf9bbe9eb44a56ddc40a15055de17d736dc175b Mon Sep 17 00:00:00 2001 From: crowlkats Date: Mon, 30 Dec 2024 14:56:03 +0100 Subject: [PATCH 11/33] fmt --- ops/webidl/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 5bdffa656..5fc7f0084 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -125,7 +125,7 @@ pub fn webidl(item: TokenStream) -> Result { } } }; - + let new_context = format!("'{string_name}' of '{ident_string}'"); quote! { From 9b8ef38667a887da9a7303226c6536383d29b88e Mon Sep 17 00:00:00 2001 From: crowlkats Date: Tue, 31 Dec 2024 14:01:19 +0100 Subject: [PATCH 12/33] eternals! --- Cargo.lock | 4 +- Cargo.toml | 2 +- core/webidl.rs | 196 +++++++++++++++++++++++++++++++++++++++++++--- ops/webidl/mod.rs | 31 +++++++- 4 files changed, 214 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3b84f565..d3f752bab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2748,9 +2748,9 @@ dependencies = [ [[package]] name = "v8" -version = "130.0.2" +version = "130.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee0be58935708fa4d7efb970c6cf9f2d9511d24ee24246481a65b6ee167348d" +checksum = "03a4b46e4786c85506823163ad5f8affc4a34f9860df641a8a099868fe21199a" dependencies = [ "bindgen", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index c00026a8c..f5d77298b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ serde_v8 = { version = "0.235.0", path = "./serde_v8" } deno_ast = { version = "=0.40.0", features = ["transpiling"] } deno_core_icudata = "0.74.0" deno_unsync = "0.4.1" -v8 = { version = "130.0.2", default-features = false } +v8 = { version = "130.0.3", default-features = false } anyhow = "1" bencher = "0.1" diff --git a/core/webidl.rs b/core/webidl.rs index bdad4e904..a39a351e5 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::borrow::Cow; +use std::collections::HashMap; use v8::HandleScope; use v8::Local; use v8::Value; @@ -265,6 +266,18 @@ impl<'a> WebIdlConverter<'a> for bool { } } +crate::v8_static_strings! { + NEXT = "next", + DONE = "done", + VALUE = "value", +} + +thread_local! { + static NEXT_ETERNAL: v8::Eternal = v8::Eternal::empty(); + static DONE_ETERNAL: v8::Eternal = v8::Eternal::empty(); + static VALUE_ETERNAL: v8::Eternal = v8::Eternal::empty(); +} + impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { type Options = T::Options; @@ -302,17 +315,46 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { let mut out = vec![]; - let next_key = NEXT - .v8_string(scope) - .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))? + let next_key = NEXT_ETERNAL + .with(|eternal| { + if eternal.is_empty() { + let key = NEXT + .v8_string(scope) + .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; + eternal.set(scope, key); + Ok(key) + } else { + Ok(eternal.get(scope)) + } + })? .into(); - let done_key = DONE - .v8_string(scope) - .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))? + + let done_key = DONE_ETERNAL + .with(|eternal| { + if eternal.is_empty() { + let key = DONE + .v8_string(scope) + .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; + eternal.set(scope, key); + Ok(key) + } else { + Ok(eternal.get(scope)) + } + })? .into(); - let value_key = VALUE - .v8_string(scope) - .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))? + + let value_key = VALUE_ETERNAL + .with(|eternal| { + if eternal.is_empty() { + let key = VALUE + .v8_string(scope) + .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; + eternal.set(scope, key); + Ok(key) + } else { + Ok(eternal.get(scope)) + } + })? .into(); loop { @@ -354,8 +396,136 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { } } -crate::v8_static_strings! { - NEXT = "next", - DONE = "done", - VALUE = "value", +impl< + 'a, + K: WebIdlConverter<'a> + Eq + std::hash::Hash, + V: WebIdlConverter<'a>, + > WebIdlConverter<'a> for HashMap +{ + type Options = V::Options; + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let Ok(obj) = value.try_cast::() else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("record"), + )); + }; + + if !obj.is_proxy() { + let Some(keys) = obj.get_own_property_names( + scope, + v8::GetPropertyNamesArgs { + mode: v8::KeyCollectionMode::OwnOnly, + property_filter: Default::default(), + index_filter: v8::IndexFilter::IncludeIndices, + key_conversion: v8::KeyConversionMode::ConvertToString, + }, + ) else { + return Ok(Default::default()); + }; + + let mut out = HashMap::with_capacity(keys.length() as _); + + for i in 0..keys.length() { + let key = keys.get_index(scope, i).unwrap(); + let value = obj.get(scope, key).unwrap(); + + let key = WebIdlConverter::convert( + scope, + key, + prefix.clone(), + &context, + &Default::default(), + )?; + let value = WebIdlConverter::convert( + scope, + value, + prefix.clone(), + &context, + options, + )?; + + out.insert(key, value); + } + + Ok(out) + } else { + todo!() + } + } +} + +pub struct DOMString(pub String); + +#[derive(Debug, Default)] +pub struct DOMStringOptions { + treat_null_as_empty_string: bool, +} + +impl<'a> WebIdlConverter<'a> for DOMString { + type Options = DOMStringOptions; + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let str = if value.is_string() { + value.try_cast::().unwrap() + } else if value.is_null() && options.treat_null_as_empty_string { + return Ok(Self(String::new())); + } else if value.is_symbol() { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("string"), + )); + } else if let Some(str) = value.to_string(scope) { + str + } else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("string"), + )); + }; + + Ok(Self(str.to_rust_string_lossy(scope))) + } +} + +pub struct ByteString(pub String); +impl<'a> WebIdlConverter<'a> for ByteString { + type Options = DOMStringOptions; + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let dom_str = DOMString::convert(scope, value, prefix, context, options)?; + + Ok(Self(dom_str.0)) + } } diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 5fc7f0084..f700e1d88 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -75,11 +75,22 @@ pub fn webidl(item: TokenStream) -> Result { quote!(#new_ident = #value) }) .collect::>(); + let v8_lazy_strings = fields + .iter() + .map(|field| { + let name = &field.name; + let v8_eternal_name = format_ident!("__v8_{name}_eternal"); + quote! { + static #v8_eternal_name: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); + } + }) + .collect::>(); let fields = fields.into_iter().map(|field| { let string_name = field.name.to_string(); let name = field.name; let v8_static_name = format_ident!("__v8_static_{name}"); + let v8_eternal_name = format_ident!("__v8_{name}_eternal"); let required_or_default = if field.required && field.default_value.is_none() { quote! { @@ -130,10 +141,20 @@ pub fn webidl(item: TokenStream) -> Result { quote! { let #name = { - let __key = #v8_static_name - .v8_string(__scope) - .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))? + let __key = #v8_eternal_name + .with(|__eternal| { + if __eternal.is_empty() { + let __key = #v8_static_name + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))?; + __eternal.set(__scope, __key); + Ok(__key) + } else { + Ok(__eternal.get(__scope)) + } + })? .into(); + if let Some(__value) = __obj.as_ref() .and_then(|__obj| __obj.get(__scope, __key)) .and_then(|__value| { @@ -163,6 +184,10 @@ pub fn webidl(item: TokenStream) -> Result { #(#v8_static_strings),* } + thread_local! { + #(#v8_lazy_strings)* + } + impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident { type Options = (); From 413aaddd498f3e6aba3b28a59cc198b92a86a696 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Tue, 31 Dec 2024 15:42:30 +0100 Subject: [PATCH 13/33] cleanup eternals, and add more converters --- core/webidl.rs | 423 +++++++++++++++++++++++++++------------------- ops/webidl/mod.rs | 6 +- 2 files changed, 249 insertions(+), 180 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index a39a351e5..4074be5c8 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -51,6 +51,7 @@ impl std::fmt::Display for WebIdlError { } WebIdlErrorKind::IntNotFinite => write!(f, "is not a finite number"), WebIdlErrorKind::IntRange { lower_bound, upper_bound } => write!(f, "is outside the accepted range of ${lower_bound} to ${upper_bound}, inclusive"), + WebIdlErrorKind::InvalidByteString => write!(f, "is not a valid ByteString"), WebIdlErrorKind::Other(other) => std::fmt::Display::fmt(other, f), } } @@ -70,6 +71,7 @@ pub enum WebIdlErrorKind { lower_bound: f64, upper_bound: f64, }, + InvalidByteString, Other(Box), } @@ -89,180 +91,45 @@ pub trait WebIdlConverter<'a>: Sized { C: Fn() -> Cow<'static, str>; } -#[derive(Debug, Default)] -pub struct IntOptions { - pub clamp: bool, - pub enforce_range: bool, -} - -/* -todo: -If bitLength is 64, then: - - Let upperBound be 2^53 − 1. - - If signedness is "unsigned", then let lowerBound be 0. - - Otherwise let lowerBound be −2^53 + 1. - - */ -// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint -macro_rules! impl_ints { - ($($t:ty: $unsigned:tt = $name: literal ),*) => { - $( - impl<'a> WebIdlConverter<'a> for $t { - type Options = IntOptions; - - fn convert( - scope: &mut HandleScope<'a>, - value: Local<'a, Value>, - prefix: Cow<'static, str>, - context: C, - options: &Self::Options, - ) -> Result - where - C: Fn() -> Cow<'static, str>, - { - if value.is_big_int() { - return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name))); - } - - let Some(mut n) = value.number_value(scope) else { - return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name))); - }; - if n == -0.0 { - n = 0.0; - } - - if options.enforce_range { - if !n.is_finite() { - return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntNotFinite)); - } - - n = n.trunc(); - if n == -0.0 { - n = 0.0; - } - - if n < Self::MIN as f64 || n > Self::MAX as f64 { - return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntRange { - lower_bound: Self::MIN as f64, - upper_bound: Self::MAX as f64, - })); - } - - return Ok(n as Self); - } - - if !n.is_nan() && options.clamp { - return Ok( - n.clamp(Self::MIN as f64, Self::MAX as f64) - .round_ties_even() as Self - ); - } - - if !n.is_finite() || n == 0.0 { - return Ok(0); - } - - n = n.trunc(); - if n == -0.0 { - n = 0.0; - } - - if n >= Self::MIN as f64 && n <= Self::MAX as f64 { - return Ok(n as Self); - } - - let bit_len_num = 2.0f64.powi(Self::BITS as i32); - - n = { - let sign_might_not_match = n % bit_len_num; - if n.is_sign_positive() != bit_len_num.is_sign_positive() { - sign_might_not_match + bit_len_num - } else { - sign_might_not_match - } - }; - - impl_ints!(@handle_unsigned $unsigned n bit_len_num); - - Ok(n as Self) - } - } - )* - }; - - (@handle_unsigned false $n:ident $bit_len_num:ident) => { - if $n >= Self::MAX as f64 { - return Ok(($n - $bit_len_num) as Self); - } - }; - - (@handle_unsigned true $n:ident $bit_len_num:ident) => {}; -} - -// https://webidl.spec.whatwg.org/#js-integer-types -impl_ints!( - i8: false = "byte", - u8: true = "octet", - i16: false = "short", - u16: true = "unsigned short", - i32: false = "long", - u32: true = "unsigned long", - i64: false = "long long", - u64: true = "unsigned long long" -); - -// TODO: float, unrestricted float, double, unrestricted double - -#[derive(Debug)] -pub struct BigInt { - pub sign: bool, - pub words: Vec, -} - -impl<'a> WebIdlConverter<'a> for BigInt { +// any converter +impl<'a> WebIdlConverter<'a> for Local<'a, Value> { type Options = (); fn convert( - scope: &mut HandleScope<'a>, + _scope: &mut HandleScope<'a>, value: Local<'a, Value>, - prefix: Cow<'static, str>, - context: C, + _prefix: Cow<'static, str>, + _context: C, _options: &Self::Options, ) -> Result where C: Fn() -> Cow<'static, str>, { - let Some(bigint) = value.to_big_int(scope) else { - return Err(WebIdlError::new( - prefix, - &context, - WebIdlErrorKind::ConvertToConverterType("bigint"), - )); - }; - - let mut words = vec![]; - let (sign, _) = bigint.to_words_array(&mut words); - Ok(Self { sign, words }) + Ok(value) } } -impl<'a> WebIdlConverter<'a> for bool { - type Options = (); +// nullable converter +impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Option { + type Options = T::Options; fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, - _prefix: Cow<'static, str>, - _context: C, - _options: &Self::Options, + prefix: Cow<'static, str>, + context: C, + options: &Self::Options, ) -> Result where C: Fn() -> Cow<'static, str>, { - Ok(value.to_boolean(scope).is_true()) + if value.is_null_or_undefined() { + Ok(None) + } else { + Ok(Some(WebIdlConverter::convert( + scope, value, prefix, context, options, + )?)) + } } } @@ -278,6 +145,7 @@ thread_local! { static VALUE_ETERNAL: v8::Eternal = v8::Eternal::empty(); } +// sequence converter impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { type Options = T::Options; @@ -317,43 +185,37 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { let next_key = NEXT_ETERNAL .with(|eternal| { - if eternal.is_empty() { + eternal.get(scope).or_else(|| { let key = NEXT .v8_string(scope) .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; eternal.set(scope, key); Ok(key) - } else { - Ok(eternal.get(scope)) - } + }) })? .into(); let done_key = DONE_ETERNAL .with(|eternal| { - if eternal.is_empty() { + eternal.get(scope).or_else(|| { let key = DONE .v8_string(scope) .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; eternal.set(scope, key); Ok(key) - } else { - Ok(eternal.get(scope)) - } + }) })? .into(); let value_key = VALUE_ETERNAL .with(|eternal| { - if eternal.is_empty() { + eternal.get(scope).or_else(|| { let key = VALUE .v8_string(scope) .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; eternal.set(scope, key); Ok(key) - } else { - Ok(eternal.get(scope)) - } + }) })? .into(); @@ -396,6 +258,8 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { } } +// record converter +// the Options only apply to the value, not the key impl< 'a, K: WebIdlConverter<'a> + Eq + std::hash::Hash, @@ -461,20 +325,194 @@ impl< Ok(out) } else { - todo!() + todo!("handle proxy") } } } -pub struct DOMString(pub String); +#[derive(Debug, Default)] +pub struct IntOptions { + pub clamp: bool, + pub enforce_range: bool, +} + +const U: u64 = 11 - 1; + +/* +todo: + If bitLength is 64, then: + Let upperBound be 2^53 − 1. + If signedness is "unsigned", then let lowerBound be 0. + Otherwise let lowerBound be −2^53 + 1. + */ +// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint +macro_rules! impl_ints { + ($($t:ty: $unsigned:tt = $name: literal ),*) => { + $( + impl<'a> WebIdlConverter<'a> for $t { + type Options = IntOptions; + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + if value.is_big_int() { + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name))); + } + + let Some(mut n) = value.number_value(scope) else { + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name))); + }; + if n == -0.0 { + n = 0.0; + } + + if options.enforce_range { + if !n.is_finite() { + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntNotFinite)); + } + + n = n.trunc(); + if n == -0.0 { + n = 0.0; + } + + if n < Self::MIN as f64 || n > Self::MAX as f64 { + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntRange { + lower_bound: Self::MIN as f64, + upper_bound: Self::MAX as f64, + })); + } + + return Ok(n as Self); + } + + if !n.is_nan() && options.clamp { + return Ok( + n.clamp(Self::MIN as f64, Self::MAX as f64) + .round_ties_even() as Self + ); + } + + if !n.is_finite() || n == 0.0 { + return Ok(0); + } + + n = n.trunc(); + if n == -0.0 { + n = 0.0; + } + + if n >= Self::MIN as f64 && n <= Self::MAX as f64 { + return Ok(n as Self); + } + + let bit_len_num = 2.0f64.powi(Self::BITS as i32); + + n = { + let sign_might_not_match = n % bit_len_num; + if n.is_sign_positive() != bit_len_num.is_sign_positive() { + sign_might_not_match + bit_len_num + } else { + sign_might_not_match + } + }; + + impl_ints!(@handle_unsigned $unsigned n bit_len_num); + + Ok(n as Self) + } + } + )* + }; + + (@handle_unsigned false $n:ident $bit_len_num:ident) => { + if $n >= Self::MAX as f64 { + return Ok(($n - $bit_len_num) as Self); + } + }; + + (@handle_unsigned true $n:ident $bit_len_num:ident) => {}; +} + +// https://webidl.spec.whatwg.org/#js-integer-types +impl_ints!( + i8: false = "byte", + u8: true = "octet", + i16: false = "short", + u16: true = "unsigned short", + i32: false = "long", + u32: true = "unsigned long", + i64: false = "long long", + u64: true = "unsigned long long" +); + +// TODO: float, unrestricted float, double, unrestricted double + +#[derive(Debug)] +pub struct BigInt { + pub sign: bool, + pub words: Vec, +} + +impl<'a> WebIdlConverter<'a> for BigInt { + type Options = (); + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + _options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let Some(bigint) = value.to_big_int(scope) else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("bigint"), + )); + }; + + let mut words = vec![]; + let (sign, _) = bigint.to_words_array(&mut words); + Ok(Self { sign, words }) + } +} + +impl<'a> WebIdlConverter<'a> for bool { + type Options = (); + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + _prefix: Cow<'static, str>, + _context: C, + _options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + Ok(value.to_boolean(scope).is_true()) + } +} #[derive(Debug, Default)] -pub struct DOMStringOptions { +pub struct StringOptions { treat_null_as_empty_string: bool, } -impl<'a> WebIdlConverter<'a> for DOMString { - type Options = DOMStringOptions; +// DOMString and USVString, since we treat them the same +impl<'a> WebIdlConverter<'a> for String { + type Options = StringOptions; fn convert( scope: &mut HandleScope<'a>, @@ -489,7 +527,7 @@ impl<'a> WebIdlConverter<'a> for DOMString { let str = if value.is_string() { value.try_cast::().unwrap() } else if value.is_null() && options.treat_null_as_empty_string { - return Ok(Self(String::new())); + return Ok(String::new()); } else if value.is_symbol() { return Err(WebIdlError::new( prefix, @@ -506,13 +544,13 @@ impl<'a> WebIdlConverter<'a> for DOMString { )); }; - Ok(Self(str.to_rust_string_lossy(scope))) + Ok(str.to_rust_string_lossy(scope)) } } pub struct ByteString(pub String); impl<'a> WebIdlConverter<'a> for ByteString { - type Options = DOMStringOptions; + type Options = StringOptions; fn convert( scope: &mut HandleScope<'a>, @@ -524,8 +562,41 @@ impl<'a> WebIdlConverter<'a> for ByteString { where C: Fn() -> Cow<'static, str>, { - let dom_str = DOMString::convert(scope, value, prefix, context, options)?; + let str = if value.is_string() { + value.try_cast::().unwrap() + } else if value.is_null() && options.treat_null_as_empty_string { + return Ok(Self(String::new())); + } else if value.is_symbol() { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("string"), + )); + } else if let Some(str) = value.to_string(scope) { + str + } else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("string"), + )); + }; - Ok(Self(dom_str.0)) + if !str.contains_only_onebyte() { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::InvalidByteString, + )); + } + + Ok(Self(str.to_rust_string_lossy(scope))) } } + +// TODO: +// object +// ArrayBuffer +// DataView +// Array buffer types +// ArrayBufferView diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index f700e1d88..d6ceff29a 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -143,15 +143,13 @@ pub fn webidl(item: TokenStream) -> Result { let #name = { let __key = #v8_eternal_name .with(|__eternal| { - if __eternal.is_empty() { + __eternal.get(__scope).or_else(|| { let __key = #v8_static_name .v8_string(__scope) .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))?; __eternal.set(__scope, __key); Ok(__key) - } else { - Ok(__eternal.get(__scope)) - } + }) })? .into(); From 137c071f753d3c2645cc0ef921d0577b188be1d2 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Tue, 31 Dec 2024 18:17:54 +0100 Subject: [PATCH 14/33] fixes and implement enum converters --- Cargo.lock | 4 +- Cargo.toml | 2 +- core/webidl.rs | 25 ++-- ops/webidl/dictionary.rs | 128 ++++++++++++++++++ ops/webidl/enum.rs | 67 +++++++++ ops/webidl/mod.rs | 246 +++++++++++++--------------------- testing/checkin/runner/ops.rs | 7 + 7 files changed, 316 insertions(+), 163 deletions(-) create mode 100644 ops/webidl/dictionary.rs create mode 100644 ops/webidl/enum.rs diff --git a/Cargo.lock b/Cargo.lock index d3f752bab..c97db791f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2748,9 +2748,9 @@ dependencies = [ [[package]] name = "v8" -version = "130.0.3" +version = "130.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a4b46e4786c85506823163ad5f8affc4a34f9860df641a8a099868fe21199a" +checksum = "8b61316a57fcd7e5f3840fe085f13e6dfd37e92d73b040033d2f598c7a1984c3" dependencies = [ "bindgen", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index f5d77298b..7d33f31e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ serde_v8 = { version = "0.235.0", path = "./serde_v8" } deno_ast = { version = "=0.40.0", features = ["transpiling"] } deno_core_icudata = "0.74.0" deno_unsync = "0.4.1" -v8 = { version = "130.0.3", default-features = false } +v8 = { version = "130.0.4", default-features = false } anyhow = "1" bencher = "0.1" diff --git a/core/webidl.rs b/core/webidl.rs index 4074be5c8..ddb6ebe0d 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -53,6 +53,7 @@ impl std::fmt::Display for WebIdlError { WebIdlErrorKind::IntRange { lower_bound, upper_bound } => write!(f, "is outside the accepted range of ${lower_bound} to ${upper_bound}, inclusive"), WebIdlErrorKind::InvalidByteString => write!(f, "is not a valid ByteString"), WebIdlErrorKind::Other(other) => std::fmt::Display::fmt(other, f), + WebIdlErrorKind::InvalidEnumVariant { converter, variant } => write!(f, "can not be converted to '{converter}' because '{variant}' is not a valid enum value") } } } @@ -72,6 +73,10 @@ pub enum WebIdlErrorKind { upper_bound: f64, }, InvalidByteString, + InvalidEnumVariant { + converter: &'static str, + variant: String, + }, Other(Box), } @@ -185,37 +190,43 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Vec { let next_key = NEXT_ETERNAL .with(|eternal| { - eternal.get(scope).or_else(|| { + if let Some(key) = eternal.get(scope) { + Ok(key) + } else { let key = NEXT .v8_string(scope) .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; eternal.set(scope, key); Ok(key) - }) + } })? .into(); let done_key = DONE_ETERNAL .with(|eternal| { - eternal.get(scope).or_else(|| { + if let Some(key) = eternal.get(scope) { + Ok(key) + } else { let key = DONE .v8_string(scope) .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; eternal.set(scope, key); Ok(key) - }) + } })? .into(); let value_key = VALUE_ETERNAL .with(|eternal| { - eternal.get(scope).or_else(|| { + if let Some(key) = eternal.get(scope) { + Ok(key) + } else { let key = VALUE .v8_string(scope) .map_err(|e| WebIdlError::other(prefix.clone(), &context, e))?; eternal.set(scope, key); Ok(key) - }) + } })? .into(); @@ -336,8 +347,6 @@ pub struct IntOptions { pub enforce_range: bool, } -const U: u64 = 11 - 1; - /* todo: If bitLength is 64, then: diff --git a/ops/webidl/dictionary.rs b/ops/webidl/dictionary.rs new file mode 100644 index 000000000..6925b626e --- /dev/null +++ b/ops/webidl/dictionary.rs @@ -0,0 +1,128 @@ +use super::kw; +use proc_macro2::Ident; +use syn::parse::Parse; +use syn::parse::ParseStream; +use syn::punctuated::Punctuated; +use syn::Error; +use syn::Expr; +use syn::Field; +use syn::LitStr; +use syn::MetaNameValue; +use syn::Token; +use syn::Type; + +pub struct DictionaryField { + pub name: Ident, + pub rename: Option, + pub default_value: Option, + pub required: bool, + pub converter_options: std::collections::HashMap, + pub ty: Type, +} + +impl TryFrom for DictionaryField { + type Error = Error; + fn try_from(value: Field) -> Result { + let mut default_value: Option = None; + let mut rename: Option = None; + let mut required = false; + let mut converter_options = std::collections::HashMap::new(); + + for attr in value.attrs { + if attr.path().is_ident("webidl") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + for argument in args { + match argument { + DictionaryFieldArgument::Default { value, .. } => { + default_value = Some(value) + } + DictionaryFieldArgument::Rename { value, .. } => { + rename = Some(value.value()) + } + DictionaryFieldArgument::Required { .. } => required = true, + } + } + } else if attr.path().is_ident("options") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + let args = args + .into_iter() + .map(|kv| { + let ident = kv.path.require_ident()?; + Ok((ident.clone(), kv.value)) + }) + .collect::, Error>>()?; + + converter_options.extend(args); + } + } + + let is_option = if let Type::Path(path) = &value.ty { + if let Some(last) = path.path.segments.last() { + last.ident == "Option" + } else { + false + } + } else { + false + }; + + Ok(Self { + name: value.ident.unwrap(), + rename, + default_value, + required: required || !is_option, + converter_options, + ty: value.ty, + }) + } +} + +#[allow(dead_code)] +pub enum DictionaryFieldArgument { + Default { + name_token: kw::default, + eq_token: Token![=], + value: Expr, + }, + Rename { + name_token: kw::rename, + eq_token: Token![=], + value: LitStr, + }, + Required { + name_token: kw::rename, + }, +} + +impl Parse for DictionaryFieldArgument { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::default) { + Ok(DictionaryFieldArgument::Default { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else if lookahead.peek(kw::rename) { + Ok(DictionaryFieldArgument::Rename { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else if lookahead.peek(kw::required) { + Ok(DictionaryFieldArgument::Required { + name_token: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} diff --git a/ops/webidl/enum.rs b/ops/webidl/enum.rs new file mode 100644 index 000000000..c051d0a67 --- /dev/null +++ b/ops/webidl/enum.rs @@ -0,0 +1,67 @@ +use crate::webidl::kw; +use proc_macro2::Ident; +use syn::parse::Parse; +use syn::parse::ParseStream; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::Error; +use syn::LitStr; +use syn::Token; +use syn::Variant; + +pub fn get_variant_name(value: Variant) -> Result<(String, Ident), Error> { + let mut rename: Option = None; + + if !value.fields.is_empty() { + return Err(Error::new( + value.span(), + "variable with fields are not allowed for enum variants", + )); + } + + for attr in value.attrs { + if attr.path().is_ident("webidl") { + let list = attr.meta.require_list()?; + let args = list.parse_args_with( + Punctuated::::parse_terminated, + )?; + + for argument in args { + match argument { + EnumVariantArgument::Rename { value, .. } => { + rename = Some(value.value()) + } + } + } + } + } + + Ok(( + rename.unwrap_or_else(|| stringcase::kebab_case(&value.ident.to_string())), + value.ident, + )) +} + +#[allow(dead_code)] +pub enum EnumVariantArgument { + Rename { + name_token: kw::rename, + eq_token: Token![=], + value: LitStr, + }, +} + +impl Parse for EnumVariantArgument { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::rename) { + Ok(EnumVariantArgument::Rename { + name_token: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index d6ceff29a..bf460b27b 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -1,26 +1,24 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +mod dictionary; +mod r#enum; + use proc_macro2::Ident; use proc_macro2::TokenStream; use quote::format_ident; use quote::quote; use quote::ToTokens; +use std::collections::HashMap; use syn::parse::Parse; use syn::parse::ParseStream; use syn::parse2; -use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::Attribute; use syn::Data; use syn::DeriveInput; use syn::Error; -use syn::Expr; -use syn::Field; use syn::Fields; -use syn::LitStr; -use syn::MetaNameValue; use syn::Token; -use syn::Type; pub fn webidl(item: TokenStream) -> Result { let input = parse2::(item)?; @@ -56,7 +54,8 @@ pub fn webidl(item: TokenStream) -> Result { .named .into_iter() .map(TryInto::try_into) - .collect::, Error>>()?; + .collect::, Error>>( + )?; fields.sort_by(|a, b| a.name.cmp(&b.name)); let names = fields @@ -143,13 +142,15 @@ pub fn webidl(item: TokenStream) -> Result { let #name = { let __key = #v8_eternal_name .with(|__eternal| { - __eternal.get(__scope).or_else(|| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { let __key = #v8_static_name .v8_string(__scope) .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))?; __eternal.set(__scope, __key); Ok(__key) - }) + } })? .into(); @@ -177,6 +178,29 @@ pub fn webidl(item: TokenStream) -> Result { } }).collect::>(); + let implementation = create_impl( + ident, + quote! { + let __obj: Option<::deno_core::v8::Local<::deno_core::v8::Object>> = if __value.is_undefined() || __value.is_null() { + None + } else { + if let Ok(obj) = __value.try_into() { + Some(obj) + } else { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("dictionary") + )); + } + }; + + #(#fields)* + + Ok(Self { #(#names),* }) + }, + ); + quote! { ::deno_core::v8_static_strings! { #(#v8_static_strings),* @@ -186,47 +210,52 @@ pub fn webidl(item: TokenStream) -> Result { #(#v8_lazy_strings)* } - impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident { - type Options = (); - - fn convert( - __scope: &mut ::deno_core::v8::HandleScope<'a>, - __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, - __prefix: std::borrow::Cow<'static, str>, - __context: C, - __options: &Self::Options, - ) -> Result - where - C: Fn() -> std::borrow::Cow<'static, str>, - { - let __obj: Option<::deno_core::v8::Local<::deno_core::v8::Object>> = if __value.is_undefined() || __value.is_null() { - None - } else { - if let Ok(obj) = __value.try_into() { - Some(obj) - } else { - return Err(::deno_core::webidl::WebIdlError::new( - __prefix, - &__context, - ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("dictionary") - )); - } - }; + #implementation + } + } + ConverterType::Enum => { + return Err(Error::new(span, "Structs do not support enum converters")); + } + }, + Data::Enum(data) => match converter { + ConverterType::Dictionary => { + return Err(Error::new( + span, + "Enums currently do not support dictionary converters", + )); + } + ConverterType::Enum => { + let variants = data + .variants + .into_iter() + .map(r#enum::get_variant_name) + .collect::, _>>()?; + + let variants = variants + .into_iter() + .map(|(name, ident)| quote!(#name => Ok(Self::#ident))) + .collect::>(); - #(#fields)* + create_impl( + ident, + quote! { + let Ok(str) = __value.try_cast::() else { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"), + )); + }; - Ok(Self { #(#names),* }) + match str.to_rust_string_lossy(__scope).as_str() { + #(#variants),*, + s => Err(::deno_core::webidl::WebIdlError::new(__prefix, &__context, ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { converter: #ident_string, variant: s.to_string() })) } - } - } + }, + ) } }, - Data::Enum(_) => { - return Err(Error::new(span, "Enums are currently not supported")); - } - Data::Union(_) => { - return Err(Error::new(span, "Unions are currently not supported")) - } + Data::Union(_) => return Err(Error::new(span, "Unions are not supported")), }; Ok(out) @@ -241,6 +270,7 @@ mod kw { enum ConverterType { Dictionary, + Enum, } impl ConverterType { @@ -262,120 +292,32 @@ impl Parse for ConverterType { if lookahead.peek(kw::dictionary) { input.parse::()?; Ok(Self::Dictionary) + } else if lookahead.peek(Token![enum]) { + input.parse::()?; + Ok(Self::Enum) } else { Err(lookahead.error()) } } } -struct DictionaryField { - name: Ident, - rename: Option, - default_value: Option, - required: bool, - converter_options: std::collections::HashMap, - ty: Type, -} - -impl TryFrom for DictionaryField { - type Error = Error; - fn try_from(value: Field) -> Result { - let mut default_value: Option = None; - let mut rename: Option = None; - let mut required = false; - let mut converter_options = std::collections::HashMap::new(); - - for attr in value.attrs { - if attr.path().is_ident("webidl") { - let list = attr.meta.require_list()?; - let args = list.parse_args_with( - Punctuated::::parse_terminated, - )?; - - for argument in args { - match argument { - FieldArgument::Default { value, .. } => default_value = Some(value), - FieldArgument::Rename { value, .. } => rename = Some(value.value()), - FieldArgument::Required { .. } => required = true, - } - } - } else if attr.path().is_ident("options") { - let list = attr.meta.require_list()?; - let args = list.parse_args_with( - Punctuated::::parse_terminated, - )?; - - let args = args - .into_iter() - .map(|kv| { - let ident = kv.path.require_ident()?; - Ok((ident.clone(), kv.value)) - }) - .collect::, Error>>()?; - - converter_options.extend(args); +fn create_impl(ident: Ident, body: TokenStream) -> TokenStream { + quote! { + impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for #ident { + type Options = (); + + fn convert( + __scope: &mut ::deno_core::v8::HandleScope<'a>, + __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, + __prefix: std::borrow::Cow<'static, str>, + __context: C, + __options: &Self::Options, + ) -> Result + where + C: Fn() -> std::borrow::Cow<'static, str>, + { + #body } } - - let is_option = if let Type::Path(path) = &value.ty { - if let Some(last) = path.path.segments.last() { - last.ident == "Option" - } else { - false - } - } else { - false - }; - - Ok(Self { - name: value.ident.unwrap(), - rename, - default_value, - required: required || !is_option, - converter_options, - ty: value.ty, - }) - } -} - -#[allow(dead_code)] -enum FieldArgument { - Default { - name_token: kw::default, - eq_token: Token![=], - value: Expr, - }, - Rename { - name_token: kw::rename, - eq_token: Token![=], - value: LitStr, - }, - Required { - name_token: kw::rename, - }, -} - -impl Parse for FieldArgument { - fn parse(input: ParseStream) -> Result { - let lookahead = input.lookahead1(); - if lookahead.peek(kw::default) { - Ok(FieldArgument::Default { - name_token: input.parse()?, - eq_token: input.parse()?, - value: input.parse()?, - }) - } else if lookahead.peek(kw::rename) { - Ok(FieldArgument::Rename { - name_token: input.parse()?, - eq_token: input.parse()?, - value: input.parse()?, - }) - } else if lookahead.peek(kw::required) { - Ok(FieldArgument::Required { - name_token: input.parse()?, - }) - } else { - Err(lookahead.error()) - } } } diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs index 6e8ec0159..8d8207956 100644 --- a/testing/checkin/runner/ops.rs +++ b/testing/checkin/runner/ops.rs @@ -190,6 +190,13 @@ pub struct Foo { my_number: Option, } +#[derive(deno_core::WebIDL)] +#[webidl(enum)] +pub enum Bar { + #[webidl(rename = "zed")] + FaaFoo, +} + #[op2] pub fn op_webidl(#[webidl] arg: Foo) { println!("{}", arg.test.len()); From 07035da6a1bbbb53beec50543a62bc954824ec35 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Tue, 31 Dec 2024 18:40:29 +0100 Subject: [PATCH 15/33] lint --- ops/webidl/dictionary.rs | 2 ++ ops/webidl/enum.rs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ops/webidl/dictionary.rs b/ops/webidl/dictionary.rs index 6925b626e..4f9c3f821 100644 --- a/ops/webidl/dictionary.rs +++ b/ops/webidl/dictionary.rs @@ -1,3 +1,5 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + use super::kw; use proc_macro2::Ident; use syn::parse::Parse; diff --git a/ops/webidl/enum.rs b/ops/webidl/enum.rs index c051d0a67..03b8f3098 100644 --- a/ops/webidl/enum.rs +++ b/ops/webidl/enum.rs @@ -1,4 +1,6 @@ -use crate::webidl::kw; +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use super::kw; use proc_macro2::Ident; use syn::parse::Parse; use syn::parse::ParseStream; From c2d58ff29a2dd8bc844764e7c83ed0fb5dbae8fd Mon Sep 17 00:00:00 2001 From: crowlkats Date: Tue, 31 Dec 2024 18:41:23 +0100 Subject: [PATCH 16/33] fix --- ops/webidl/enum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/webidl/enum.rs b/ops/webidl/enum.rs index 03b8f3098..6c8e58242 100644 --- a/ops/webidl/enum.rs +++ b/ops/webidl/enum.rs @@ -17,7 +17,7 @@ pub fn get_variant_name(value: Variant) -> Result<(String, Ident), Error> { if !value.fields.is_empty() { return Err(Error::new( value.span(), - "variable with fields are not allowed for enum variants", + "variants with fields are not allowed for enum variants", )); } From 69774cd127cd3507b7b79c625c5a4f023b2256d2 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 09:05:33 +0100 Subject: [PATCH 17/33] properly handle all number types (except size and 128) --- core/webidl.rs | 195 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 168 insertions(+), 27 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index ddb6ebe0d..4d69b3da5 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -49,11 +49,12 @@ impl std::fmt::Display for WebIdlError { "can not be converted to '{converter}' because '{key}' is required in '{converter}'", ) } - WebIdlErrorKind::IntNotFinite => write!(f, "is not a finite number"), + WebIdlErrorKind::NotFinite => write!(f, "is not a finite number"), WebIdlErrorKind::IntRange { lower_bound, upper_bound } => write!(f, "is outside the accepted range of ${lower_bound} to ${upper_bound}, inclusive"), WebIdlErrorKind::InvalidByteString => write!(f, "is not a valid ByteString"), + WebIdlErrorKind::Precision => write!(f, "is outside the range of a single-precision floating-point value"), + WebIdlErrorKind::InvalidEnumVariant { converter, variant } => write!(f, "can not be converted to '{converter}' because '{variant}' is not a valid enum value"), WebIdlErrorKind::Other(other) => std::fmt::Display::fmt(other, f), - WebIdlErrorKind::InvalidEnumVariant { converter, variant } => write!(f, "can not be converted to '{converter}' because '{variant}' is not a valid enum value") } } } @@ -67,11 +68,12 @@ pub enum WebIdlErrorKind { converter: &'static str, key: &'static str, }, - IntNotFinite, + NotFinite, IntRange { lower_bound: f64, upper_bound: f64, }, + Precision, InvalidByteString, InvalidEnumVariant { converter: &'static str, @@ -347,20 +349,14 @@ pub struct IntOptions { pub enforce_range: bool, } -/* -todo: - If bitLength is 64, then: - Let upperBound be 2^53 − 1. - If signedness is "unsigned", then let lowerBound be 0. - Otherwise let lowerBound be −2^53 + 1. - */ // https://webidl.spec.whatwg.org/#abstract-opdef-converttoint macro_rules! impl_ints { - ($($t:ty: $unsigned:tt = $name: literal ),*) => { + ($($t:ty: $unsigned:tt = $name:literal: $min:expr => $max:expr),*) => { $( impl<'a> WebIdlConverter<'a> for $t { type Options = IntOptions; + #[allow(clippy::manual_range_contains)] fn convert( scope: &mut HandleScope<'a>, value: Local<'a, Value>, @@ -371,6 +367,9 @@ macro_rules! impl_ints { where C: Fn() -> Cow<'static, str>, { + const MIN: f64 = $min as f64; + const MAX: f64 = $max as f64; + if value.is_big_int() { return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::ConvertToConverterType($name))); } @@ -384,7 +383,7 @@ macro_rules! impl_ints { if options.enforce_range { if !n.is_finite() { - return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntNotFinite)); + return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::NotFinite)); } n = n.trunc(); @@ -392,10 +391,10 @@ macro_rules! impl_ints { n = 0.0; } - if n < Self::MIN as f64 || n > Self::MAX as f64 { + if n < MIN || n > MAX { return Err(WebIdlError::new(prefix, &context, WebIdlErrorKind::IntRange { - lower_bound: Self::MIN as f64, - upper_bound: Self::MAX as f64, + lower_bound: MIN, + upper_bound: MAX, })); } @@ -404,7 +403,7 @@ macro_rules! impl_ints { if !n.is_nan() && options.clamp { return Ok( - n.clamp(Self::MIN as f64, Self::MAX as f64) + n.clamp(MIN, MAX) .round_ties_even() as Self ); } @@ -418,7 +417,7 @@ macro_rules! impl_ints { n = 0.0; } - if n >= Self::MIN as f64 && n <= Self::MAX as f64 { + if n >= MIN && n <= MAX { return Ok(n as Self); } @@ -442,7 +441,7 @@ macro_rules! impl_ints { }; (@handle_unsigned false $n:ident $bit_len_num:ident) => { - if $n >= Self::MAX as f64 { + if $n >= MAX { return Ok(($n - $bit_len_num) as Self); } }; @@ -452,17 +451,159 @@ macro_rules! impl_ints { // https://webidl.spec.whatwg.org/#js-integer-types impl_ints!( - i8: false = "byte", - u8: true = "octet", - i16: false = "short", - u16: true = "unsigned short", - i32: false = "long", - u32: true = "unsigned long", - i64: false = "long long", - u64: true = "unsigned long long" + i8: false = "byte": i8::MIN => i8::MAX, + u8: true = "octet": u8::MIN => u8::MAX, + i16: false = "short": i16::MIN => i16::MAX, + u16: true = "unsigned short": u16::MIN => u16::MAX, + i32: false = "long": i32::MIN => i32::MAX, + u32: true = "unsigned long": u32::MIN => u32::MAX, + i64: false = "long long": ((-2i64).pow(53) + 1) => (2i64.pow(53) - 1), + u64: true = "unsigned long long": u64::MIN => (2u64.pow(53) - 1) ); -// TODO: float, unrestricted float, double, unrestricted double +// float +impl<'a> WebIdlConverter<'a> for f32 { + type Options = (); + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + _options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let Some(n) = value.number_value(scope) else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("float"), + )); + }; + + if !n.is_finite() { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::NotFinite, + )); + } + + let n = n as f32; + + if !n.is_finite() { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::Precision, + )); + } + + Ok(n) + } +} + +pub struct UnrestrictedFloat(pub f32); +impl std::ops::Deref for UnrestrictedFloat { + type Target = f32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> WebIdlConverter<'a> for UnrestrictedFloat { + type Options = (); + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + _options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let Some(n) = value.number_value(scope) else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("float"), + )); + }; + + Ok(UnrestrictedFloat(n as f32)) + } +} + +// double +impl<'a> WebIdlConverter<'a> for f64 { + type Options = (); + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + _options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let Some(n) = value.number_value(scope) else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("float"), + )); + }; + + if !n.is_finite() { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::NotFinite, + )); + } + + Ok(n) + } +} + +pub struct UnrestrictedDouble(pub f64); +impl std::ops::Deref for UnrestrictedDouble { + type Target = f64; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> WebIdlConverter<'a> for UnrestrictedDouble { + type Options = (); + + fn convert( + scope: &mut HandleScope<'a>, + value: Local<'a, Value>, + prefix: Cow<'static, str>, + context: C, + _options: &Self::Options, + ) -> Result + where + C: Fn() -> Cow<'static, str>, + { + let Some(n) = value.number_value(scope) else { + return Err(WebIdlError::new( + prefix, + &context, + WebIdlErrorKind::ConvertToConverterType("float"), + )); + }; + + Ok(UnrestrictedDouble(n)) + } +} #[derive(Debug)] pub struct BigInt { From cfd7dfdddea0eef1626156e02c69cc77c295d565 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 10:15:34 +0100 Subject: [PATCH 18/33] add bypass to better_alternative_exists --- ops/op2/signature.rs | 17 +++++++++++++++-- testing/checkin/runner/ops.rs | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs index b9f6e3ceb..2d258a45a 100644 --- a/ops/op2/signature.rs +++ b/ops/op2/signature.rs @@ -1517,7 +1517,16 @@ fn parse_cppgc(position: Position, ty: &Type) -> Result { } } -fn better_alternative_exists(position: Position, of: &TypePath) -> bool { +fn better_alternative_exists( + position: Position, + of: &TypePath, + is_webidl: bool, +) -> bool { + // if this is #[webidl], we want to stick to it + if is_webidl { + return false; + } + // If this type will parse without #[serde]/#[to_v8]/#[from_v8], it is illegal to use this type // with #[serde]/#[to_v8]/#[from_v8] if parse_type_path(position, Attributes::default(), TypePathContext::None, of) @@ -1587,7 +1596,11 @@ pub(crate) fn parse_type( match ty { Type::Tuple(of) => return Ok(make_arg(stringify_token(of))), Type::Path(of) => { - if better_alternative_exists(position, of) { + if better_alternative_exists( + position, + of, + matches!(primary, AttributeModifier::WebIDL(_)), + ) { return Err(ArgError::InvalidAttributeType( primary.name(), stringify_token(ty), diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs index 8d8207956..3bbbe4a68 100644 --- a/testing/checkin/runner/ops.rs +++ b/testing/checkin/runner/ops.rs @@ -198,7 +198,7 @@ pub enum Bar { } #[op2] -pub fn op_webidl(#[webidl] arg: Foo) { +pub fn op_webidl(#[webidl] arg: Foo, #[webidl] bar: f64) { println!("{}", arg.test.len()); println!("{:#?}", arg.my_number); } From cf5aedcafdcfee953a5243a90aa561d45824bd61 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 10:35:20 +0100 Subject: [PATCH 19/33] cleanup --- ops/op2/signature.rs | 19 ++++--------------- testing/checkin/runner/ops.rs | 2 +- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs index 2d258a45a..718cd42fa 100644 --- a/ops/op2/signature.rs +++ b/ops/op2/signature.rs @@ -1517,16 +1517,7 @@ fn parse_cppgc(position: Position, ty: &Type) -> Result { } } -fn better_alternative_exists( - position: Position, - of: &TypePath, - is_webidl: bool, -) -> bool { - // if this is #[webidl], we want to stick to it - if is_webidl { - return false; - } - +fn better_alternative_exists(position: Position, of: &TypePath) -> bool { // If this type will parse without #[serde]/#[to_v8]/#[from_v8], it is illegal to use this type // with #[serde]/#[to_v8]/#[from_v8] if parse_type_path(position, Attributes::default(), TypePathContext::None, of) @@ -1596,11 +1587,9 @@ pub(crate) fn parse_type( match ty { Type::Tuple(of) => return Ok(make_arg(stringify_token(of))), Type::Path(of) => { - if better_alternative_exists( - position, - of, - matches!(primary, AttributeModifier::WebIDL(_)), - ) { + if !matches!(primary, AttributeModifier::WebIDL(_)) + && better_alternative_exists(position, of) + { return Err(ArgError::InvalidAttributeType( primary.name(), stringify_token(ty), diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs index 3bbbe4a68..da22a6394 100644 --- a/testing/checkin/runner/ops.rs +++ b/testing/checkin/runner/ops.rs @@ -198,7 +198,7 @@ pub enum Bar { } #[op2] -pub fn op_webidl(#[webidl] arg: Foo, #[webidl] bar: f64) { +pub fn op_webidl(#[webidl] arg: Foo, #[webidl] _bar: f64) { println!("{}", arg.test.len()); println!("{:#?}", arg.my_number); } From 4da8f0aa48184a5fea74b50cc7556787e46b91d4 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 10:36:36 +0100 Subject: [PATCH 20/33] deref bytestring --- core/webidl.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/webidl.rs b/core/webidl.rs index 4d69b3da5..8fc30b2f5 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -699,6 +699,12 @@ impl<'a> WebIdlConverter<'a> for String { } pub struct ByteString(pub String); +impl std::ops::Deref for ByteString { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.0 + } +} impl<'a> WebIdlConverter<'a> for ByteString { type Options = StringOptions; From 22c1f6ef16351e4a450954001a4990eef8377f04 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 10:42:30 +0100 Subject: [PATCH 21/33] handle proxy --- core/webidl.rs | 74 +++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index 8fc30b2f5..4b53ee619 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -299,47 +299,53 @@ impl< )); }; - if !obj.is_proxy() { - let Some(keys) = obj.get_own_property_names( - scope, - v8::GetPropertyNamesArgs { - mode: v8::KeyCollectionMode::OwnOnly, - property_filter: Default::default(), - index_filter: v8::IndexFilter::IncludeIndices, - key_conversion: v8::KeyConversionMode::ConvertToString, - }, - ) else { + let obj = if let Ok(proxy) = obj.try_cast::() { + if let Ok(obj) = proxy.get_target(scope).try_cast() { + obj + } else { return Ok(Default::default()); - }; + } + } else { + obj + }; - let mut out = HashMap::with_capacity(keys.length() as _); + let Some(keys) = obj.get_own_property_names( + scope, + v8::GetPropertyNamesArgs { + mode: v8::KeyCollectionMode::OwnOnly, + property_filter: Default::default(), + index_filter: v8::IndexFilter::IncludeIndices, + key_conversion: v8::KeyConversionMode::ConvertToString, + }, + ) else { + return Ok(Default::default()); + }; - for i in 0..keys.length() { - let key = keys.get_index(scope, i).unwrap(); - let value = obj.get(scope, key).unwrap(); + let mut out = HashMap::with_capacity(keys.length() as _); - let key = WebIdlConverter::convert( - scope, - key, - prefix.clone(), - &context, - &Default::default(), - )?; - let value = WebIdlConverter::convert( - scope, - value, - prefix.clone(), - &context, - options, - )?; + for i in 0..keys.length() { + let key = keys.get_index(scope, i).unwrap(); + let value = obj.get(scope, key).unwrap(); - out.insert(key, value); - } + let key = WebIdlConverter::convert( + scope, + key, + prefix.clone(), + &context, + &Default::default(), + )?; + let value = WebIdlConverter::convert( + scope, + value, + prefix.clone(), + &context, + options, + )?; - Ok(out) - } else { - todo!("handle proxy") + out.insert(key, value); } + + Ok(out) } } From 1a9874329263286bb6668111e6c2311182fb4ec6 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 11:19:43 +0100 Subject: [PATCH 22/33] fix --- core/webidl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/webidl.rs b/core/webidl.rs index 4b53ee619..da7efc892 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -37,7 +37,7 @@ impl WebIdlError { impl std::fmt::Display for WebIdlError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}: {}", self.prefix, self.context)?; + write!(f, "{}: {} ", self.prefix, self.context)?; match &self.kind { WebIdlErrorKind::ConvertToConverterType(kind) => { From e1a92458c3f865bb32196ccbaf99b32643f34c8d Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 20:14:12 +0100 Subject: [PATCH 23/33] macro tests --- ops/compile_test_runner/src/lib.rs | 29 ++- ops/op2/test_cases/sync/object_wrap.out | 4 +- ops/op2/test_cases/sync/webidl.out | 130 +++++++++++ ops/op2/test_cases/sync/webidl.rs | 8 + ops/webidl/dictionary.rs | 17 ++ ops/webidl/mod.rs | 107 ++++++++- ops/webidl/test_cases/dict.out | 274 ++++++++++++++++++++++++ ops/webidl/test_cases/dict.rs | 16 ++ ops/webidl/test_cases/enum.out | 39 ++++ ops/webidl/test_cases/enum.rs | 10 + ops/webidl/test_cases_fail/enum.rs | 10 + ops/webidl/test_cases_fail/enum.stderr | 39 ++++ testing/checkin/runner/extensions.rs | 1 - testing/checkin/runner/ops.rs | 22 -- 14 files changed, 670 insertions(+), 36 deletions(-) create mode 100644 ops/op2/test_cases/sync/webidl.out create mode 100644 ops/op2/test_cases/sync/webidl.rs create mode 100644 ops/webidl/test_cases/dict.out create mode 100644 ops/webidl/test_cases/dict.rs create mode 100644 ops/webidl/test_cases/enum.out create mode 100644 ops/webidl/test_cases/enum.rs create mode 100644 ops/webidl/test_cases_fail/enum.rs create mode 100644 ops/webidl/test_cases_fail/enum.stderr diff --git a/ops/compile_test_runner/src/lib.rs b/ops/compile_test_runner/src/lib.rs index ed6e3619a..0666717db 100644 --- a/ops/compile_test_runner/src/lib.rs +++ b/ops/compile_test_runner/src/lib.rs @@ -2,14 +2,17 @@ #[macro_export] macro_rules! prelude { () => { + #[allow(unused_imports)] use deno_ops::op2; + #[allow(unused_imports)] + use deno_ops::WebIDL; pub fn main() {} }; } #[cfg(test)] -mod tests { +mod op2_tests { use std::path::PathBuf; // TODO(mmastrac): It's faster to do things with testing_macros::fixture? @@ -40,3 +43,27 @@ mod tests { } } } + +#[cfg(test)] +mod webidl_tests { + #[rustversion::nightly] + #[test] + fn compile_test_all() { + // Run all the tests on a nightly build (which should take advantage of cargo's --keep-going to + // run in parallel: https://github.com/dtolnay/trybuild/pull/168) + let t = trybuild::TestCases::new(); + t.pass("../webidl/test_cases/*.rs"); + t.compile_fail("../webidl/test_cases_fail/*.rs"); + } + + #[rustversion::not(nightly)] + #[test] + fn compile_test_all() { + // Run all the tests if we're in the CI + if let Ok(true) = std::env::var("CI").map(|s| s == "true") { + let t = trybuild::TestCases::new(); + t.compile_fail("../webidl/test_cases_fail/*.rs"); + t.pass("../webidl/test_cases/*.rs"); + } + } +} diff --git a/ops/op2/test_cases/sync/object_wrap.out b/ops/op2/test_cases/sync/object_wrap.out index d95b86a55..9f6919702 100644 --- a/ops/op2/test_cases/sync/object_wrap.out +++ b/ops/op2/test_cases/sync/object_wrap.out @@ -422,8 +422,8 @@ impl Foo { ); if args.length() < 1u8 as i32 { let msg = format!( - "{}{} {} required, but only {} present", - "Failed to execute 'call' on 'Foo': ", + "{}: {} {} required, but only {} present", + "Failed to execute 'call' on 'Foo'", 1u8, "argument", args.length(), diff --git a/ops/op2/test_cases/sync/webidl.out b/ops/op2/test_cases/sync/webidl.out new file mode 100644 index 000000000..bce19ff88 --- /dev/null +++ b/ops/op2/test_cases/sync/webidl.out @@ -0,0 +1,130 @@ +#[allow(non_camel_case_types)] +const fn op_webidl() -> ::deno_core::_ops::OpDecl { + #[allow(non_camel_case_types)] + struct op_webidl { + _unconstructable: ::std::marker::PhantomData<()>, + } + impl ::deno_core::_ops::Op for op_webidl { + const NAME: &'static str = stringify!(op_webidl); + const DECL: ::deno_core::_ops::OpDecl = ::deno_core::_ops::OpDecl::new_internal_op2( + ::deno_core::__op_name_fast!(op_webidl), + false, + false, + 2usize as u8, + false, + Self::v8_fn_ptr as _, + Self::v8_fn_ptr_metrics as _, + ::deno_core::AccessorType::None, + None, + None, + ::deno_core::OpMetadata { + ..::deno_core::OpMetadata::default() + }, + ); + } + impl op_webidl { + pub const fn name() -> &'static str { + ::NAME + } + #[inline(always)] + fn slow_function_impl<'s>( + info: &'s deno_core::v8::FunctionCallbackInfo, + ) -> usize { + #[cfg(debug_assertions)] + let _reentrancy_check_guard = deno_core::_ops::reentrancy_check( + &::DECL, + ); + let mut scope = unsafe { deno_core::v8::CallbackScope::new(info) }; + let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(info); + let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info( + info, + ); + let result = { + let arg0 = args.get(0usize as i32); + let arg0 = match ::convert( + &mut scope, + arg0, + "Failed to execute 'UNINIT.call'".into(), + || std::borrow::Cow::Borrowed("Argument 1"), + &Default::default(), + ) { + Ok(t) => t, + Err(arg0_err) => { + let msg = deno_core::v8::String::new( + &mut scope, + &format!("{}", deno_core::anyhow::Error::from(arg0_err)), + ) + .unwrap(); + let exc = deno_core::v8::Exception::type_error(&mut scope, msg); + scope.throw_exception(exc); + return 1; + } + }; + let arg1 = args.get(1usize as i32); + let arg1 = match ::convert( + &mut scope, + arg1, + "Failed to execute 'UNINIT.call'".into(), + || std::borrow::Cow::Borrowed("Argument 2"), + &Default::default(), + ) { + Ok(t) => t, + Err(arg1_err) => { + let msg = deno_core::v8::String::new( + &mut scope, + &format!("{}", deno_core::anyhow::Error::from(arg1_err)), + ) + .unwrap(); + let exc = deno_core::v8::Exception::type_error(&mut scope, msg); + scope.throw_exception(exc); + return 1; + } + }; + Self::call(arg0, arg1) + }; + deno_core::_ops::RustToV8RetVal::to_v8_rv(result, &mut rv); + return 0; + } + extern "C" fn v8_fn_ptr<'s>(info: *const deno_core::v8::FunctionCallbackInfo) { + let info: &'s _ = unsafe { &*info }; + Self::slow_function_impl(info); + } + extern "C" fn v8_fn_ptr_metrics<'s>( + info: *const deno_core::v8::FunctionCallbackInfo, + ) { + let info: &'s _ = unsafe { &*info }; + let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info( + info, + ); + let opctx: &'s _ = unsafe { + &*(deno_core::v8::Local::< + deno_core::v8::External, + >::cast_unchecked(args.data()) + .value() as *const deno_core::_ops::OpCtx) + }; + deno_core::_ops::dispatch_metrics_slow( + opctx, + deno_core::_ops::OpMetricsEvent::Dispatched, + ); + let res = Self::slow_function_impl(info); + if res == 0 { + deno_core::_ops::dispatch_metrics_slow( + opctx, + deno_core::_ops::OpMetricsEvent::Completed, + ); + } else { + deno_core::_ops::dispatch_metrics_slow( + opctx, + deno_core::_ops::OpMetricsEvent::Error, + ); + } + } + } + impl op_webidl { + #[inline(always)] + fn call(s: String, _n: u32) -> u32 { + s.len() as _ + } + } + ::DECL +} diff --git a/ops/op2/test_cases/sync/webidl.rs b/ops/op2/test_cases/sync/webidl.rs new file mode 100644 index 000000000..3a8a8345f --- /dev/null +++ b/ops/op2/test_cases/sync/webidl.rs @@ -0,0 +1,8 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +#![deny(warnings)] +deno_ops_compile_test_runner::prelude!(); + +#[op2] +fn op_webidl(#[webidl] s: String, #[webidl] _n: u32) -> u32 { + s.len() as _ +} diff --git a/ops/webidl/dictionary.rs b/ops/webidl/dictionary.rs index 4f9c3f821..1f72cdc77 100644 --- a/ops/webidl/dictionary.rs +++ b/ops/webidl/dictionary.rs @@ -2,9 +2,11 @@ use super::kw; use proc_macro2::Ident; +use proc_macro2::Span; use syn::parse::Parse; use syn::parse::ParseStream; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::Error; use syn::Expr; use syn::Field; @@ -14,6 +16,7 @@ use syn::Token; use syn::Type; pub struct DictionaryField { + pub span: Span, pub name: Ident, pub rename: Option, pub default_value: Option, @@ -22,9 +25,22 @@ pub struct DictionaryField { pub ty: Type, } +impl DictionaryField { + pub fn get_name(&self) -> Ident { + Ident::new( + &self + .rename + .clone() + .unwrap_or_else(|| stringcase::camel_case(&self.name.to_string())), + self.span, + ) + } +} + impl TryFrom for DictionaryField { type Error = Error; fn try_from(value: Field) -> Result { + let span = value.span(); let mut default_value: Option = None; let mut rename: Option = None; let mut required = false; @@ -77,6 +93,7 @@ impl TryFrom for DictionaryField { }; Ok(Self { + span, name: value.ident.unwrap(), rename, default_value, diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index bf460b27b..97ab4554e 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -65,19 +65,16 @@ pub fn webidl(item: TokenStream) -> Result { let v8_static_strings = fields .iter() .map(|field| { - let name = &field.name; + let name = field.get_name(); let new_ident = format_ident!("__v8_static_{name}"); - let value = field - .rename - .clone() - .unwrap_or_else(|| stringcase::camel_case(&name.to_string())); - quote!(#new_ident = #value) + let name_str = name.to_string(); + quote!(#new_ident = #name_str) }) .collect::>(); let v8_lazy_strings = fields .iter() .map(|field| { - let name = &field.name; + let name = field.get_name(); let v8_eternal_name = format_ident!("__v8_{name}_eternal"); quote! { static #v8_eternal_name: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); @@ -86,8 +83,9 @@ pub fn webidl(item: TokenStream) -> Result { .collect::>(); let fields = fields.into_iter().map(|field| { - let string_name = field.name.to_string(); - let name = field.name; + let name = field.get_name(); + let string_name = name.to_string(); + let original_name = field.name; let v8_static_name = format_ident!("__v8_static_{name}"); let v8_eternal_name = format_ident!("__v8_{name}_eternal"); @@ -139,7 +137,7 @@ pub fn webidl(item: TokenStream) -> Result { let new_context = format!("'{string_name}' of '{ident_string}'"); quote! { - let #name = { + let #original_name = { let __key = #v8_eternal_name .with(|__eternal| { if let Some(__key) = __eternal.get(__scope) { @@ -321,3 +319,92 @@ fn create_impl(ident: Ident, body: TokenStream) -> TokenStream { } } } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use quote::ToTokens; + use std::path::PathBuf; + use syn::punctuated::Punctuated; + use syn::File; + use syn::Item; + + fn derives_webidl<'a>( + attrs: impl IntoIterator, + ) -> bool { + attrs.into_iter().any(|attr| { + attr.path().is_ident("derive") && { + let list = attr.meta.require_list().unwrap(); + let idents = list + .parse_args_with(Punctuated::::parse_terminated) + .unwrap(); + idents.iter().any(|ident| ident == "WebIDL") + } + }) + } + + #[testing_macros::fixture("webidl/test_cases/*.rs")] + fn test_proc_macro_sync(input: PathBuf) { + test_proc_macro_output(input) + } + + fn expand_webidl(item: impl ToTokens) -> String { + let tokens = + webidl(item.to_token_stream()).expect("Failed to generate WebIDL"); + println!("======== Raw tokens ========:\n{}", tokens.clone()); + let tree = syn::parse2(tokens).unwrap(); + let actual = prettyplease::unparse(&tree); + println!("======== Generated ========:\n{}", actual); + actual + } + + fn test_proc_macro_output(input: PathBuf) { + let update_expected = std::env::var("UPDATE_EXPECTED").is_ok(); + + let source = + std::fs::read_to_string(&input).expect("Failed to read test file"); + + const PRELUDE: &str = r"// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +#![deny(warnings)] +deno_ops_compile_test_runner::prelude!();"; + + if !source.starts_with(PRELUDE) { + panic!("Source does not start with expected prelude:]n{PRELUDE}"); + } + + let file = + syn::parse_str::(&source).expect("Failed to parse Rust file"); + let mut expected_out = vec![]; + for item in file.items { + match item { + Item::Struct(struct_item) => { + if derives_webidl(&struct_item.attrs) { + expected_out.push(expand_webidl(struct_item)); + } + } + Item::Enum(enum_item) => { + dbg!(); + if derives_webidl(&enum_item.attrs) { + expected_out.push(expand_webidl(enum_item)); + } + } + _ => {} + } + } + + let expected_out = expected_out.join("\n"); + + if update_expected { + std::fs::write(input.with_extension("out"), expected_out) + .expect("Failed to write expectation file"); + } else { + let expected = std::fs::read_to_string(input.with_extension("out")) + .expect("Failed to read expectation file"); + assert_eq!( + expected, expected_out, + "Failed to match expectation. Use UPDATE_EXPECTED=1." + ); + } + } +} diff --git a/ops/webidl/test_cases/dict.out b/ops/webidl/test_cases/dict.out new file mode 100644 index 000000000..f679cdf61 --- /dev/null +++ b/ops/webidl/test_cases/dict.out @@ -0,0 +1,274 @@ +::deno_core::v8_static_strings! { + __v8_static_a = "a", __v8_static_b = "b", __v8_static_c = "c", __v8_static_e = "e", + __v8_static_f = "f" +} +thread_local! { + static __v8_a_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); + static __v8_b_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); + static __v8_c_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); + static __v8_e_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); + static __v8_f_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); +} +impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { + type Options = (); + fn convert( + __scope: &mut ::deno_core::v8::HandleScope<'a>, + __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, + __prefix: std::borrow::Cow<'static, str>, + __context: C, + __options: &Self::Options, + ) -> Result + where + C: Fn() -> std::borrow::Cow<'static, str>, + { + let __obj: Option<::deno_core::v8::Local<::deno_core::v8::Object>> = if __value + .is_undefined() || __value.is_null() + { + None + } else { + if let Ok(obj) = __value.try_into() { + Some(obj) + } else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType( + "dictionary", + ), + ), + ); + } + }; + let a = { + let __key = __v8_a_eternal + .with(|__eternal| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { + let __key = __v8_static_a + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other( + __prefix.clone(), + &__context, + e, + ))?; + __eternal.set(__scope, __key); + Ok(__key) + } + })? + .into(); + if let Some(__value) = __obj + .as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { None } else { Some(__value) } + }) + { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", "'a' of 'Dict'", __context()).into(), + &Default::default(), + )?; + val + } else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: "Dict", + key: "a", + }, + ), + ); + } + }; + let b = { + let __key = __v8_b_eternal + .with(|__eternal| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { + let __key = __v8_static_b + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other( + __prefix.clone(), + &__context, + e, + ))?; + __eternal.set(__scope, __key); + Ok(__key) + } + })? + .into(); + if let Some(__value) = __obj + .as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { None } else { Some(__value) } + }) + { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", "'b' of 'Dict'", __context()).into(), + &{ + type Alias<'a> = as ::deno_core::webidl::WebIdlConverter<'a>>::Options; + Alias { + clamp: true, + ..Default::default() + } + }, + )?; + val + } else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: "Dict", + key: "b", + }, + ), + ); + } + }; + let c = { + let __key = __v8_c_eternal + .with(|__eternal| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { + let __key = __v8_static_c + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other( + __prefix.clone(), + &__context, + e, + ))?; + __eternal.set(__scope, __key); + Ok(__key) + } + })? + .into(); + if let Some(__value) = __obj + .as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { None } else { Some(__value) } + }) + { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", "'c' of 'Dict'", __context()).into(), + &Default::default(), + )?; + Some(val) + } else { + Some(3) + } + }; + let d = { + let __key = __v8_e_eternal + .with(|__eternal| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { + let __key = __v8_static_e + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other( + __prefix.clone(), + &__context, + e, + ))?; + __eternal.set(__scope, __key); + Ok(__key) + } + })? + .into(); + if let Some(__value) = __obj + .as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { None } else { Some(__value) } + }) + { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", "'e' of 'Dict'", __context()).into(), + &Default::default(), + )?; + val + } else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: "Dict", + key: "e", + }, + ), + ); + } + }; + let f = { + let __key = __v8_f_eternal + .with(|__eternal| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { + let __key = __v8_static_f + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other( + __prefix.clone(), + &__context, + e, + ))?; + __eternal.set(__scope, __key); + Ok(__key) + } + })? + .into(); + if let Some(__value) = __obj + .as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { None } else { Some(__value) } + }) + { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", "'f' of 'Dict'", __context()).into(), + &Default::default(), + )?; + val + } else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: "Dict", + key: "f", + }, + ), + ); + } + }; + Ok(Self { a, b, c, d, f }) + } +} diff --git a/ops/webidl/test_cases/dict.rs b/ops/webidl/test_cases/dict.rs new file mode 100644 index 000000000..c28e15e83 --- /dev/null +++ b/ops/webidl/test_cases/dict.rs @@ -0,0 +1,16 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +#![deny(warnings)] +deno_ops_compile_test_runner::prelude!(); + +#[derive(WebIDL)] +#[webidl(dictionary)] +pub struct Dict { + a: u8, + #[options(clamp = true)] + b: Vec, + #[webidl(default = Some(3))] + c: Option, + #[webidl(rename = "e")] + d: u64, + f: std::collections::HashMap, +} diff --git a/ops/webidl/test_cases/enum.out b/ops/webidl/test_cases/enum.out new file mode 100644 index 000000000..c3ae41511 --- /dev/null +++ b/ops/webidl/test_cases/enum.out @@ -0,0 +1,39 @@ +impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Enumeration { + type Options = (); + fn convert( + __scope: &mut ::deno_core::v8::HandleScope<'a>, + __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, + __prefix: std::borrow::Cow<'static, str>, + __context: C, + __options: &Self::Options, + ) -> Result + where + C: Fn() -> std::borrow::Cow<'static, str>, + { + let Ok(str) = __value.try_cast::() else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"), + ), + ); + }; + match str.to_rust_string_lossy(__scope).as_str() { + "foo-bar" => Ok(Self::FooBar), + "baz" => Ok(Self::Baz), + s => { + Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { + converter: "Enumeration", + variant: s.to_string(), + }, + ), + ) + } + } + } +} diff --git a/ops/webidl/test_cases/enum.rs b/ops/webidl/test_cases/enum.rs new file mode 100644 index 000000000..1a87d0405 --- /dev/null +++ b/ops/webidl/test_cases/enum.rs @@ -0,0 +1,10 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +#![deny(warnings)] +deno_ops_compile_test_runner::prelude!(); + +#[derive(WebIDL)] +#[webidl(enum)] +pub enum Enumeration { + FooBar, + Baz, +} diff --git a/ops/webidl/test_cases_fail/enum.rs b/ops/webidl/test_cases_fail/enum.rs new file mode 100644 index 000000000..b2a991e77 --- /dev/null +++ b/ops/webidl/test_cases_fail/enum.rs @@ -0,0 +1,10 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +#![deny(warnings)] +deno_ops_compile_test_runner::prelude!(); + +#[derive(WebIDL)] +#[webidl(enum)] +pub enum Enumeration { + FooBar(u32), + Baz, +} diff --git a/ops/webidl/test_cases_fail/enum.stderr b/ops/webidl/test_cases_fail/enum.stderr new file mode 100644 index 000000000..c3ae41511 --- /dev/null +++ b/ops/webidl/test_cases_fail/enum.stderr @@ -0,0 +1,39 @@ +impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Enumeration { + type Options = (); + fn convert( + __scope: &mut ::deno_core::v8::HandleScope<'a>, + __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, + __prefix: std::borrow::Cow<'static, str>, + __context: C, + __options: &Self::Options, + ) -> Result + where + C: Fn() -> std::borrow::Cow<'static, str>, + { + let Ok(str) = __value.try_cast::() else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"), + ), + ); + }; + match str.to_rust_string_lossy(__scope).as_str() { + "foo-bar" => Ok(Self::FooBar), + "baz" => Ok(Self::Baz), + s => { + Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { + converter: "Enumeration", + variant: s.to_string(), + }, + ), + ) + } + } + } +} diff --git a/testing/checkin/runner/extensions.rs b/testing/checkin/runner/extensions.rs index ccfcfd7ae..a54008242 100644 --- a/testing/checkin/runner/extensions.rs +++ b/testing/checkin/runner/extensions.rs @@ -23,7 +23,6 @@ deno_core::extension!( ops::op_stats_dump, ops::op_stats_delete, ops::op_nop_generic

, - ops::op_webidl, ops_io::op_pipe_create, ops_io::op_file_open, ops_io::op_path_to_url, diff --git a/testing/checkin/runner/ops.rs b/testing/checkin/runner/ops.rs index d756b29f2..f20a3579d 100644 --- a/testing/checkin/runner/ops.rs +++ b/testing/checkin/runner/ops.rs @@ -207,25 +207,3 @@ impl DOMPoint { pub fn op_nop_generic(state: &mut OpState) { state.take::(); } - -#[derive(deno_core::WebIDL)] -#[webidl(dictionary)] -pub struct Foo { - #[options(clamp = true)] - test: Vec, - #[webidl(default = Some(3))] - my_number: Option, -} - -#[derive(deno_core::WebIDL)] -#[webidl(enum)] -pub enum Bar { - #[webidl(rename = "zed")] - FaaFoo, -} - -#[op2] -pub fn op_webidl(#[webidl] arg: Foo, #[webidl] _bar: f64) { - println!("{}", arg.test.len()); - println!("{:#?}", arg.my_number); -} From 84229d2dd7ef0137d7cfbeda318f3a27eae098a7 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 20:18:00 +0100 Subject: [PATCH 24/33] fix --- ops/webidl/mod.rs | 2 +- ops/webidl/test_cases/enum.out | 2 +- ops/webidl/test_cases_fail/enum.stderr | 44 +++----------------------- 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 97ab4554e..1695b775e 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -237,7 +237,7 @@ pub fn webidl(item: TokenStream) -> Result { create_impl( ident, quote! { - let Ok(str) = __value.try_cast::() else { + let Ok(str) = __value.try_cast::<::deno_core::v8::String>() else { return Err(::deno_core::webidl::WebIdlError::new( __prefix, &__context, diff --git a/ops/webidl/test_cases/enum.out b/ops/webidl/test_cases/enum.out index c3ae41511..32f442708 100644 --- a/ops/webidl/test_cases/enum.out +++ b/ops/webidl/test_cases/enum.out @@ -10,7 +10,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Enumeration { where C: Fn() -> std::borrow::Cow<'static, str>, { - let Ok(str) = __value.try_cast::() else { + let Ok(str) = __value.try_cast::<::deno_core::v8::String>() else { return Err( ::deno_core::webidl::WebIdlError::new( __prefix, diff --git a/ops/webidl/test_cases_fail/enum.stderr b/ops/webidl/test_cases_fail/enum.stderr index c3ae41511..3258d17a3 100644 --- a/ops/webidl/test_cases_fail/enum.stderr +++ b/ops/webidl/test_cases_fail/enum.stderr @@ -1,39 +1,5 @@ -impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Enumeration { - type Options = (); - fn convert( - __scope: &mut ::deno_core::v8::HandleScope<'a>, - __value: ::deno_core::v8::Local<'a, ::deno_core::v8::Value>, - __prefix: std::borrow::Cow<'static, str>, - __context: C, - __options: &Self::Options, - ) -> Result - where - C: Fn() -> std::borrow::Cow<'static, str>, - { - let Ok(str) = __value.try_cast::() else { - return Err( - ::deno_core::webidl::WebIdlError::new( - __prefix, - &__context, - ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"), - ), - ); - }; - match str.to_rust_string_lossy(__scope).as_str() { - "foo-bar" => Ok(Self::FooBar), - "baz" => Ok(Self::Baz), - s => { - Err( - ::deno_core::webidl::WebIdlError::new( - __prefix, - &__context, - ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { - converter: "Enumeration", - variant: s.to_string(), - }, - ), - ) - } - } - } -} +error: variants with fields are not allowed for enum variants + --> ../webidl/test_cases_fail/enum.rs:8:3 + | +8 | FooBar(u32), + | ^^^^^^^^^^^ From f6ae464ce63c50596b7431126249fd0f6cf81ed1 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Wed, 1 Jan 2025 20:24:33 +0100 Subject: [PATCH 25/33] fix --- Cargo.lock | 17 ++++++++++++----- Cargo.toml | 1 + core/Cargo.toml | 2 +- ops/Cargo.toml | 1 + ops/webidl/mod.rs | 3 +-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 283833a2a..f9b3416c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -485,7 +485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -650,6 +650,7 @@ dependencies = [ name = "deno_ops" version = "0.203.0" dependencies = [ + "indexmap", "pretty_assertions", "prettyplease", "proc-macro-rules", @@ -991,6 +992,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + [[package]] name = "heck" version = "0.4.1" @@ -1018,7 +1025,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96274be293b8877e61974a607105d09c84caebe9620b47774aa8a6b942042dd4" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", "new_debug_unreachable", "once_cell", "phf", @@ -1128,12 +1135,12 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b39bdc917..404828f90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ thiserror = "1" tokio = { version = "1", features = ["full"] } twox-hash = { version = "2.0.0", default-features = false, features = ["xxhash64"] } url = { version = "2", features = ["serde", "expose_internals"] } +indexmap = "2.1.0" # macros proc-macro-rules = "0.4.0" diff --git a/core/Cargo.toml b/core/Cargo.toml index b7cfdbe7e..fe92837ca 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -35,7 +35,7 @@ deno_core_icudata = { workspace = true, optional = true } deno_ops.workspace = true deno_unsync.workspace = true futures.workspace = true -indexmap = "2.1.0" +indexmap.workspace = true libc.workspace = true memoffset.workspace = true parking_lot.workspace = true diff --git a/ops/Cargo.toml b/ops/Cargo.toml index 5d18fffe5..7ce667f3f 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -23,6 +23,7 @@ strum.workspace = true strum_macros.workspace = true syn.workspace = true thiserror.workspace = true +indexmap.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 1695b775e..212828843 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -8,7 +8,6 @@ use proc_macro2::TokenStream; use quote::format_ident; use quote::quote; use quote::ToTokens; -use std::collections::HashMap; use syn::parse::Parse; use syn::parse::ParseStream; use syn::parse2; @@ -227,7 +226,7 @@ pub fn webidl(item: TokenStream) -> Result { .variants .into_iter() .map(r#enum::get_variant_name) - .collect::, _>>()?; + .collect::, _>>()?; let variants = variants .into_iter() From 95c2564b161c672081e62797e9007da5bbdc56fc Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 07:07:30 +0100 Subject: [PATCH 26/33] fmt --- Cargo.toml | 2 +- ops/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 404828f90..2a6865bb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ cooked-waker = "5" criterion = "0.5" fastrand = "2" futures = "0.3.21" +indexmap = "2.1.0" libc = "0.2.126" memoffset = ">=0.9" num-bigint = { version = "0.4", features = ["rand"] } @@ -63,7 +64,6 @@ thiserror = "1" tokio = { version = "1", features = ["full"] } twox-hash = { version = "2.0.0", default-features = false, features = ["xxhash64"] } url = { version = "2", features = ["serde", "expose_internals"] } -indexmap = "2.1.0" # macros proc-macro-rules = "0.4.0" diff --git a/ops/Cargo.toml b/ops/Cargo.toml index 7ce667f3f..8446d0324 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -15,6 +15,7 @@ path = "./lib.rs" proc-macro = true [dependencies] +indexmap.workspace = true proc-macro-rules.workspace = true proc-macro2.workspace = true quote.workspace = true @@ -23,7 +24,6 @@ strum.workspace = true strum_macros.workspace = true syn.workspace = true thiserror.workspace = true -indexmap.workspace = true [dev-dependencies] pretty_assertions.workspace = true From 04c83704833551a8a51c6922a4d8165d77a5be8d Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 07:43:05 +0100 Subject: [PATCH 27/33] change error message --- ops/webidl/enum.rs | 2 +- ops/webidl/test_cases_fail/enum.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ops/webidl/enum.rs b/ops/webidl/enum.rs index 6c8e58242..f520dcb4b 100644 --- a/ops/webidl/enum.rs +++ b/ops/webidl/enum.rs @@ -17,7 +17,7 @@ pub fn get_variant_name(value: Variant) -> Result<(String, Ident), Error> { if !value.fields.is_empty() { return Err(Error::new( value.span(), - "variants with fields are not allowed for enum variants", + "variants with fields are not allowed for enum converters", )); } diff --git a/ops/webidl/test_cases_fail/enum.stderr b/ops/webidl/test_cases_fail/enum.stderr index 3258d17a3..84b12e2fc 100644 --- a/ops/webidl/test_cases_fail/enum.stderr +++ b/ops/webidl/test_cases_fail/enum.stderr @@ -1,4 +1,4 @@ -error: variants with fields are not allowed for enum variants +error: variants with fields are not allowed for enum converters --> ../webidl/test_cases_fail/enum.rs:8:3 | 8 | FooBar(u32), From d9ab4d2373c52462eb46adb70c8f31b2cd4d7578 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 10:08:58 +0100 Subject: [PATCH 28/33] add more tests and update test expectation --- core/error.rs | 9 +- core/webidl.rs | 313 +++++++++++++++++- ops/webidl/enum.rs | 2 +- .../{enum.rs => enum_fields.rs} | 0 .../{enum.stderr => enum_fields.stderr} | 4 +- 5 files changed, 322 insertions(+), 6 deletions(-) rename ops/webidl/test_cases_fail/{enum.rs => enum_fields.rs} (100%) rename ops/webidl/test_cases_fail/{enum.stderr => enum_fields.stderr} (56%) diff --git a/core/error.rs b/core/error.rs index 1a2763638..9b6d699ae 100644 --- a/core/error.rs +++ b/core/error.rs @@ -97,7 +97,14 @@ impl std::error::Error for CustomError {} /// If this error was crated with `custom_error()`, return the specified error /// class name. In all other cases this function returns `None`. pub fn get_custom_error_class(error: &Error) -> Option<&'static str> { - error.downcast_ref::().map(|e| e.class) + error + .downcast_ref::() + .map(|e| e.class) + .or_else(|| { + error + .downcast_ref::() + .map(|_| "TypeError") + }) } /// A wrapper around `anyhow::Error` that implements `std::error::Error` diff --git a/core/webidl.rs b/core/webidl.rs index da7efc892..d597ba665 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -82,8 +82,6 @@ pub enum WebIdlErrorKind { Other(Box), } -pub type WebIdlContext = Box Cow<'static, str>>; - pub trait WebIdlConverter<'a>: Sized { type Options: Default; @@ -511,6 +509,7 @@ impl<'a> WebIdlConverter<'a> for f32 { } } +#[derive(Debug, Copy, Clone)] pub struct UnrestrictedFloat(pub f32); impl std::ops::Deref for UnrestrictedFloat { type Target = f32; @@ -578,6 +577,7 @@ impl<'a> WebIdlConverter<'a> for f64 { } } +#[derive(Debug, Copy, Clone)] pub struct UnrestrictedDouble(pub f64); impl std::ops::Deref for UnrestrictedDouble { type Target = f64; @@ -704,6 +704,7 @@ impl<'a> WebIdlConverter<'a> for String { } } +#[derive(Debug, Clone)] pub struct ByteString(pub String); impl std::ops::Deref for ByteString { type Target = String; @@ -762,3 +763,311 @@ impl<'a> WebIdlConverter<'a> for ByteString { // DataView // Array buffer types // ArrayBufferView + +#[cfg(test)] +mod tests { + use super::*; + use crate::JsRuntime; + + #[test] + fn integers() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + macro_rules! test_integer { + ($t:ty: $($val:expr => $expected:literal$(, $opts:expr)?);+;) => { + $( + let val = v8::Number::new(scope, $val as f64); + let val = Local::new(scope, val); + let converted = <$t>::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &test_integer!(@opts $($opts)?), + ); + assert_eq!(converted.unwrap(), $expected); + )+ + }; + + ($t:ty: $($val:expr => ERR$(, $opts:expr)?);+;) => { + $( + let val = v8::Number::new(scope, $val as f64); + let val = Local::new(scope, val); + let converted = <$t>::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &test_integer!(@opts $($opts)?), + ); + assert!(converted.is_err()); + )+ + }; + + (@opts $opts:expr) => { $opts }; + (@opts) => { Default::default() }; + } + + test_integer!( + i8: + 50 => 50; + -10 => -10; + 130 => -126; + -130 => 126; + 130 => 127, IntOptions { clamp: true, enforce_range: false }; + ); + test_integer!( + i8: + f64::INFINITY => ERR, IntOptions { clamp: false, enforce_range: true }; + -f64::INFINITY => ERR, IntOptions { clamp: false, enforce_range: true }; + f64::NAN => ERR, IntOptions { clamp: false, enforce_range: true }; + 130 => ERR, IntOptions { clamp: false, enforce_range: true }; + ); + + test_integer!( + u8: + 50 => 50; + -10 => 246; + 260 => 4; + 260 => 255, IntOptions { clamp: true, enforce_range: false }; + ); + test_integer!( + u8: + f64::INFINITY => ERR, IntOptions { clamp: false, enforce_range: true }; + f64::NAN => ERR, IntOptions { clamp: false, enforce_range: true }; + 260 => ERR, IntOptions { clamp: false, enforce_range: true }; + ); + + let val = v8::String::new(scope, "3").unwrap(); + let converted = u8::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), 3); + + let val = v8::String::new(scope, "test").unwrap(); + let converted = u8::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), 0); + + let val = v8::BigInt::new_from_i64(scope, 0); + let val = Local::new(scope, val); + let converted = u8::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + + let val = v8::Symbol::new(scope, None); + let converted = u8::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + + let val = v8::undefined(scope); + let converted = u8::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), 0); + } + + #[test] + fn float() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::Number::new(scope, 3.0); + let val = Local::new(scope, val); + let converted = f32::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), 3.0); + + let val = v8::Number::new(scope, f64::INFINITY); + let val = Local::new(scope, val); + let converted = f32::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + + let val = v8::Number::new(scope, f64::MAX); + let val = Local::new(scope, val); + let converted = f32::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + } + + #[test] + fn unrestricted_float() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::Number::new(scope, 3.0); + let val = Local::new(scope, val); + let converted = UnrestrictedFloat::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), 3.0); + + let val = v8::Number::new(scope, f32::INFINITY as f64); + let val = Local::new(scope, val); + let converted = UnrestrictedFloat::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), f32::INFINITY); + + let val = v8::Number::new(scope, f64::NAN); + let val = Local::new(scope, val); + let converted = UnrestrictedFloat::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + + assert!(converted.unwrap().is_nan()); + + let val = v8::Number::new(scope, f64::MAX); + let val = Local::new(scope, val); + let converted = UnrestrictedFloat::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.unwrap().is_infinite()); + } + + #[test] + fn double() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::Number::new(scope, 3.0); + let val = Local::new(scope, val); + let converted = f64::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), 3.0); + + let val = v8::Number::new(scope, f64::INFINITY); + let val = Local::new(scope, val); + let converted = f64::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + + let val = v8::Number::new(scope, f64::MAX); + let val = Local::new(scope, val); + let converted = f64::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), f64::MAX); + } + + #[test] + fn unrestricted_double() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::Number::new(scope, 3.0); + let val = Local::new(scope, val); + let converted = UnrestrictedDouble::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), 3.0); + + let val = v8::Number::new(scope, f64::INFINITY); + let val = Local::new(scope, val); + let converted = UnrestrictedDouble::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), f64::INFINITY); + + let val = v8::Number::new(scope, f64::NAN); + let val = Local::new(scope, val); + let converted = UnrestrictedDouble::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + + assert!(converted.unwrap().is_nan()); + + let val = v8::Number::new(scope, f64::MAX); + let val = Local::new(scope, val); + let converted = UnrestrictedDouble::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), f64::MAX); + } +} diff --git a/ops/webidl/enum.rs b/ops/webidl/enum.rs index f520dcb4b..71f3bb8a2 100644 --- a/ops/webidl/enum.rs +++ b/ops/webidl/enum.rs @@ -16,7 +16,7 @@ pub fn get_variant_name(value: Variant) -> Result<(String, Ident), Error> { if !value.fields.is_empty() { return Err(Error::new( - value.span(), + value.fields.span(), "variants with fields are not allowed for enum converters", )); } diff --git a/ops/webidl/test_cases_fail/enum.rs b/ops/webidl/test_cases_fail/enum_fields.rs similarity index 100% rename from ops/webidl/test_cases_fail/enum.rs rename to ops/webidl/test_cases_fail/enum_fields.rs diff --git a/ops/webidl/test_cases_fail/enum.stderr b/ops/webidl/test_cases_fail/enum_fields.stderr similarity index 56% rename from ops/webidl/test_cases_fail/enum.stderr rename to ops/webidl/test_cases_fail/enum_fields.stderr index 84b12e2fc..a590fd892 100644 --- a/ops/webidl/test_cases_fail/enum.stderr +++ b/ops/webidl/test_cases_fail/enum_fields.stderr @@ -1,5 +1,5 @@ error: variants with fields are not allowed for enum converters - --> ../webidl/test_cases_fail/enum.rs:8:3 + --> ../webidl/test_cases_fail/enum_fields.rs:8:9 | 8 | FooBar(u32), - | ^^^^^^^^^^^ + | ^^^^^ From e94c2c6c3b2a699700cda397b7adc71bf8239c14 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 10:52:54 +0100 Subject: [PATCH 29/33] more tests --- core/webidl.rs | 260 +++++++++++++++++++++++++++++++--- ops/webidl/dictionary.rs | 2 +- ops/webidl/test_cases/dict.rs | 2 + ops/webidl/test_cases/enum.rs | 2 + 4 files changed, 248 insertions(+), 18 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index d597ba665..7f83d5b91 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -778,7 +778,6 @@ mod tests { ($t:ty: $($val:expr => $expected:literal$(, $opts:expr)?);+;) => { $( let val = v8::Number::new(scope, $val as f64); - let val = Local::new(scope, val); let converted = <$t>::convert( scope, val.into(), @@ -793,7 +792,6 @@ mod tests { ($t:ty: $($val:expr => ERR$(, $opts:expr)?);+;) => { $( let val = v8::Number::new(scope, $val as f64); - let val = Local::new(scope, val); let converted = <$t>::convert( scope, val.into(), @@ -860,7 +858,6 @@ mod tests { assert_eq!(converted.unwrap(), 0); let val = v8::BigInt::new_from_i64(scope, 0); - let val = Local::new(scope, val); let converted = u8::convert( scope, val.into(), @@ -897,7 +894,6 @@ mod tests { let scope = &mut runtime.handle_scope(); let val = v8::Number::new(scope, 3.0); - let val = Local::new(scope, val); let converted = f32::convert( scope, val.into(), @@ -908,7 +904,6 @@ mod tests { assert_eq!(converted.unwrap(), 3.0); let val = v8::Number::new(scope, f64::INFINITY); - let val = Local::new(scope, val); let converted = f32::convert( scope, val.into(), @@ -919,7 +914,6 @@ mod tests { assert!(converted.is_err()); let val = v8::Number::new(scope, f64::MAX); - let val = Local::new(scope, val); let converted = f32::convert( scope, val.into(), @@ -936,7 +930,6 @@ mod tests { let scope = &mut runtime.handle_scope(); let val = v8::Number::new(scope, 3.0); - let val = Local::new(scope, val); let converted = UnrestrictedFloat::convert( scope, val.into(), @@ -947,7 +940,6 @@ mod tests { assert_eq!(*converted.unwrap(), 3.0); let val = v8::Number::new(scope, f32::INFINITY as f64); - let val = Local::new(scope, val); let converted = UnrestrictedFloat::convert( scope, val.into(), @@ -958,7 +950,6 @@ mod tests { assert_eq!(*converted.unwrap(), f32::INFINITY); let val = v8::Number::new(scope, f64::NAN); - let val = Local::new(scope, val); let converted = UnrestrictedFloat::convert( scope, val.into(), @@ -970,7 +961,6 @@ mod tests { assert!(converted.unwrap().is_nan()); let val = v8::Number::new(scope, f64::MAX); - let val = Local::new(scope, val); let converted = UnrestrictedFloat::convert( scope, val.into(), @@ -987,7 +977,6 @@ mod tests { let scope = &mut runtime.handle_scope(); let val = v8::Number::new(scope, 3.0); - let val = Local::new(scope, val); let converted = f64::convert( scope, val.into(), @@ -998,7 +987,6 @@ mod tests { assert_eq!(converted.unwrap(), 3.0); let val = v8::Number::new(scope, f64::INFINITY); - let val = Local::new(scope, val); let converted = f64::convert( scope, val.into(), @@ -1009,7 +997,6 @@ mod tests { assert!(converted.is_err()); let val = v8::Number::new(scope, f64::MAX); - let val = Local::new(scope, val); let converted = f64::convert( scope, val.into(), @@ -1026,7 +1013,6 @@ mod tests { let scope = &mut runtime.handle_scope(); let val = v8::Number::new(scope, 3.0); - let val = Local::new(scope, val); let converted = UnrestrictedDouble::convert( scope, val.into(), @@ -1037,7 +1023,6 @@ mod tests { assert_eq!(*converted.unwrap(), 3.0); let val = v8::Number::new(scope, f64::INFINITY); - let val = Local::new(scope, val); let converted = UnrestrictedDouble::convert( scope, val.into(), @@ -1048,7 +1033,6 @@ mod tests { assert_eq!(*converted.unwrap(), f64::INFINITY); let val = v8::Number::new(scope, f64::NAN); - let val = Local::new(scope, val); let converted = UnrestrictedDouble::convert( scope, val.into(), @@ -1060,7 +1044,6 @@ mod tests { assert!(converted.unwrap().is_nan()); let val = v8::Number::new(scope, f64::MAX); - let val = Local::new(scope, val); let converted = UnrestrictedDouble::convert( scope, val.into(), @@ -1070,4 +1053,247 @@ mod tests { ); assert_eq!(*converted.unwrap(), f64::MAX); } + + #[test] + fn string() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::String::new(scope, "foo").unwrap(); + let converted = String::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), "foo"); + + let val = v8::Number::new(scope, 1.0); + let converted = String::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), "1"); + + let val = v8::Symbol::new(scope, None); + let converted = String::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + + let val = v8::null(scope); + let converted = String::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), "null"); + + let val = v8::null(scope); + let converted = String::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &StringOptions { + treat_null_as_empty_string: true, + }, + ); + assert_eq!(converted.unwrap(), ""); + + let val = v8::Object::new(scope); + let converted = String::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &StringOptions { + treat_null_as_empty_string: true, + }, + ); + assert_eq!(converted.unwrap(), "[object Object]"); + + let val = v8::String::new(scope, "生").unwrap(); + let converted = String::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), "生"); + } + + #[test] + fn byte_string() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::String::new(scope, "foo").unwrap(); + let converted = ByteString::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), "foo"); + + let val = v8::Number::new(scope, 1.0); + let converted = ByteString::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), "1"); + + let val = v8::Symbol::new(scope, None); + let converted = ByteString::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + + let val = v8::null(scope); + let converted = ByteString::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(*converted.unwrap(), "null"); + + let val = v8::null(scope); + let converted = ByteString::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &StringOptions { + treat_null_as_empty_string: true, + }, + ); + assert_eq!(*converted.unwrap(), ""); + + let val = v8::Object::new(scope); + let converted = ByteString::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &StringOptions { + treat_null_as_empty_string: true, + }, + ); + assert_eq!(*converted.unwrap(), "[object Object]"); + + let val = v8::String::new(scope, "生").unwrap(); + let converted = ByteString::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + } + + #[test] + fn any() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::Object::new(scope); + let converted = v8::Local::::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.unwrap().is_object()); + } + + #[test] + fn sequence() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let a = v8::Number::new(scope, 1.0); + let b = v8::String::new(scope, "2").unwrap(); + let val = v8::Array::new_with_elements(scope, &[a.into(), b.into()]); + let converted = Vec::::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), vec![1, 2]); + } + + #[test] + fn nullable() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::undefined(scope); + let converted = Option::::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), None); + + let val = v8::Number::new(scope, 1.0); + let converted = Option::::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), Some(1)); + } + + #[test] + fn record() { + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let key = v8::String::new(scope, "foo").unwrap(); + let val = v8::Number::new(scope, 1.0); + let obj = v8::Object::new(scope); + obj.set(scope, key.into(), val.into()); + + let converted = HashMap::::convert( + scope, + obj.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!( + converted.unwrap(), + HashMap::from([(String::from("foo"), 1)]) + ); + } } diff --git a/ops/webidl/dictionary.rs b/ops/webidl/dictionary.rs index 1f72cdc77..745969196 100644 --- a/ops/webidl/dictionary.rs +++ b/ops/webidl/dictionary.rs @@ -117,7 +117,7 @@ pub enum DictionaryFieldArgument { value: LitStr, }, Required { - name_token: kw::rename, + name_token: kw::required, }, } diff --git a/ops/webidl/test_cases/dict.rs b/ops/webidl/test_cases/dict.rs index c28e15e83..0f68c5a90 100644 --- a/ops/webidl/test_cases/dict.rs +++ b/ops/webidl/test_cases/dict.rs @@ -13,4 +13,6 @@ pub struct Dict { #[webidl(rename = "e")] d: u64, f: std::collections::HashMap, + #[webidl(required)] + g: Option, } diff --git a/ops/webidl/test_cases/enum.rs b/ops/webidl/test_cases/enum.rs index 1a87d0405..bdb0eaeca 100644 --- a/ops/webidl/test_cases/enum.rs +++ b/ops/webidl/test_cases/enum.rs @@ -7,4 +7,6 @@ deno_ops_compile_test_runner::prelude!(); pub enum Enumeration { FooBar, Baz, + #[webidl(rename = "hello")] + World, } From e07d1d3e13b133951cc6a3967263ebfa76fa120f Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 11:33:39 +0100 Subject: [PATCH 30/33] cleanup and more tests --- core/webidl.rs | 104 +++++++++++++++++ ops/webidl/dictionary.rs | 192 ++++++++++++++++++++++++++++++-- ops/webidl/enum.rs | 38 ++++++- ops/webidl/mod.rs | 198 +-------------------------------- ops/webidl/test_cases/dict.out | 51 ++++++++- ops/webidl/test_cases/enum.out | 1 + 6 files changed, 376 insertions(+), 208 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index 7f83d5b91..8e06d753d 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -1296,4 +1296,108 @@ mod tests { HashMap::from([(String::from("foo"), 1)]) ); } + + #[test] + fn dictionary() { + #[derive(deno_ops::WebIDL, Debug, Eq, PartialEq)] + #[webidl(dictionary)] + pub struct Dict { + a: u8, + #[options(clamp = true)] + b: Vec, + #[webidl(default = Some(3))] + c: Option, + #[webidl(rename = "e")] + d: u32, + f: HashMap, + #[webidl(required)] + g: Option, + } + + let mut runtime = JsRuntime::new(Default::default()); + let val = runtime + .execute_script( + "", + "({ a: 1, b: [500000], e: 2, f: { 'foo': 1 }, g: undefined })", + ) + .unwrap(); + + let scope = &mut runtime.handle_scope(); + let val = Local::new(scope, val); + + let converted = Dict::convert( + scope, + val, + "prefix".into(), + || "context".into(), + &Default::default(), + ); + + assert_eq!( + converted.unwrap(), + Dict { + a: 1, + b: vec![500], + c: Some(3), + d: 2, + f: HashMap::from([(String::from("foo"), 1)]), + g: None, + } + ); + } + + #[test] + fn r#enum() { + #[derive(deno_ops::WebIDL, Debug, Eq, PartialEq)] + #[webidl(enum)] + pub enum Enumeration { + FooBar, + Baz, + #[webidl(rename = "hello")] + World, + } + + let mut runtime = JsRuntime::new(Default::default()); + let scope = &mut runtime.handle_scope(); + + let val = v8::String::new(scope, "foo-bar").unwrap(); + let converted = Enumeration::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), Enumeration::FooBar); + + let val = v8::String::new(scope, "baz").unwrap(); + let converted = Enumeration::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), Enumeration::Baz); + + let val = v8::String::new(scope, "hello").unwrap(); + let converted = Enumeration::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert_eq!(converted.unwrap(), Enumeration::World); + + let val = v8::String::new(scope, "unknown").unwrap(); + let converted = Enumeration::convert( + scope, + val.into(), + "prefix".into(), + || "context".into(), + &Default::default(), + ); + assert!(converted.is_err()); + } } diff --git a/ops/webidl/dictionary.rs b/ops/webidl/dictionary.rs index 745969196..e53b37f6a 100644 --- a/ops/webidl/dictionary.rs +++ b/ops/webidl/dictionary.rs @@ -3,30 +3,202 @@ use super::kw; use proc_macro2::Ident; use proc_macro2::Span; +use proc_macro2::TokenStream; +use quote::format_ident; +use quote::quote; +use quote::ToTokens; use syn::parse::Parse; use syn::parse::ParseStream; use syn::punctuated::Punctuated; use syn::spanned::Spanned; +use syn::DataStruct; use syn::Error; use syn::Expr; use syn::Field; +use syn::Fields; use syn::LitStr; use syn::MetaNameValue; use syn::Token; use syn::Type; -pub struct DictionaryField { - pub span: Span, - pub name: Ident, - pub rename: Option, - pub default_value: Option, - pub required: bool, - pub converter_options: std::collections::HashMap, - pub ty: Type, +pub fn get_body( + ident_string: String, + span: Span, + data: DataStruct, +) -> Result<(TokenStream, Vec, Vec), Error> { + let fields = match data.fields { + Fields::Named(fields) => fields, + Fields::Unnamed(_) => { + return Err(Error::new( + span, + "Unnamed fields are currently not supported", + )) + } + Fields::Unit => { + return Err(Error::new(span, "Unit fields are currently not supported")) + } + }; + + let mut fields = fields + .named + .into_iter() + .map(TryInto::try_into) + .collect::, Error>>()?; + fields.sort_by(|a, b| a.name.cmp(&b.name)); + + let names = fields + .iter() + .map(|field| field.name.clone()) + .collect::>(); + let v8_static_strings = fields + .iter() + .map(|field| { + let name = field.get_name(); + let new_ident = format_ident!("__v8_static_{name}"); + let name_str = name.to_string(); + quote!(#new_ident = #name_str) + }) + .collect::>(); + let v8_lazy_strings = fields + .iter() + .map(|field| { + let name = field.get_name(); + let v8_eternal_name = format_ident!("__v8_{name}_eternal"); + quote! { + static #v8_eternal_name: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); + } + }) + .collect::>(); + + let fields = fields.into_iter().map(|field| { + let name = field.get_name(); + let string_name = name.to_string(); + let original_name = field.name; + let v8_static_name = format_ident!("__v8_static_{name}"); + let v8_eternal_name = format_ident!("__v8_{name}_eternal"); + + let required_or_default = if field.required && field.default_value.is_none() { + quote! { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: #ident_string, + key: #string_name, + }, + )); + } + } else if let Some(default) = field.default_value { + default.to_token_stream() + } else { + quote! { None } + }; + + let val = if field.required { + quote!(val) + } else { + quote!(Some(val)) + }; + + let options = if field.converter_options.is_empty() { + quote!(Default::default()) + } else { + let inner = field.converter_options + .into_iter() + .map(|(k, v)| quote!(#k: #v)) + .collect::>(); + + let ty = field.ty; + + // Type-alias to workaround https://github.com/rust-lang/rust/issues/86935 + quote! { + { + type Alias<'a> = <#ty as ::deno_core::webidl::WebIdlConverter<'a>>::Options; + Alias { + #(#inner),*, + ..Default::default() + } + } + } + }; + + let new_context = format!("'{string_name}' of '{ident_string}'"); + + quote! { + let #original_name = { + let __key = #v8_eternal_name + .with(|__eternal| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { + let __key = #v8_static_name + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))?; + __eternal.set(__scope, __key); + Ok(__key) + } + })? + .into(); + + if let Some(__value) = __obj.as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { + None + } else { + Some(__value) + } + }) { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", #new_context, __context()).into(), + &#options, + )?; + #val + } else { + #required_or_default + } + }; + } + }).collect::>(); + + let body = quote! { + let __obj: Option<::deno_core::v8::Local<::deno_core::v8::Object>> = if __value.is_undefined() || __value.is_null() { + None + } else { + if let Ok(obj) = __value.try_into() { + Some(obj) + } else { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("dictionary") + )); + } + }; + + #(#fields)* + + Ok(Self { #(#names),* }) + }; + + Ok((body, v8_static_strings, v8_lazy_strings)) +} + +struct DictionaryField { + span: Span, + name: Ident, + rename: Option, + default_value: Option, + required: bool, + converter_options: std::collections::HashMap, + ty: Type, } impl DictionaryField { - pub fn get_name(&self) -> Ident { + fn get_name(&self) -> Ident { Ident::new( &self .rename @@ -105,7 +277,7 @@ impl TryFrom for DictionaryField { } #[allow(dead_code)] -pub enum DictionaryFieldArgument { +enum DictionaryFieldArgument { Default { name_token: kw::default, eq_token: Token![=], diff --git a/ops/webidl/enum.rs b/ops/webidl/enum.rs index 71f3bb8a2..3f0f06187 100644 --- a/ops/webidl/enum.rs +++ b/ops/webidl/enum.rs @@ -2,16 +2,50 @@ use super::kw; use proc_macro2::Ident; +use proc_macro2::TokenStream; +use quote::quote; use syn::parse::Parse; use syn::parse::ParseStream; use syn::punctuated::Punctuated; use syn::spanned::Spanned; +use syn::DataEnum; use syn::Error; use syn::LitStr; use syn::Token; use syn::Variant; -pub fn get_variant_name(value: Variant) -> Result<(String, Ident), Error> { +pub fn get_body( + ident_string: String, + data: DataEnum, +) -> Result { + let variants = data + .variants + .into_iter() + .map(get_variant_name) + .collect::, _>>()?; + + let variants = variants + .into_iter() + .map(|(name, ident)| quote!(#name => Ok(Self::#ident))) + .collect::>(); + + Ok(quote! { + let Ok(str) = __value.try_cast::<::deno_core::v8::String>() else { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"), + )); + }; + + match str.to_rust_string_lossy(__scope).as_str() { + #(#variants),*, + s => Err(::deno_core::webidl::WebIdlError::new(__prefix, &__context, ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { converter: #ident_string, variant: s.to_string() })) + } + }) +} + +fn get_variant_name(value: Variant) -> Result<(String, Ident), Error> { let mut rename: Option = None; if !value.fields.is_empty() { @@ -45,7 +79,7 @@ pub fn get_variant_name(value: Variant) -> Result<(String, Ident), Error> { } #[allow(dead_code)] -pub enum EnumVariantArgument { +enum EnumVariantArgument { Rename { name_token: kw::rename, eq_token: Token![=], diff --git a/ops/webidl/mod.rs b/ops/webidl/mod.rs index 212828843..bbbd41528 100644 --- a/ops/webidl/mod.rs +++ b/ops/webidl/mod.rs @@ -5,9 +5,7 @@ mod r#enum; use proc_macro2::Ident; use proc_macro2::TokenStream; -use quote::format_ident; use quote::quote; -use quote::ToTokens; use syn::parse::Parse; use syn::parse::ParseStream; use syn::parse2; @@ -16,7 +14,6 @@ use syn::Attribute; use syn::Data; use syn::DeriveInput; use syn::Error; -use syn::Fields; use syn::Token; pub fn webidl(item: TokenStream) -> Result { @@ -33,170 +30,10 @@ pub fn webidl(item: TokenStream) -> Result { let out = match input.data { Data::Struct(data) => match converter { ConverterType::Dictionary => { - let fields = match data.fields { - Fields::Named(fields) => fields, - Fields::Unnamed(_) => { - return Err(Error::new( - span, - "Unnamed fields are currently not supported", - )) - } - Fields::Unit => { - return Err(Error::new( - span, - "Unit fields are currently not supported", - )) - } - }; - - let mut fields = fields - .named - .into_iter() - .map(TryInto::try_into) - .collect::, Error>>( - )?; - fields.sort_by(|a, b| a.name.cmp(&b.name)); - - let names = fields - .iter() - .map(|field| field.name.clone()) - .collect::>(); - let v8_static_strings = fields - .iter() - .map(|field| { - let name = field.get_name(); - let new_ident = format_ident!("__v8_static_{name}"); - let name_str = name.to_string(); - quote!(#new_ident = #name_str) - }) - .collect::>(); - let v8_lazy_strings = fields - .iter() - .map(|field| { - let name = field.get_name(); - let v8_eternal_name = format_ident!("__v8_{name}_eternal"); - quote! { - static #v8_eternal_name: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); - } - }) - .collect::>(); - - let fields = fields.into_iter().map(|field| { - let name = field.get_name(); - let string_name = name.to_string(); - let original_name = field.name; - let v8_static_name = format_ident!("__v8_static_{name}"); - let v8_eternal_name = format_ident!("__v8_{name}_eternal"); - - let required_or_default = if field.required && field.default_value.is_none() { - quote! { - return Err(::deno_core::webidl::WebIdlError::new( - __prefix, - &__context, - ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { - converter: #ident_string, - key: #string_name, - }, - )); - } - } else if let Some(default) = field.default_value { - default.to_token_stream() - } else { - quote! { None } - }; - - let val = if field.required { - quote!(val) - } else { - quote!(Some(val)) - }; - - let options = if field.converter_options.is_empty() { - quote!(Default::default()) - } else { - let inner = field.converter_options - .into_iter() - .map(|(k, v)| quote!(#k: #v)) - .collect::>(); - - let ty = field.ty; - - // Type-alias to workaround https://github.com/rust-lang/rust/issues/86935 - quote! { - { - type Alias<'a> = <#ty as ::deno_core::webidl::WebIdlConverter<'a>>::Options; - Alias { - #(#inner),*, - ..Default::default() - } - } - } - }; + let (body, v8_static_strings, v8_lazy_strings) = + dictionary::get_body(ident_string, span, data)?; - let new_context = format!("'{string_name}' of '{ident_string}'"); - - quote! { - let #original_name = { - let __key = #v8_eternal_name - .with(|__eternal| { - if let Some(__key) = __eternal.get(__scope) { - Ok(__key) - } else { - let __key = #v8_static_name - .v8_string(__scope) - .map_err(|e| ::deno_core::webidl::WebIdlError::other(__prefix.clone(), &__context, e))?; - __eternal.set(__scope, __key); - Ok(__key) - } - })? - .into(); - - if let Some(__value) = __obj.as_ref() - .and_then(|__obj| __obj.get(__scope, __key)) - .and_then(|__value| { - if __value.is_undefined() { - None - } else { - Some(__value) - } - }) { - let val = ::deno_core::webidl::WebIdlConverter::convert( - __scope, - __value, - __prefix.clone(), - || format!("{} ({})", #new_context, __context()).into(), - &#options, - )?; - #val - } else { - #required_or_default - } - }; - } - }).collect::>(); - - let implementation = create_impl( - ident, - quote! { - let __obj: Option<::deno_core::v8::Local<::deno_core::v8::Object>> = if __value.is_undefined() || __value.is_null() { - None - } else { - if let Ok(obj) = __value.try_into() { - Some(obj) - } else { - return Err(::deno_core::webidl::WebIdlError::new( - __prefix, - &__context, - ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("dictionary") - )); - } - }; - - #(#fields)* - - Ok(Self { #(#names),* }) - }, - ); + let implementation = create_impl(ident, body); quote! { ::deno_core::v8_static_strings! { @@ -222,34 +59,7 @@ pub fn webidl(item: TokenStream) -> Result { )); } ConverterType::Enum => { - let variants = data - .variants - .into_iter() - .map(r#enum::get_variant_name) - .collect::, _>>()?; - - let variants = variants - .into_iter() - .map(|(name, ident)| quote!(#name => Ok(Self::#ident))) - .collect::>(); - - create_impl( - ident, - quote! { - let Ok(str) = __value.try_cast::<::deno_core::v8::String>() else { - return Err(::deno_core::webidl::WebIdlError::new( - __prefix, - &__context, - ::deno_core::webidl::WebIdlErrorKind::ConvertToConverterType("enum"), - )); - }; - - match str.to_rust_string_lossy(__scope).as_str() { - #(#variants),*, - s => Err(::deno_core::webidl::WebIdlError::new(__prefix, &__context, ::deno_core::webidl::WebIdlErrorKind::InvalidEnumVariant { converter: #ident_string, variant: s.to_string() })) - } - }, - ) + create_impl(ident, r#enum::get_body(ident_string, data)?) } }, Data::Union(_) => return Err(Error::new(span, "Unions are not supported")), diff --git a/ops/webidl/test_cases/dict.out b/ops/webidl/test_cases/dict.out index f679cdf61..2b777be62 100644 --- a/ops/webidl/test_cases/dict.out +++ b/ops/webidl/test_cases/dict.out @@ -1,6 +1,6 @@ ::deno_core::v8_static_strings! { __v8_static_a = "a", __v8_static_b = "b", __v8_static_c = "c", __v8_static_e = "e", - __v8_static_f = "f" + __v8_static_f = "f", __v8_static_g = "g" } thread_local! { static __v8_a_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); @@ -8,6 +8,7 @@ thread_local! { static __v8_c_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); static __v8_e_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); static __v8_f_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); + static __v8_g_eternal: ::deno_core::v8::Eternal<::deno_core::v8::String> = ::deno_core::v8::Eternal::empty(); } impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { type Options = (); @@ -269,6 +270,52 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { ); } }; - Ok(Self { a, b, c, d, f }) + let g = { + let __key = __v8_g_eternal + .with(|__eternal| { + if let Some(__key) = __eternal.get(__scope) { + Ok(__key) + } else { + let __key = __v8_static_g + .v8_string(__scope) + .map_err(|e| ::deno_core::webidl::WebIdlError::other( + __prefix.clone(), + &__context, + e, + ))?; + __eternal.set(__scope, __key); + Ok(__key) + } + })? + .into(); + if let Some(__value) = __obj + .as_ref() + .and_then(|__obj| __obj.get(__scope, __key)) + .and_then(|__value| { + if __value.is_undefined() { None } else { Some(__value) } + }) + { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", "'g' of 'Dict'", __context()).into(), + &Default::default(), + )?; + val + } else { + return Err( + ::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: "Dict", + key: "g", + }, + ), + ); + } + }; + Ok(Self { a, b, c, d, f, g }) } } diff --git a/ops/webidl/test_cases/enum.out b/ops/webidl/test_cases/enum.out index 32f442708..dc09959d4 100644 --- a/ops/webidl/test_cases/enum.out +++ b/ops/webidl/test_cases/enum.out @@ -22,6 +22,7 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Enumeration { match str.to_rust_string_lossy(__scope).as_str() { "foo-bar" => Ok(Self::FooBar), "baz" => Ok(Self::Baz), + "hello" => Ok(Self::World), s => { Err( ::deno_core::webidl::WebIdlError::new( From 666f4db09d77787d73de69c43db9bfb812c21909 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 12:31:50 +0100 Subject: [PATCH 31/33] fix --- core/webidl.rs | 8 +- ops/webidl/dictionary.rs | 129 ++++++++++++++++++++++----------- ops/webidl/test_cases/dict.out | 35 +++------ 3 files changed, 101 insertions(+), 71 deletions(-) diff --git a/core/webidl.rs b/core/webidl.rs index 8e06d753d..ffa4217a2 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -1308,7 +1308,7 @@ mod tests { #[webidl(default = Some(3))] c: Option, #[webidl(rename = "e")] - d: u32, + d: u16, f: HashMap, #[webidl(required)] g: Option, @@ -1318,7 +1318,7 @@ mod tests { let val = runtime .execute_script( "", - "({ a: 1, b: [500000], e: 2, f: { 'foo': 1 }, g: undefined })", + "({ a: 1, b: [70000], e: 70000, f: { 'foo': 1 }, g: undefined })", ) .unwrap(); @@ -1337,9 +1337,9 @@ mod tests { converted.unwrap(), Dict { a: 1, - b: vec![500], + b: vec![65535], c: Some(3), - d: 2, + d: 4464, f: HashMap::from([(String::from("foo"), 1)]), g: None, } diff --git a/ops/webidl/dictionary.rs b/ops/webidl/dictionary.rs index e53b37f6a..8beae3e3b 100644 --- a/ops/webidl/dictionary.rs +++ b/ops/webidl/dictionary.rs @@ -77,29 +77,6 @@ pub fn get_body( let v8_static_name = format_ident!("__v8_static_{name}"); let v8_eternal_name = format_ident!("__v8_{name}_eternal"); - let required_or_default = if field.required && field.default_value.is_none() { - quote! { - return Err(::deno_core::webidl::WebIdlError::new( - __prefix, - &__context, - ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { - converter: #ident_string, - key: #string_name, - }, - )); - } - } else if let Some(default) = field.default_value { - default.to_token_stream() - } else { - quote! { None } - }; - - let val = if field.required { - quote!(val) - } else { - quote!(Some(val)) - }; - let options = if field.converter_options.is_empty() { quote!(Default::default()) } else { @@ -124,6 +101,67 @@ pub fn get_body( let new_context = format!("'{string_name}' of '{ident_string}'"); + let convert = quote! { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", #new_context, __context()).into(), + &#options, + )?; + }; + + let convert_body = if field.option_is_required { + quote! { + if __value.is_undefined() { + None + } else { + #convert + Some(val) + } + } + } else { + let val = if field.is_option { + quote!(Some(val)) + } else { + quote!(val) + }; + + quote! { + #convert + #val + } + }; + + let undefined_as_none = if field.default_value.is_some() { + quote! { + .and_then(|__value| { + if __value.is_undefined() { + None + } else { + Some(__value) + } + }) + } + } else { + quote!() + }; + + let required_or_default = if let Some(default) = field.default_value { + default.to_token_stream() + } else { + quote! { + return Err(::deno_core::webidl::WebIdlError::new( + __prefix, + &__context, + ::deno_core::webidl::WebIdlErrorKind::DictionaryCannotConvertKey { + converter: #ident_string, + key: #string_name, + }, + )); + } + }; + quote! { let #original_name = { let __key = #v8_eternal_name @@ -140,23 +178,8 @@ pub fn get_body( })? .into(); - if let Some(__value) = __obj.as_ref() - .and_then(|__obj| __obj.get(__scope, __key)) - .and_then(|__value| { - if __value.is_undefined() { - None - } else { - Some(__value) - } - }) { - let val = ::deno_core::webidl::WebIdlConverter::convert( - __scope, - __value, - __prefix.clone(), - || format!("{} ({})", #new_context, __context()).into(), - &#options, - )?; - #val + if let Some(__value) = __obj.as_ref().and_then(|__obj| __obj.get(__scope, __key))#undefined_as_none { + #convert_body } else { #required_or_default } @@ -192,7 +215,8 @@ struct DictionaryField { name: Ident, rename: Option, default_value: Option, - required: bool, + is_option: bool, + option_is_required: bool, converter_options: std::collections::HashMap, ty: Type, } @@ -215,7 +239,7 @@ impl TryFrom for DictionaryField { let span = value.span(); let mut default_value: Option = None; let mut rename: Option = None; - let mut required = false; + let mut option_is_required = false; let mut converter_options = std::collections::HashMap::new(); for attr in value.attrs { @@ -233,7 +257,9 @@ impl TryFrom for DictionaryField { DictionaryFieldArgument::Rename { value, .. } => { rename = Some(value.value()) } - DictionaryFieldArgument::Required { .. } => required = true, + DictionaryFieldArgument::Required { .. } => { + option_is_required = true + } } } } else if attr.path().is_ident("options") { @@ -264,12 +290,27 @@ impl TryFrom for DictionaryField { false }; + if option_is_required && !is_option { + return Err(Error::new( + span, + "Required option can only be used with an Option", + )); + } + + if option_is_required && default_value.is_some() { + return Err(Error::new( + span, + "Required option and default value cannot be used together", + )); + } + Ok(Self { span, name: value.ident.unwrap(), rename, default_value, - required: required || !is_option, + is_option, + option_is_required, converter_options, ty: value.ty, }) diff --git a/ops/webidl/test_cases/dict.out b/ops/webidl/test_cases/dict.out index 2b777be62..04f4cfc33 100644 --- a/ops/webidl/test_cases/dict.out +++ b/ops/webidl/test_cases/dict.out @@ -62,9 +62,6 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { if let Some(__value) = __obj .as_ref() .and_then(|__obj| __obj.get(__scope, __key)) - .and_then(|__value| { - if __value.is_undefined() { None } else { Some(__value) } - }) { let val = ::deno_core::webidl::WebIdlConverter::convert( __scope, @@ -108,9 +105,6 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { if let Some(__value) = __obj .as_ref() .and_then(|__obj| __obj.get(__scope, __key)) - .and_then(|__value| { - if __value.is_undefined() { None } else { Some(__value) } - }) { let val = ::deno_core::webidl::WebIdlConverter::convert( __scope, @@ -199,9 +193,6 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { if let Some(__value) = __obj .as_ref() .and_then(|__obj| __obj.get(__scope, __key)) - .and_then(|__value| { - if __value.is_undefined() { None } else { Some(__value) } - }) { let val = ::deno_core::webidl::WebIdlConverter::convert( __scope, @@ -245,9 +236,6 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { if let Some(__value) = __obj .as_ref() .and_then(|__obj| __obj.get(__scope, __key)) - .and_then(|__value| { - if __value.is_undefined() { None } else { Some(__value) } - }) { let val = ::deno_core::webidl::WebIdlConverter::convert( __scope, @@ -291,18 +279,19 @@ impl<'a> ::deno_core::webidl::WebIdlConverter<'a> for Dict { if let Some(__value) = __obj .as_ref() .and_then(|__obj| __obj.get(__scope, __key)) - .and_then(|__value| { - if __value.is_undefined() { None } else { Some(__value) } - }) { - let val = ::deno_core::webidl::WebIdlConverter::convert( - __scope, - __value, - __prefix.clone(), - || format!("{} ({})", "'g' of 'Dict'", __context()).into(), - &Default::default(), - )?; - val + if __value.is_undefined() { + None + } else { + let val = ::deno_core::webidl::WebIdlConverter::convert( + __scope, + __value, + __prefix.clone(), + || format!("{} ({})", "'g' of 'Dict'", __context()).into(), + &Default::default(), + )?; + Some(val) + } } else { return Err( ::deno_core::webidl::WebIdlError::new( From ce9c21ea061d3ef0326ffe02c81c365a9cac73d2 Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 13:01:20 +0100 Subject: [PATCH 32/33] fix miri for webidl tests --- core/webidl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/webidl.rs b/core/webidl.rs index ffa4217a2..e9174f188 100644 --- a/core/webidl.rs +++ b/core/webidl.rs @@ -764,7 +764,7 @@ impl<'a> WebIdlConverter<'a> for ByteString { // Array buffer types // ArrayBufferView -#[cfg(test)] +#[cfg(all(test, not(miri)))] mod tests { use super::*; use crate::JsRuntime; From 21b1cd08df5f7fd8594fde17213a76707338856a Mon Sep 17 00:00:00 2001 From: crowlkats Date: Thu, 2 Jan 2025 13:32:08 +0100 Subject: [PATCH 33/33] fix --- ops/op2/test_cases/sync/webidl.out | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/op2/test_cases/sync/webidl.out b/ops/op2/test_cases/sync/webidl.out index bce19ff88..8d06ddb47 100644 --- a/ops/op2/test_cases/sync/webidl.out +++ b/ops/op2/test_cases/sync/webidl.out @@ -10,6 +10,7 @@ const fn op_webidl() -> ::deno_core::_ops::OpDecl { ::deno_core::__op_name_fast!(op_webidl), false, false, + false, 2usize as u8, false, Self::v8_fn_ptr as _, @@ -26,7 +27,6 @@ const fn op_webidl() -> ::deno_core::_ops::OpDecl { pub const fn name() -> &'static str { ::NAME } - #[inline(always)] fn slow_function_impl<'s>( info: &'s deno_core::v8::FunctionCallbackInfo, ) -> usize {