Skip to content

Commit

Permalink
feat(webidl): distinguish Nullable from Option, allow setting default…
Browse files Browse the repository at this point in the history
… value in webidl op2 macro attribute
  • Loading branch information
crowlKats committed Jan 3, 2025
1 parent b734c8e commit 9120351
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 30 deletions.
31 changes: 31 additions & 0 deletions core/cppgc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,34 @@ impl FunctionTemplateData {
.collect();
}
}

pub struct SameObject<T: GarbageCollected + 'static> {
cell: std::cell::OnceCell<v8::Global<v8::Object>>,
_phantom_data: std::marker::PhantomData<T>,
}
impl<T: GarbageCollected + 'static> SameObject<T> {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
cell: Default::default(),
_phantom_data: Default::default(),
}
}

pub fn get<F>(
&self,
scope: &mut v8::HandleScope,
f: F,
) -> v8::Global<v8::Object>
where
F: FnOnce() -> T,
{
self
.cell
.get_or_init(|| {
let obj = make_cppgc_object(scope, f());
v8::Global::new(scope, obj)
})
.clone()
}
}
103 changes: 88 additions & 15 deletions core/webidl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2018-2025 the Deno authors. MIT license.

use indexmap::IndexMap;
use std::borrow::Cow;
use std::collections::HashMap;
use v8::HandleScope;
use v8::Local;
use v8::Value;
Expand Down Expand Up @@ -82,6 +82,41 @@ pub enum WebIdlErrorKind {
Other(Box<dyn std::error::Error + Send + Sync + 'static>),
}

#[derive(Debug, Eq, PartialEq)]
pub enum Type {
Null,
Undefined,
Boolean,
Number,
String,
Symbol,
BigInt,
Object,
}

pub fn type_of<'a>(
scope: &mut HandleScope<'a>,
value: Local<'a, Value>,
) -> Type {
if value.is_null() {
return Type::Null;
}

#[allow(clippy::wildcard_in_or_patterns)]
match value.type_of(scope).to_rust_string_lossy(scope).as_str() {
"undefined" => Type::Undefined,
"boolean" => Type::Boolean,
"number" => Type::Number,
"string" => Type::String,
"symbol" => Type::Symbol,
"bigint" => Type::BigInt,
// Per ES spec, typeof returns an implementation-defined value that is not any of the existing ones for
// uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for
// such cases. So treat the default case as an object.
"object" | "function" | _ => Type::Object,
}
}

pub trait WebIdlConverter<'a>: Sized {
type Options: Default;

Expand All @@ -96,6 +131,31 @@ pub trait WebIdlConverter<'a>: Sized {
C: Fn() -> Cow<'static, str>;
}

// Option's None is treated as undefined. this behaviour differs from a nullable
// converter, as it doesn't treat null as None.
impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Option<T> {
type Options = T::Options;

fn convert<C>(
scope: &mut HandleScope<'a>,
value: Local<'a, Value>,
prefix: Cow<'static, str>,
context: C,
options: &Self::Options,
) -> Result<Self, WebIdlError>
where
C: Fn() -> Cow<'static, str>,
{
if value.is_undefined() {
Ok(None)
} else {
Ok(Some(WebIdlConverter::convert(
scope, value, prefix, context, options,
)?))
}
}
}

// any converter
impl<'a> WebIdlConverter<'a> for Local<'a, Value> {
type Options = ();
Expand All @@ -114,8 +174,21 @@ impl<'a> WebIdlConverter<'a> for Local<'a, Value> {
}
}

