1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-10 16:11:13 -05:00
denoland-deno/ops/op2/signature.rs
Matt Mastracci 6afdcf59b8
refactor(ops): op2 supports strings in argument and return position (#19613)
Support strings (&str, String, and Cow) in the argument position and String in the return position. Avoids
copies where possible, though this is not always something we can do.
2023-07-01 22:07:05 +00:00

741 lines
21 KiB
Rust

// 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<Arg>,
// The argument names
pub names: Vec<String>,
// The parsed return value
pub ret_val: RetVal,
// One and only one lifetime allowed
pub lifetime: Option<String>,
// Generic bounds: each generic must have one and only simple trait bound
pub generic_bounds: BTreeMap<String, String>,
}
#[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 <T> and 'where T: Trait', or <T: Trait>)")]
GenericBoundCardinality(String),
#[error("Where clause predicate '{0}' (eg: where T: Trait) must appear in generics list (eg: <T>)")]
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<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))?,
);
}
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<Option<String>, 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<BTreeMap<String, String>, 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<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 :: 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<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 {
(
// 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::<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(|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::<ItemFn>(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<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 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::<ReturnType>("-> Result < (), Error >")
.expect("Failed to parse");
println!("{:?}", parse_return(Attributes::default(), &rt));
}
}