From 31ee126de35038ec0e7c23283aa40112e28a66db Mon Sep 17 00:00:00 2001 From: ldm0 Date: Wed, 21 Oct 2026 04:01:12 +0800 Subject: [PATCH] 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). --- moli-webapi-declare-derive/src/expand.rs | 76 +++++++++++++++++++++++- moli-webapi-declare/src/__private.rs | 52 ++++++++++++++++ moli-webapi-declare/tests/declare.rs | 13 +++- 3 files changed, 137 insertions(+), 4 deletions(-) diff --git a/moli-webapi-declare-derive/src/expand.rs b/moli-webapi-declare-derive/src/expand.rs index c32a6dfa97..a9d4fb2349 100644 --- a/moli-webapi-declare-derive/src/expand.rs +++ b/moli-webapi-declare-derive/src/expand.rs @@ -552,6 +552,70 @@ fn static_template_method( })) } +fn flush_template_accessors( + output: &mut Vec, + accessors: &mut Vec, +) { + 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, 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")); diff --git a/moli-webapi-declare/src/__private.rs b/moli-webapi-declare/src/__private.rs index aeb82e4c17..91c4643d88 100644 --- a/moli-webapi-declare/src/__private.rs +++ b/moli-webapi-declare/src/__private.rs @@ -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 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); + } +} diff --git a/moli-webapi-declare/tests/declare.rs b/moli-webapi-declare/tests/declare.rs index 23a0f607fd..72b425d0f7 100644 --- a/moli-webapi-declare/tests/declare.rs +++ b/moli-webapi-declare/tests/declare.rs @@ -3162,6 +3162,8 @@ struct GenericCallbacks { call: (), #[webapi(method, callback = Self::call)] no_data: (), + #[webapi(accessor_property, getter = Self::call)] + value: (), marker: std::marker::PhantomData, } @@ -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; })() "# )