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.rs741
1 files changed, 0 insertions, 741 deletions
diff --git a/ops/op2/signature.rs b/ops/op2/signature.rs
deleted file mode 100644
index 5d472fcf3..000000000
--- a/ops/op2/signature.rs
+++ /dev/null
@@ -1,741 +0,0 @@
-// 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));
- }
-}