perf(webidl): install template accessors from static metadata

Extend metadata installation to consecutive statically named accessors
without callback data. Preserve run boundaries, receiver checks and
getter/setter names and lengths, with native callback mappings resolved
during installation. Cover mixed method/accessor order and generic getters.

This step passes focused WebIDL clippy and 98 binding/derive tests.

Validation on the unchanged final source tree: cargo fmt --all;
cargo clippy --workspace --all-targets --all-features -- -D warnings;
cargo nextest run --no-fail-fast (20,505 passed, 18 existing skips).
This commit is contained in:
ldm0
2026-10-21 23:16:23 +08:00
committed by Donough Liu
parent e1d3638300
commit 31ee126de3
3 changed files with 137 additions and 4 deletions
+75 -1
View File
@@ -552,6 +552,70 @@ fn static_template_method(
}))
}
fn flush_template_accessors(
output: &mut Vec<proc_macro2::TokenStream>,
accessors: &mut Vec<proc_macro2::TokenStream>,
) {
if !accessors.is_empty() {
let accessors = std::mem::take(accessors);
output.push(quote! {
::moli_webapi_declare::__private::install_function_template_accessors(
scope, prototype, const { &[#(#accessors),*] },
);
});
}
}
fn static_template_accessor(
field: &Field,
attrs: &FieldAttrs,
callback_mappings: &mut CallbackMappings,
) -> Result<Option<proc_macro2::TokenStream>, Error> {
if !matches!(attrs.kind, FieldKind::AccessorProperty)
|| attrs.data.is_some()
|| attrs.setter_data.is_some()
|| attrs.symbol.is_some()
|| attrs.getter_value.is_some()
{
return Ok(None);
}
let Some(name) = webapi_field_name_literal(attrs.name.as_ref()) else {
return Ok(None);
};
let getter = attrs.getter.as_ref().ok_or_else(|| {
Error::new(
field.span(),
"`accessor_property` field requires #[webapi(getter = path)]",
)
})?;
let getter = callback_mappings.map(expand_callback(getter, attrs, false));
let setter = match attrs.setter.as_ref() {
Some(setter) => {
let setter = callback_mappings.map(expand_callback(setter, attrs, true));
quote!(::std::option::Option::Some(#setter))
}
None => quote!(::std::option::Option::None),
};
let enumerable = attrs.enumerable;
let readonly = attrs.readonly;
let dont_delete = attrs.dont_delete;
let receiver = attrs.receiver_access_check.unwrap_or_default();
let getter_receiver = receiver.checks_getter();
let setter_receiver = receiver.checks_setter();
Ok(Some(quote! {
::moli_webapi_declare::__private::FunctionTemplateAccessor {
name: #name,
getter: #getter,
setter: #setter,
enumerable: #enumerable,
readonly: #readonly,
dont_delete: #dont_delete,
getter_receiver_access_check: #getter_receiver,
setter_receiver_access_check: #setter_receiver,
}
}))
}
fn expand_function_template_fields(
fields: &[DeclaredField],
template_name: &proc_macro2::TokenStream,
@@ -565,13 +629,22 @@ fn expand_function_template_fields(
.iter()
.any(|field| matches!(field.attrs.kind, FieldKind::Alias(_)));
let mut methods = Vec::new();
let mut accessors = Vec::new();
for (index, DeclaredField { field, attrs }) in fields.iter().enumerate() {
if can_batch && let Some(method) = static_template_method(field, attrs, callback_mappings)?
{
flush_template_accessors(&mut prototype_methods, &mut accessors);
methods.push(method);
continue;
}
flush_template_methods(&mut prototype_methods, &mut methods);
if can_batch
&& let Some(accessor) = static_template_accessor(field, attrs, callback_mappings)?
{
accessors.push(accessor);
continue;
}
flush_template_accessors(&mut prototype_methods, &mut accessors);
if matches!(attrs.kind, FieldKind::Constant) {
template_methods.push(expand_function_template_constant_field(
field,
@@ -646,6 +719,7 @@ fn expand_function_template_fields(
}
}
flush_template_methods(&mut prototype_methods, &mut methods);
flush_template_accessors(&mut prototype_methods, &mut accessors);
Ok(FunctionTemplateFieldExpansions {
template_methods,
prototype_methods,
@@ -2187,7 +2261,7 @@ mod tests {
let tokens = expand_webapi_function_template(input)
.expect("template accessors should expand")
.to_string();
assert!(tokens.contains("String :: new (scope , \"get value\")"));
assert!(tokens.contains("FunctionTemplateAccessor"));
assert!(tokens.contains("String :: new (scope , \"get named\")"));
assert!(tokens.contains("String :: new (scope , \"set named\")"));
assert!(tokens.contains("install_function_template_accessor"));
+52
View File
@@ -168,3 +168,55 @@ pub fn install_function_template_methods<'s>(
);
}
}
/// Static metadata for a string-named accessor without callback data.
/// Mappings preserve constant tables while using V8's original call-time path.
#[doc(hidden)]
pub struct FunctionTemplateAccessor {
pub name: &'static str,
pub getter: fn() -> v8::FunctionCallback,
pub setter: Option<fn() -> v8::FunctionCallback>,
pub enumerable: bool,
pub readonly: bool,
pub dont_delete: bool,
pub getter_receiver_access_check: bool,
pub setter_receiver_access_check: bool,
}
/// Installs a run of accessors while preserving getter/setter function names.
#[doc(hidden)]
#[inline(never)]
pub fn install_function_template_accessors<'s>(
scope: &v8::PinScope<'s, '_, ()>,
target: v8::Local<'s, v8::ObjectTemplate>,
accessors: &[FunctionTemplateAccessor],
) {
for accessor in accessors {
let key = v8str(scope, accessor.name);
let mut attributes = v8::PropertyAttribute::NONE;
if !accessor.enumerable {
attributes = attributes | v8::PropertyAttribute::DONT_ENUM;
}
if accessor.readonly {
attributes = attributes | v8::PropertyAttribute::READ_ONLY;
}
if accessor.dont_delete {
attributes = attributes | v8::PropertyAttribute::DONT_DELETE;
}
let getter = FunctionTemplateMember {
callback: (accessor.getter)(),
length: 0,
data: None,
class_name: v8::String::new(scope, &format!("get {}", accessor.name)),
receiver_access_check: accessor.getter_receiver_access_check,
};
let setter = accessor.setter.map(|mapping| FunctionTemplateMember {
callback: mapping(),
length: 1,
data: None,
class_name: v8::String::new(scope, &format!("set {}", accessor.name)),
receiver_access_check: accessor.setter_receiver_access_check,
});
install_function_template_accessor(scope, target, key.into(), getter, setter, attributes);
}
}
+10 -3
View File
@@ -3162,6 +3162,8 @@ struct GenericCallbacks<T: CallbackMarker> {
call: (),
#[webapi(method, callback = Self::call)]
no_data: (),
#[webapi(accessor_property, getter = Self::call)]
value: (),
marker: std::marker::PhantomData<T>,
}
@@ -3219,7 +3221,7 @@ fn generic_callbacks_keep_data_and_return_values_across_nested_v8_entries() {
r#"
(() => {
const template = GenericCallbacks.prototype;
if (template.call() !== 10 || object.call() !== 12 || template.noData() !== 7) return false;
if (template.call() !== 10 || object.call() !== 12 || template.noData() !== 7 || template.value !== 7) return false;
if (template.call(n => object.call(m => n + m)) !== 22) return false;
const error = {};
try { object.call(() => { throw error; }); return false; }
@@ -3238,7 +3240,7 @@ struct MethodOrder {
first: (),
#[webapi(method, callback = sample_now_callback, length = 2, dont_delete)]
second: (),
#[webapi(accessor_property, getter = sample_now_callback)]
#[webapi(accessor_property, getter = sample_now_callback, setter = sample_accessor_setter)]
middle: (),
#[webapi(method, callback = sample_data_callback, data = "payload")]
data: (),
@@ -3266,11 +3268,16 @@ fn template_method_runs_preserve_order_descriptors_and_callback_data() {
if (names.join(',') !== 'first,second,middle,data,last') return false;
const first = Object.getOwnPropertyDescriptor(p, 'first');
const second = Object.getOwnPropertyDescriptor(p, 'second');
const middle = Object.getOwnPropertyDescriptor(p, 'middle');
p.middle = 13;
return !first.writable && first.configurable && first.enumerable
&& second.writable && !second.configurable && second.enumerable
&& p.first.name === 'first' && p.first.length === 0
&& p.second.name === 'second' && p.second.length === 2
&& p.middle === 7 && p.data() === 'payload' && p.last() === 7;
&& middle.get.name === 'get middle' && middle.get.length === 0
&& middle.set.name === 'set middle' && middle.set.length === 1
&& p.middle === 7 && p.writtenValue === 13 && p.setterPayload === 'undefined'
&& p.data() === 'payload' && p.last() === 7;
})()
"#
)