diff options
Diffstat (limited to 'ops/op2/signature.rs')
| -rw-r--r-- | ops/op2/signature.rs | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs new file mode 100644 index 000000000..6158b2a55 --- /dev/null +++ b/ops/op2/signature.rs @@ -0,0 +1,516 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_proc_macro_rules::rules; +use proc_macro2::Ident; +use proc_macro2::Span; +use quote::quote; +use quote::ToTokens; +use strum::IntoEnumIterator; +use strum::IntoStaticStr; +use strum_macros::EnumIter; +use strum_macros::EnumString; +use syn2::Attribute; +use syn2::FnArg; +use syn2::Pat; +use syn2::ReturnType; +use syn2::Signature; +use syn2::Type; +use syn2::TypePath; +use thiserror::Error; + +#[allow(non_camel_case_types)] +#[derive( + Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString, EnumIter, +)] +pub enum NumericArg { + /// A placeholder argument for arguments annotated with #[smi]. + __SMI__, + /// A placeholder argument for void data. + __VOID__, + bool, + i8, + u8, + i16, + u16, + i32, + u32, + i64, + u64, + f32, + f64, + isize, + usize, +} + +impl ToTokens for NumericArg { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let ident = Ident::new(self.into(), Span::call_site()); + tokens.extend(quote! { #ident }) + } +} + +#[derive( + Copy, Clone, Debug, Eq, PartialEq, IntoStaticStr, EnumString, EnumIter, +)] +pub enum V8Arg { + External, + Object, + Array, + ArrayBuffer, + ArrayBufferView, + DataView, + TypedArray, + BigInt64Array, + BigUint64Array, + Float32Array, + Float64Array, + Int16Array, + Int32Array, + Int8Array, + Uint16Array, + Uint32Array, + Uint8Array, + Uint8ClampedArray, + BigIntObject, + BooleanObject, + Date, + Function, + Map, + NumberObject, + Promise, + PromiseResolver, + Proxy, + RegExp, + Set, + SharedArrayBuffer, + StringObject, + SymbolObject, + WasmMemoryObject, + WasmModuleObject, + Primitive, + BigInt, + Boolean, + Name, + String, + Symbol, + Number, + Integer, + Int32, + Uint32, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Special { + HandleScope, + OpState, + String, + RefStr, + FastApiCallbackOptions, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum RefType { + Ref, + Mut, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Arg { + Void, + Special(Special), + Ref(RefType, Special), + RcRefCell(Special), + Option(Special), + OptionNumeric(NumericArg), + Slice(RefType, NumericArg), + Ptr(RefType, NumericArg), + V8Local(V8Arg), + Numeric(NumericArg), + SerdeV8(String), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RetVal { + Infallible(Arg), + Result(Arg), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ParsedSignature { + pub args: Vec<Arg>, + pub names: Vec<String>, + pub ret_val: RetVal, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum AttributeModifier { + /// #[serde], for serde_v8 types. + Serde, + /// #[smi], for small integers + Smi, + /// #[string], for strings. + String, +} + +#[derive(Error, Debug)] +pub enum SignatureError { + #[error("Invalid argument: {0}")] + ArgError(String, #[source] ArgError), + #[error("Invalid return type")] + RetError(#[from] ArgError), +} + +#[derive(Error, Debug)] +pub enum ArgError { + #[error("Invalid self argument")] + InvalidSelf, + #[error("Invalid argument type: {0}")] + InvalidType(String), + #[error( + "Invalid argument type path (should this be #[smi] or #[serde]?): {0}" + )] + InvalidTypePath(String), + #[error("Too many attributes")] + TooManyAttributes, + #[error("Invalid #[serde] type: {0}")] + InvalidSerdeType(String), + #[error("Cannot use #[serde] for type: {0}")] + InvalidSerdeAttributeType(String), + #[error("Invalid v8 type: {0}")] + InvalidV8Type(String), + #[error("Internal error: {0}")] + InternalError(String), + #[error("Missing a #[string] attribute")] + MissingStringAttribute, +} + +#[derive(Copy, Clone, Default)] +struct Attributes { + primary: Option<AttributeModifier>, +} + +fn stringify_token(tokens: impl ToTokens) -> String { + tokens + .into_token_stream() + .into_iter() + .map(|s| s.to_string()) + .collect::<Vec<_>>() + .join("") +} + +pub fn parse_signature( + attributes: Vec<Attribute>, + signature: Signature, +) -> Result<ParsedSignature, SignatureError> { + let mut args = vec![]; + let mut names = vec![]; + for input in signature.inputs { + let name = match &input { + FnArg::Receiver(_) => "self".to_owned(), + FnArg::Typed(ty) => match &*ty.pat { + Pat::Ident(ident) => ident.ident.to_string(), + _ => "(complex)".to_owned(), + }, + }; + names.push(name.clone()); + args.push( + parse_arg(input).map_err(|err| SignatureError::ArgError(name, err))?, + ); + } + Ok(ParsedSignature { + args, + names, + ret_val: parse_return(parse_attributes(&attributes)?, &signature.output)?, + }) +} + +fn parse_attributes(attributes: &[Attribute]) -> Result<Attributes, ArgError> { + let attrs = attributes + .iter() + .filter_map(parse_attribute) + .collect::<Vec<_>>(); + + if attrs.is_empty() { + return Ok(Attributes::default()); + } + if attrs.len() > 1 { + return Err(ArgError::TooManyAttributes); + } + Ok(Attributes { + primary: Some(*attrs.get(0).unwrap()), + }) +} + +fn parse_attribute(attr: &Attribute) -> Option<AttributeModifier> { + let tokens = attr.into_token_stream(); + use syn2 as syn; + std::panic::catch_unwind(|| { + rules!(tokens => { + (#[serde]) => Some(AttributeModifier::Serde), + (#[smi]) => Some(AttributeModifier::Smi), + (#[string]) => Some(AttributeModifier::String), + (#[$_attr:meta]) => None, + }) + }) + .expect("Failed to parse an attribute") +} + +fn parse_return( + attrs: Attributes, + rt: &ReturnType, +) -> Result<RetVal, ArgError> { + match rt { + ReturnType::Default => Ok(RetVal::Infallible(Arg::Void)), + ReturnType::Type(_, ty) => { + let s = stringify_token(ty); + let tokens = ty.into_token_stream(); + use syn2 as syn; + + std::panic::catch_unwind(|| { + rules!(tokens => { + // x::y::Result<Value>, like io::Result and other specialty result types + ($($_package:ident ::)* Result < $ty:ty >) => { + Ok(RetVal::Result(parse_type(attrs, &ty)?)) + } + // x::y::Result<Value, Error> + ($($_package:ident ::)* Result < $ty:ty, $_error:ty >) => { + Ok(RetVal::Result(parse_type(attrs, &ty)?)) + } + ($ty:ty) => { + Ok(RetVal::Infallible(parse_type(attrs, &ty)?)) + } + }) + }) + .map_err(|e| { + ArgError::InternalError(format!( + "parse_return({}) {}", + s, + e.downcast::<&str>().unwrap_or_default() + )) + })? + } + } +} + +fn parse_type_path(attrs: Attributes, tp: &TypePath) -> Result<Arg, ArgError> { + if tp.path.segments.len() == 1 { + let segment = tp.path.segments.first().unwrap().ident.to_string(); + for numeric in NumericArg::iter() { + if Into::<&'static str>::into(numeric) == segment.as_str() { + return Ok(Arg::Numeric(numeric)); + } + } + } + + use syn2 as syn; + + let tokens = tp.clone().into_token_stream(); + std::panic::catch_unwind(|| { + rules!(tokens => { + ( $( std :: str :: )? String ) => { + if attrs.primary == Some(AttributeModifier::String) { + Ok(Arg::Special(Special::String)) + } else { + Err(ArgError::MissingStringAttribute) + } + } + ( $( std :: ffi :: )? c_void ) => Ok(Arg::Numeric(NumericArg::__VOID__)), + ( OpState ) => Ok(Arg::Special(Special::OpState)), + ( v8 :: HandleScope ) => Ok(Arg::Special(Special::HandleScope)), + ( v8 :: FastApiCallbackOptions ) => Ok(Arg::Special(Special::FastApiCallbackOptions)), + ( v8 :: Local < $( $_scope:lifetime , )? v8 :: $v8:ident >) => Ok(Arg::V8Local(parse_v8_type(&v8)?)), + ( Rc < RefCell < $ty:ty > > ) => Ok(Arg::RcRefCell(parse_type_special(attrs, &ty)?)), + ( Option < $ty:ty > ) => { + match parse_type(attrs, &ty)? { + Arg::Special(special) => Ok(Arg::Option(special)), + Arg::Numeric(numeric) => Ok(Arg::OptionNumeric(numeric)), + _ => Err(ArgError::InvalidType(stringify_token(ty))) + } + } + ( $any:ty ) => Err(ArgError::InvalidTypePath(stringify_token(any))), + }) + }).map_err(|e| ArgError::InternalError(format!("parse_type_path {e:?}")))? +} + +fn parse_v8_type(v8: &Ident) -> Result<V8Arg, ArgError> { + let v8 = v8.to_string(); + V8Arg::try_from(v8.as_str()).map_err(|_| ArgError::InvalidV8Type(v8)) +} + +fn parse_type_special( + attrs: Attributes, + ty: &Type, +) -> Result<Special, ArgError> { + match parse_type(attrs, ty)? { + Arg::Special(special) => Ok(special), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } +} + +fn parse_type(attrs: Attributes, ty: &Type) -> Result<Arg, ArgError> { + if let Some(primary) = attrs.primary { + match primary { + AttributeModifier::Serde => match ty { + Type::Path(of) => { + // If this type will parse without #[serde], it is illegal to use this type with #[serde] + if parse_type_path(Attributes::default(), of).is_ok() { + return Err(ArgError::InvalidSerdeAttributeType(stringify_token( + ty, + ))); + } + return Ok(Arg::SerdeV8(stringify_token(of.path.clone()))); + } + _ => return Err(ArgError::InvalidSerdeType(stringify_token(ty))), + }, + AttributeModifier::String => match ty { + Type::Path(of) => { + return parse_type_path(attrs, of); + } + Type::Reference(of) => { + let mut_type = if of.mutability.is_some() { + RefType::Mut + } else { + RefType::Ref + }; + let tokens = of.elem.clone().into_token_stream(); + use syn2 as syn; + return rules!(tokens => { + (str) => Ok(Arg::Special(Special::RefStr)), + ($_ty:ty) => Ok(Arg::Ref(mut_type, parse_type_special(attrs, &of.elem)?)), + }); + } + _ => return Err(ArgError::InvalidSerdeType(stringify_token(ty))), + }, + AttributeModifier::Smi => { + return Ok(Arg::Numeric(NumericArg::__SMI__)); + } + } + }; + match ty { + Type::Tuple(of) => { + if of.elems.is_empty() { + Ok(Arg::Void) + } else { + Err(ArgError::InvalidType(stringify_token(ty))) + } + } + Type::Reference(of) => { + let mut_type = if of.mutability.is_some() { + RefType::Mut + } else { + RefType::Ref + }; + match &*of.elem { + Type::Slice(of) => match parse_type(attrs, &of.elem)? { + Arg::Numeric(numeric) => Ok(Arg::Slice(mut_type, numeric)), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + }, + Type::Path(of) => match parse_type_path(attrs, of)? { + Arg::Special(special) => Ok(Arg::Ref(mut_type, special)), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + }, + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } + } + Type::Ptr(of) => { + let mut_type = if of.mutability.is_some() { + RefType::Mut + } else { + RefType::Ref + }; + match &*of.elem { + Type::Path(of) => match parse_type_path(attrs, of)? { + Arg::Numeric(numeric) => Ok(Arg::Ptr(mut_type, numeric)), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + }, + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } + } + Type::Path(of) => parse_type_path(attrs, of), + _ => Err(ArgError::InvalidType(stringify_token(ty))), + } +} + +fn parse_arg(arg: FnArg) -> Result<Arg, ArgError> { + let FnArg::Typed(typed) = arg else { + return Err(ArgError::InvalidSelf); + }; + parse_type(parse_attributes(&typed.attrs)?, &typed.ty) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::op2::signature::parse_signature; + use syn2::parse_str; + use syn2::ItemFn; + + // We can't test pattern args :/ + // https://github.com/rust-lang/rfcs/issues/2688 + macro_rules! test { + ( $(# [ $fn_attr:ident ])? fn $name:ident ( $( $(# [ $attr:ident ])? $ident:ident : $ty:ty ),* ) $(-> $(# [ $ret_attr:ident ])? $ret:ty)?, ( $( $arg_res:expr ),* ) -> $ret_res:expr ) => { + #[test] + fn $name() { + test( + stringify!($( #[$fn_attr] )? fn op( $( $( #[$attr] )? $ident : $ty ),* ) $(-> $( #[$ret_attr] )? $ret)? {}), + stringify!($($arg_res),*), + stringify!($ret_res) + ); + } + }; + } + + fn test(op: &str, args_expected: &str, return_expected: &str) { + let item_fn = parse_str::<ItemFn>(op) + .unwrap_or_else(|_| panic!("Failed to parse {op} as a ItemFn")); + let attrs = item_fn.attrs; + let sig = parse_signature(attrs, item_fn.sig).unwrap_or_else(|_| { + panic!("Failed to successfully parse signature from {op}") + }); + + assert_eq!( + args_expected, + format!("{:?}", sig.args).trim_matches(|c| c == '[' || c == ']') + ); + assert_eq!(return_expected, format!("{:?}", sig.ret_val)); + } + + test!( + fn op_state_and_number(opstate: &mut OpState, a: u32) -> (), + (Ref(Mut, OpState), Numeric(u32)) -> Infallible(Void) + ); + test!( + fn op_slices(r#in: &[u8], out: &mut [u8]), + (Slice(Ref, u8), Slice(Mut, u8)) -> Infallible(Void) + ); + test!( + #[serde] fn op_serde(#[serde] input: package::SerdeInputType) -> Result<package::SerdeReturnType, Error>, + (SerdeV8("package::SerdeInputType")) -> Result(SerdeV8("package::SerdeReturnType")) + ); + test!( + fn op_local(input: v8::Local<v8::String>) -> Result<v8::Local<v8::String>, Error>, + (V8Local(String)) -> Result(V8Local(String)) + ); + test!( + fn op_resource(#[smi] rid: ResourceId, buffer: &[u8]), + (Numeric(__SMI__), Slice(Ref, u8)) -> Infallible(Void) + ); + test!( + fn op_option_numeric_result(state: &mut OpState) -> Result<Option<u32>, AnyError>, + (Ref(Mut, OpState)) -> Result(OptionNumeric(u32)) + ); + test!( + fn op_ffi_read_f64(state: &mut OpState, ptr: * mut c_void, offset: isize) -> Result <f64, AnyError>, + (Ref(Mut, OpState), Ptr(Mut, __VOID__), Numeric(isize)) -> Result(Numeric(f64)) + ); + test!( + fn op_print(#[string] msg: &str, is_err: bool) -> Result<(), Error>, + (Special(RefStr), Numeric(bool)) -> Result(Void) + ); + + #[test] + fn test_parse_result() { + let rt = parse_str::<ReturnType>("-> Result < (), Error >") + .expect("Failed to parse"); + println!("{:?}", parse_return(Attributes::default(), &rt)); + } +} |