// nullable converter
impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Option<T> {
#[derive(Debug, Eq, PartialEq)]
pub enum Nullable<T> {
Value(T),
Null,
}
impl<T> Nullable<T> {
fn into_option(self) -> Option<T> {
match self {
Nullable::Value(v) => Some(v),
Nullable::Null => None,
}
}
}

impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Nullable<T> {
type Options = T::Options;

fn convert<C>(
Expand All @@ -129,9 +202,9 @@ impl<'a, T: WebIdlConverter<'a>> WebIdlConverter<'a> for Option<T> {
C: Fn() -> Cow<'static, str>,
{
if value.is_null_or_undefined() {
Ok(None)
Ok(Self::Null)
} else {
Ok(Some(WebIdlConverter::convert(
Ok(Self::Value(WebIdlConverter::convert(
scope, value, prefix, context, options,
)?))
}
Expand Down Expand Up @@ -275,7 +348,7 @@ impl<
'a,
K: WebIdlConverter<'a> + Eq + std::hash::Hash,
V: WebIdlConverter<'a>,
> WebIdlConverter<'a> for HashMap<K, V>
> WebIdlConverter<'a> for IndexMap<K, V>
{
type Options = V::Options;

Expand Down Expand Up @@ -319,7 +392,7 @@ impl<
return Ok(Default::default());
};

let mut out = HashMap::with_capacity(keys.length() as _);
let mut out = IndexMap::with_capacity(keys.length() as _);

for i in 0..keys.length() {
let key = keys.get_index(scope, i).unwrap();
Expand Down Expand Up @@ -1254,24 +1327,24 @@ mod tests {
let scope = &mut runtime.handle_scope();

let val = v8::undefined(scope);
let converted = Option::<u8>::convert(
let converted = Nullable::<u8>::convert(
scope,
val.into(),
"prefix".into(),
|| "context".into(),
&Default::default(),
);
assert_eq!(converted.unwrap(), None);
assert_eq!(converted.unwrap(), Nullable::Null);

let val = v8::Number::new(scope, 1.0);
let converted = Option::<u8>::convert(
let converted = Nullable::<u8>::convert(
scope,
val.into(),
"prefix".into(),
|| "context".into(),
&Default::default(),
);
assert_eq!(converted.unwrap(), Some(1));
assert_eq!(converted.unwrap(), Nullable::Value(1));
}

#[test]
Expand All @@ -1284,7 +1357,7 @@ mod tests {
let obj = v8::Object::new(scope);
obj.set(scope, key.into(), val.into());

let converted = HashMap::<String, u8>::convert(
let converted = IndexMap::<String, u8>::convert(
scope,
obj.into(),
"prefix".into(),
Expand All @@ -1293,7 +1366,7 @@ mod tests {
);
assert_eq!(
converted.unwrap(),
HashMap::from([(String::from("foo"), 1)])
IndexMap::from([(String::from("foo"), 1)])
);
}

Expand All @@ -1309,7 +1382,7 @@ mod tests {
c: Option<u32>,
#[webidl(rename = "e")]
d: u16,
f: HashMap<String, u32>,
f: IndexMap<String, u32>,
#[webidl(required)]
g: Option<u32>,
}
Expand Down Expand Up @@ -1340,7 +1413,7 @@ mod tests {
b: vec![65535],
c: Some(3),
d: 4464,
f: HashMap::from([(String::from("foo"), 1)]),
f: IndexMap::from([(String::from("foo"), 1)]),
g: None,
}
);
Expand Down
4 changes: 2 additions & 2 deletions ops/op2/dispatch_fast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,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),
Expand Down Expand Up @@ -958,7 +958,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, _)
Expand Down
28 changes: 25 additions & 3 deletions ops/op2/dispatch_slow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ use super::V8MappingError;
use super::V8SignatureMappingError;
use proc_macro2::Ident;
use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
use syn::Type;
use quote::{format_ident, ToTokens};
use syn::{parse2, Type};

pub(crate) fn generate_dispatch_slow_call(
generator_state: &mut GeneratorState,
Expand Down Expand Up @@ -652,7 +652,7 @@ pub fn from_arg(
};
}
}
Arg::WebIDL(ty, options) => {
Arg::WebIDL(ty, options, default) => {
*needs_scope = true;
let ty =
syn::parse_str::<syn::Type>(ty).expect("Failed to reparse state type");
Expand All @@ -678,7 +678,29 @@ pub fn from_arg(
}
};

let default = if let Some(default) = default {
let tokens = default.0.to_token_stream();
let default = if let Ok(lit) = parse2::<syn::LitStr>(tokens) {
quote! {
v8::String::new(&mut #scope, #lit).unwrap()
}
} else {
return Err("unsupported WebIDL default value");
};

quote! {
let #arg_ident = if #arg_ident.is_undefined() {
#default.into()
} else {
#arg_ident
};
}
} else {
quote!()
};

quote! {
#default
let #arg_ident = match <#ty as deno_core::webidl::WebIdlConverter>::convert(
&mut #scope,
#arg_ident,
Expand Down
32 changes: 22 additions & 10 deletions ops/op2/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,15 @@ impl PartialEq for WebIDLPairs {
}
impl Eq for WebIDLPairs {}

#[derive(Clone, Debug)]
pub struct WebIDLDefault(pub syn::Expr);
impl PartialEq for WebIDLDefault {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for WebIDLDefault {}

/// 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.
Expand Down Expand Up @@ -290,7 +299,7 @@ pub enum Arg {
OptionCppGcResource(String),
FromV8(String),
ToV8(String),
WebIDL(String, Vec<WebIDLPairs>),
WebIDL(String, Vec<WebIDLPairs>, Option<WebIDLDefault>),
VarArgs,
}

Expand Down Expand Up @@ -754,7 +763,10 @@ pub enum AttributeModifier {
/// #[from_v8] for types that impl `FromV8`
FromV8,
/// #[webidl], for types that impl `WebIdlConverter`
WebIDL(Vec<WebIDLPairs>),
WebIDL {
options: Vec<WebIDLPairs>,
default: Option<WebIDLDefault>,
},
/// #[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,
Expand Down Expand Up @@ -788,7 +800,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",
Expand Down Expand Up @@ -1247,8 +1259,8 @@ fn parse_attribute(
(#[bigint]) => Some(AttributeModifier::Bigint),
(#[number]) => Some(AttributeModifier::Number),
(#[serde]) => Some(AttributeModifier::Serde),
(#[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())),
(#[webidl]) => Some(AttributeModifier::WebIDL { options: vec![],default: None }),
(#[webidl($(default = $default:expr)?$($(,)? options($($key:ident = $value:literal),*))?)]) => Some(AttributeModifier::WebIDL { options: key.map(|key| key.into_iter().zip(value.unwrap().into_iter()).map(|v| WebIDLPairs(v.0, v.1)).collect()).unwrap_or_default(), default: default.map(WebIDLDefault) }),
(#[smi]) => Some(AttributeModifier::Smi),
(#[string]) => Some(AttributeModifier::String(StringMode::Default)),
(#[string(onebyte)]) => Some(AttributeModifier::String(StringMode::OneByte)),
Expand Down Expand Up @@ -1577,20 +1589,20 @@ pub(crate) fn parse_type(
AttributeModifier::Serde
| AttributeModifier::FromV8
| AttributeModifier::ToV8
| AttributeModifier::WebIDL(_) => {
let make_arg: Box<dyn Fn(String) -> Arg> = match primary {
| AttributeModifier::WebIDL { .. } => {
let make_arg: Box<dyn Fn(String) -> Arg> = match &primary {
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()))
AttributeModifier::WebIDL { options, default } => {
Box::new(move |s| Arg::WebIDL(s, options.clone(), default.clone()))
}
_ => unreachable!(),
};
match ty {
Type::Tuple(of) => return Ok(make_arg(stringify_token(of))),
Type::Path(of) => {
if !matches!(primary, AttributeModifier::WebIDL(_))
if !matches!(primary, AttributeModifier::WebIDL { .. })
&& better_alternative_exists(position, of)
{
return Err(ArgError::InvalidAttributeType(
Expand Down

0 comments on commit 9120351

Please sign in to comment.