feat(webidl): support signed short argument conversion

This commit is contained in:
ldm0
2026-10-05 05:24:54 +08:00
committed by Donough Liu
parent f4ee76bffa
commit 815ab6bbcd
5 changed files with 127 additions and 2 deletions
+8
View File
@@ -15,6 +15,7 @@ pub(crate) enum ConverterKind {
CallbackInterface,
Long,
EnforceRangeLong,
Short,
UnsignedShort,
ClampedUnsignedShort,
UnsignedLong,
@@ -45,6 +46,7 @@ impl ConverterKind {
"callback_interface" => Ok(Self::CallbackInterface),
"long" => Ok(Self::Long),
"enforce_range_long" => Ok(Self::EnforceRangeLong),
"short" => Ok(Self::Short),
"unsigned_short" => Ok(Self::UnsignedShort),
"clamped_unsigned_short" => Ok(Self::ClampedUnsignedShort),
"unsigned_long" => Ok(Self::UnsignedLong),
@@ -74,6 +76,7 @@ impl ConverterKind {
Self::CallbackInterface => quote!(::moli_webidl::WebIdlCallbackInterface),
Self::Long => quote!(::moli_webidl::Long),
Self::EnforceRangeLong => quote!(::moli_webidl::EnforceRangeLong),
Self::Short => quote!(::moli_webidl::Short),
Self::UnsignedShort => quote!(::moli_webidl::UnsignedShort),
Self::ClampedUnsignedShort => quote!(::moli_webidl::ClampedUnsignedShort),
Self::UnsignedLong => quote!(::moli_webidl::UnsignedLong),
@@ -110,6 +113,7 @@ impl ConverterKind {
Self::CallbackFunction | Self::CallbackInterface => quote!(#expr),
Self::Long => quote!(::moli_webidl::Long(#expr)),
Self::EnforceRangeLong => quote!(::moli_webidl::EnforceRangeLong(#expr)),
Self::Short => quote!(::moli_webidl::Short(#expr)),
Self::UnsignedShort => quote!(::moli_webidl::UnsignedShort(#expr)),
Self::ClampedUnsignedShort => {
quote!(::moli_webidl::ClampedUnsignedShort(#expr))
@@ -141,6 +145,7 @@ impl ConverterKind {
| Self::UsvString
| Self::Long
| Self::EnforceRangeLong
| Self::Short
| Self::UnsignedShort
| Self::ClampedUnsignedShort
| Self::UnsignedLong
@@ -242,6 +247,9 @@ fn infer_converter_kind(ty: &Type) -> Option<ConverterKind> {
if is_type_ident(ty, "u16") {
return Some(ConverterKind::UnsignedShort);
}
if is_type_ident(ty, "i16") {
return Some(ConverterKind::Short);
}
if is_type_ident(ty, "u64") {
return Some(ConverterKind::UnsignedLongLong);
}
+15
View File
@@ -261,6 +261,21 @@ impl<'s> WebIdlConverter<'s> for EnforceRangeUnsignedLong {
}
}
impl<'s> WebIdlConverter<'s> for crate::Short {
type Options = ();
fn convert(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
context: Context,
_options: &Self::Options,
) -> Result<Self, WebIdlError> {
// Both 16-bit integer types truncate and reduce modulo 2^16; signed
// short then interprets the high bit as a negative value.
number_value(scope, value, context, "short").map(|value| Self(unsigned_short(value) as i16))
}
}
impl<'s> WebIdlConverter<'s> for UnsignedShort {
type Options = ();
+2 -2
View File
@@ -64,8 +64,8 @@ pub use traits::{ParseOutcome, WebIdlArguments, WebIdlConverter, WebIdlDictionar
pub use types::{
Boolean, BufferSource, ByteString, ClampedUnsignedShort, DomString, DomString16, Double,
EnforceRangeLong, EnforceRangeUnsignedLong, EnforceRangeUnsignedLongLong, EnumValue,
EventListenerOptions, Long, Record, Sequence, StringOptions, UnrestrictedDouble, UnsignedLong,
UnsignedLongLong, UnsignedShort, UsvString,
EventListenerOptions, Long, Record, Sequence, Short, StringOptions, UnrestrictedDouble,
UnsignedLong, UnsignedLongLong, UnsignedShort, UsvString,
};
/// Restricted WebIDL float represented as a finite binary32 value.
+9
View File
@@ -227,6 +227,15 @@ impl From<EnforceRangeUnsignedLong> for u32 {
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Short(pub i16);
impl From<Short> for i16 {
fn from(value: Short) -> Self {
value.0
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct UnsignedShort(pub u16);
+93
View File
@@ -0,0 +1,93 @@
use moli_webidl::WebIdlArgs;
#[derive(WebIdlArgs)]
#[webidl(prefix = "Test.short")]
struct InferredShortArgs {
#[webidl(required)]
value: i16,
}
#[derive(WebIdlArgs)]
#[webidl(prefix = "Test.explicitShort")]
struct ExplicitShortArgs {
#[webidl(required, converter = "short")]
value: i16,
}
fn inferred_short<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s>,
) {
if let Some(parsed) = moli_webidl::parse_args::<InferredShortArgs>(scope, &args) {
rv.set(v8::Number::new(scope, f64::from(parsed.value)).into());
}
}
fn explicit_short<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s>,
) {
if let Some(parsed) = moli_webidl::parse_args::<ExplicitShortArgs>(scope, &args) {
rv.set(v8::Number::new(scope, f64::from(parsed.value)).into());
}
}
#[test]
fn signed_short_wraps_and_preserves_conversion_errors_in_the_callee_realm() {
moli_v8_test_util::ensure_v8();
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
let scope = &mut scope.init();
let callee = v8::Context::new(scope, Default::default());
let values = {
let scope = &mut v8::ContextScope::new(scope, callee);
let inferred = v8::Function::new(scope, inferred_short).unwrap();
let explicit = v8::Function::new(scope, explicit_short).unwrap();
let key = v8::String::new(scope, "TypeError").unwrap();
let error = callee.global(scope).get(scope, key.into()).unwrap();
[
("inferred", inferred.into()),
("explicit", explicit.into()),
("CalleeTypeError", error),
]
};
let caller = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, caller);
for (name, value) in values {
let key = v8::String::new(scope, name).unwrap();
assert_eq!(
caller.global(scope).set(scope, key.into(), value),
Some(true)
);
}
let source = v8::String::new(scope, r#"
(() => {
const assert = (ok, message) => { if (!ok) throw Error(message); };
const values = [[0,0],[-0,0],[3.9,3],[-3.9,-3],[32767,32767],
[32768,-32768],[65535,-1],[65536,0],[65537,1],[-65537,-1],
[4294967295,-1],[4294967297,1],[NaN,0],[Infinity,0],[-Infinity,0],
[undefined,0],[null,0],[true,1],['65535',-1]];
for (const convert of [inferred, explicit]) {
for (const [value, expected] of values) assert(Object.is(convert(value),expected),'modulo conversion '+value);
for (const value of [Symbol('number'), 1n]) {
let error;try { convert(value); } catch(caught) { error=caught; }
assert(error instanceof CalleeTypeError && !(error instanceof TypeError),'callee realm TypeError');
}
let reads=0;const value={[Symbol.toPrimitive](hint){reads++;assert(hint==='number','numeric hint');return 65535;}};
assert(convert(value)===-1&&reads===1,'one observable conversion');
const sentinel={};let error;try{convert({valueOf(){throw sentinel;}});}catch(caught){error=caught;}assert(error===sentinel,'exception identity');
try { convert(); } catch(caught) { error=caught; }assert(error instanceof CalleeTypeError,'required argument');
}
return true;
})()
"#).unwrap();
let script = v8::Script::compile(scope, source, None).unwrap();
assert!(
script
.run(scope)
.expect("signed short conversion matrix")
.is_true()
);
}