diff options
Diffstat (limited to 'ops/lib.rs')
-rw-r--r-- | ops/lib.rs | 1025 |
1 files changed, 0 insertions, 1025 deletions
diff --git a/ops/lib.rs b/ops/lib.rs deleted file mode 100644 index 31398a14d..000000000 --- a/ops/lib.rs +++ /dev/null @@ -1,1025 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use attrs::Attributes; -use optimizer::BailoutReason; -use optimizer::Optimizer; -use proc_macro::TokenStream; -use proc_macro2::Span; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use quote::ToTokens; -use std::error::Error; -use syn::parse; -use syn::parse_macro_input; -use syn::punctuated::Punctuated; -use syn::token::Comma; -use syn::FnArg; -use syn::GenericParam; -use syn::Ident; -use syn::ItemFn; -use syn::Lifetime; -use syn::LifetimeDef; - -mod attrs; -mod deno; -mod fast_call; -mod op2; -mod optimizer; - -const SCOPE_LIFETIME: &str = "'scope"; - -/// Add the 'scope lifetime to the function signature. -fn add_scope_lifetime(func: &mut ItemFn) { - let span = Span::call_site(); - let lifetime = LifetimeDef::new(Lifetime::new(SCOPE_LIFETIME, span)); - let generics = &mut func.sig.generics; - if !generics.lifetimes().any(|def| *def == lifetime) { - generics.params.push(GenericParam::Lifetime(lifetime)); - } -} - -struct Op { - orig: ItemFn, - item: ItemFn, - /// Is this an async op? - /// - `async fn` - /// - returns a Future - is_async: bool, - // optimizer: Optimizer, - core: TokenStream2, - attrs: Attributes, -} - -impl Op { - fn new(item: ItemFn, attrs: Attributes) -> Self { - // Preserve the original function. Change the name to `call`. - // - // impl op_foo { - // fn call() {} - // ... - // } - let mut orig = item.clone(); - orig.sig.ident = Ident::new("call", Span::call_site()); - - let is_async = item.sig.asyncness.is_some() || is_future(&item.sig.output); - let scope_params = exclude_non_lifetime_params(&item.sig.generics.params); - orig.sig.generics.params = scope_params; - orig.sig.generics.where_clause.take(); - add_scope_lifetime(&mut orig); - - #[cfg(test)] - let core = quote!(deno_core); - #[cfg(not(test))] - let core = deno::import(); - - Self { - orig, - item, - is_async, - core, - attrs, - } - } - - fn gen(mut self) -> TokenStream2 { - let mut optimizer = Optimizer::new(); - match optimizer.analyze(&mut self) { - Err(BailoutReason::MustBeSingleSegment) - | Err(BailoutReason::FastUnsupportedParamType) => { - optimizer.fast_compatible = false; - } - _ => {} - }; - - let Self { - core, - item, - is_async, - orig, - attrs, - } = self; - let name = &item.sig.ident; - - // TODO(mmastrac): this code is a little awkward but eventually it'll disappear in favour of op2 - let mut generics = item.sig.generics.clone(); - generics.where_clause.take(); - generics.params = exclude_lifetime_params(&generics.params); - let params = &generics.params.iter().collect::<Vec<_>>(); - let where_clause = &item.sig.generics.where_clause; - - // First generate fast call bindings to opt-in to error handling in slow call - let fast_call::FastImplItems { - impl_and_fn, - decl, - active, - } = fast_call::generate(&core, &mut optimizer, &item); - - let docline = format!("Use `{name}::decl()` to get an op-declaration"); - - let is_v8 = attrs.is_v8; - let is_unstable = attrs.is_unstable; - - if let Some(v8_fn) = attrs.relation { - return quote! { - #[allow(non_camel_case_types)] - #[doc="Auto-generated by `deno_ops`, i.e: `#[op]`"] - #[doc=""] - #[doc=#docline] - #[doc="you can include in a `deno_core::Extension`."] - pub struct #name #generics { - _phantom_data: ::std::marker::PhantomData<(#(#params),*)> - } - - impl #generics #core::_ops::Op for #name #generics #where_clause { - const NAME: &'static str = stringify!(#name); - const DECL: #core::OpDecl = #core::OpDecl { - name: Self::name(), - v8_fn_ptr: #v8_fn::v8_fn_ptr as _, - enabled: true, - fast_fn: #decl, - is_async: #is_async, - is_unstable: #is_unstable, - is_v8: #is_v8, - // TODO(mmastrac) - arg_count: 0, - }; - } - - #[doc(hidden)] - impl #generics #name #generics #where_clause { - pub const fn name() -> &'static str { - stringify!(#name) - } - - pub const fn decl () -> #core::OpDecl { - #core::OpDecl { - name: Self::name(), - v8_fn_ptr: #v8_fn::v8_fn_ptr as _, - enabled: true, - fast_fn: #decl, - is_async: #is_async, - is_unstable: #is_unstable, - is_v8: #is_v8, - // TODO(mmastrac) - arg_count: 0, - } - } - - #[inline] - #[allow(clippy::too_many_arguments)] - #[allow(clippy::extra_unused_lifetimes)] - #orig - } - - #impl_and_fn - }; - } - - let has_fallible_fast_call = active && optimizer.returns_result; - - let (v8_body, arg_count) = if is_async { - let deferred: bool = attrs.deferred; - codegen_v8_async( - &core, - &item, - attrs, - item.sig.asyncness.is_some(), - deferred, - ) - } else { - codegen_v8_sync(&core, &item, attrs, has_fallible_fast_call) - }; - - // Generate wrapper - quote! { - #[allow(non_camel_case_types)] - #[doc="Auto-generated by `deno_ops`, i.e: `#[op]`"] - #[doc=""] - #[doc=#docline] - #[doc="you can include in a `deno_core::Extension`."] - pub struct #name #generics { - _phantom_data: ::std::marker::PhantomData<(#(#params),*)> - } - - impl #generics #core::_ops::Op for #name #generics #where_clause { - const NAME: &'static str = stringify!(#name); - const DECL: #core::OpDecl = #core::OpDecl { - name: Self::name(), - v8_fn_ptr: Self::v8_fn_ptr as _, - enabled: true, - fast_fn: #decl, - is_async: #is_async, - is_unstable: #is_unstable, - is_v8: #is_v8, - // TODO(mmastrac) - arg_count: 0, - }; - } - - #[doc(hidden)] - impl #generics #name #generics #where_clause { - pub const fn name() -> &'static str { - stringify!(#name) - } - - #[allow(clippy::not_unsafe_ptr_arg_deref)] - pub extern "C" fn v8_fn_ptr (info: *const #core::v8::FunctionCallbackInfo) { - let info = unsafe { &*info }; - let scope = &mut unsafe { #core::v8::CallbackScope::new(info) }; - let args = #core::v8::FunctionCallbackArguments::from_function_callback_info(info); - let rv = #core::v8::ReturnValue::from_function_callback_info(info); - Self::v8_func(scope, args, rv); - } - - pub const fn decl () -> #core::OpDecl { - #core::OpDecl { - name: Self::name(), - v8_fn_ptr: Self::v8_fn_ptr as _, - enabled: true, - fast_fn: #decl, - is_async: #is_async, - is_unstable: #is_unstable, - is_v8: #is_v8, - arg_count: #arg_count as u8, - } - } - - #[inline] - #[allow(clippy::too_many_arguments)] - #[allow(clippy::extra_unused_lifetimes)] - #orig - - pub fn v8_func<'scope>( - scope: &mut #core::v8::HandleScope<'scope>, - args: #core::v8::FunctionCallbackArguments, - mut rv: #core::v8::ReturnValue, - ) { - #v8_body - } - } - - #impl_and_fn - } - } -} - -#[proc_macro_attribute] -pub fn op(attr: TokenStream, item: TokenStream) -> TokenStream { - let margs = parse_macro_input!(attr as Attributes); - let func = parse::<ItemFn>(item).expect("expected a function"); - let op = Op::new(func, margs); - op.gen().into() -} - -#[proc_macro_attribute] -pub fn op2(attr: TokenStream, item: TokenStream) -> TokenStream { - match crate::op2::op2(attr.into(), item.into()) { - Ok(output) => output.into(), - Err(err) => { - let mut err: &dyn Error = &err; - let mut output = "Failed to parse #[op2]:\n".to_owned(); - loop { - output += &format!(" - {err}\n"); - if let Some(source) = err.source() { - err = source; - } else { - break; - } - } - panic!("{output}"); - } - } -} - -/// Generate the body of a v8 func for an async op -fn codegen_v8_async( - core: &TokenStream2, - f: &syn::ItemFn, - margs: Attributes, - asyncness: bool, - deferred: bool, -) -> (TokenStream2, usize) { - let Attributes { is_v8, .. } = margs; - let special_args = f - .sig - .inputs - .iter() - .map_while(|a| { - (if is_v8 { scope_arg(a) } else { None }) - .or_else(|| rc_refcell_opstate_arg(a)) - }) - .collect::<Vec<_>>(); - let rust_i0 = special_args.len(); - let args_head = special_args.into_iter().collect::<TokenStream2>(); - - let (arg_decls, args_tail, _) = codegen_args(core, f, rust_i0, 1, asyncness); - - let wrapper = match (asyncness, is_result(&f.sig.output)) { - (true, true) => { - quote! { - let fut = #core::_ops::map_async_op1(ctx, Self::call(#args_head #args_tail)); - let maybe_response = #core::_ops::queue_async_op( - ctx, - scope, - #deferred, - promise_id, - fut, - ); - } - } - (true, false) => { - quote! { - let fut = #core::_ops::map_async_op2(ctx, Self::call(#args_head #args_tail)); - let maybe_response = #core::_ops::queue_async_op( - ctx, - scope, - #deferred, - promise_id, - fut, - ); - } - } - (false, true) => { - quote! { - let fut = #core::_ops::map_async_op3(ctx, Self::call(#args_head #args_tail)); - let maybe_response = #core::_ops::queue_async_op( - ctx, - scope, - #deferred, - promise_id, - fut, - ); - } - } - (false, false) => { - quote! { - let fut = #core::_ops::map_async_op4(ctx, Self::call(#args_head #args_tail)); - let maybe_response = #core::_ops::queue_async_op( - ctx, - scope, - #deferred, - promise_id, - fut, - ); - } - } - }; - - let token_stream = quote! { - use #core::futures::FutureExt; - // SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime - let ctx = unsafe { - &*(#core::v8::Local::<#core::v8::External>::cast(args.data()).value() - as *const #core::_ops::OpCtx) - }; - - let promise_id = args.get(0); - let promise_id = #core::v8::Local::<#core::v8::Integer>::try_from(promise_id) - .map(|l| l.value() as #core::PromiseId) - .map_err(#core::anyhow::Error::from); - // Fail if promise id invalid (not an int) - let promise_id: #core::PromiseId = match promise_id { - Ok(promise_id) => promise_id, - Err(err) => { - #core::_ops::throw_type_error(scope, format!("invalid promise id: {}", err)); - return; - } - }; - - #arg_decls - #wrapper - - if let Some(response) = maybe_response { - rv.set(response); - } - }; - - // +1 arg for the promise ID - (token_stream, 1 + f.sig.inputs.len() - rust_i0) -} - -fn scope_arg(arg: &FnArg) -> Option<TokenStream2> { - if is_handle_scope(arg) { - Some(quote! { scope, }) - } else { - None - } -} - -fn opstate_arg(arg: &FnArg) -> Option<TokenStream2> { - match arg { - arg if is_rc_refcell_opstate(arg) => Some(quote! { ctx.state.clone(), }), - arg if is_mut_ref_opstate(arg) => { - Some(quote! { &mut std::cell::RefCell::borrow_mut(&ctx.state), }) - } - _ => None, - } -} - -fn rc_refcell_opstate_arg(arg: &FnArg) -> Option<TokenStream2> { - match arg { - arg if is_rc_refcell_opstate(arg) => Some(quote! { ctx.state.clone(), }), - arg if is_mut_ref_opstate(arg) => Some( - quote! { compile_error!("mutable opstate is not supported in async ops"), }, - ), - _ => None, - } -} - -/// Generate the body of a v8 func for a sync op -fn codegen_v8_sync( - core: &TokenStream2, - f: &syn::ItemFn, - margs: Attributes, - has_fallible_fast_call: bool, -) -> (TokenStream2, usize) { - let Attributes { is_v8, .. } = margs; - let special_args = f - .sig - .inputs - .iter() - .map_while(|a| { - (if is_v8 { scope_arg(a) } else { None }).or_else(|| opstate_arg(a)) - }) - .collect::<Vec<_>>(); - let rust_i0 = special_args.len(); - let args_head = special_args.into_iter().collect::<TokenStream2>(); - let (arg_decls, args_tail, _) = codegen_args(core, f, rust_i0, 0, false); - let ret = codegen_sync_ret(core, &f.sig.output); - - let fast_error_handler = if has_fallible_fast_call { - quote! { - { - let op_state = &mut std::cell::RefCell::borrow_mut(&ctx.state); - if let Some(err) = op_state.last_fast_op_error.take() { - let exception = #core::error::to_v8_error(scope, op_state.get_error_class_fn, &err); - scope.throw_exception(exception); - return; - } - } - } - } else { - quote! {} - }; - - let token_stream = quote! { - // SAFETY: #core guarantees args.data() is a v8 External pointing to an OpCtx for the isolates lifetime - let ctx = unsafe { - &*(#core::v8::Local::<#core::v8::External>::cast(args.data()).value() - as *const #core::_ops::OpCtx) - }; - - #fast_error_handler - #arg_decls - - let result = Self::call(#args_head #args_tail); - - // use RefCell::borrow instead of state.borrow to avoid clash with std::borrow::Borrow - let op_state = ::std::cell::RefCell::borrow(&*ctx.state); - op_state.tracker.track_sync(ctx.id); - - #ret - }; - - (token_stream, f.sig.inputs.len() - rust_i0) -} - -/// (full declarations, idents, v8 argument count) -type ArgumentDecl = (TokenStream2, TokenStream2, usize); - -fn codegen_args( - core: &TokenStream2, - f: &syn::ItemFn, - rust_i0: usize, // Index of first generic arg in rust - v8_i0: usize, // Index of first generic arg in v8/js - asyncness: bool, -) -> ArgumentDecl { - let inputs = &f.sig.inputs.iter().skip(rust_i0).enumerate(); - let ident_seq: TokenStream2 = inputs - .clone() - .map(|(i, _)| format!("arg_{i}")) - .collect::<Vec<_>>() - .join(", ") - .parse() - .unwrap(); - let decls: TokenStream2 = inputs - .clone() - .map(|(i, arg)| { - codegen_arg(core, arg, format!("arg_{i}").as_ref(), v8_i0 + i, asyncness) - }) - .collect(); - (decls, ident_seq, inputs.len()) -} - -fn codegen_arg( - core: &TokenStream2, - arg: &syn::FnArg, - name: &str, - idx: usize, - asyncness: bool, -) -> TokenStream2 { - let ident = quote::format_ident!("{name}"); - let (pat, ty) = match arg { - syn::FnArg::Typed(pat) => { - if is_optional_fast_callback_option(&pat.ty) - || is_optional_wasm_memory(&pat.ty) - { - return quote! { let #ident = None; }; - } - (&pat.pat, &pat.ty) - } - _ => unreachable!(), - }; - // Fast path if arg should be skipped - if matches!(**pat, syn::Pat::Wild(_)) { - return quote! { let #ident = (); }; - } - // Fast path for `String` - if let Some(is_ref) = is_string(&**ty) { - let ref_block = if is_ref { - quote! { let #ident = #ident.as_ref(); } - } else { - quote! {} - }; - return quote! { - let #ident = match #core::v8::Local::<#core::v8::String>::try_from(args.get(#idx as i32)) { - Ok(v8_string) => #core::serde_v8::to_utf8(v8_string, scope), - Err(_) => { - return #core::_ops::throw_type_error(scope, format!("Expected string at position {}", #idx)); - } - }; - #ref_block - }; - } - // Fast path for `Cow<'_, str>` - if is_cow_str(&**ty) { - return quote! { - let #ident = match #core::v8::Local::<#core::v8::String>::try_from(args.get(#idx as i32)) { - Ok(v8_string) => ::std::borrow::Cow::Owned(#core::serde_v8::to_utf8(v8_string, scope)), - Err(_) => { - return #core::_ops::throw_type_error(scope, format!("Expected string at position {}", #idx)); - } - }; - }; - } - // Fast path for `Option<String>` - if is_option_string(&**ty) { - return quote! { - let #ident = match #core::v8::Local::<#core::v8::String>::try_from(args.get(#idx as i32)) { - Ok(v8_string) => Some(#core::serde_v8::to_utf8(v8_string, scope)), - Err(_) => None - }; - }; - } - // Fast path for &/&mut [u8] and &/&mut [u32] - match is_ref_slice(&**ty) { - None => {} - Some(SliceType::U32Mut) => { - assert!(!asyncness, "Memory slices are not allowed in async ops"); - let blck = codegen_u32_mut_slice(core, idx); - return quote! { - let #ident = #blck; - }; - } - Some(SliceType::F64Mut) => { - assert!(!asyncness, "Memory slices are not allowed in async ops"); - let blck = codegen_f64_mut_slice(core, idx); - return quote! { - let #ident = #blck; - }; - } - Some(_) => { - assert!(!asyncness, "Memory slices are not allowed in async ops"); - let blck = codegen_u8_slice(core, idx); - return quote! { - let #ident = #blck; - }; - } - } - // Fast path for `*const u8` - if is_ptr_u8(&**ty) { - let blk = codegen_u8_ptr(core, idx); - return quote! { - let #ident = #blk; - }; - } - // Fast path for `*const c_void` and `*mut c_void` - if is_ptr_cvoid(&**ty) { - let blk = codegen_cvoid_ptr(core, idx); - return quote! { - let #ident = #blk; - }; - } - // Otherwise deserialize it via serde_v8 - quote! { - let #ident = args.get(#idx as i32); - let #ident = match #core::serde_v8::from_v8(scope, #ident) { - Ok(v) => v, - Err(err) => { - let msg = format!("Error parsing args at position {}: {}", #idx, #core::anyhow::Error::from(err)); - return #core::_ops::throw_type_error(scope, msg); - } - }; - } -} - -fn codegen_u8_slice(core: &TokenStream2, idx: usize) -> TokenStream2 { - quote! {{ - let value = args.get(#idx as i32); - match #core::v8::Local::<#core::v8::ArrayBuffer>::try_from(value) { - Ok(b) => { - let byte_length = b.byte_length(); - if let Some(data) = b.data() { - let store = data.cast::<u8>().as_ptr(); - // SAFETY: rust guarantees that lifetime of slice is no longer than the call. - unsafe { ::std::slice::from_raw_parts_mut(store, byte_length) } - } else { - &mut [] - } - }, - Err(_) => { - if let Ok(view) = #core::v8::Local::<#core::v8::ArrayBufferView>::try_from(value) { - let len = view.byte_length(); - let offset = view.byte_offset(); - let buffer = match view.buffer(scope) { - Some(v) => v, - None => { - return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx)); - } - }; - if let Some(data) = buffer.data() { - let store = data.cast::<u8>().as_ptr(); - // SAFETY: rust guarantees that lifetime of slice is no longer than the call. - unsafe { ::std::slice::from_raw_parts_mut(store.add(offset), len) } - } else { - &mut [] - } - } else { - return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx)); - } - } - }} - } -} - -fn codegen_u8_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 { - quote! {{ - let value = args.get(#idx as i32); - match #core::v8::Local::<#core::v8::ArrayBuffer>::try_from(value) { - Ok(b) => { - if let Some(data) = b.data() { - data.cast::<u8>().as_ptr() - } else { - std::ptr::null::<u8>() - } - }, - Err(_) => { - if let Ok(view) = #core::v8::Local::<#core::v8::ArrayBufferView>::try_from(value) { - let offset = view.byte_offset(); - let buffer = match view.buffer(scope) { - Some(v) => v, - None => { - return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx)); - } - }; - let store = if let Some(data) = buffer.data() { - data.cast::<u8>().as_ptr() - } else { - std::ptr::null_mut::<u8>() - }; - unsafe { store.add(offset) } - } else { - return #core::_ops::throw_type_error(scope, format!("Expected ArrayBufferView at position {}", #idx)); - } - } - } - }} -} - -fn codegen_cvoid_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 { - quote! {{ - let value = args.get(#idx as i32); - if value.is_null() { - std::ptr::null_mut() - } else if let Ok(b) = #core::v8::Local::<#core::v8::External>::try_from(value) { - b.value() - } else { - return #core::_ops::throw_type_error(scope, format!("Expected External at position {}", #idx)); - } - }} -} - -fn codegen_u32_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 { - quote! { - if let Ok(view) = #core::v8::Local::<#core::v8::Uint32Array>::try_from(args.get(#idx as i32)) { - let (offset, len) = (view.byte_offset(), view.byte_length()); - let buffer = match view.buffer(scope) { - Some(v) => v, - None => { - return #core::_ops::throw_type_error(scope, format!("Expected Uint32Array at position {}", #idx)); - } - }; - if let Some(data) = buffer.data() { - let store = data.cast::<u8>().as_ptr(); - // SAFETY: buffer from Uint32Array. Rust guarantees that lifetime of slice is no longer than the call. - unsafe { ::std::slice::from_raw_parts_mut(store.add(offset) as *mut u32, len / 4) } - } else { - &mut [] - } - } else { - return #core::_ops::throw_type_error(scope, format!("Expected Uint32Array at position {}", #idx)); - } - } -} - -fn codegen_f64_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 { - quote! { - if let Ok(view) = #core::v8::Local::<#core::v8::Float64Array>::try_from(args.get(#idx as i32)) { - let (offset, len) = (view.byte_offset(), view.byte_length()); - let buffer = match view.buffer(scope) { - Some(v) => v, - None => { - return #core::_ops::throw_type_error(scope, format!("Expected Float64Array at position {}", #idx)); - } - }; - if let Some(data) = buffer.data() { - let store = data.cast::<u8>().as_ptr(); - unsafe { ::std::slice::from_raw_parts_mut(store.add(offset) as *mut f64, len / 8) } - } else { - &mut [] - } - } else { - return #core::_ops::throw_type_error(scope, format!("Expected Float64Array at position {}", #idx)); - } - } -} - -fn codegen_sync_ret( - core: &TokenStream2, - output: &syn::ReturnType, -) -> TokenStream2 { - if is_void(output) { - return quote! {}; - } - - if is_u32_rv(output) { - return quote! { - rv.set_uint32(result as u32); - }; - } - - // Optimize Result<(), Err> to skip serde_v8 when Ok(...) - let ok_block = if is_unit_result(output) { - quote! {} - } else if is_u32_rv_result(output) { - quote! { - rv.set_uint32(result as u32); - } - } else if is_ptr_cvoid(output) || is_ptr_cvoid_rv(output) { - quote! { - if result.is_null() { - // External cannot contain a null pointer, null pointers are instead represented as null. - rv.set_null(); - } else { - rv.set(v8::External::new(scope, result as *mut ::std::ffi::c_void).into()); - } - } - } else { - quote! { - match #core::serde_v8::to_v8(scope, result) { - Ok(ret) => rv.set(ret), - Err(err) => #core::_ops::throw_type_error( - scope, - format!("Error serializing return: {}", #core::anyhow::Error::from(err)), - ), - }; - } - }; - - if !is_result(output) { - return ok_block; - } - - quote! { - match result { - Ok(result) => { - #ok_block - }, - Err(err) => { - let exception = #core::error::to_v8_error(scope, op_state.get_error_class_fn, &err); - scope.throw_exception(exception); - }, - }; - } -} - -fn is_void(ty: impl ToTokens) -> bool { - tokens(ty).is_empty() -} - -fn is_result(ty: impl ToTokens) -> bool { - let tokens = tokens(ty); - if tokens.trim_start_matches("-> ").starts_with("Result <") { - return true; - } - // Detect `io::Result<...>`, `anyhow::Result<...>`, etc... - // i.e: Result aliases/shorthands which are unfortunately "opaque" at macro-time - match tokens.find(":: Result <") { - Some(idx) => !tokens.split_at(idx).0.contains('<'), - None => false, - } -} - -fn is_string(ty: impl ToTokens) -> Option<bool> { - let toks = tokens(ty); - if toks == "String" { - return Some(false); - } - if toks == "& str" { - return Some(true); - } - None -} - -fn is_option_string(ty: impl ToTokens) -> bool { - tokens(ty) == "Option < String >" -} - -fn is_cow_str(ty: impl ToTokens) -> bool { - tokens(&ty).starts_with("Cow <") && tokens(&ty).ends_with("str >") -} - -enum SliceType { - U8, - U8Mut, - U32Mut, - F64Mut, -} - -fn is_ref_slice(ty: impl ToTokens) -> Option<SliceType> { - if is_u8_slice(&ty) { - return Some(SliceType::U8); - } - if is_u8_slice_mut(&ty) { - return Some(SliceType::U8Mut); - } - if is_u32_slice_mut(&ty) { - return Some(SliceType::U32Mut); - } - if is_f64_slice_mut(&ty) { - return Some(SliceType::F64Mut); - } - None -} - -fn is_u8_slice(ty: impl ToTokens) -> bool { - tokens(ty) == "& [u8]" -} - -fn is_u8_slice_mut(ty: impl ToTokens) -> bool { - tokens(ty) == "& mut [u8]" -} - -fn is_u32_slice_mut(ty: impl ToTokens) -> bool { - tokens(ty) == "& mut [u32]" -} - -fn is_f64_slice_mut(ty: impl ToTokens) -> bool { - tokens(ty) == "& mut [f64]" -} - -fn is_ptr_u8(ty: impl ToTokens) -> bool { - tokens(ty) == "* const u8" -} - -fn is_ptr_cvoid(ty: impl ToTokens) -> bool { - tokens(&ty) == "* const c_void" || tokens(&ty) == "* mut c_void" -} - -fn is_ptr_cvoid_rv(ty: impl ToTokens) -> bool { - tokens(&ty).contains("Result < * const c_void") - || tokens(&ty).contains("Result < * mut c_void") -} - -fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool { - tokens(&ty).contains("Option < & mut FastApiCallbackOptions") -} - -fn is_optional_wasm_memory(ty: impl ToTokens) -> bool { - tokens(&ty).contains("Option < & mut [u8]") -} - -/// Detects if the type can be set using `rv.set_uint32` fast path -fn is_u32_rv(ty: impl ToTokens) -> bool { - ["u32", "u8", "u16"].iter().any(|&s| tokens(&ty) == s) || is_resource_id(&ty) -} - -/// Detects if the type is of the format Result<u32/u8/u16, Err> -fn is_u32_rv_result(ty: impl ToTokens) -> bool { - is_result(&ty) - && (tokens(&ty).contains("Result < u32") - || tokens(&ty).contains("Result < u8") - || tokens(&ty).contains("Result < u16") - || is_resource_id(&ty)) -} - -/// Detects if a type is of the form Result<(), Err> -fn is_unit_result(ty: impl ToTokens) -> bool { - is_result(&ty) && tokens(&ty).contains("Result < ()") -} - -fn is_resource_id(arg: impl ToTokens) -> bool { - let re = lazy_regex::regex!(r#": (?:deno_core :: )?ResourceId$"#); - re.is_match(&tokens(arg)) -} - -fn is_mut_ref_opstate(arg: impl ToTokens) -> bool { - let re = lazy_regex::regex!(r#": & mut (?:deno_core :: )?OpState$"#); - re.is_match(&tokens(arg)) -} - -fn is_rc_refcell_opstate(arg: &syn::FnArg) -> bool { - let re = - lazy_regex::regex!(r#": Rc < RefCell < (?:deno_core :: )?OpState > >$"#); - re.is_match(&tokens(arg)) -} - -fn is_handle_scope(arg: &syn::FnArg) -> bool { - let re = lazy_regex::regex!( - r#": & mut (?:deno_core :: )?v8 :: HandleScope(?: < '\w+ >)?$"# - ); - re.is_match(&tokens(arg)) -} - -fn is_future(ty: impl ToTokens) -> bool { - tokens(&ty).contains("impl Future < Output =") -} - -fn tokens(x: impl ToTokens) -> String { - x.to_token_stream().to_string() -} - -fn exclude_lifetime_params( - generic_params: &Punctuated<GenericParam, Comma>, -) -> Punctuated<GenericParam, Comma> { - generic_params - .iter() - .filter(|t| !tokens(t).starts_with('\'')) - .cloned() - .collect::<Punctuated<GenericParam, Comma>>() -} - -fn exclude_non_lifetime_params( - generic_params: &Punctuated<GenericParam, Comma>, -) -> Punctuated<GenericParam, Comma> { - generic_params - .iter() - .filter(|t| tokens(t).starts_with('\'')) - .cloned() - .collect::<Punctuated<GenericParam, Comma>>() -} - -#[cfg(test)] -mod tests { - use crate::Attributes; - use crate::Op; - use pretty_assertions::assert_eq; - use std::path::PathBuf; - - #[testing_macros::fixture("optimizer_tests/**/*.rs")] - fn test_codegen(input: PathBuf) { - let update_expected = std::env::var("UPDATE_EXPECTED").is_ok(); - - let source = - std::fs::read_to_string(&input).expect("Failed to read test file"); - - let mut attrs = Attributes::default(); - if source.contains("// @test-attr:fast") { - attrs.must_be_fast = true; - } - if source.contains("// @test-attr:wasm") { - attrs.is_wasm = true; - attrs.must_be_fast = true; - } - - let item = syn::parse_str(&source).expect("Failed to parse test file"); - let op = Op::new(item, attrs); - - let expected = std::fs::read_to_string(input.with_extension("out")) - .expect("Failed to read expected output file"); - - // Print the raw tokens in case we fail to parse - let actual = op.gen(); - println!("-----Raw tokens-----\n{}----------\n", actual); - - // Validate syntax tree. - let tree = syn::parse2(actual).unwrap(); - let actual = prettyplease::unparse(&tree); - if update_expected { - std::fs::write(input.with_extension("out"), actual) - .expect("Failed to write expected file"); - } else { - assert_eq!(actual, expected); - } - } -} |