// 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, pub names: Vec, 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, } fn stringify_token(tokens: impl ToTokens) -> String { tokens .into_token_stream() .into_iter() .map(|s| s.to_string()) .collect::>() .join("") } pub fn parse_signature( attributes: Vec, signature: Signature, ) -> Result { 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 { let attrs = attributes .iter() .filter_map(parse_attribute) .collect::>(); 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 { 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 { 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, like io::Result and other specialty result types ($($_package:ident ::)* Result < $ty:ty >) => { Ok(RetVal::Result(parse_type(attrs, &ty)?)) } // x::y::Result ($($_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 { 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 { 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 { match parse_type(attrs, ty)? { Arg::Special(special) => Ok(special), _ => Err(ArgError::InvalidType(stringify_token(ty))), } } fn parse_type(attrs: Attributes, ty: &Type) -> Result { 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 { 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::(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, (SerdeV8("package::SerdeInputType")) -> Result(SerdeV8("package::SerdeReturnType")) ); test!( fn op_local(input: v8::Local) -> Result, 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, AnyError>, (Ref(Mut, OpState)) -> Result(OptionNumeric(u32)) ); test!( fn op_ffi_read_f64(state: &mut OpState, ptr: * mut c_void, offset: isize) -> Result , (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::("-> Result < (), Error >") .expect("Failed to parse"); println!("{:?}", parse_return(Attributes::default(), &rt)); } }