mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 08:07:38 +00:00
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:
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
})()
|
||||
"#
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user