summaryrefslogtreecommitdiff
path: root/ops/op2/signature.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ops/op2/signature.rs')
-rw-r--r--ops/op2/signature.rs516
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));
+ }
+}