use bridge_types::Param;
use bridge_types::PassingStyle;
use bridge_types::TypeHandle;
use quote::quote;
use quote::ToTokens;
use quote::__private::TokenStream;
use std::fmt::{Display, Formatter};
use syn::__private::{Span, TokenStream2};
use syn::spanned::Spanned;
use syn::{
parse_macro_input, AttributeArgs, Error, FnArg, GenericArgument, Generics, Ident, Item, ItemFn,
Lit, Meta, NestedMeta, PathArguments, ReturnType, Type, TypeBareFn, TypePath, TypeReference,
TypeTuple,
};
extern crate static_assertions;
type MacroResult<T> = Result<T, Error>;
const POSSIBLE_RETURN_TYPES: [&str; 2] = ["VMResult", "Option"];
const SPECIAL_ARG_TYPES: [&str; 2] = ["Option", "VarArgs"];
const POSSIBLE_ARG_TYPES: [&str; 3] = ["Option", "VarArgs", "Vec"];
#[derive(Copy, Clone)]
enum SupportedGenericReturnTypes {
VMResult,
Option,
}
enum RustType {
#[allow(dead_code)]
BareFn(TypeBareFn, Span),
Path(TypePath, Span),
Tuple(TypeTuple, Span),
Reference(TypeReference, Span),
Unsupported(Span),
}
impl RustType {
pub fn span(&self) -> Span {
match self {
RustType::BareFn(_, x) => *x,
RustType::Path(_, x) => *x,
RustType::Tuple(_, x) => *x,
RustType::Reference(_, x) => *x,
RustType::Unsupported(x) => *x,
}
}
}
impl From<Type> for RustType {
fn from(ty: Type) -> Self {
match ty {
Type::BareFn(x) => {
let span = x.span();
RustType::BareFn(x, span)
}
Type::Path(x) => {
let span = x.span();
RustType::Path(x, span)
}
Type::Reference(x) => {
let span = x.span();
RustType::Reference(x, span)
}
Type::Tuple(x) => {
let span = x.span();
RustType::Tuple(x, span)
}
x => {
let span = x.span();
RustType::Unsupported(span)
}
}
}
}
impl Display for SupportedGenericReturnTypes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SupportedGenericReturnTypes::VMResult => {
write!(f, "VMResult")
}
SupportedGenericReturnTypes::Option => {
write!(f, "Option")
}
}
}
}
fn get_return_type(
original_item_fn: &ItemFn,
) -> MacroResult<(Option<Type>, Option<SupportedGenericReturnTypes>)> {
let return_type = match &original_item_fn.sig.output {
ReturnType::Default => return Ok((None, None)),
ReturnType::Type(_ra_arrow, ty) => *ty.clone(),
};
if let Some((inner_type, type_path)) = get_generic_argument_from_type(&return_type) {
let wrapper = is_valid_generic_type(type_path, POSSIBLE_RETURN_TYPES.as_slice())?;
match inner_type {
GenericArgument::Type(ty) => Ok((Some(ty.clone()), Some(wrapper))),
_ => Err(Error::new(
original_item_fn.span(),
format!(
"sl_sh_fn macros can only return generic arguments of types {:?}.",
&POSSIBLE_RETURN_TYPES
),
)),
}
} else {
Ok((Some(return_type), None))
}
}
fn opt_is_valid_generic_type<'a>(
type_path: &TypePath,
possible_types: &'a [&str],
) -> Option<&'a str> {
match type_path.path.segments.first() {
Some(path_segment) if type_path.path.segments.len() == 1 => {
let ident = &path_segment.ident;
for type_name in possible_types {
if ident == type_name {
return Some(type_name);
}
}
}
_ => {}
}
None
}
fn is_valid_generic_type(
type_path: &TypePath,
possible_types: &[&str],
) -> MacroResult<SupportedGenericReturnTypes> {
if type_path.path.segments.len() == 1 && type_path.path.segments.first().is_some() {
let path_segment = &type_path.path.segments.first().unwrap();
let ident = &path_segment.ident;
for type_name in possible_types {
if ident == type_name {
if type_name == &SupportedGenericReturnTypes::VMResult.to_string().as_str() {
return Ok(SupportedGenericReturnTypes::VMResult);
} else if type_name == &SupportedGenericReturnTypes::Option.to_string().as_str() {
return Ok(SupportedGenericReturnTypes::Option);
}
}
}
}
Err(Error::new(
type_path.span(),
format!(
"Functions can only return GenericArguments of type {possible_types:?}, try wrapping this value in Option or VMResult."
),
))
}
fn get_generic_argument_from_type_path(
type_path: &TypePath,
) -> Option<(&GenericArgument, &TypePath)> {
if type_path.path.segments.len() == 1 {
if let Some(path_segment) = &type_path.path.segments.iter().next_back() {
if let PathArguments::AngleBracketed(args) = &path_segment.arguments {
if let Some(ty) = args.args.iter().next_back() {
return Some((ty, type_path));
}
}
}
}
None
}
fn get_generic_argument_from_type(ty: &Type) -> Option<(&GenericArgument, &TypePath)> {
if let Type::Path(ref type_path) = ty {
get_generic_argument_from_type_path(type_path)
} else {
None
}
}
fn generate_assertions_code_for_return_type_conversions(return_type: &Type) -> TokenStream2 {
quote! {
static_assertions::assert_impl_all!(#return_type: bridge_adapters::lisp_adapters::SlInto<slvm::Value>);
}
}
fn get_attribute_value_with_key(
original_item_fn: &ItemFn,
key: &str,
values: &[(String, String)],
) -> MacroResult<Option<String>> {
if values.is_empty() {
Err(Error::new(
original_item_fn.span(),
"sl_sh_fn requires at least one name-value pair, 'fn_name = \"<name-of-sl-sh-fun>\"'.",
))
} else {
for name_value in values {
if name_value.0 == key {
return Ok(Some(name_value.1.to_string()));
}
}
Ok(None)
}
}
fn get_bool_attribute_value_with_key(
original_item_fn: &ItemFn,
key: &str,
values: &[(String, String)],
) -> MacroResult<bool> {
if values.is_empty() {
Err(Error::new(
original_item_fn.span(),
"sl_sh_fn requires at least one name-value pair, 'fn_name = \"<name-of-sl-sh-fun>\"'.",
))
} else {
for name_value in values {
if name_value.0 == key {
return Ok(name_value.1 == "true");
}
}
Ok(false)
}
}
fn get_attribute_name_value(nested_meta: &NestedMeta) -> MacroResult<(String, String)> {
match nested_meta {
NestedMeta::Meta(meta) => match meta {
Meta::NameValue(pair) => {
let path = &pair.path;
let lit = &pair.lit;
match (path.get_ident(), lit) {
(Some(ident), Lit::Str(partial_name)) => {
Ok((ident.to_string(), partial_name.value()))
}
(Some(ident), Lit::Bool(b)) => {
Ok((ident.to_string(), b.value.to_string()))
}
(_, _) => Err(Error::new(
meta.span(),
"sl_sh_fn requires one name-value pair, 'fn_name'. Supports optional name-value pair 'eval_values = true')",
)),
}
}
other => Err(Error::new(
other.span(),
"sl_sh_fn only supports one name-value pair attribute argument, 'fn_name'.",
)),
},
other => Err(Error::new(
other.span(),
"sl_sh_fn only supports one name-value pair attribute argument, 'fn_name'.",
)),
}
}
fn get_type_handle(type_path: &TypePath) -> TypeHandle {
if let Some((_generic, type_path)) = get_generic_argument_from_type_path(type_path) {
let wrapper = opt_is_valid_generic_type(type_path, SPECIAL_ARG_TYPES.as_slice());
match wrapper {
Some("Option") => {
return TypeHandle::Optional;
}
Some("VarArgs") => {
return TypeHandle::VarArgs;
}
_ => {}
}
}
TypeHandle::Direct
}
fn no_parse_param(
_arg_name: &Ident,
inner: TokenStream,
_param: Param,
_required_args: usize,
_idx: usize,
) -> TokenStream {
inner
}
fn parse_param(
arg_name: &Ident,
inner: TokenStream,
param: Param,
required_args: usize,
idx: usize,
) -> TokenStream {
match param.handle {
TypeHandle::Direct => {
quote! {
let param = arg_types[#idx];
match param.handle {
bridge_types::TypeHandle::Direct => match args.get(#idx) {
None => {
return Err(slvm::VMError::new_vm(format!(
"{} not given enough arguments, expected at least {} arguments, got {}.",
fn_name,
#required_args,
args.len()
)));
}
Some(#arg_name) => {
let #arg_name = *#arg_name;
#inner
},
},
_ => {
return Err(slvm::VMError::new_vm(format!(
"{} failed to parse its arguments, internal error.",
fn_name,
)));
},
}
}
}
TypeHandle::Optional => {
quote! {
let param = arg_types[#idx];
let #arg_name = args.get(#idx);
match param.handle {
bridge_types::TypeHandle::Optional => {
#inner
},
_ => {
return Err(slvm::VMError::new_vm(format!(
"{} failed to parse its arguments, internal error.",
fn_name,
)));
},
}
}
}
TypeHandle::VarArgs => {
quote! {
let param = arg_types[#idx];
let arg = args.get(#idx);
match param.handle {
bridge_types::TypeHandle::VarArgs => {
let #arg_name: &[slvm::Value] = if arg.is_none() {
&[]
} else {
&args[#idx..]
};
#inner
},
_ => {
return Err(slvm::VMError::new_vm(format!(
"{} failed to parse its arguments, internal error.",
fn_name,
)));
}
}
}
}
}
}
fn get_parser_for_type_handle(
noop_outer_parse: bool,
) -> fn(&Ident, TokenStream, Param, usize, usize) -> TokenStream {
match noop_outer_parse {
true => no_parse_param,
false => parse_param,
}
}
fn make_orig_fn_call(
inline: bool,
takes_env: bool,
original_item_fn: &ItemFn,
original_fn_name: &Ident,
required_args: usize,
arg_names: Vec<Ident>,
) -> MacroResult<TokenStream> {
let skip = usize::from(takes_env);
let takes_env = if takes_env {
quote! {environment, } } else {
quote! {}
};
let (return_type, lisp_return) = get_return_type(original_item_fn)?;
let returns_none = "()" == return_type.to_token_stream().to_string();
let fn_body = if inline {
let mut inline_toks = vec![];
for (fn_arg, arg_name) in original_item_fn
.sig
.inputs
.iter()
.skip(skip)
.zip(arg_names.iter())
{
match fn_arg {
FnArg::Receiver(_) => {}
FnArg::Typed(typed) => {
let pat = typed.pat.clone();
let ty = typed.ty.clone();
let binding = quote! {
let #pat: #ty = #arg_name;
};
inline_toks.push(binding);
}
}
}
let block = original_item_fn.block.clone();
match &original_item_fn.sig.output {
ReturnType::Default => {
quote! {{
#(#inline_toks)*
let res = {
#block
};
res
}}
}
ReturnType::Type(_ra_arrow, ty) => {
quote! {{
#(#inline_toks)*
let res: #ty = {
#block
};
res
}}
}
}
} else {
quote! {
#original_fn_name(#takes_env #(#arg_names),*)
}
};
let original_fn_call = match (return_type, lisp_return, returns_none) {
(Some(_), Some(SupportedGenericReturnTypes::VMResult), true) => quote! {
#fn_body?;
return Ok(slvm::Value::Nil);
},
(Some(_), Some(SupportedGenericReturnTypes::Option), true) => quote! {
#fn_body;
return Ok(slvm::Value::Nil);
},
(Some(_), Some(SupportedGenericReturnTypes::VMResult), false) => quote! {
use bridge_adapters::lisp_adapters::SlInto;
return #fn_body.and_then(|x| x.sl_into(environment));
},
(Some(_), Some(SupportedGenericReturnTypes::Option), false) => quote! {
if let Some(val) = #fn_body {
use bridge_adapters::lisp_adapters::SlFrom;
let val = slvm::Value::sl_from(val, environment)?;
Ok(val)
} else {
return Ok(slvm::Value::Nil);
}
},
(Some(_), None, _) => quote! {
use bridge_adapters::lisp_adapters::SlInto;
return #fn_body.sl_into(environment);
},
(None, Some(_), _) => {
unreachable!("If this functions returns a VMResult it must also return a value.");
}
(None, None, _) => quote! {
#fn_body;
return Ok(slvm::Value::Nil);
},
};
let const_params_len = get_const_params_len_ident();
Ok(quote! {
match args.get(#const_params_len) {
Some(_) if #const_params_len == 0 || arg_types[#const_params_len - 1].handle != bridge_types::TypeHandle::VarArgs => {
return Err(slvm::VMError::new_vm(format!(
"{} given too many arguments, expected at least {} arguments, got {}.",
fn_name,
#required_args,
args.len()
)));
}
_ => {
#original_fn_call
}
}
})
}
fn generate_inner_fn_signature_to_orig_fn_call(
original_item_fn: &ItemFn,
takes_env: bool,
) -> MacroResult<Vec<Ident>> {
let len = if takes_env {
original_item_fn.sig.inputs.len() - 1
} else {
original_item_fn.sig.inputs.len()
};
let mut arg_names = vec![];
for i in 0..len {
let parse_name = "arg_".to_string() + &i.to_string();
let parse_name = Ident::new(&parse_name, Span::call_site());
arg_names.push(parse_name);
}
Ok(arg_names)
}
fn tokens_for_matching_references(passing_style: PassingStyle, ty: &TypePath) -> TokenStream {
match passing_style {
PassingStyle::Value => quote! {#ty},
PassingStyle::Reference => quote! {&#ty},
PassingStyle::MutReference => quote! {& mut #ty},
}
}
fn get_arg_pos(ident: &Ident) -> MacroResult<String> {
let arg_string = ident.to_string();
arg_string.split('_').nth(1).map(|x| x.to_string()).ok_or_else(|| Error::new(
ident.span(),
"Arg name should be in form arg_2 which means it should always have two and only two items. Internal error.",
))
}
fn parse_variadic_args_type(
arg_name_itself_is_iter: bool,
ty: &TypePath,
fn_name: &str,
arg_name: &Ident,
inner: TokenStream,
collect_type: TokenStream,
) -> MacroResult<TokenStream> {
let rust_type = get_type_or_wrapped_type(ty, POSSIBLE_ARG_TYPES.as_slice());
let arg_pos = get_arg_pos(arg_name)?;
match rust_type {
RustType::Path(_wrapped_ty, _span) => {
if arg_name_itself_is_iter {
Ok(quote! {
let #arg_name = if matches!(#arg_name, slvm::Value::List(_, _) | slvm::Value::Vector(_) | slvm::Value::Pair(_))
{
let #arg_name = #arg_name
.iter(environment)
.map(|#arg_name| {
use bridge_adapters::lisp_adapters::SlIntoRef;
#arg_name.sl_into_ref(environment)
})
.collect::<slvm::VMResult<#ty>>()?;
#inner
} else {
let err_str = format!("{}: Expected a vector or list for argument at position {}.", #fn_name, #arg_pos);
return Err(slvm::VMError::new_vm(err_str));
};
})
} else {
Ok(quote! {
let #arg_name = #arg_name.iter()
.flat_map(|#arg_name| #arg_name.iter_all(environment))
.map(|#arg_name| {
use bridge_adapters::lisp_adapters::SlIntoRef;
#arg_name.sl_into_ref(environment)
})
.collect::<slvm::VMResult<#ty>>()?;
#inner
})
}
}
RustType::Tuple(type_tuple, _span) => {
if !type_tuple.elems.is_empty() {
let arg_pos = get_arg_pos(arg_name)?;
let arg_check = if arg_name_itself_is_iter {
quote! {
if !matches!(#arg_name, slvm::Value::List(_, _) | slvm::Value::Vector(_) | slvm::Value::Pair(_))
{
let err_str = format!("{}: Expected a vector or list for argument at position {}.", #fn_name, #arg_pos);
return Err(slvm::VMError::new_vm(err_str));
}
}
} else {
quote! {}
};
let tuple_len = type_tuple.elems.len();
let arg_name_base = arg_name.to_string() + "_";
let arg_names = (0..type_tuple.elems.len())
.map(|x| {
Ident::new(
&(arg_name_base.to_string() + &x.to_string()),
Span::call_site(),
)
})
.collect::<Vec<Ident>>();
let mut types = vec![];
let mut type_assertions = vec![];
let mut args = vec![];
for (elem, arg_name) in type_tuple.elems.iter().zip(arg_names.iter()) {
types.push(elem.clone());
type_assertions.push(quote! {
static_assertions::assert_impl_all!(slvm::Value: bridge_adapters::lisp_adapters::SlFrom<#elem>);
});
args.push(quote! {
use bridge_adapters::lisp_adapters::SlInto;
let #arg_name: #elem = #arg_name.sl_into(environment)?;
})
}
Ok(quote! {{
use bridge_adapters::lisp_adapters::SlInto;
#(#type_assertions)*
#arg_check
let #arg_name = #arg_name
.iter()
.map(|#arg_name| {
let #arg_name = #arg_name.iter().collect::<Vec<slvm::Value>>();
match #arg_name.sl_into(environment) {
Ok(#arg_name) => {
let #arg_name: [slvm::Value; #tuple_len] = #arg_name;
let [#(#arg_names),*] = #arg_name;
#(#args)*
Ok((#(#arg_names),*))
}
Err(_) => {
let err_str = format!("{}: Expected a sl_sh vector or list of tuples of length {} corresponding to the argument at position {}.", #fn_name, #tuple_len, #arg_pos);
Err(slvm::VMError::new_vm(err_str))
}
}
})
.collect::<slvm::VMResult<#collect_type<(#(#types),*)>>>()?;
#inner
}})
} else {
let arg_pos = get_arg_pos(arg_name)?;
let err_str = format!(
"Error with argument at position {}, sl_sh_fn only supports tuple pairs.",
arg_pos
);
Err(Error::new(type_tuple.span(), err_str))
}
}
ty => {
let err_str = "Vec<T> and VarArgs<T> only support T of type Type::Path or Type::Tuple.";
Err(Error::new(ty.span(), err_str))
}
}
}
#[allow(clippy::too_many_arguments)]
fn parse_optional_type(
ty: &TypePath,
fn_name: &str,
fn_name_ident: &Ident,
arg_name: &Ident,
passing_style: PassingStyle,
inner: TokenStream,
idx: usize,
required_args: usize,
param: Param,
) -> MacroResult<TokenStream> {
let some_inner = quote! {
let #arg_name = Some(#arg_name);
#inner
};
let some_arg_value_type_parsing_code = parse_direct_type(
ty,
fn_name,
fn_name_ident,
arg_name,
passing_style,
some_inner,
idx,
required_args,
param,
)?;
Ok(quote! {
match #arg_name {
None => {
let #arg_name = None;
#inner
}
Some(#arg_name) => {
let #arg_name = *#arg_name;
#some_arg_value_type_parsing_code
}
}
})
}
fn is_vec(ty: &TypePath) -> Option<Type> {
if let Some((ty, type_path)) = get_generic_argument_from_type_path(ty) {
let wrapper = opt_is_valid_generic_type(type_path, &["Vec"]);
if let (GenericArgument::Type(ty), Some(_)) = (ty, wrapper) {
match <Type as Into<RustType>>::into(ty.clone()) {
RustType::Path(_, _) | RustType::Tuple(_, _) => return Some(ty.clone()),
_ => {}
}
}
}
None
}
fn get_type_or_wrapped_type<'a>(ty: &'a TypePath, possible_types: &'a [&str]) -> RustType {
if let Some((ty, type_path)) = get_generic_argument_from_type_path(ty) {
let wrapper = opt_is_valid_generic_type(type_path, possible_types);
if let (GenericArgument::Type(ty), Some(_)) = (ty, wrapper) {
return <Type as Into<RustType>>::into(ty.clone());
}
}
RustType::Path(ty.clone(), ty.span())
}
#[allow(clippy::too_many_arguments)]
fn parse_direct_type(
ty: &TypePath,
fn_name: &str,
fn_name_ident: &Ident,
arg_name: &Ident,
passing_style: PassingStyle,
inner: TokenStream,
idx: usize,
required_args: usize,
param: Param,
) -> MacroResult<TokenStream> {
if is_vec(ty).is_some() {
parse_variadic_args_type(true, ty, fn_name, arg_name, inner, quote! { Vec })
} else {
let ty = get_type_or_wrapped_type(ty, SPECIAL_ARG_TYPES.as_slice());
match ty {
RustType::Path(ty, _span) => {
let (passing_style, ty) = (
passing_style,
tokens_for_matching_references(passing_style, &ty),
);
match passing_style {
PassingStyle::Value => Ok(quote! {{
use bridge_adapters::lisp_adapters::SlIntoRef;
let #arg_name: #ty = #arg_name.sl_into_ref(environment)?;
#inner
}}),
PassingStyle::Reference => Ok(quote! {{
use bridge_adapters::lisp_adapters::SlIntoRef;
let #arg_name: #ty = #arg_name.sl_into_ref(environment)?;
#inner
}}),
PassingStyle::MutReference => Ok(quote! {{
use bridge_adapters::lisp_adapters::SlIntoRefMut;
let #arg_name: #ty = #arg_name.sl_into_ref_mut(environment)?;
#inner
}}),
}
}
RustType::Tuple(type_tuple, _span) => parse_type_tuple(
&type_tuple,
fn_name,
fn_name_ident,
inner,
arg_name,
idx,
required_args,
param,
no_parse_param,
),
RustType::BareFn(_, _) | RustType::Reference(_, _) | RustType::Unsupported(_) => {
let arg_pos = get_arg_pos(arg_name)?;
let err_str = format!(
"Error with argument at position {}, sl_sh_fn only supports Vec<T>, Option<T>, and T where T is a Type::Path or Type::Tuple and can be moved, passed by reference, or passed by mutable reference (|&|&mut )(Type Path | (Type Path,*))",
arg_pos
);
Err(Error::new(ty.span(), err_str))
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn parse_type(
ty: &TypePath,
fn_name: (&str, &Ident),
inner: TokenStream,
param: Param,
arg_name: &Ident,
passing_style: PassingStyle,
idx: usize,
required_args: usize,
outer_parse: fn(&Ident, TokenStream, Param, usize, usize) -> TokenStream,
) -> MacroResult<TokenStream> {
let tokens = match param.handle {
TypeHandle::Direct => parse_direct_type(
ty,
fn_name.0,
fn_name.1,
arg_name,
passing_style,
inner,
idx,
required_args,
param,
)?,
TypeHandle::Optional => parse_optional_type(
ty,
fn_name.0,
fn_name.1,
arg_name,
passing_style,
inner,
idx,
required_args,
param,
)?,
TypeHandle::VarArgs => parse_variadic_args_type(
false,
ty,
fn_name.0,
arg_name,
inner,
quote! { bridge_types::VarArgs },
)?,
};
Ok(outer_parse(arg_name, tokens, param, required_args, idx))
}
fn embed_params_vec(params: &[Param]) -> TokenStream {
let mut tokens = vec![];
for param in params {
tokens.push(match (param.handle, param.passing_style) {
(TypeHandle::Direct, PassingStyle::MutReference) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::Direct,
passing_style: bridge_types::PassingStyle::MutReference
}}
}
(TypeHandle::Optional, PassingStyle::MutReference) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::Optional,
passing_style: bridge_types::PassingStyle::MutReference
}}
}
(TypeHandle::VarArgs, PassingStyle::MutReference) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::VarArgs,
passing_style: bridge_types::PassingStyle::MutReference
}}
}
(TypeHandle::Direct, PassingStyle::Reference) => {
quote! {bridge_types::Param {
handle: bridge_types::TypeHandle::Direct,
passing_style: bridge_types::PassingStyle::Reference
}}
}
(TypeHandle::Optional, PassingStyle::Reference) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::Optional,
passing_style: bridge_types::PassingStyle::Reference
}}
}
(TypeHandle::VarArgs, PassingStyle::Reference) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::VarArgs,
passing_style: bridge_types::PassingStyle::Reference
}}
}
(TypeHandle::Direct, PassingStyle::Value) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::Direct,
passing_style: bridge_types::PassingStyle::Value
}}
}
(TypeHandle::Optional, PassingStyle::Value) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::Optional,
passing_style: bridge_types::PassingStyle::Value
}}
}
(TypeHandle::VarArgs, PassingStyle::Value) => {
quote! { bridge_types::Param {
handle: bridge_types::TypeHandle::VarArgs,
passing_style: bridge_types::PassingStyle::Value
}}
}
});
}
let const_params_len = get_const_params_len_ident();
quote! {
let arg_types: [bridge_types::Param; #const_params_len] = [ #(#tokens),* ];
}
}
fn generate_intern_fn(
original_fn_name_str: &str,
fn_name_ident: &Ident,
fn_name: &str,
doc_comments: String,
) -> TokenStream {
let parse_name = get_parse_fn_name(original_fn_name_str);
let intern_name = get_intern_fn_name(original_fn_name_str);
quote! {
fn #intern_name(env: &mut compile_state::state::SloshVm) {
let #fn_name_ident = #fn_name;
bridge_adapters::add_builtin(env, #fn_name_ident, #parse_name, #doc_comments);
}
}
}
fn generate_parse_fn(
generics: Option<Generics>,
original_fn_name_str: &str,
fn_name_ident: &Ident,
fn_name: &str,
args_len: usize,
params: &[Param],
inner: TokenStream,
) -> TokenStream {
let parse_name = get_parse_fn_name(original_fn_name_str);
let arg_vec_literal = embed_params_vec(params);
let const_params_len = get_const_params_len_ident();
if let Some(generics) = generics {
let params = generics.params.clone();
quote! {
fn #parse_name #generics(
environment: &#params mut compile_state::state::SloshVm,
args: &#params [slvm::Value],
) -> slvm::VMResult<slvm::Value> {
let #fn_name_ident = #fn_name;
const #const_params_len: usize = #args_len;
#arg_vec_literal
#inner
}
}
} else {
quote! {
fn #parse_name(
environment: &mut compile_state::state::SloshVm,
args: &[slvm::Value],
) -> slvm::VMResult<slvm::Value> {
let #fn_name_ident = #fn_name;
const #const_params_len: usize = #args_len;
#arg_vec_literal
#inner
}
}
}
}
fn num_required_args(params: &[Param]) -> usize {
params.iter().fold(0, |accumulator, nxt| {
if nxt.handle == TypeHandle::Direct {
accumulator + 1
} else {
accumulator
}
})
}
fn generate_builtin_fn(
original_item_fn: &ItemFn,
original_fn_name_str: &str,
fn_name: &str,
params: &[Param],
fn_name_ident: &Ident,
takes_env: bool,
inline: bool,
) -> MacroResult<TokenStream> {
let original_fn_name = Ident::new(original_fn_name_str, Span::call_site());
let arg_names = generate_inner_fn_signature_to_orig_fn_call(original_item_fn, takes_env)?;
let required_args = num_required_args(params);
let orig_fn_call = make_orig_fn_call(
inline,
takes_env,
original_item_fn,
&original_fn_name,
required_args,
arg_names.clone(),
)?;
let mut prev_token_stream = orig_fn_call;
let skip = usize::from(takes_env);
let inputs_less_env_len = original_item_fn.sig.inputs.len() - skip;
if inputs_less_env_len != params.len() {
let err_str = format!(
"sl_sh_fn macro is broken, signature of target function has an arity of {}, but this macro computed its arity as: {} (arity is - 1 if takes_env is true).",
inputs_less_env_len,
params.len(),
);
return Err(Error::new(original_item_fn.span(), err_str));
}
let fn_args = original_item_fn
.sig
.inputs
.iter()
.skip(skip)
.zip(arg_names.iter())
.zip(params.iter());
for (idx, ((fn_arg, arg_name), param)) in fn_args.enumerate() {
if let FnArg::Typed(ty) = fn_arg {
prev_token_stream = parse_fn_arg_type(
&ty.ty,
fn_name,
fn_name_ident,
prev_token_stream,
arg_name,
false,
idx,
*param,
required_args,
)?;
}
}
let (return_type, _) = get_return_type(original_item_fn)?;
let mut conversions_assertions_code = vec![];
if let Some(return_type) = return_type {
if get_generic_argument_from_type(&return_type).is_none() {
conversions_assertions_code.push(generate_assertions_code_for_return_type_conversions(
&return_type,
));
}
}
let tokens = quote! {
#(#conversions_assertions_code)*
#prev_token_stream
};
Ok(tokens)
}
#[allow(clippy::too_many_arguments)]
fn parse_fn_arg_type(
ty: &Type,
fn_name: &str,
fn_name_ident: &Ident,
prev_token_stream: TokenStream,
arg_name: &Ident,
noop_outer_parse: bool,
idx: usize,
param: Param,
required_args: usize,
) -> MacroResult<TokenStream> {
match <Type as Into<RustType>>::into(ty.clone()) {
RustType::Path(ty, _span) => {
let parse_layer_1 = get_parser_for_type_handle(noop_outer_parse);
let passing_style = PassingStyle::Value;
parse_type(
&ty,
(fn_name, fn_name_ident),
prev_token_stream,
param,
arg_name,
passing_style,
idx,
required_args,
parse_layer_1,
)
}
RustType::Tuple(type_tuple, _span) => {
let parse_layer_1 = get_parser_for_type_handle(noop_outer_parse);
parse_type_tuple(
&type_tuple,
fn_name,
fn_name_ident,
prev_token_stream,
arg_name,
idx,
required_args,
param,
parse_layer_1,
)
}
RustType::Reference(ty_ref, _span) => match <Type as Into<RustType>>::into(*ty_ref.elem) {
RustType::Path(ty, _span) => {
let parse_layer_1 = get_parser_for_type_handle(noop_outer_parse);
let passing_style = if ty_ref.mutability.is_some() {
PassingStyle::MutReference
} else {
PassingStyle::Reference
};
parse_type(
&ty,
(fn_name, fn_name_ident),
prev_token_stream,
param,
arg_name,
passing_style,
idx,
required_args,
parse_layer_1,
)
}
RustType::Tuple(type_tuple, _span) => {
let parse_layer_1 = get_parser_for_type_handle(noop_outer_parse);
parse_type_tuple(
&type_tuple,
fn_name,
fn_name_ident,
prev_token_stream,
arg_name,
idx,
required_args,
param,
parse_layer_1,
)
}
RustType::BareFn(_, _) | RustType::Unsupported(_) | RustType::Reference(_, _) => {
let arg_pos = get_arg_pos(arg_name)?;
let err_str = format!(
"Error with argument at position {}, sl_sh_fn only supports Vec<T>, Option<T>, and T where T is a Type::Path or Type::Tuple and can be moved, passed by reference, or passed by mutable reference (|&|&mut )(Type Path | (Type Path,*))",
arg_pos
);
Err(Error::new(ty.span(), err_str))
}
},
RustType::BareFn(_, _) | RustType::Unsupported(_) => {
let arg_pos = get_arg_pos(arg_name)?;
let err_str = format!(
"Error with argument at position {}, sl_sh_fn only supports Vec<T>, Option<T>, and T where T is a Type::Path or Type::Tuple and can be moved, passed by reference, or passed by mutable reference (|&|&mut )(Type Path | (Type Path,*))",
arg_pos
);
Err(Error::new(ty.span(), err_str))
}
}
}
#[allow(clippy::too_many_arguments)]
fn parse_type_tuple(
type_tuple: &TypeTuple,
fn_name: &str,
fn_name_ident: &Ident,
inner: TokenStream,
arg_name: &Ident,
idx: usize,
required_args: usize,
param: Param,
outer_parse: fn(&Ident, TokenStream, Param, usize, usize) -> TokenStream,
) -> MacroResult<TokenStream> {
let arg_name_base = arg_name.to_string() + "_";
let arg_names = (0..type_tuple.elems.len())
.map(|x| {
Ident::new(
&(arg_name_base.to_string() + &x.to_string()),
Span::call_site(),
)
})
.collect::<Vec<Ident>>();
let mut inner = quote! {
let #arg_name = (#(#arg_names),*);
#inner
};
let mut expressions = vec![];
let tuple_len = type_tuple.elems.len();
let tokens = if !type_tuple.elems.is_empty() {
for (i, ty) in type_tuple.elems.iter().enumerate() {
expressions.push(quote! { slvm::Value });
let arg_name_pair = Ident::new(
&(arg_name_base.to_string() + &i.to_string()),
Span::call_site(),
);
let param = get_param_from_type(ty.clone(), ty.span(), i)?;
inner = parse_fn_arg_type(
ty,
fn_name,
fn_name_ident,
inner,
&arg_name_pair,
true,
i,
param,
required_args,
)?;
}
inner
} else {
inner
};
let arg_pos = get_arg_pos(arg_name)?;
let tokens = quote! {{
use bridge_adapters::lisp_adapters::SlInto;
if !matches!(#arg_name, slvm::Value::List(_, _) | slvm::Value::Vector(_) | slvm::Value::Pair(_))
{
let err_str = format!("{}: Expected a vector or list for argument at position {}.", #fn_name, #arg_pos);
return Err(slvm::VMError::new_vm(err_str));
}
let #arg_name = #arg_name.iter().collect::<Vec<slvm::Value>>();
match #arg_name.sl_into(environment) {
Ok(#arg_name) => {
let #arg_name: [slvm::Value; #tuple_len] = #arg_name;
let [#(#arg_names),*] = #arg_name;
#tokens
}
Err(_) => {
let err_str = format!("{}: Expected a sl_sh vector or list with {} elements corresponding to the tuple at argument position {}.", #fn_name, #tuple_len, #arg_pos);
return Err(slvm::VMError::new_vm(err_str));
}
}
}};
Ok(outer_parse(arg_name, tokens, param, required_args, idx))
}
fn are_args_valid(original_item_fn: &ItemFn, params: &[Param], takes_env: bool) -> MacroResult<()> {
if params.is_empty() || (!takes_env && params.len() == 1 || takes_env && params.len() == 2) {
Ok(())
} else {
let mut found_opt = false;
let mut found_value = false;
for (i, param) in params.iter().rev().enumerate() {
match (i, param.handle, found_opt, found_value) {
(i, TypeHandle::VarArgs, _, _) if i > 0 => {
return Err(Error::new(
original_item_fn.span(),
"Only one Vec argument is supported and it must be the last argument.",
));
}
(_, TypeHandle::Optional, _, true) => {
return Err(Error::new(
original_item_fn.span(),
"Optional argument(s) must be placed last.",
));
}
(_, TypeHandle::Optional, _, _) => {
found_opt = true;
}
(_, TypeHandle::Direct, _, _) => {
found_value = true;
}
(_, _, _, _) => {}
}
}
Ok(())
}
}
fn get_param_from_type(ty: Type, span: Span, pos: usize) -> MacroResult<Param> {
let ty_clone = ty.clone();
let param = match <Type as Into<RustType>>::into(ty) {
RustType::Path(ty, _span) => {
let val = get_type_handle(&ty);
Param {
handle: val,
passing_style: PassingStyle::Value,
}
}
RustType::Tuple(_type_tuple, _span) => Param {
handle: TypeHandle::Direct,
passing_style: PassingStyle::Value,
},
RustType::Reference(ty_ref, _span) => {
let passing_style = if ty_ref.mutability.is_some() {
PassingStyle::MutReference
} else {
PassingStyle::Reference
};
match <Type as Into<RustType>>::into(*ty_ref.elem) {
RustType::Path(ty, _span) => {
let val = get_type_handle(&ty);
Param {
handle: val,
passing_style,
}
}
RustType::Tuple(_type_tuple, _span) => Param {
handle: TypeHandle::Direct,
passing_style,
},
_ => {
return Err(Error::new(
span,
format!(
"Error with argument at position {}, sl_sh_fn only supports passing Type::Path and Type::Tuple by value or ref/ref mut, no either syn::Type's are supported: {:?}.",
pos,
ty_clone.to_token_stream(),
),
));
}
}
}
_ => {
return Err(Error::new(
span,
format!(
"Error with argument at position {}, sl_sh_fn only supports passing Type::Path and Type::Tuple by value or ref/ref mut, no either syn::Type's are supported: {:?}.",
pos,
ty_clone.to_token_stream(),
),
));
}
};
Ok(param)
}
fn parse_src_function_arguments(
original_item_fn: &ItemFn,
takes_env: bool,
) -> MacroResult<Vec<Param>> {
let mut parsed_args = vec![];
let len = if takes_env {
original_item_fn.sig.inputs.len() - 1
} else {
original_item_fn.sig.inputs.len()
};
let mut arg_names = vec![];
for i in 0..len {
let parse_name = "arg_".to_string() + &i.to_string();
let parse_name = Ident::new(&parse_name, Span::call_site());
arg_names.push(parse_name);
}
let skip = usize::from(takes_env);
for (i, fn_arg) in original_item_fn.sig.inputs.iter().enumerate().skip(skip) {
match fn_arg {
FnArg::Receiver(_) => {
return Err(Error::new(
original_item_fn.span(),
"Associated functions that take the self argument are not supported.",
))
}
FnArg::Typed(ty) => {
parsed_args.push(get_param_from_type(
*ty.ty.clone(),
original_item_fn.span(),
i,
)?);
}
}
}
Ok(parsed_args)
}
fn get_intern_fn_name(original_fn_name: &str) -> Ident {
let builtin_name = "intern_".to_string() + original_fn_name;
Ident::new(&builtin_name, Span::call_site())
}
fn get_parse_fn_name(original_fn_name: &str) -> Ident {
let builtin_name = "parse_".to_string() + original_fn_name;
Ident::new(&builtin_name, Span::call_site())
}
fn get_documentation_for_fn(original_item_fn: &ItemFn) -> MacroResult<String> {
let mut docs = "".to_string();
for attr in &original_item_fn.attrs {
for path_segment in attr.path.segments.iter() {
if &path_segment.ident.to_string() == "doc" {
if let Ok(Meta::NameValue(pair)) = attr.parse_meta() {
if let Lit::Str(partial_name) = &pair.lit {
docs += (*partial_name.value()).trim();
docs += "\n";
}
}
}
}
}
if docs.is_empty() {
Err(Error::new(
original_item_fn.span(),
"Functions with this attribute included must have documentation.",
))
} else {
Ok(docs)
}
}
fn get_const_params_len_ident() -> Ident {
Ident::new("PARAMS_LEN", Span::call_site())
}
fn parse_attributes(
original_item_fn: &ItemFn,
attr_args: AttributeArgs,
) -> MacroResult<(String, Ident, bool, bool, Option<Generics>)> {
let vals = attr_args
.iter()
.map(get_attribute_name_value)
.collect::<MacroResult<Vec<(String, String)>>>()?;
let fn_name_ident = "fn_name".to_string();
let fn_name = get_attribute_value_with_key(original_item_fn, &fn_name_ident, vals.as_slice())?
.ok_or_else(|| {
Error::new(
original_item_fn.span(),
"sl_sh_fn requires name-value pair, 'fn_name'",
)
})?;
let fn_name_ident = Ident::new(&fn_name_ident, Span::call_site());
let takes_env =
get_bool_attribute_value_with_key(original_item_fn, "takes_env", vals.as_slice())?;
let inline =
!get_bool_attribute_value_with_key(original_item_fn, "do_not_inline", vals.as_slice())?;
let generics = original_item_fn.sig.generics.clone();
let generics = match generics.params.len() {
0 => None,
1 => Some(generics),
_ => {
return Err(Error::new(
original_item_fn.sig.generics.span(),
"sl_sh_fn only supports functions with 0 or 1 generic lifetime parameters.",
))
}
};
Ok((fn_name, fn_name_ident, takes_env, inline, generics))
}
fn generate_sl_sh_fn(
original_item_fn: &ItemFn,
attr_args: AttributeArgs,
) -> MacroResult<TokenStream> {
let (fn_name, fn_name_ident, takes_env, inline, generics) =
parse_attributes(original_item_fn, attr_args)?;
let original_fn_name_str = original_item_fn.sig.ident.to_string();
let original_fn_name_str = original_fn_name_str.as_str();
let params = parse_src_function_arguments(original_item_fn, takes_env)?;
are_args_valid(original_item_fn, params.as_slice(), takes_env)?;
let builtin_fn = generate_builtin_fn(
original_item_fn,
original_fn_name_str,
fn_name.as_str(),
params.as_slice(),
&fn_name_ident,
takes_env,
inline,
)?;
let args_len = if takes_env {
original_item_fn.sig.inputs.len() - 1
} else {
original_item_fn.sig.inputs.len()
};
let doc_comments = get_documentation_for_fn(original_item_fn)?;
let intern_fn = generate_intern_fn(
original_fn_name_str,
&fn_name_ident,
fn_name.as_str(),
doc_comments,
);
let parse_fn = generate_parse_fn(
generics,
original_fn_name_str,
&fn_name_ident,
fn_name.as_str(),
args_len,
params.as_slice(),
builtin_fn,
);
let tokens = quote! {
#parse_fn
#intern_fn
};
Ok(tokens)
}
#[proc_macro_attribute]
pub fn sl_sh_fn(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr_args = parse_macro_input!(attr as AttributeArgs);
let tokens = match syn::parse::<Item>(input) {
Ok(item) => match &item {
Item::Fn(original_item_fn) => {
let generated_code = match generate_sl_sh_fn(original_item_fn, attr_args) {
Ok(generated_code) => generated_code,
Err(e) => e.to_compile_error(),
};
let original_fn_code = item.into_token_stream();
quote! {
#generated_code
#[allow(dead_code)]
#original_fn_code
}
}
_ => Error::new(item.span(), "This attribute only supports functions.")
.to_compile_error(),
},
Err(e) => Error::new(e.span(), "Failed to parse proc_macro_attribute.").to_compile_error(),
};
proc_macro::TokenStream::from(tokens)
}
#[cfg(test)]
mod test {
use super::*;
fn loop_over_to_inline<T, const N: usize>(
fn_name: &str,
params: &[Param; N],
args: &[T],
) -> Result<(), String> {
let required_args = num_required_args(params);
for idx in 0..N {
to_inline(fn_name, idx, required_args, params, args)?;
}
if N > 0 {
too_many_args_detection(fn_name, params, N, args)?;
}
Ok(())
}
fn too_many_args_detection<T>(
fn_name: &str,
arg_types: &[Param],
len: usize,
args: &[T],
) -> Result<(), String> {
match args.get(len) {
Some(_) if arg_types[len - 1].handle != TypeHandle::VarArgs => {
return Err(format!(
"{} given too many arguments, expected {}, got {}.",
fn_name,
arg_types.len(),
args.len()
));
}
_ => {
println!("macro")
}
}
Ok(())
}
fn to_inline<T>(
fn_name: &str,
idx: usize,
required_args: usize,
params: &[Param],
args: &[T],
) -> Result<(), String> {
let param = params[idx];
match args.get(idx) {
None if param.handle == TypeHandle::Direct => {
return Err(format!(
"{} not given enough arguments, expected at least {} arguments, got {}.",
fn_name,
required_args,
args.len()
));
}
_arg => {
println!("macro");
}
}
Ok(())
}
struct Foo {}
#[test]
fn test_params_values_only() {
let two_moved_values = vec![
Param {
handle: TypeHandle::Direct,
passing_style: PassingStyle::Value,
},
Param {
handle: TypeHandle::Direct,
passing_style: PassingStyle::Value,
},
];
let args = vec![Foo {}];
let args = loop_over_to_inline::<Foo, 2>(
"foo",
two_moved_values.as_slice().try_into().unwrap(),
args.as_slice(),
);
assert!(args.unwrap_err().contains("not given enough arguments"));
let args = vec![Foo {}, Foo {}, Foo {}];
let args = loop_over_to_inline::<Foo, 2>(
"foo",
two_moved_values.as_slice().try_into().unwrap(),
args.as_slice(),
);
assert!(args.unwrap_err().contains("given too many"));
let args = vec![Foo {}, Foo {}];
loop_over_to_inline::<Foo, 2>(
"foo",
two_moved_values.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
}
#[test]
fn test_params_optionals() {
let one_val_one_opt = vec![
Param {
handle: TypeHandle::Direct,
passing_style: PassingStyle::Value,
},
Param {
handle: TypeHandle::Optional,
passing_style: PassingStyle::Value,
},
];
let args = vec![Foo {}, Foo {}];
loop_over_to_inline::<Foo, 2>(
"foo",
one_val_one_opt.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}];
loop_over_to_inline::<Foo, 2>(
"foo",
one_val_one_opt.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![];
let args = loop_over_to_inline::<Foo, 2>(
"foo",
one_val_one_opt.as_slice().try_into().unwrap(),
args.as_slice(),
);
assert!(args.unwrap_err().contains("not given enough arguments"));
let args = vec![Foo {}, Foo {}, Foo {}];
let args = loop_over_to_inline::<Foo, 2>(
"foo",
one_val_one_opt.as_slice().try_into().unwrap(),
args.as_slice(),
);
assert!(args.unwrap_err().contains("given too many"));
let val_and_opt = vec![
Param {
handle: TypeHandle::Direct,
passing_style: PassingStyle::Value,
},
Param {
handle: TypeHandle::Optional,
passing_style: PassingStyle::Value,
},
];
let args = vec![Foo {}, Foo {}];
loop_over_to_inline::<Foo, 2>(
"foo",
val_and_opt.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}];
loop_over_to_inline::<Foo, 2>(
"foo",
val_and_opt.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![];
let args = loop_over_to_inline::<Foo, 2>(
"foo",
val_and_opt.as_slice().try_into().unwrap(),
args.as_slice(),
);
assert!(args.unwrap_err().contains("not given enough arguments"));
let args = vec![Foo {}, Foo {}, Foo {}];
let args = loop_over_to_inline::<Foo, 2>(
"foo",
val_and_opt.as_slice().try_into().unwrap(),
args.as_slice(),
);
assert!(args.unwrap_err().contains("given too many"));
}
#[test]
fn test_params_vec() {
let one_vec = vec![Param {
handle: TypeHandle::VarArgs,
passing_style: PassingStyle::MutReference,
}];
let args = vec![];
loop_over_to_inline::<Foo, 1>(
"foo",
one_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}];
loop_over_to_inline::<Foo, 1>(
"foo",
one_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}];
loop_over_to_inline::<Foo, 1>(
"foo",
one_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}, Foo {}];
loop_over_to_inline::<Foo, 1>(
"foo",
one_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
}
#[test]
fn test_params_vec_with_options() {
let val_opt_and_vec = vec![
Param {
handle: TypeHandle::Direct,
passing_style: PassingStyle::Reference,
},
Param {
handle: TypeHandle::Optional,
passing_style: PassingStyle::MutReference,
},
Param {
handle: TypeHandle::VarArgs,
passing_style: PassingStyle::Value,
},
];
let args = vec![];
let args = loop_over_to_inline::<Foo, 3>(
"foo",
val_opt_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
);
assert!(args.unwrap_err().contains("not given enough arguments"));
let args = vec![Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
val_opt_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
val_opt_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}, Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
val_opt_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}, Foo {}, Foo {}, Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
val_opt_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let opts_and_vec = vec![
Param {
handle: TypeHandle::Optional,
passing_style: PassingStyle::Reference,
},
Param {
handle: TypeHandle::Optional,
passing_style: PassingStyle::MutReference,
},
Param {
handle: TypeHandle::VarArgs,
passing_style: PassingStyle::Value,
},
];
let args = vec![];
loop_over_to_inline::<Foo, 3>(
"foo",
opts_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
opts_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
opts_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}, Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
opts_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
let args = vec![Foo {}, Foo {}, Foo {}, Foo {}, Foo {}];
loop_over_to_inline::<Foo, 3>(
"foo",
opts_and_vec.as_slice().try_into().unwrap(),
args.as_slice(),
)
.expect("Parsing should succeed.");
}
}