feat(webidl): support EnforceRange signed long long

This commit is contained in:
ldm0
2026-10-10 18:34:31 +08:00
committed by Donough Liu
parent f1e5bf23a3
commit 2d38fd438d
5 changed files with 132 additions and 5 deletions
+5
View File
@@ -20,6 +20,7 @@ pub(crate) enum ConverterKind {
ClampedUnsignedShort,
UnsignedLong,
EnforceRangeUnsignedLong,
EnforceRangeLongLong,
UnsignedLongLong,
EnforceRangeUnsignedLongLong,
Float,
@@ -54,6 +55,7 @@ impl ConverterKind {
"clamped_unsigned_short" => Ok(Self::ClampedUnsignedShort),
"unsigned_long" => Ok(Self::UnsignedLong),
"enforce_range_unsigned_long" => Ok(Self::EnforceRangeUnsignedLong),
"enforce_range_long_long" => Ok(Self::EnforceRangeLongLong),
"unsigned_long_long" => Ok(Self::UnsignedLongLong),
"enforce_range_unsigned_long_long" => Ok(Self::EnforceRangeUnsignedLongLong),
"float" => Ok(Self::Float),
@@ -87,6 +89,7 @@ impl ConverterKind {
Self::EnforceRangeUnsignedLong => {
quote!(::moli_webidl::EnforceRangeUnsignedLong)
}
Self::EnforceRangeLongLong => quote!(::moli_webidl::EnforceRangeLongLong),
Self::UnsignedLongLong => quote!(::moli_webidl::UnsignedLongLong),
Self::EnforceRangeUnsignedLongLong => {
quote!(::moli_webidl::EnforceRangeUnsignedLongLong)
@@ -129,6 +132,7 @@ impl ConverterKind {
Self::EnforceRangeUnsignedLong => {
quote!(::moli_webidl::EnforceRangeUnsignedLong(#expr))
}
Self::EnforceRangeLongLong => quote!(::moli_webidl::EnforceRangeLongLong(#expr)),
Self::UnsignedLongLong => quote!(::moli_webidl::UnsignedLongLong(#expr)),
Self::EnforceRangeUnsignedLongLong => {
quote!(::moli_webidl::EnforceRangeUnsignedLongLong(#expr))
@@ -160,6 +164,7 @@ impl ConverterKind {
| Self::ClampedUnsignedShort
| Self::UnsignedLong
| Self::EnforceRangeUnsignedLong
| Self::EnforceRangeLongLong
| Self::UnsignedLongLong
| Self::EnforceRangeUnsignedLongLong
| Self::Float
+32 -5
View File
@@ -1,10 +1,10 @@
use crate::{
Boolean, BufferSource, ByteString, ClampedUnsignedShort, Context, Dictionary, DomString,
DomString16, Double, EnforceRangeLong, EnforceRangeUnsignedLong, EnforceRangeUnsignedLongLong,
EnumValue, Long, Record, Sequence, StringOptions, UnrestrictedDouble, UnsignedLong,
UnsignedLongLong, UnsignedShort, UsvString, WebIdlArguments, WebIdlConverter, WebIdlDictionary,
WebIdlEnum, WebIdlError, WebIdlErrorKind, dictionary_value, is_nullish, property_result,
symbol_property_result, throw_error, throw_type_error,
DomString16, Double, EnforceRangeLong, EnforceRangeLongLong, EnforceRangeUnsignedLong,
EnforceRangeUnsignedLongLong, EnumValue, Long, Record, Sequence, StringOptions,
UnrestrictedDouble, UnsignedLong, UnsignedLongLong, UnsignedShort, UsvString, WebIdlArguments,
WebIdlConverter, WebIdlDictionary, WebIdlEnum, WebIdlError, WebIdlErrorKind, dictionary_value,
is_nullish, property_result, symbol_property_result, throw_error, throw_type_error,
};
impl<'s> WebIdlConverter<'s> for DomString {
@@ -331,6 +331,20 @@ impl<'s> WebIdlConverter<'s> for UnsignedLongLong {
}
}
impl<'s> WebIdlConverter<'s> for EnforceRangeLongLong {
type Options = ();
fn convert(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
context: Context,
_options: &Self::Options,
) -> Result<Self, WebIdlError> {
let value = number_value(scope, value, context, "[EnforceRange] long long")?;
enforce_range_long_long(value, context).map(Self)
}
}
impl<'s> WebIdlConverter<'s> for EnforceRangeUnsignedLongLong {
type Options = ();
@@ -1402,6 +1416,19 @@ fn unsigned_long_long(value: f64) -> u64 {
}
}
fn enforce_range_long_long(value: f64, context: Context) -> Result<i64, WebIdlError> {
// ConvertToInt uses the exact JavaScript integer range for 64-bit types.
let value = value.trunc();
if !value.is_finite() || !(-9_007_199_254_740_991.0..=9_007_199_254_740_991.0).contains(&value)
{
return Err(WebIdlError::new(
context,
WebIdlErrorKind::CannotConvert("[EnforceRange] long long"),
));
}
Ok(value as i64)
}
fn enforce_range_unsigned_long_long(value: f64, context: Context) -> Result<u64, WebIdlError> {
// ConvertToInt uses the exact JavaScript integer range for 64-bit types.
let value = value.trunc();
+1
View File
@@ -98,5 +98,6 @@ pub use types::{
UnsignedShort, UsvString,
};
pub use types::EnforceRangeLongLong;
/// Restricted WebIDL float represented as a finite binary32 value.
pub use types::Float;
+8
View File
@@ -316,6 +316,14 @@ impl From<EnforceRangeUnsignedLongLong> for u64 {
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct EnforceRangeLongLong(pub i64);
impl From<EnforceRangeLongLong> for i64 {
fn from(value: EnforceRangeLongLong) -> Self {
value.0
}
}
#[derive(Debug, Default)]
pub struct StringOptions {
pub treat_null_as_empty_string: bool,
+86
View File
@@ -0,0 +1,86 @@
use moli_webidl::{WebIdlArgs, WebIdlDictionary};
#[derive(WebIdlArgs)]
#[webidl(prefix = "Test.convert")]
struct RequiredArgs {
#[webidl(required, converter = "enforce_range_long_long")]
value: i64,
}
#[derive(WebIdlDictionary)]
#[webidl(prefix = "TestOptions")]
struct TestOptions {
#[webidl(default = -7, converter = "enforce_range_long_long")]
value: i64,
}
#[derive(WebIdlArgs)]
#[webidl(prefix = "Test.dictionary")]
struct DictionaryArgs {
#[webidl(dictionary)]
options: TestOptions,
}
fn required<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s>,
) {
if let Some(parsed) = moli_webidl::parse_args::<RequiredArgs>(scope, &args) {
rv.set(v8::Number::new(scope, parsed.value as f64).into());
}
}
fn dictionary<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'s>,
) {
if let Some(parsed) = moli_webidl::parse_args::<DictionaryArgs>(scope, &args) {
rv.set(v8::Number::new(scope, parsed.options.value as f64).into());
}
}
#[test]
fn numeric_conversion_preserves_boundaries_defaults_and_callee_realm_errors() {
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 required = v8::Function::new(scope, required).unwrap();
let dictionary = v8::Function::new(scope, dictionary).unwrap();
let key = v8::String::new(scope, "TypeError").unwrap();
let error = callee.global(scope).get(scope, key.into()).unwrap();
[
("required", required.into()),
("dictionary", dictionary.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=(value,message)=>{if(!value)throw Error(message);};
for(const convert of [required,value=>dictionary({value})]) {
for(const [value,expected] of [[0,0],[-0,0],[3.9,3],[-3.9,-3],[9007199254740991,9007199254740991],[-9007199254740991,-9007199254740991],['-7.9',-7],[null,0],[true,1]])assert(Object.is(convert(value),expected),'boundary '+String(value));
for(const value of [NaN,Infinity,-Infinity,9007199254740992,-9007199254740992,Symbol('number'),1n]) {let error;try{convert(value);}catch(caught){error=caught;}assert(error instanceof CalleeTypeError&&!(error instanceof TypeError),'callee TypeError');}
let reads=0;assert(convert({[Symbol.toPrimitive](hint){reads++;assert(hint==='number','numeric hint');return 4;}})===4&&reads===1,'one conversion');
const sentinel={};let error;try{convert({valueOf(){throw sentinel;}});}catch(caught){error=caught;}assert(error===sentinel,'exception identity');
}
assert(dictionary()===-7&&dictionary(null)===-7&&dictionary({})===-7,'dictionary defaults');
const sentinel={};let error;try{dictionary({get value(){throw sentinel;}});}catch(caught){error=caught;}assert(error===sentinel,'getter exception');
try{required();}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("numeric conversion regression")
.is_true()
);
}