// 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 std::collections::BTreeMap; use strum::IntoEnumIterator; use strum::IntoStaticStr; use strum_macros::EnumIter; use strum_macros::EnumString; use syn2::Attribute; use syn2::FnArg; use syn2::GenericParam; use syn2::Generics; 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, CowStr, 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 { // The parsed arguments pub args: Vec, // The argument names pub names: Vec, // The parsed return value pub ret_val: RetVal, // One and only one lifetime allowed pub lifetime: Option, // Generic bounds: each generic must have one and only simple trait bound pub generic_bounds: BTreeMap, } #[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), #[error("Only one lifetime is permitted")] TooManyLifetimes, #[error("Generic '{0}' must have one and only bound (either and 'where T: Trait', or )")] GenericBoundCardinality(String), #[error("Where clause predicate '{0}' (eg: where T: Trait) must appear in generics list (eg: )")] WherePredicateMustAppearInGenerics(String), #[error("All generics must appear only once in the generics parameter list or where clause")] DuplicateGeneric(String), #[error("Generic lifetime '{0}' may not have bounds (eg: <'a: 'b>)")] LifetimesMayNotHaveBounds(String), #[error("Invalid generic: '{0}' Only simple generics bounds are allowed (eg: T: Trait)")] InvalidGeneric(String), #[error("Invalid predicate: '{0}' Only simple where predicates are allowed (eg: T: Trait)")] InvalidWherePredicate(String), } #[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))?, ); } let ret_val = parse_return(parse_attributes(&attributes)?, &signature.output)?; let lifetime = parse_lifetime(&signature.generics)?; let generic_bounds = parse_generics(&signature.generics)?; Ok(ParsedSignature { args, names, ret_val, lifetime, generic_bounds, }) } /// Extract one lifetime from the [`syn2::Generics`], ensuring that the lifetime is valid /// and has no bounds. fn parse_lifetime( generics: &Generics, ) -> Result, SignatureError> { let mut res = None; for param in &generics.params { if let GenericParam::Lifetime(lt) = param { if !lt.bounds.is_empty() { return Err(SignatureError::LifetimesMayNotHaveBounds( lt.lifetime.to_string(), )); } if res.is_some() { return Err(SignatureError::TooManyLifetimes); } res = Some(lt.lifetime.ident.to_string()); } } Ok(res) } /// Parse and validate generics. We require one and only one trait bound for each generic /// parameter. Tries to sanity check and return reasonable errors for possible signature errors. fn parse_generics( generics: &Generics, ) -> Result, SignatureError> { let mut where_clauses = BTreeMap::new(); // First, extract the where clause so we can detect duplicated predicates if let Some(where_clause) = &generics.where_clause { for predicate in &where_clause.predicates { let predicate = predicate.to_token_stream(); let (generic_name, bound) = std::panic::catch_unwind(|| { use syn2 as syn; rules!(predicate => { ($t:ident : $bound:path) => (t.to_string(), stringify_token(bound)), }) }) .map_err(|_| { SignatureError::InvalidWherePredicate(predicate.to_string()) })?; if where_clauses.insert(generic_name.clone(), bound).is_some() { return Err(SignatureError::DuplicateGeneric(generic_name)); } } } let mut res = BTreeMap::new(); for param in &generics.params { if let GenericParam::Type(ty) = param { let ty = ty.to_token_stream(); let (name, bound) = std::panic::catch_unwind(|| { use syn2 as syn; rules!(ty => { ($t:ident : $bound:path) => (t.to_string(), Some(stringify_token(bound))), ($t:ident) => (t.to_string(), None), }) }).map_err(|_| SignatureError::InvalidGeneric(ty.to_string()))?; let bound = match bound { Some(bound) => { if where_clauses.contains_key(&name) { return Err(SignatureError::GenericBoundCardinality(name)); } bound } None => { let Some(bound) = where_clauses.remove(&name) else { return Err(SignatureError::GenericBoundCardinality(name)); }; bound } }; if res.contains_key(&name) { return Err(SignatureError::DuplicateGeneric(name)); } res.insert(name, bound); } } if !where_clauses.is_empty() { return Err(SignatureError::WherePredicateMustAppearInGenerics( where_clauses.into_keys().next().unwrap(), )); } Ok(res) } 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 :: str :: )? str ) => { // We should not hit this path with a #[string] argument Err(ArgError::MissingStringAttribute) } ( $( std :: borrow :: )? Cow < str > ) => { if attrs.primary == Some(AttributeModifier::String) { Ok(Arg::Special(Special::CowStr)) } 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 { ( // Function attributes $(# [ $fn_attr:ident ])? // fn name < 'scope, GENERIC1, GENERIC2, ... > fn $name:ident $( < $scope:lifetime $( , $generic:ident)* >)? ( // Argument attribute, argument $( $(# [ $attr:ident ])? $ident:ident : $ty:ty ),* ) // Return value $(-> $(# [ $ret_attr:ident ])? $ret:ty)? // Where clause $( where $($trait:ident : $bounds:path),* )? ; // Expected return value $( < $( $lifetime_res:lifetime )? $(, $generic_res:ident : $bounds_res:path )* >)? ( $( $arg_res:expr ),* ) -> $ret_res:expr ) => { #[test] fn $name() { test( stringify!($( #[$fn_attr] )? fn op $( < $scope $( , $generic)* >)? ( $( $( #[$attr] )? $ident : $ty ),* ) $(-> $( #[$ret_attr] )? $ret)? $( where $($trait : $bounds),* )? {}), stringify!($( < $( $lifetime_res )? $(, $generic_res : $bounds_res)* > )?), stringify!($($arg_res),*), stringify!($ret_res) ); } }; } fn test( op: &str, generics_expected: &str, args_expected: &str, return_expected: &str, ) { // Parse the provided macro input as an ItemFn 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(|err| { panic!("Failed to successfully parse signature from {op} ({err:?})") }); println!("Raw parsed signatures = {sig:?}"); let mut generics_res = vec![]; if let Some(lifetime) = sig.lifetime { generics_res.push(format!("'{lifetime}")); } for (name, bounds) in sig.generic_bounds { generics_res.push(format!("{name} : {bounds}")); } if !generics_res.is_empty() { assert_eq!( generics_expected, format!("< {} >", generics_res.join(", ")) ); } assert_eq!( args_expected, format!("{:?}", sig.args).trim_matches(|c| c == '[' || c == ']') ); assert_eq!(return_expected, format!("{:?}", sig.ret_val)); } macro_rules! expect_fail { ($name:ident, $error:expr, $f:item) => { #[test] pub fn $name() { expect_fail(stringify!($f), stringify!($error)); } }; } fn expect_fail(op: &str, error: &str) { // Parse the provided macro input as an ItemFn let item_fn = parse_str::(op) .unwrap_or_else(|_| panic!("Failed to parse {op} as a ItemFn")); let attrs = item_fn.attrs; let err = parse_signature(attrs, item_fn.sig) .expect_err("Expected function to fail to parse"); assert_eq!(format!("{err:?}"), error.to_owned()); } 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 op_scope<'s>(#[string] msg: &'s str); <'s> (Special(RefStr)) -> Infallible(Void) ); test!( fn op_scope_and_generics<'s, AB, BC>(#[string] msg: &'s str) where AB: some::Trait, BC: OtherTrait; <'s, AB: some::Trait, BC: OtherTrait> (Special(RefStr)) -> Infallible(Void) ); expect_fail!(op_with_two_lifetimes, TooManyLifetimes, fn f<'a, 'b>() {}); expect_fail!( op_with_lifetime_bounds, LifetimesMayNotHaveBounds("'a"), fn f<'a: 'b, 'b>() {} ); expect_fail!( op_with_missing_bounds, GenericBoundCardinality("B"), fn f<'a, B>() {} ); expect_fail!( op_with_duplicate_bounds, GenericBoundCardinality("B"), fn f<'a, B: Trait>() where B: Trait, { } ); expect_fail!( op_with_extra_bounds, WherePredicateMustAppearInGenerics("C"), fn f<'a, B>() where B: Trait, C: Trait, { } ); #[test] fn test_parse_result() { let rt = parse_str::("-> Result < (), Error >") .expect("Failed to parse"); println!("{:?}", parse_return(Attributes::default(), &rt)); } }