From cd093fa98a18586fff97357ff52ca8e10c0fc392 Mon Sep 17 00:00:00 2001 From: Nikita Strygin Date: Mon, 21 Aug 2023 17:48:49 +0300 Subject: [PATCH] WIP: make it work with syn 2.0 only --- Cargo.lock | 2 - ffi/derive/Cargo.toml | 3 +- ffi/derive/examples/aboba.rs | 147 ++++ ffi/derive/src/attr_parse/derive.rs | 184 ++++ ffi/derive/src/attr_parse/doc.rs | 18 + ffi/derive/src/attr_parse/getset.rs | 831 ++++++++++++++++++ ffi/derive/src/attr_parse/mod.rs | 4 + ffi/derive/src/{ => attr_parse}/repr.rs | 11 + ffi/derive/src/convert.rs | 237 ++--- ffi/derive/src/emitter.rs | 110 +++ ffi/derive/src/ffi_fn.rs | 14 +- ffi/derive/src/getset_gen.rs | 190 ++++ ffi/derive/src/impl_visitor.rs | 94 +- ffi/derive/src/lib.rs | 154 ++-- ffi/derive/src/util.rs | 209 ----- ffi/derive/src/wrapper.rs | 158 ++-- .../tests/ui_fail/derive_skip_struct.rs | 6 +- .../tests/ui_fail/derive_skip_struct.stderr | 8 +- 18 files changed, 1797 insertions(+), 583 deletions(-) create mode 100644 ffi/derive/examples/aboba.rs create mode 100644 ffi/derive/src/attr_parse/derive.rs create mode 100644 ffi/derive/src/attr_parse/doc.rs create mode 100644 ffi/derive/src/attr_parse/getset.rs create mode 100644 ffi/derive/src/attr_parse/mod.rs rename ffi/derive/src/{ => attr_parse}/repr.rs (98%) create mode 100644 ffi/derive/src/emitter.rs create mode 100644 ffi/derive/src/getset_gen.rs delete mode 100644 ffi/derive/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index c7a43ec54fc..2b72c38c4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3248,7 +3248,6 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash", - "syn 1.0.109", "syn 2.0.26", "trybuild", ] @@ -3757,7 +3756,6 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn 1.0.109", "syn 2.0.26", ] diff --git a/ffi/derive/Cargo.toml b/ffi/derive/Cargo.toml index 03cc8a2dd33..22a3ada6e81 100644 --- a/ffi/derive/Cargo.toml +++ b/ffi/derive/Cargo.toml @@ -12,11 +12,10 @@ categories = ["development-tools::ffi"] proc-macro = true [dependencies] -syn = { workspace = true, features = ["full", "visit", "visit-mut", "extra-traits"] } syn2 = { workspace = true, features = ["full", "visit", "visit-mut", "extra-traits"] } quote = { workspace = true } proc-macro2 = { workspace = true } -manyhow = { workspace = true, features = ["syn1"] } +manyhow = { workspace = true } darling = { workspace = true } rustc-hash = { workspace = true } diff --git a/ffi/derive/examples/aboba.rs b/ffi/derive/examples/aboba.rs new file mode 100644 index 00000000000..318bfd97f4a --- /dev/null +++ b/ffi/derive/examples/aboba.rs @@ -0,0 +1,147 @@ +#![feature(trivial_bounds)] + +use getset::Getters; +use iroha_ffi::decl_ffi_fns; +use iroha_ffi_derive::{ffi, ffi_export, ffi_import}; + +ffi! { + #[derive(Getters)] + #[getset(get = "pub")] + #[ffi_type(opaque)] + pub struct A { + a: i32, + } +} + +// #[repr(transparent)] +// pub struct A(*mut iroha_ffi::Extern) where A: iroha_ffi::Handle; +// #[derive(Clone, Copy)] +// #[repr(transparent)] +// pub struct RefA<'a> (*const iroha_ffi::Extern, core::marker::PhantomData<&'a ()>) where A: iroha_ffi::Handle; +// #[repr(transparent)] +// pub struct RefMutA<'a> (*mut iroha_ffi::Extern, core::marker::PhantomData<&'a mut ()>) where A: iroha_ffi::Handle; +// impl Drop for A where A: iroha_ffi::Handle { +// fn drop(&mut self) { +// let handle_id = iroha_ffi::FfiConvert::into_ffi(::ID, &mut ()); +// let drop_result = unsafe { crate::__drop(handle_id, self.0) }; +// if drop_result != iroha_ffi::FfiReturn::Ok { panic!("Drop returned: {}", drop_result); } +// } +// } +// impl A where A: iroha_ffi::Handle { fn from_extern_ptr(opaque_ptr: *mut iroha_ffi::Extern) -> Self { Self(opaque_ptr) } } +// impl<'a> A where A: iroha_ffi::Handle { +// fn as_ref(&self) -> RefA<'a> { RefA(self.0, core::marker::PhantomData) } +// fn as_mut(&mut self) -> RefMutA<'a> where A: iroha_ffi::Handle { RefMutA(self.0, core::marker::PhantomData) } +// } +// impl<'a> core::ops::Deref for RefA<'a> where A: iroha_ffi::Handle { +// type Target = A; +// fn deref(&self) -> &Self::Target { unsafe { &*(&self.0 as *const *const iroha_ffi::Extern).cast() } } +// } +// impl<'a> core::ops::Deref for RefMutA<'a> where A: iroha_ffi::Handle { +// type Target = RefA<'a>; +// fn deref(&self) -> &Self::Target { unsafe { &*(&self.0 as *const *mut iroha_ffi::Extern).cast() } } +// } +// impl<'a> core::ops::DerefMut for RefMutA<'a> where A: iroha_ffi::Handle { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *(&mut self.0 as *mut *mut iroha_ffi::Extern).cast() } } } +// unsafe impl iroha_ffi::ir::External for A where A: iroha_ffi::Handle { +// type RefType<'a> = RefA<'a>; +// type RefMutType<'a> = RefMutA<'a>; +// fn as_extern_ptr(&self) -> *const iroha_ffi::Extern { self.0 } +// fn as_extern_ptr_mut(&mut self) -> *mut iroha_ffi::Extern { self.0 } +// unsafe fn from_extern_ptr(opaque_ptr: *mut iroha_ffi::Extern) -> Self { Self::from_extern_ptr(opaque_ptr) } +// } +// unsafe impl iroha_ffi::ir::Transmute for A where A: iroha_ffi::Handle { +// type Target = *mut iroha_ffi::Extern; +// #[inline] +// unsafe fn is_valid(target: &Self::Target) -> bool { !target.is_null() } +// } +// impl iroha_ffi::ir::Ir for A where A: iroha_ffi::Handle { type Type = Self; } +// impl iroha_ffi::repr_c::CType for A where A: iroha_ffi::Handle { type ReprC = *mut iroha_ffi::Extern; } +// impl iroha_ffi::repr_c::CTypeConvert<'_, Self, *mut iroha_ffi::Extern> for A where A: iroha_ffi::Handle { +// type RustStore = (); +// type FfiStore = (); +// fn into_repr_c(self, _: &mut ()) -> *mut iroha_ffi::Extern { core::mem::ManuallyDrop::new(self).0 } +// unsafe fn try_from_repr_c(source: *mut iroha_ffi::Extern, _: &mut ()) -> iroha_ffi::Result { +// if source.is_null() { return Err(iroha_ffi::FfiReturn::ArgIsNull); } +// Ok(Self::from_extern_ptr(source)) +// } +// } +// impl iroha_ffi::repr_c::CWrapperType for A where A: iroha_ffi::Handle { +// type InputType = Self; +// type ReturnType = Self; +// } +// impl iroha_ffi::repr_c::COutPtr for A where A: iroha_ffi::Handle { type OutPtr = Self::ReprC; } +// impl iroha_ffi::repr_c::COutPtrRead for A where A: iroha_ffi::Handle { unsafe fn try_read_out(out_ptr: Self::OutPtr) -> iroha_ffi::Result { iroha_ffi::repr_c::read_non_local::<_, Self>(out_ptr) } } +// impl iroha_ffi::ir::IrTypeFamily for A where A: iroha_ffi::Handle { +// type Ref<'a> = &'a iroha_ffi::Extern where; +// type RefMut<'a> = &'a mut iroha_ffi::Extern where; +// type Box = Box; +// type SliceRef<'a> = &'a [iroha_ffi::ir::Transparent] where; +// type SliceRefMut<'a> = &'a mut [iroha_ffi::ir::Transparent] where; +// type Vec = Vec; +// type Arr = iroha_ffi::ir::Transparent; +// } +// unsafe impl iroha_ffi::repr_c::NonLocal for A where A: iroha_ffi::Handle {} +// impl<'a> ::iroha_ffi::ir::Ir for RefA<'a> where A: iroha_ffi::Handle { +// type Type = ::iroha_ffi::ir::Transparent; +// } +// unsafe impl<'a> ::iroha_ffi::ir::Transmute for RefA<'a> where A: iroha_ffi::Handle { +// type Target = *const iroha_ffi::Extern; +// +// #[inline] +// unsafe fn is_valid(target: &Self::Target) -> bool { +// (|target: &*const iroha_ffi::Extern| !target.is_null())(target) +// } +// } +// impl<'a> ::iroha_ffi::option::Niche<'_> for RefA<'a> where A: iroha_ffi::Handle { +// const NICHE_VALUE: <*const iroha_ffi::Extern as ::iroha_ffi::FfiType>::ReprC = (core::ptr::null()); +// } +// impl<'a> ::iroha_ffi::ir::Ir for RefMutA<'a> where A: iroha_ffi::Handle { +// type Type = ::iroha_ffi::ir::Transparent; +// } +// unsafe impl<'a> ::iroha_ffi::ir::Transmute for RefMutA<'a> where A: iroha_ffi::Handle { +// type Target = *mut iroha_ffi::Extern; +// +// #[inline] +// unsafe fn is_valid(target: &Self::Target) -> bool { +// (|target: &*mut iroha_ffi::Extern| !target.is_null())(target) +// } +// } +// impl<'a> ::iroha_ffi::option::Niche<'_> for RefMutA<'a> where A: iroha_ffi::Handle { +// const NICHE_VALUE: <*mut iroha_ffi::Extern as ::iroha_ffi::FfiType>::ReprC = (core::ptr::null_mut()); +// } +// unsafe impl iroha_ffi::ir::InfallibleTransmute for A where A: iroha_ffi::Handle {} +// unsafe impl<'a> iroha_ffi::ir::InfallibleTransmute for RefA<'a> where A: iroha_ffi::Handle {} +// unsafe impl<'a> iroha_ffi::ir::InfallibleTransmute for RefMutA<'a> where A: iroha_ffi::Handle {} +// impl iroha_ffi::WrapperTypeOf for A where A: iroha_ffi::Handle { type Type = Self; } +// impl<'a> iroha_ffi::WrapperTypeOf<&'a A> for RefA<'a> where A: iroha_ffi::Handle { type Type = Self; } +// impl<'a> iroha_ffi::WrapperTypeOf<&'a mut A> for RefMutA<'a> where A: iroha_ffi::Handle { type Type = Self; } +// impl iroha_ffi::option::Niche<'_> for A where A: iroha_ffi::Handle { const NICHE_VALUE: *mut iroha_ffi::Extern = core::ptr::null_mut(); } +// impl A { +// pub fn a(&self: <&Self as iroha_ffi::FfiWrapperType>::InputType) -> <&i32 as iroha_ffi::FfiWrapperType>::ReturnType { self.as_ref().a() } +// } +// impl<'a> RefA<'a> { +// pub fn a(&self: <&Self as iroha_ffi::FfiWrapperType>::InputType) -> <&'a i32 as iroha_ffi::FfiWrapperType>::ReturnType { +// let __handle = self; +// let __handle = __handle.0; +// let mut __handle_store = Default::default(); +// let __handle = iroha_ffi::FfiConvert::into_ffi(__handle, &mut __handle_store); +// let mut a = core::mem::MaybeUninit::uninit(); +// unsafe { +// let __ffi_return = A__a(__handle, a.as_mut_ptr()); +// match __ffi_return { +// iroha_ffi::FfiReturn::Ok => {} +// _ => panic!(concat!(stringify!(A__a ), " returned {}" ), __ffi_return) +// } +// let a = a.assume_init(); +// let a = iroha_ffi::FfiOutPtrRead::try_read_out(a).expect("Invalid out-pointer value returned"); +// a +// } +// } +// } +// extern { +// #[doc = " FFI function equivalent of [`A::a`]\n \n # Safety\n \n All of the given pointers must be valid"] +// fn A__a(__handle: <<&A as iroha_ffi::FfiWrapperType>::InputType as iroha_ffi::FfiType>::ReprC, a: *mut <<&i32 as iroha_ffi::FfiWrapperType>::ReturnType as iroha_ffi::FfiOutPtr>::OutPtr) -> iroha_ffi::FfiReturn; +// } +// +// iroha_ffi::decl_ffi_fns! { link_prefix="iroha_aboba" Drop, Clone, Eq, Ord, Default } + +fn main() {} diff --git a/ffi/derive/src/attr_parse/derive.rs b/ffi/derive/src/attr_parse/derive.rs new file mode 100644 index 00000000000..c5091d5b57b --- /dev/null +++ b/ffi/derive/src/attr_parse/derive.rs @@ -0,0 +1,184 @@ +//! This module provides parsing of `#[derive(...)]` attributes + +use darling::FromAttributes; +use quote::ToTokens; +use syn2::{punctuated::Punctuated, Attribute, Token}; + +use super::getset::GetSetDerive; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RustcDerive { + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Copy, + Hash, + Default, + Debug, +} + +impl RustcDerive { + fn try_from_path(path: &syn2::Path) -> Option { + let Some(ident) = path.get_ident() else { + return None; + }; + + match ident.to_string().as_str() { + "Eq" => Some(Self::Eq), + "PartialEq" => Some(Self::PartialEq), + "Ord" => Some(Self::Ord), + "PartialOrd" => Some(Self::PartialOrd), + "Clone" => Some(Self::Clone), + "Copy" => Some(Self::Copy), + "Hash" => Some(Self::Hash), + "Default" => Some(Self::Default), + "Debug" => Some(Self::Debug), + _ => None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Derive { + Rustc(RustcDerive), + GetSet(GetSetDerive), + Other(String), +} + +/// Represents a collection of all `#[derive(...)]` attributes placed on the item +/// +/// NOTE: strictly speaking, correctly parsing this is impossible, since it requires +/// us to resolve the paths in the attributes, which is not possible in a proc-macro context. +/// +/// We just __hope__ that the user refers to the derives by their canonical names (no aliases). +/// +/// This, however, will mistakingly thing that `derive_more` derives are actually rustc's built-in ones. +/// +/// Care should be taken, and it should be documented in the macro APIs that use this. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DeriveAttr { + pub derives: Vec, +} + +impl FromAttributes for DeriveAttr { + fn from_attributes(attrs: &[Attribute]) -> darling::Result { + let mut derives = Vec::new(); + let mut accumulator = darling::error::Accumulator::default(); + + for attr in attrs { + if attr.path().is_ident("derive") { + let Some(list) = accumulator.handle(attr.meta.require_list().map_err(Into::into)) else { + continue + }; + let Some(paths) = accumulator.handle( + list.parse_args_with(Punctuated::::parse_terminated).map_err(Into::into) + ) else { + continue + }; + + for path in paths { + let derive = if let Some(derive) = RustcDerive::try_from_path(&path) { + Derive::Rustc(derive) + } else if let Some(derive) = GetSetDerive::try_from_path(&path) { + Derive::GetSet(derive) + } else { + Derive::Other(path.to_token_stream().to_string()) + }; + + // I __think__ it's an error to use the same derive twice + // I don't think we care in this case though + derives.push(derive); + } + } + } + + accumulator.finish_with(Self { derives }) + } +} + +#[cfg(test)] +mod test { + use darling::FromAttributes; + use proc_macro2::TokenStream; + use quote::quote; + use syn2::parse::ParseStream; + + use super::{Derive, DeriveAttr, GetSetDerive, RustcDerive}; + + fn parse_derives(attrs: TokenStream) -> darling::Result { + struct Attributes(Vec); + + impl syn2::parse::Parse for Attributes { + fn parse(input: ParseStream) -> syn2::Result { + Ok(Self(input.call(syn2::Attribute::parse_outer)?)) + } + } + + let attrs = syn2::parse2::(attrs) + .expect("Failed to parse tokens as outer attributes") + .0; + DeriveAttr::from_attributes(&attrs) + } + + macro_rules! assert_derive_ok { + ($( #[$meta:meta] )*, + $expected:expr + ) => { + assert_eq!(parse_derives(quote!( + $( #[$meta] )* + )).unwrap(), + $expected + ) + }; + } + + #[test] + fn derive_rustc() { + assert_derive_ok!( + #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Default, Debug)], + DeriveAttr { + derives: vec![ + RustcDerive::Eq, + RustcDerive::PartialEq, + RustcDerive::Ord, + RustcDerive::PartialOrd, + RustcDerive::Clone, + RustcDerive::Copy, + RustcDerive::Hash, + RustcDerive::Default, + RustcDerive::Debug, + ].into_iter().map(Derive::Rustc).collect(), + } + ) + } + + #[test] + fn derive_getset() { + assert_derive_ok!( + #[derive(Getters, Setters, MutGetters, CopyGetters)], + DeriveAttr { + derives: vec![ + GetSetDerive::Getters, + GetSetDerive::Setters, + GetSetDerive::MutGetters, + GetSetDerive::CopyGetters, + ].into_iter().map(Derive::GetSet).collect(), + } + ) + } + + #[test] + fn derive_unknown() { + assert_derive_ok!( + #[derive(Aboba, Kek)], + DeriveAttr { + derives: vec![ + "Aboba".to_string(), + "Kek".to_string(), + ].into_iter().map(Derive::Other).collect(), + } + ) + } +} diff --git a/ffi/derive/src/attr_parse/doc.rs b/ffi/derive/src/attr_parse/doc.rs new file mode 100644 index 00000000000..253fcf9e491 --- /dev/null +++ b/ffi/derive/src/attr_parse/doc.rs @@ -0,0 +1,18 @@ +use darling::FromAttributes; +use syn2::Attribute; + +pub struct DocAttrs { + pub attrs: Vec, +} + +impl FromAttributes for DocAttrs { + fn from_attributes(attrs: &[Attribute]) -> darling::Result { + let mut docs = Vec::new(); + for attr in attrs { + if attr.path().is_ident("doc") { + docs.push(attr.clone()); + } + } + Ok(DocAttrs { attrs: docs }) + } +} diff --git a/ffi/derive/src/attr_parse/getset.rs b/ffi/derive/src/attr_parse/getset.rs new file mode 100644 index 00000000000..3c3336928e4 --- /dev/null +++ b/ffi/derive/src/attr_parse/getset.rs @@ -0,0 +1,831 @@ +//! This module provides parsing of custom attributes from the [`getset`](https://docs.rs/getset/latest/getset/) crate + +use std::{collections::hash_map::Entry, fmt::Display, str::FromStr}; + +use proc_macro2::Span; +use rustc_hash::{FxHashMap, FxHashSet}; +use syn2::{parse::ParseStream, punctuated::Punctuated, Attribute, Token}; + +use crate::attr_parse::derive::{Derive, DeriveAttr}; + +/// Type of accessor method derived for a structure +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum GetSetDerive { + Setters, + Getters, + MutGetters, + CopyGetters, +} + +impl GetSetDerive { + pub fn try_from_path(path: &syn2::Path) -> Option { + // try to be smart and handle two cases: + // - bare attribute name (like `Getters`, when it's imported) + // - fully qualified path (like `getset::Getters`, when it's not imported) + let ident = match path.get_ident() { + Some(i) => i.clone(), + None => { + let mut segments = path.segments.iter(); + if segments.len() == 2 + && segments.next().unwrap().ident.to_string().as_str() == "getset" + { + segments.next().unwrap().ident.clone() + } else { + return None; + } + } + }; + + match ident.to_string().as_str() { + "Setters" => Some(Self::Setters), + "Getters" => Some(Self::Getters), + "MutGetters" => Some(Self::MutGetters), + "CopyGetters" => Some(Self::CopyGetters), + _ => None, + } + } + + pub fn get_mode(&self) -> GetSetGenMode { + match self { + Self::Setters => GetSetGenMode::Set, + Self::Getters => GetSetGenMode::Get, + Self::MutGetters => GetSetGenMode::GetMut, + Self::CopyGetters => GetSetGenMode::GetCopy, + } + } +} + +#[derive(Default, Debug, Eq, PartialEq, Clone)] +pub struct GetSetOptions { + pub visibility: Option, + pub with_prefix: bool, +} + +struct SpannedGetSetOptions { + span: Span, + options: GetSetOptions, +} + +impl syn2::parse::Parse for SpannedGetSetOptions { + fn parse(input: ParseStream) -> syn2::Result { + let mut result = GetSetOptions::default(); + // an accumulator for syn errors? + // this is getting out of hand... + // we need an accumulator to rule them all! + let mut errors = Vec::new(); + + let lit = input.parse::()?; + for part in lit.value().split(' ') { + if part == "with_prefix" { + result.with_prefix = true; + } else if let Ok(vis) = syn2::parse_str::(part) { + if result.visibility.is_none() { + result.visibility = Some(vis); + } else { + errors.push(syn2::Error::new( + lit.span(), + format!( + "Failed to parse getset options at {}: duplicate visibility", + part + ), + )); + } + } else { + errors.push(syn2::Error::new(lit.span(), format!("Failed to parse getset options at `{}`: expected visibility or `with_prefix`", part))); + } + } + + if errors.is_empty() { + Ok(SpannedGetSetOptions { + span: lit.span(), + options: result, + }) + } else { + let mut errors = errors.into_iter(); + let mut error = errors.next().expect("darling::Error can never be empty"); + + for next_error in errors { + error.combine(next_error); + } + + Err(error) + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +pub enum GetSetGenMode { + Get, + GetCopy, + Set, + GetMut, +} + +impl Display for GetSetGenMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GetSetGenMode::Get => write!(f, "get"), + GetSetGenMode::GetCopy => write!(f, "get_copy"), + GetSetGenMode::Set => write!(f, "set"), + GetSetGenMode::GetMut => write!(f, "get_mut"), + } + } +} + +impl FromStr for GetSetGenMode { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "get" => Ok(GetSetGenMode::Get), + "get_copy" => Ok(GetSetGenMode::GetCopy), + "set" => Ok(GetSetGenMode::Set), + "get_mut" => Ok(GetSetGenMode::GetMut), + _ => Err(()), + } + } +} + +enum GetSetAttrToken { + Skip, + Gen(GetSetGenMode, GetSetOptions), +} + +struct SpannedGetSetAttrToken { + span: Span, + token: GetSetAttrToken, +} + +impl syn2::parse::Parse for SpannedGetSetAttrToken { + fn parse(input: ParseStream) -> syn2::Result { + let ident = input.parse::()?; + + match ident.to_string().as_str() { + "skip" => Ok(SpannedGetSetAttrToken { + span: ident.span(), + token: GetSetAttrToken::Skip, + }), + s @ ("get" | "get_copy" | "set" | "get_mut") => { + let mode = s.parse().unwrap(); + + if input.peek(Token![=]) { + input.parse::()?; + let options = input.parse::()?; + let span = ident + .span() + .join(options.span) + .expect("must be in the same file"); + + Ok(SpannedGetSetAttrToken { + span, + token: GetSetAttrToken::Gen(mode, options.options), + }) + } else { + Ok(SpannedGetSetAttrToken { + span: ident.span(), + token: GetSetAttrToken::Gen(mode, GetSetOptions::default()), + }) + } + } + _ => Err(syn2::Error::new( + ident.span(), + "expected one of `skip`, `get`, `get_copy`, `set`, `get_mut`", + )), + } + } +} + +type RequestedAccessors = FxHashMap; + +/// Insert an accessor into the map, emitting an error if such kind of accessor is already present in the map +fn insert_gen_request( + accumulator: &mut darling::error::Accumulator, + gen_map: &mut RequestedAccessors, + span: Span, + mode: GetSetGenMode, + options: GetSetOptions, +) { + if options.with_prefix && mode == GetSetGenMode::Set { + accumulator.push( + darling::Error::custom("`with_prefix` is not supported for `set`").with_span(&span), + ); + } + + match gen_map.entry(mode) { + Entry::Occupied(_) => accumulator.push( + darling::Error::custom(format!("duplicate `getset({})` attribute", mode)) + .with_span(&span), + ), + Entry::Vacant(v) => { + v.insert(options); + } + } +} + +#[derive(Default, Debug, Eq, PartialEq, Clone)] +pub struct GetSetFieldAttr { + pub skip: bool, + pub gen: RequestedAccessors, +} + +impl darling::FromAttributes for GetSetFieldAttr { + fn from_attributes(attrs: &[Attribute]) -> darling::Result { + let mut accumulator = darling::error::Accumulator::default(); + let mut skip_span = None; + let mut result = GetSetFieldAttr { + skip: false, + gen: FxHashMap::default(), + }; + for attr in attrs { + // getset crate is quite liberal in what it accepts + // it allows both the `#[getset(get)]` and `#[get]` syntax to be used + // iroha doesn't use the latter form, so it is not supported by `iroha_ffi_derive` + if attr.path().is_ident("getset") { + let Some(list) = accumulator.handle(attr.meta.require_list().map_err(Into::into)) + else { continue }; + let Some(tokens): Option> + = accumulator.handle(list.parse_args_with(Punctuated::parse_terminated).map_err(Into::into)) + else { continue }; + + for token in tokens { + match token.token { + GetSetAttrToken::Skip => { + result.skip = true; + skip_span = Some(token.span); + } + GetSetAttrToken::Gen(mode, options) => insert_gen_request( + &mut accumulator, + &mut result.gen, + token.span, + mode, + options, + ), + } + } + } else if attr + .path() + .get_ident() + .and_then(|ident| GetSetGenMode::from_str(&ident.to_string()).ok()) + .is_some() + { + accumulator.push( + darling::Error::custom( + "getset attributes without `getset` prefix are not supported by iroha_ffi_derive", + ) + .with_span(attr), + ) + } + } + + if result.skip && !result.gen.is_empty() { + accumulator.push( + darling::Error::custom( + "`skip` is used, but attributes requesting a getter or setter are also present", + ) + .with_span(&skip_span.unwrap()), + ); + } + + accumulator.finish_with(result) + } +} + +#[derive(Default, Debug, Eq, PartialEq, Clone)] +pub struct GetSetStructAttr { + pub gen: FxHashMap, +} + +impl darling::FromAttributes for GetSetStructAttr { + fn from_attributes(attrs: &[Attribute]) -> darling::Result { + let mut accumulator = darling::error::Accumulator::default(); + let mut result = GetSetStructAttr { + gen: FxHashMap::default(), + }; + for attr in attrs { + if attr.path().is_ident("getset") { + let Some(list) = accumulator.handle(attr.meta.require_list().map_err(Into::into)) + else { continue }; + let Some(tokens): Option> + = accumulator.handle(list.parse_args_with(Punctuated::parse_terminated).map_err(Into::into)) + else { continue }; + + for token in tokens { + match token.token { + GetSetAttrToken::Skip => { + accumulator.push( + darling::Error::custom("`skip` is not valid on a struct") + .with_span(&token.span), + ); + } + GetSetAttrToken::Gen(mode, options) => insert_gen_request( + &mut accumulator, + &mut result.gen, + token.span, + mode, + options, + ), + } + } + } else if attr + .path() + .get_ident() + .and_then(|ident| GetSetGenMode::from_str(&ident.to_string()).ok()) + .is_some() + { + accumulator.push( + darling::Error::custom( + "getset attributes without `getset` prefix are not supported by iroha_ffi_derive", + ) + .with_span(attr), + ) + } + } + + accumulator.finish_with(result) + } +} + +impl GetSetFieldAttr { + pub fn get_field_accessors( + &self, + derives: &DeriveAttr, + struct_attr: &GetSetStructAttr, + ) -> RequestedAccessors { + if self.skip { + return FxHashMap::default(); + } + + let mut result = struct_attr.gen.clone(); + for (mode, options) in &self.gen { + match result.entry(*mode) { + Entry::Occupied(mut o) => { + let o = o.get_mut(); + // visibility is overwritten, while the "with_prefix" is merged + o.visibility = options.visibility.clone(); + o.with_prefix |= options.with_prefix; + } + Entry::Vacant(v) => { + v.insert(options.clone()); + } + } + } + + // filter out the modes that are not requested by the `#[derive(...)]` attribute + let derived_modes = derives + .derives + .iter() + .filter_map(|d| match d { + Derive::GetSet(derive) => Some(derive.get_mode()), + _ => None, + }) + .collect::>(); + result.retain(|&mode, _| derived_modes.contains(&mode)); + + result + } +} + +#[cfg(test)] +mod test { + use super::{ + GetSetFieldAttr, GetSetGenMode, GetSetOptions, GetSetStructAttr, RequestedAccessors, + }; + + mod parse { + use darling::FromAttributes; + use proc_macro2::TokenStream; + use quote::quote; + use rustc_hash::FxHashMap; + use syn2::{parse::ParseStream, parse_quote, Attribute}; + + use super::{GetSetFieldAttr, GetSetGenMode, GetSetOptions, GetSetStructAttr}; + + // TODO: this can go into a common module + fn parse_attributes(ts: TokenStream) -> Vec { + struct Attributes(Vec); + impl syn2::parse::Parse for Attributes { + fn parse(input: ParseStream) -> syn2::Result { + Attribute::parse_outer(input).map(Attributes) + } + } + + syn2::parse2::(ts) + .expect("Failed to parse attributes") + .0 + } + + macro_rules! assert_getset_ok { + ($( #[$meta:meta] )*, + $ty:ident $body:tt + ) => { + { + assert_eq!( + $ty::from_attributes(&parse_attributes(quote! { + $( #[$meta] )* + })) + .unwrap_or_else(|e| panic!("Parsing {} from attributes failed: {:#}", stringify!($ty), e)), + $ty $body + ); + } + }; + } + + #[test] + fn field_empty() { + assert_getset_ok!( + #[abra_cadabra], // unrelated attr + GetSetFieldAttr { + ..Default::default() + } + ); + } + + #[test] + fn struct_empty() { + assert_getset_ok!( + #[abra_cadabra], // unrelated attr + GetSetStructAttr { + ..Default::default() + } + ); + } + + #[test] + fn field_skip() { + assert_getset_ok!( + #[getset(skip)], + GetSetFieldAttr { + skip: true, + ..Default::default() + } + ); + } + + #[test] + fn field_get() { + assert_getset_ok!( + #[getset(get)], + GetSetFieldAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions::default()), + ]), + ..Default::default() + } + ); + } + + #[test] + fn field_get_pub() { + assert_getset_ok!( + #[getset(get = "pub")], + GetSetFieldAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub }), + ..Default::default() + }), + ]), + ..Default::default() + } + ); + } + + #[test] + fn field_get_pub_with_prefix() { + assert_getset_ok!( + #[getset(get = "pub with_prefix")], + GetSetFieldAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub }), + with_prefix: true, + }), + ]), + ..Default::default() + } + ); + assert_getset_ok!( + #[getset(get = "with_prefix pub")], + GetSetFieldAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub }), + with_prefix: true, + }), + ]), + ..Default::default() + } + ); + } + + #[test] + fn struct_get() { + assert_getset_ok!( + #[getset(get)], + GetSetStructAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions::default()), + ]) + } + ); + } + + #[test] + fn struct_get_pub() { + assert_getset_ok!( + #[getset(get = "pub")], + GetSetStructAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub }), + ..Default::default() + }), + ]) + } + ); + } + + #[test] + fn struct_get_pub_with_prefix() { + assert_getset_ok!( + #[getset(get = "pub with_prefix")], + GetSetStructAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub }), + with_prefix: true, + }), + ]) + } + ); + assert_getset_ok!( + #[getset(get = "with_prefix pub")], + GetSetStructAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub }), + with_prefix: true, + }), + ]) + } + ); + } + + #[test] + fn field_get_copy() { + assert_getset_ok!( + #[getset(get_copy)], + GetSetFieldAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::GetCopy, GetSetOptions::default()), + ]), + ..Default::default() + } + ); + } + + #[test] + fn field_set() { + assert_getset_ok!( + #[getset(set)], + GetSetFieldAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Set, GetSetOptions::default()), + ]), + ..Default::default() + } + ); + } + + #[test] + fn field_get_mut() { + assert_getset_ok!( + #[getset(get_mut)], + GetSetFieldAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::GetMut, GetSetOptions::default()), + ]), + ..Default::default() + } + ); + } + + #[test] + fn struct_get_copy() { + assert_getset_ok!( + #[getset(get_copy)], + GetSetStructAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::GetCopy, GetSetOptions::default()), + ]) + } + ); + } + + #[test] + fn struct_set() { + assert_getset_ok!( + #[getset(set)], + GetSetStructAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::Set, GetSetOptions::default()), + ]) + } + ); + } + + #[test] + fn struct_get_mut() { + assert_getset_ok!( + #[getset(get_mut)], + GetSetStructAttr { + gen: FxHashMap::from_iter([ + (GetSetGenMode::GetMut, GetSetOptions::default()), + ]) + } + ); + } + + macro_rules! assert_getset_err { + ($( #[$meta:meta] )*, $ty:ident, $error:expr) => { + assert_eq!( + $ty::from_attributes(&parse_attributes(quote! { + $( #[$meta] )* + })) + .unwrap_err() + .to_string(), + $error, + "The error message does not match the expected one" + ) + }; + } + + #[test] + fn err_unknown_token() { + assert_getset_err!( + #[getset(unknown_token)], + GetSetStructAttr, + "expected one of `skip`, `get`, `get_copy`, `set`, `get_mut`" + ); + } + + #[test] + fn err_skip_struct() { + assert_getset_err!( + #[getset(skip)], + GetSetStructAttr, + "`skip` is not valid on a struct" + ); + } + + #[test] + fn err_duplicate_accessor() { + assert_getset_err!( + #[getset(get = "pub", get)], + GetSetStructAttr, + "duplicate `getset(get)` attribute" + ); + } + + #[test] + fn err_unknown_option() { + assert_getset_err!( + #[getset(get = "aboba")], + GetSetStructAttr, + "Failed to parse getset options at `aboba`: expected visibility or `with_prefix`" + ); + } + } + mod inherit { + use darling::FromAttributes; + use proc_macro2::TokenStream; + use quote::quote; + use syn2::{parse::ParseStream, parse_quote, Attribute}; + + use super::{ + GetSetFieldAttr, GetSetGenMode, GetSetOptions, GetSetStructAttr, RequestedAccessors, + }; + use crate::attr_parse::derive::DeriveAttr; + + fn get_field_derives( + derive: TokenStream, + struct_attr: TokenStream, + field_attr: TokenStream, + ) -> RequestedAccessors { + fn parse_attributes(ts: TokenStream) -> T { + struct Attributes(Vec); + impl syn2::parse::Parse for Attributes { + fn parse(input: ParseStream) -> syn2::Result { + Attribute::parse_outer(input).map(Attributes) + } + } + + let attrs = syn2::parse2::(ts) + .expect("Failed to parse attributes") + .0; + T::from_attributes(&attrs).expect("Failed to parse attributes") + } + + let derive = parse_attributes::(derive); + let struct_attr = parse_attributes::(struct_attr); + let field_attr = parse_attributes::(field_attr); + + field_attr.get_field_accessors(&derive, &struct_attr) + } + + macro_rules! assert_getset_ok { + ( + $( #[$derive:meta] )*, + $( #[$struct_attr:meta] )*, + $( #[$field_attr:meta] )*, + $expected:expr + ) => { + assert_eq!( + get_field_derives( + quote! { $( #[$derive] )* }, + quote! { $( #[$struct_attr] )* }, + quote! { $( #[$field_attr] )* }, + ), + $expected + ) + }; + } + + #[test] + fn getset_basic() { + assert_getset_ok!( + #[derive(Getters, Setters)], + , + #[getset(get, set)], + RequestedAccessors::from_iter([ + (GetSetGenMode::Get, GetSetOptions::default()), + (GetSetGenMode::Set, GetSetOptions::default()), + ]) + ); + } + + #[test] + fn getset_derive_disabled() { + // no Setters - no Set generated + assert_getset_ok!( + #[derive(Getters)], + , + #[getset(get, set)], + RequestedAccessors::from_iter([ + (GetSetGenMode::Get, GetSetOptions::default()) + ]) + ); + } + + #[test] + fn getset_inherit() { + assert_getset_ok!( + #[derive(Getters, Setters)], + #[getset(get)], + #[getset(set)], + RequestedAccessors::from_iter([ + (GetSetGenMode::Get, GetSetOptions::default()), + (GetSetGenMode::Set, GetSetOptions::default()), + ]) + ) + } + + #[test] + fn getset_overwrite_visibility() { + assert_getset_ok!( + #[derive(Getters, Setters)], + #[getset(get = "pub(crate)", set = "pub(crate)")], + #[getset(set = "pub")], + RequestedAccessors::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub(crate) }), + ..Default::default() + }), + (GetSetGenMode::Set, GetSetOptions { + visibility: Some(parse_quote! { pub }), + ..Default::default() + }), + ]) + ) + } + + #[test] + fn inherit_with_prefix() { + assert_getset_ok!( + #[derive(Getters, CopyGetters)], + #[getset(get = "with_prefix", get_copy = "pub")], + #[getset(get = "pub", get_copy = "pub(crate) with_prefix")], + RequestedAccessors::from_iter([ + (GetSetGenMode::Get, GetSetOptions { + visibility: Some(parse_quote! { pub }), + with_prefix: true, + }), + (GetSetGenMode::GetCopy, GetSetOptions { + visibility: Some(parse_quote! { pub(crate) }), + with_prefix: true, + }), + ]) + ) + } + } +} diff --git a/ffi/derive/src/attr_parse/mod.rs b/ffi/derive/src/attr_parse/mod.rs new file mode 100644 index 00000000000..08a8215773b --- /dev/null +++ b/ffi/derive/src/attr_parse/mod.rs @@ -0,0 +1,4 @@ +pub mod derive; +pub mod doc; +pub mod getset; +pub mod repr; diff --git a/ffi/derive/src/repr.rs b/ffi/derive/src/attr_parse/repr.rs similarity index 98% rename from ffi/derive/src/repr.rs rename to ffi/derive/src/attr_parse/repr.rs index 585229f8f72..84846411460 100644 --- a/ffi/derive/src/repr.rs +++ b/ffi/derive/src/attr_parse/repr.rs @@ -234,6 +234,17 @@ mod test { }; } + #[test] + fn repr_empty() { + assert_repr_ok!( + #[aboba], // unrelated attr + Repr { + kind: None, + alignment: None, + } + ); + } + #[test] fn repr_c() { assert_repr_ok!( diff --git a/ffi/derive/src/convert.rs b/ffi/derive/src/convert.rs index 01161c25f83..0f32aef36c9 100644 --- a/ffi/derive/src/convert.rs +++ b/ffi/derive/src/convert.rs @@ -7,116 +7,22 @@ use std::{ use darling::{ ast::Style, util::SpannedValue, FromAttributes, FromDeriveInput, FromField, FromVariant, }; -use drop_bomb::DropBomb; -use manyhow::{emit, error_message, Result, ToTokensError}; +use manyhow::{emit, error_message}; use proc_macro2::{Delimiter, Span, TokenStream}; use quote::quote; use syn2::{ parse::ParseStream, spanned::Spanned as _, visit::Visit as _, Attribute, Field, Ident, Meta, }; -use crate::repr::{Repr, ReprKind, ReprPrimitive}; - -// TODO: move this type to `derive-primitives` crate -/// A wrapper type around [`manyhow::Emitter`] that provides a more ergonomic API. -/// -/// This type is used to accumulate errors during parsing and code generation. -/// -/// NOTE: you must call [`Emitter::finish`] or similar function to consume the accumulated errors. -/// `Emitter` will panic if dropped without consuming the errors. -pub struct Emitter { - inner: manyhow::Emitter, - bomb: DropBomb, -} - -impl Emitter { - pub fn new() -> Self { - Self { - inner: manyhow::Emitter::new(), - bomb: DropBomb::new("Emitter dropped without consuming accumulated errors"), - } - } - - /// Add a new error to the emitter. - pub fn emit(&mut self, err: E) { - self.inner.emit(err); - } - - /// Handle a [`Result`] by either returning the value or emitting the error. - /// - /// If the passed value is `Err`, the error will be emitted and `None` will be returned. - pub fn handle(&mut self, result: Result) -> Option { - match result { - Ok(value) => Some(value), - Err(err) => { - self.emit(err); - None - } - } - } - - /// Same as [`Emitter::handle`], but returns the default value of `T` if the passed value is `Err`. - #[allow(unused)] - pub fn handle_or_default( - &mut self, - result: Result, - ) -> T { - self.handle(result).unwrap_or_default() - } - - /// Consume the emitter, returning a [`manyhow::Error`] if any errors were emitted. - pub fn finish(mut self) -> Result<()> { - self.bomb.defuse(); - self.inner.into_result() - } - - /// Same as [`Emitter::finish`], but returns the given value if no errors were emitted. - #[allow(unused)] - pub fn finish_with(self, result: T) -> Result { - self.finish().map(|_| result) - } - - /// Handles the given [`Result`] and consumes the emitter. - #[allow(unused)] - pub fn finish_and(mut self, result: Result) -> Result { - match result { - Ok(value) => self.finish_with(value), - Err(err) => { - self.emit(err); - Err(self.finish().unwrap_err()) - } - } - } - - /// Consume the emitter, convert all errors into a token stream and append it to the given token stream. - pub fn into_tokens(self, tokens: &mut TokenStream) { - match self.finish() { - Ok(()) => {} - Err(e) => e.to_tokens(tokens), - } - } - - /// Consume the emitter, convert all errors into a token stream. - pub fn into_token_stream(self) -> TokenStream { - let mut tokens_stream = TokenStream::new(); - self.into_tokens(&mut tokens_stream); - tokens_stream - } - - /// Consume the emitter, convert all errors into a token stream and append it to the given token stream. - /// - /// This function is useful when you want to handle errors in a macro, but want to emit some tokens even in case of an error. - pub fn finish_token_stream(self, mut tokens_stream: TokenStream) -> TokenStream { - self.into_tokens(&mut tokens_stream); - tokens_stream - } -} - -impl Extend for Emitter { - fn extend>(&mut self, iter: T) { - self.inner.extend(iter) - } -} +use crate::{ + attr_parse::{ + derive::DeriveAttr, + doc::DocAttrs, + getset::{GetSetFieldAttr, GetSetStructAttr}, + repr::{Repr, ReprKind, ReprPrimitive}, + }, + emitter::Emitter, +}; #[derive(Debug)] enum FfiTypeToken { @@ -185,7 +91,7 @@ impl syn2::parse::Parse for SpannedFfiTypeToken { } #[derive(Debug, PartialEq, Eq, Copy, Clone)] -enum FfiTypeKindAttribute { +pub enum FfiTypeKindAttribute { // NOTE: these are not all the possible ffi type kinds, but only those that can be referred to using an attribute // Enums are treated quite funnily... Opaque, @@ -212,15 +118,15 @@ impl syn2::parse::Parse for FfiTypeKindAttribute { } #[derive(Debug, PartialEq, Eq, Copy, Clone)] -enum FfiTypeKindFieldAttribute { - UnsafNonOwning, +pub enum FfiTypeKindFieldAttribute { + UnsafeNonOwning, } impl syn2::parse::Parse for FfiTypeKindFieldAttribute { fn parse(input: ParseStream) -> syn2::Result { input.call(SpannedFfiTypeToken::parse).and_then(|token| { Ok(match token.token { - FfiTypeToken::UnsafeNonOwning => FfiTypeKindFieldAttribute::UnsafNonOwning, + FfiTypeToken::UnsafeNonOwning => FfiTypeKindFieldAttribute::UnsafeNonOwning, other => { return Err(syn2::Error::new( token.span, @@ -274,8 +180,8 @@ fn parse_ffi_type_attr(attrs: &[Attribute]) -> darling::R accumulator.finish_with(kind) } -struct FfiTypeAttr { - kind: Option, +pub struct FfiTypeAttr { + pub kind: Option, } impl FromAttributes for FfiTypeAttr { @@ -284,7 +190,7 @@ impl FromAttributes for FfiTypeAttr { } } -struct FfiTypeFieldAttr { +pub struct FfiTypeFieldAttr { kind: Option, } @@ -294,54 +200,96 @@ impl FromAttributes for FfiTypeFieldAttr { } } -type FfiTypeData = darling::ast::Data, FfiTypeField>; +pub type FfiTypeData = darling::ast::Data, FfiTypeField>; +pub type FfiTypeFields = darling::ast::Fields; -struct FfiTypeInput { - ident: syn2::Ident, - generics: syn2::Generics, - data: FfiTypeData, - repr: Repr, - attr: FfiTypeAttr, - span: Span, +pub struct FfiTypeInput { + pub vis: syn2::Visibility, + pub ident: syn2::Ident, + pub generics: syn2::Generics, + pub data: FfiTypeData, + pub doc_attrs: DocAttrs, + pub derive_attr: DeriveAttr, + pub repr_attr: Repr, + pub ffi_type_attr: FfiTypeAttr, + pub getset_attr: GetSetStructAttr, + pub span: Span, + pub ast: syn2::DeriveInput, +} + +impl FfiTypeInput { + pub fn is_opaque(&self) -> bool { + self.ffi_type_attr.kind == Some(FfiTypeKindAttribute::Opaque) + || !self.data.is_enum() && self.repr_attr.kind.as_deref().is_none() + } } impl darling::FromDeriveInput for FfiTypeInput { fn from_derive_input(input: &syn2::DeriveInput) -> darling::Result { + let vis = input.vis.clone(); let ident = input.ident.clone(); let generics = input.generics.clone(); let data = darling::ast::Data::try_from(&input.data)?; - let repr = Repr::from_attributes(&input.attrs)?; - let attr = FfiTypeAttr::from_attributes(&input.attrs)?; + let doc_attrs = DocAttrs::from_attributes(&input.attrs)?; + let derive_attr = DeriveAttr::from_attributes(&input.attrs)?; + let repr_attr = Repr::from_attributes(&input.attrs)?; + let ffi_type_attr = FfiTypeAttr::from_attributes(&input.attrs)?; + let getset_attr = GetSetStructAttr::from_attributes(&input.attrs)?; let span = input.span(); Ok(FfiTypeInput { + vis, ident, generics, data, - repr, - attr, + doc_attrs, + derive_attr, + repr_attr, + ffi_type_attr, + getset_attr, span, + ast: input.clone(), }) } } +impl quote::ToTokens for FfiTypeInput { + fn to_tokens(&self, tokens: &mut TokenStream) { + // TODO: this ignores changes that may be done to Generics + // Would this be a problem? + self.ast.to_tokens(tokens); + } +} + #[derive(FromVariant)] -struct FfiTypeVariant { - ident: syn2::Ident, - discriminant: Option, - fields: darling::ast::Fields, +pub struct FfiTypeVariant { + pub ident: syn2::Ident, + pub discriminant: Option, + pub fields: darling::ast::Fields, } -struct FfiTypeField { - ty: syn2::Type, - attr: FfiTypeFieldAttr, +pub struct FfiTypeField { + pub ident: Option, + pub ty: syn2::Type, + pub doc_attrs: DocAttrs, + pub ffi_type_attr: FfiTypeFieldAttr, + pub getset_attr: GetSetFieldAttr, } impl FromField for FfiTypeField { fn from_field(field: &Field) -> darling::Result { + let ident = field.ident.clone(); let ty = field.ty.clone(); - let attr = FfiTypeFieldAttr::from_attributes(&field.attrs)?; - Ok(Self { ty, attr }) + let doc_attrs = DocAttrs::from_attributes(&field.attrs)?; + let ffi_type_attr = FfiTypeFieldAttr::from_attributes(&field.attrs)?; + let getset_attr = GetSetFieldAttr::from_attributes(&field.attrs)?; + Ok(Self { + ident, + ty, + doc_attrs, + ffi_type_attr, + getset_attr, + }) } } @@ -365,12 +313,10 @@ pub fn derive_ffi_type(emitter: &mut Emitter, input: syn2::DeriveInput) -> Token // the logic of `is_opaque` is somewhat convoluted and I am not sure if it is even correct // there is also `is_opaque_struct`... // NOTE: this mirrors the logic in `is_opaque`. The function should be updated to use darling-parsed attrs together with the call sites - if input2.attr.kind == Some(FfiTypeKindAttribute::Opaque) - || !input2.data.is_enum() && input2.repr.kind.as_deref() == None - { + if input2.is_opaque() { return derive_ffi_type_for_opaque_item(name, &input2.generics); } - if input2.repr.kind.as_deref() == Some(&ReprKind::Transparent) { + if input2.repr_attr.kind.as_deref() == Some(&ReprKind::Transparent) { return derive_ffi_type_for_transparent_item(emitter, &input2); } @@ -392,10 +338,15 @@ pub fn derive_ffi_type(emitter: &mut Emitter, input: syn2::DeriveInput) -> Token ) } - derive_ffi_type_for_fieldless_enum(emitter, &input2.ident, variants, input2.repr) + derive_ffi_type_for_fieldless_enum( + emitter, + &input2.ident, + variants, + input2.repr_attr, + ) } else { verify_is_non_owning(emitter, &input2.data); - let local = input2.attr.kind == Some(FfiTypeKindAttribute::Local); + let local = input2.ffi_type_attr.kind == Some(FfiTypeKindAttribute::Local); derive_ffi_type_for_data_carrying_enum( emitter, @@ -489,7 +440,7 @@ fn derive_ffi_type_for_transparent_item( input: &FfiTypeInput, ) -> TokenStream { assert_eq!( - input.repr.kind.as_deref().copied(), + input.repr_attr.kind.as_deref().copied(), Some(ReprKind::Transparent) ); @@ -526,7 +477,7 @@ fn derive_ffi_type_for_transparent_item( } }; - if input.attr.kind == Some(FfiTypeKindAttribute::UnsafeRobust) { + if input.ffi_type_attr.kind == Some(FfiTypeKindAttribute::UnsafeRobust) { return quote! { iroha_ffi::ffi_type! { // SAFETY: User must make sure the type is robust @@ -795,9 +746,9 @@ fn derive_ffi_type_for_data_carrying_enum( fn derive_ffi_type_for_repr_c(emitter: &mut Emitter, input: &FfiTypeInput) -> TokenStream { verify_is_non_owning(emitter, &input.data); - if input.repr.kind.as_deref().copied() != Some(ReprKind::C) { + if input.repr_attr.kind.as_deref().copied() != Some(ReprKind::C) { let span = input - .repr + .repr_attr .kind .map_or_else(|| Span::call_site(), |kind| kind.span()); // TODO: this error message may be unclear. Consider adding a note about the `#[ffi_type]` attribute @@ -970,7 +921,7 @@ fn verify_is_non_owning(emitter: &mut Emitter, data: &FfiTypeData) { } fn visit_field(ptr_visitor: &mut PtrVistor, field: &FfiTypeField) { - if field.attr.kind == Some(FfiTypeKindFieldAttribute::UnsafNonOwning) { + if field.ffi_type_attr.kind == Some(FfiTypeKindFieldAttribute::UnsafeNonOwning) { return; } ptr_visitor.visit_type(&field.ty); diff --git a/ffi/derive/src/emitter.rs b/ffi/derive/src/emitter.rs new file mode 100644 index 00000000000..83555ae0ec6 --- /dev/null +++ b/ffi/derive/src/emitter.rs @@ -0,0 +1,110 @@ +use drop_bomb::DropBomb; +use manyhow::ToTokensError; +use proc_macro2::TokenStream; + +// TODO: move this type to `derive-primitives` crate +/// A wrapper type around [`manyhow::Emitter`] that provides a more ergonomic API. +/// +/// This type is used to accumulate errors during parsing and code generation. +/// +/// NOTE: you must call [`Emitter::finish`] or similar function to consume the accumulated errors. +/// `Emitter` will panic if dropped without consuming the errors. +pub struct Emitter { + inner: manyhow::Emitter, + bomb: DropBomb, +} + +impl Emitter { + pub fn new() -> Self { + Self { + inner: manyhow::Emitter::new(), + bomb: DropBomb::new("Emitter dropped without consuming accumulated errors"), + } + } + + /// Add a new error to the emitter. + pub fn emit(&mut self, err: E) { + self.inner.emit(err); + } + + /// Handle a [`manyhow::Result`] by either returning the value or emitting the error. + /// + /// If the passed value is `Err`, the error will be emitted and `None` will be returned. + pub fn handle( + &mut self, + result: manyhow::Result, + ) -> Option { + match result { + Ok(value) => Some(value), + Err(err) => { + self.emit(err); + None + } + } + } + + /// Same as [`Emitter::handle`], but returns the default value of `T` if the passed value is `Err`. + #[allow(unused)] + pub fn handle_or_default( + &mut self, + result: manyhow::Result, + ) -> T { + self.handle(result).unwrap_or_default() + } + + /// Consume the emitter, returning a [`manyhow::Error`] if any errors were emitted. + pub fn finish(mut self) -> manyhow::Result<()> { + self.bomb.defuse(); + self.inner.into_result() + } + + /// Same as [`Emitter::finish`], but returns the given value if no errors were emitted. + #[allow(unused)] + pub fn finish_with(self, result: T) -> manyhow::Result { + self.finish().map(|_| result) + } + + /// Handles the given [`manyhow::Result`] and consumes the emitter. + #[allow(unused)] + pub fn finish_and( + mut self, + result: manyhow::Result, + ) -> manyhow::Result { + match result { + Ok(value) => self.finish_with(value), + Err(err) => { + self.emit(err); + Err(self.finish().unwrap_err()) + } + } + } + + /// Consume the emitter, convert all errors into a token stream and append it to the given token stream. + pub fn into_tokens(self, tokens: &mut TokenStream) { + match self.finish() { + Ok(()) => {} + Err(e) => e.to_tokens(tokens), + } + } + + /// Consume the emitter, convert all errors into a token stream. + pub fn into_token_stream(self) -> TokenStream { + let mut tokens_stream = TokenStream::new(); + self.into_tokens(&mut tokens_stream); + tokens_stream + } + + /// Consume the emitter, convert all errors into a token stream and append it to the given token stream. + /// + /// This function is useful when you want to handle errors in a macro, but want to emit some tokens even in case of an error. + pub fn finish_token_stream(self, mut tokens_stream: TokenStream) -> TokenStream { + self.into_tokens(&mut tokens_stream); + tokens_stream + } +} + +impl Extend for Emitter { + fn extend>(&mut self, iter: T) { + self.inner.extend(iter) + } +} diff --git a/ffi/derive/src/ffi_fn.rs b/ffi/derive/src/ffi_fn.rs index 8340568d744..3b414cdb8e0 100644 --- a/ffi/derive/src/ffi_fn.rs +++ b/ffi/derive/src/ffi_fn.rs @@ -1,17 +1,17 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{visit_mut::VisitMut, Ident}; +use syn2::{visit_mut::VisitMut, Ident}; use crate::{ + getset_gen::{gen_resolve_type, gen_store_name}, impl_visitor::{Arg, FnDescriptor}, - util::{gen_resolve_type, gen_store_name}, }; -fn prune_fn_declaration_attributes<'a>(attrs: &[&'a syn::Attribute]) -> Vec<&'a syn::Attribute> { +fn prune_fn_declaration_attributes<'a>(attrs: &[&'a syn2::Attribute]) -> Vec<&'a syn2::Attribute> { let mut pruned = Vec::new(); for attr in attrs { - if **attr == syn::parse_quote! {#[inline]} { + if **attr == syn2::parse_quote! {#[inline]} { continue; } @@ -88,7 +88,7 @@ fn gen_doc(fn_descriptor: &FnDescriptor, trait_name: Option<&Ident>) -> String { let self_type = fn_descriptor .self_ty .as_ref() - .and_then(syn::Path::get_ident); + .and_then(syn2::Path::get_ident); let path = self_type.map_or_else( || method_name.to_string(), @@ -213,9 +213,9 @@ pub struct InjectColon; impl VisitMut for InjectColon { fn visit_angle_bracketed_generic_arguments_mut( &mut self, - i: &mut syn::AngleBracketedGenericArguments, + i: &mut syn2::AngleBracketedGenericArguments, ) { - i.colon2_token = Some(syn::parse_quote!(::)); + i.colon2_token = Some(syn2::parse_quote!(::)); } } diff --git a/ffi/derive/src/getset_gen.rs b/ffi/derive/src/getset_gen.rs new file mode 100644 index 00000000000..1242d9446a2 --- /dev/null +++ b/ffi/derive/src/getset_gen.rs @@ -0,0 +1,190 @@ +use std::default::Default; + +use darling::ast::Style; +use manyhow::{emit, error_message, Result}; +use proc_macro2::TokenStream; +use quote::quote; +use rustc_hash::FxHashMap; +use syn2::{parse_quote, visit::Visit, Ident}; + +use crate::{ + attr_parse::{ + derive::DeriveAttr, + getset::{GetSetGenMode, GetSetStructAttr}, + }, + convert::{FfiTypeField, FfiTypeFields}, + emitter::Emitter, + impl_visitor::{unwrap_result_type, Arg, FnDescriptor}, +}; + +/// Generate FFI function equivalents of getset-derived methods +pub fn gen_derived_methods<'a>( + name: &Ident, + derives: &DeriveAttr, + getset_struct_attrs: &GetSetStructAttr, + fields: &'a FfiTypeFields, +) -> Result>> { + let mut emitter = Emitter::new(); + let mut ffi_derives = FxHashMap::default(); + + match fields.style { + Style::Struct => {} + Style::Tuple | Style::Unit => { + return emitter.finish_and(Err(error_message!(name, "Only named structs supported"))); + } + } + + for field in fields.iter() { + for (mode, options) in field + .getset_attr + .get_field_accessors(derives, getset_struct_attrs) + { + if options.with_prefix { + emit!( + emitter, + "with_prefix option of getset crate is not supported by iroha_ffi_derive" + ); + } + if options.visibility != Some(parse_quote!(pub)) { + // ignore non-public accessors + continue; + } + + let fn_ = gen_derived_method(name, field, mode); + ffi_derives.insert(fn_.sig.ident.clone(), fn_); + } + } + + emitter.finish_with(ffi_derives.into_values()) +} + +pub fn gen_resolve_type(arg: &Arg) -> TokenStream { + let (arg_name, src_type) = (arg.name(), arg.src_type()); + + if unwrap_result_type(src_type).is_some() { + return quote! { + let #arg_name = if let Ok(ok) = #arg_name { + ok + } else { + // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) + return Err(iroha_ffi::FfiReturn::ExecutionFail); + }; + }; + } + + let mut type_resolver = FfiTypeResolver(arg_name, quote! {}); + type_resolver.visit_type(src_type); + type_resolver.1 +} + +fn gen_derived_method<'ast>( + item_name: &Ident, + field: &'ast FfiTypeField, + mode: GetSetGenMode, +) -> FnDescriptor<'ast> { + let handle_name = Ident::new("__handle", proc_macro2::Span::call_site()); + let field_name = field + .ident + .as_ref() + .expect("BUG: Field name not defined") + .clone(); + let sig = gen_derived_method_sig(field, mode); + let self_ty = Some(parse_quote! {#item_name}); + + let doc = field.doc_attrs.attrs.iter().collect(); + + let field_ty = &field.ty; + let (receiver, input_args, output_arg) = match mode { + GetSetGenMode::Set => ( + Arg::new(self_ty.clone(), handle_name, parse_quote! {&mut Self}), + vec![Arg::new(self_ty.clone(), field_name, field_ty.clone())], + None, + ), + GetSetGenMode::Get => ( + Arg::new(self_ty.clone(), handle_name, parse_quote! {&Self}), + Vec::new(), + Some(Arg::new( + self_ty.clone(), + field_name, + parse_quote!(& #field_ty), + )), + ), + GetSetGenMode::GetCopy => ( + Arg::new(self_ty.clone(), handle_name, parse_quote! {&Self}), + Vec::new(), + Some(Arg::new( + self_ty.clone(), + field_name, + parse_quote!(#field_ty), + )), + ), + GetSetGenMode::GetMut => ( + Arg::new(self_ty.clone(), handle_name, parse_quote! {&mut Self}), + Vec::new(), + Some(Arg::new( + self_ty.clone(), + field_name, + parse_quote!(&mut #field_ty), + )), + ), + }; + + FnDescriptor { + attrs: Vec::new(), + self_ty, + doc, + sig, + receiver: Some(receiver), + input_args, + output_arg, + } +} + +fn gen_derived_method_sig(field: &FfiTypeField, mode: GetSetGenMode) -> syn2::Signature { + let field_name = field.ident.as_ref().expect("BUG: Field name not defined"); + let field_ty = &field.ty; + + let method_name = Ident::new( + &match mode { + GetSetGenMode::Set => format!("set_{field_name}"), + GetSetGenMode::Get | GetSetGenMode::GetCopy => format!("{field_name}"), + GetSetGenMode::GetMut => format!("{field_name}_mut"), + }, + proc_macro2::Span::call_site(), + ); + + match mode { + GetSetGenMode::Set => parse_quote! { + fn #method_name(&mut self, #field_name: #field_ty) + }, + GetSetGenMode::Get => parse_quote! { + fn #method_name(&self) -> &#field_ty + }, + GetSetGenMode::GetCopy => parse_quote! { + fn #method_name(&self) -> #field_ty + }, + GetSetGenMode::GetMut => parse_quote! { + fn #method_name(&mut self) -> &mut #field_ty + }, + } +} + +pub fn gen_store_name(arg_name: &Ident) -> Ident { + Ident::new(&format!("{arg_name}_store"), proc_macro2::Span::call_site()) +} + +struct FfiTypeResolver<'itm>(&'itm Ident, TokenStream); +impl<'itm> Visit<'itm> for FfiTypeResolver<'itm> { + fn visit_trait_bound(&mut self, i: &'itm syn2::TraitBound) { + let trait_ = i.path.segments.last().expect("Defined"); + + let arg_name = self.0; + if trait_.ident == "IntoIterator" || trait_.ident == "ExactSizeIterator" { + self.1 = quote! { let #arg_name: Vec<_> = #arg_name.into_iter().collect(); }; + } else if trait_.ident == "Into" { + self.1 = quote! { let #arg_name = #arg_name.into(); }; + } else if trait_.ident == "AsRef" { + self.1 = quote! { let #arg_name = #arg_name.as_ref(); }; + } + } +} diff --git a/ffi/derive/src/impl_visitor.rs b/ffi/derive/src/impl_visitor.rs index fc32c179ea8..0af8f9abc86 100644 --- a/ffi/derive/src/impl_visitor.rs +++ b/ffi/derive/src/impl_visitor.rs @@ -1,6 +1,10 @@ +//! This module implements a visitor that walks over an impl block and collects information to generate the FFI functions +//! +//! It also defines descriptors - types that are used for the codegen step + use manyhow::{emit, Emitter, Result}; use proc_macro2::Span; -use syn::{ +use syn2::{ parse_quote, visit::{visit_signature, Visit}, visit_mut::VisitMut, @@ -28,7 +32,7 @@ impl Arg { &self.type_ } pub fn src_type_is_empty_tuple(&self) -> bool { - matches!(self.src_type_resolved(), Type::Tuple(syn::TypeTuple { ref elems, .. }) if elems.is_empty()) + matches!(self.src_type_resolved(), Type::Tuple(syn2::TypeTuple { ref elems, .. }) if elems.is_empty()) } pub fn src_type_resolved(&self) -> Type { resolve_type(self.self_ty.as_ref(), self.type_.clone()) @@ -86,9 +90,9 @@ pub struct FnDescriptor<'ast> { /// Function documentation // TODO: Could just be a part of all attrs? - pub doc: Vec<&'ast syn::Attribute>, + pub doc: Vec<&'ast Attribute>, /// Original signature of the method - pub sig: syn::Signature, + pub sig: syn2::Signature, /// Receiver argument, i.e. `self` pub receiver: Option, @@ -111,13 +115,13 @@ struct ImplVisitor<'ast> { struct FnVisitor<'ast> { emitter: Emitter, attrs: Vec<&'ast Attribute>, - doc: Vec<&'ast syn::Attribute>, + doc: Vec<&'ast Attribute>, trait_name: Option<&'ast Path>, /// Resolved type of the `Self` type self_ty: Option<&'ast Path>, /// Original signature of the method - sig: Option<&'ast syn::Signature>, + sig: Option<&'ast syn2::Signature>, /// Receiver argument, i.e. `self` receiver: Option, @@ -131,7 +135,7 @@ struct FnVisitor<'ast> { } impl<'ast> ImplDescriptor<'ast> { - pub fn from_impl(node: &'ast syn::ItemImpl) -> Result { + pub fn from_impl(node: &'ast syn2::ItemImpl) -> Result { let mut visitor = ImplVisitor::new(); visitor.visit_item_impl(node); @@ -157,15 +161,15 @@ impl<'ast> FnDescriptor<'ast> { pub fn from_impl_method( self_ty: &'ast Path, trait_name: Option<&'ast Path>, - node: &'ast syn::ImplItemMethod, + node: &'ast syn2::ImplItemFn, ) -> Result { let mut visitor = FnVisitor::new(Some(self_ty), trait_name); - visitor.visit_impl_item_method(node); + visitor.visit_impl_item_fn(node); FnDescriptor::from_visitor(visitor) } - pub fn from_fn(node: &'ast syn::ItemFn) -> Result { + pub fn from_fn(node: &'ast syn2::ItemFn) -> Result { let mut visitor = FnVisitor::new(None, None); visitor.visit_item_fn(node); @@ -268,13 +272,13 @@ impl<'ast> FnVisitor<'ast> { } impl<'ast> Visit<'ast> for ImplVisitor<'ast> { - fn visit_attribute(&mut self, node: &'ast syn::Attribute) { + fn visit_attribute(&mut self, node: &'ast syn2::Attribute) { self.attrs.push(node); } - fn visit_generic_param(&mut self, node: &'ast syn::GenericParam) { + fn visit_generic_param(&mut self, node: &'ast syn2::GenericParam) { emit!(self.emitter, node, "Generics are not supported"); } - fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) { + fn visit_item_impl(&mut self, node: &'ast syn2::ItemImpl) { if node.unsafety.is_some() { emit!(self.emitter, node.unsafety, "Unsafe impl not supported"); } @@ -294,14 +298,14 @@ impl<'ast> Visit<'ast> for ImplVisitor<'ast> { let self_ty = self.self_ty.expect("Defined"); self.associated_types .extend(node.items.iter().filter_map(|item| match item { - syn::ImplItem::Type(associated_type) => { + syn2::ImplItem::Type(associated_type) => { Some((&associated_type.ident, &associated_type.ty)) } _ => None, })); for item in node.items.iter() { - if let syn::ImplItem::Method(method) = item { + if let syn2::ImplItem::Fn(method) = item { // NOTE: private methods in inherent impl are skipped if self.trait_name.is_none() && !matches!(method.vis, Visibility::Public(_)) { continue; @@ -316,7 +320,7 @@ impl<'ast> Visit<'ast> for ImplVisitor<'ast> { } impl<'ast> Visit<'ast> for FnVisitor<'ast> { - fn visit_attribute(&mut self, node: &'ast syn::Attribute) { + fn visit_attribute(&mut self, node: &'ast syn2::Attribute) { if is_doc_attr(node) { self.doc.push(node); } else { @@ -324,13 +328,13 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { } } - fn visit_abi(&mut self, node: &'ast syn::Abi) { + fn visit_abi(&mut self, node: &'ast syn2::Abi) { emit!(self.emitter, node, "You shouldn't specify function ABI"); } - fn visit_generic_param(&mut self, node: &'ast syn::GenericParam) { + fn visit_generic_param(&mut self, node: &'ast syn2::GenericParam) { emit!(self.emitter, node, "Generics are not supported"); } - fn visit_impl_item_method(&mut self, node: &'ast syn::ImplItemMethod) { + fn visit_impl_item_fn(&mut self, node: &'ast syn2::ImplItemFn) { for attr in &node.attrs { self.visit_attribute(attr); } @@ -339,7 +343,7 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { self.visit_visibility(&node.vis); self.visit_signature(&node.sig); } - fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) { + fn visit_item_fn(&mut self, node: &'ast syn2::ItemFn) { for attr in &node.attrs { self.visit_attribute(attr); } @@ -348,12 +352,12 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { self.visit_visibility(&node.vis); self.visit_signature(&node.sig); } - fn visit_visibility(&mut self, node: &'ast syn::Visibility) { + fn visit_visibility(&mut self, node: &'ast Visibility) { if self.trait_name.is_none() && !matches!(node, Visibility::Public(_)) { emit!(self.emitter, node, "Private methods should not be exported"); } } - fn visit_signature(&mut self, node: &'ast syn::Signature) { + fn visit_signature(&mut self, node: &'ast syn2::Signature) { if node.constness.is_some() { emit!( self.emitter, @@ -393,7 +397,7 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { visit_signature(self, node); } - fn visit_receiver(&mut self, node: &'ast syn::Receiver) { + fn visit_receiver(&mut self, node: &'ast syn2::Receiver) { for it in &node.attrs { self.visit_attribute(it); } @@ -426,12 +430,12 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { )); } - fn visit_pat_type(&mut self, node: &'ast syn::PatType) { + fn visit_pat_type(&mut self, node: &'ast syn2::PatType) { for it in &node.attrs { self.visit_attribute(it); } - if let syn::Pat::Ident(ident) = &*node.pat { + if let syn2::Pat::Ident(ident) = &*node.pat { self.visit_pat_ident(ident); } else { emit!( @@ -444,7 +448,7 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { self.add_input_arg(&node.ty); } - fn visit_pat_ident(&mut self, node: &'ast syn::PatIdent) { + fn visit_pat_ident(&mut self, node: &'ast syn2::PatIdent) { for it in &node.attrs { self.visit_attribute(it); } @@ -469,24 +473,18 @@ impl<'ast> Visit<'ast> for FnVisitor<'ast> { self.curr_arg_name = Some(&node.ident); } - fn visit_return_type(&mut self, node: &'ast syn::ReturnType) { + fn visit_return_type(&mut self, node: &'ast syn2::ReturnType) { match node { - syn::ReturnType::Default => {} - syn::ReturnType::Type(_, src_type) => { + syn2::ReturnType::Default => {} + syn2::ReturnType::Type(_, src_type) => { self.add_output_arg(src_type); } } } } -pub fn is_doc_attr(attr: &syn::Attribute) -> bool { - if let Ok(meta) = attr.parse_meta() { - if meta.path().is_ident("doc") { - return true; - } - } - - false +fn is_doc_attr(attr: &syn2::Attribute) -> bool { + attr.path().is_ident("doc") } /// Visitor replaces all occurrences of `Self` in a path type with a fully qualified type @@ -528,13 +526,13 @@ impl VisitMut for TypeImplTraitResolver { if let Type::ImplTrait(impl_trait) = node { for bound in &impl_trait.bounds { - if let syn::TypeParamBound::Trait(trait_) = bound { + if let syn2::TypeParamBound::Trait(trait_) = bound { let trait_ = trait_.path.segments.last().expect("Defined"); if trait_.ident == "IntoIterator" || trait_.ident == "ExactSizeIterator" { - if let syn::PathArguments::AngleBracketed(args) = &trait_.arguments { + if let syn2::PathArguments::AngleBracketed(args) = &trait_.arguments { for arg in &args.args { - if let syn::GenericArgument::Binding(binding) = arg { + if let syn2::GenericArgument::AssocType(binding) = arg { if binding.ident == "Item" { let mut ty = binding.ty.clone(); TypeImplTraitResolver.visit_type_mut(&mut ty); @@ -544,18 +542,18 @@ impl VisitMut for TypeImplTraitResolver { } } } else if trait_.ident == "Into" { - if let syn::PathArguments::AngleBracketed(args) = &trait_.arguments { + if let syn2::PathArguments::AngleBracketed(args) = &trait_.arguments { for arg in &args.args { - if let syn::GenericArgument::Type(type_) = arg { + if let syn2::GenericArgument::Type(type_) = arg { new_node = Some(type_.clone()); } } } } else if trait_.ident == "AsRef" { - if let syn::PathArguments::AngleBracketed(args) = &trait_.arguments { + if let syn2::PathArguments::AngleBracketed(args) = &trait_.arguments { for arg in &args.args { - if let syn::GenericArgument::Type(type_) = arg { - new_node = Some(syn::parse_quote!(&#type_)); + if let syn2::GenericArgument::Type(type_) = arg { + new_node = Some(syn2::parse_quote!(&#type_)); } } } @@ -570,7 +568,7 @@ impl VisitMut for TypeImplTraitResolver { } } -fn last_seg_ident(path: &syn::Path) -> &Ident { +fn last_seg_ident(path: &syn2::Path) -> &Ident { &path.segments.last().expect("Defined").ident } @@ -579,8 +577,8 @@ pub fn unwrap_result_type(node: &Type) -> Option<(&Type, &Type)> { let last_seg = type_.path.segments.last().expect("Defined"); if last_seg.ident == "Result" { - if let syn::PathArguments::AngleBracketed(args) = &last_seg.arguments { - if let (syn::GenericArgument::Type(ok), syn::GenericArgument::Type(err)) = + if let syn2::PathArguments::AngleBracketed(args) = &last_seg.arguments { + if let (syn2::GenericArgument::Type(ok), syn2::GenericArgument::Type(err)) = (&args.args[0], &args.args[1]) { return Some((ok, err)); diff --git a/ffi/derive/src/lib.rs b/ffi/derive/src/lib.rs index d5909cab712..571ed071a10 100644 --- a/ffi/derive/src/lib.rs +++ b/ffi/derive/src/lib.rs @@ -1,58 +1,44 @@ //! Crate containing FFI related macro functionality #![allow(clippy::arithmetic_side_effects)] +use darling::FromDeriveInput; use impl_visitor::{FnDescriptor, ImplDescriptor}; use manyhow::{bail, manyhow, Result}; use proc_macro2::TokenStream; use quote::quote; -use syn::{parse_quote, Item, NestedMeta}; +use syn2::Item; use wrapper::wrap_method; -use crate::convert::derive_ffi_type; +use crate::{ + attr_parse::derive::Derive, + convert::{derive_ffi_type, FfiTypeData, FfiTypeInput}, +}; +mod attr_parse; mod convert; +mod emitter; mod ffi_fn; +mod getset_gen; mod impl_visitor; -mod repr; -mod util; mod wrapper; -struct FfiItems(Vec); +struct FfiItems(Vec); -impl syn::parse::Parse for FfiItems { - fn parse(input: syn::parse::ParseStream) -> syn::Result { +impl syn2::parse::Parse for FfiItems { + fn parse(input: syn2::parse::ParseStream) -> syn2::Result { let mut items = Vec::new(); while !input.is_empty() { - items.push(input.parse()?); + let input = input.parse::()?; + let input = FfiTypeInput::from_derive_input(&input)?; + + items.push(input); } Ok(Self(items)) } } -impl quote::ToTokens for FfiItems { - fn to_tokens(&self, tokens: &mut TokenStream) { - let items = &self.0; - tokens.extend(quote! {#(#items)*}) - } -} - -fn has_getset_attr(attrs: &[syn::Attribute]) -> bool { - for derive in find_attr(attrs, "derive") { - if let NestedMeta::Meta(syn::Meta::Path(path)) = &derive { - if path.segments.first().expect("Must have one segment").ident == "getset" { - return true; - } - if path.is_ident("Setters") || path.is_ident("Getters") || path.is_ident("MutGetters") { - return true; - } - } - } - - false -} - /// Replace struct/enum/union definition with opaque pointer. This applies to types that /// are converted to an opaque pointer when sent across FFI but does not affect any other /// item wrapped with this macro (e.g. fieldless enums). This is so that most of the time @@ -61,27 +47,36 @@ fn has_getset_attr(attrs: &[syn::Attribute]) -> bool { #[manyhow] #[proc_macro] pub fn ffi(input: TokenStream) -> Result { - let items = syn::parse2::(input)?.0; + let items = syn2::parse2::(input)?.0; let items = items .into_iter() .map(|item| { - if !matches!(item.vis, syn::Visibility::Public(_)) { + if !matches!(item.vis, syn2::Visibility::Public(_)) { bail!(item, "Only public types are allowed in FFI"); } - if !is_opaque(&item) { + if !item.is_opaque() { return Ok(quote! { #[derive(iroha_ffi::FfiType)] #item }); } - if let syn::Data::Struct(struct_) = &item.data { - if has_getset_attr(&item.attrs) { - let derived_methods: Vec<_> = - util::gen_derived_methods(&item.ident, &item.attrs, &struct_.fields)? - .collect(); + if let FfiTypeData::Struct(fields) = &item.data { + if item + .derive_attr + .derives + .iter() + .any(|d| matches!(d, Derive::GetSet(_))) + { + let derived_methods: Vec<_> = getset_gen::gen_derived_methods( + &item.ident, + &item.derive_attr, + &item.getset_attr, + fields, + )? + .collect(); let ffi_fns: Vec<_> = derived_methods .iter() @@ -157,7 +152,7 @@ pub fn ffi(input: TokenStream) -> Result { #[manyhow] #[proc_macro_derive(FfiType, attributes(ffi_type))] pub fn ffi_type_derive(input: TokenStream) -> TokenStream { - let mut emitter = convert::Emitter::new(); + let mut emitter = emitter::Emitter::new(); let Some(item) = emitter.handle(syn2::parse2::(input)) else { return emitter.into_token_stream(); @@ -176,6 +171,8 @@ pub fn ffi_type_derive(input: TokenStream) -> TokenStream { /// When placed on a structure, it integrates with [`getset`] to export derived getter/setter methods. /// To be visible this attribute must be placed before/on top of any [`getset`] derive macro attributes /// +/// It also works on impl blocks (by visiting all methods in the impl block) and on enums and unions (as a no-op) +/// /// # Example: /// ```rust /// use std::alloc::alloc; @@ -223,7 +220,7 @@ pub fn ffi_type_derive(input: TokenStream) -> TokenStream { #[manyhow] #[proc_macro_attribute] pub fn ffi_export(attr: TokenStream, item: TokenStream) -> Result { - Ok(match syn::parse2(item)? { + Ok(match syn2::parse2(item)? { Item::Impl(item) => { if !attr.is_empty() { bail!(item, "Unknown tokens in the attribute"); @@ -250,16 +247,35 @@ pub fn ffi_export(attr: TokenStream, item: TokenStream) -> Result { } } Item::Struct(item) => { - if !is_opaque_struct(&item) || !has_getset_attr(&item.attrs) { - return Ok(quote! { #item }); + let input = syn2::parse2(quote!(#item)).unwrap(); + let input = FfiTypeInput::from_derive_input(&input)?; + + // we don't need ffi fns for getset accessors if the type is not opaque or there are no accessors + if !input.is_opaque() + || !input + .derive_attr + .derives + .iter() + .any(|d| matches!(d, Derive::GetSet(_))) + { + let input = input.ast; + return Ok(quote! { #input }); } - if !item.generics.params.is_empty() { - bail!(item.generics, "Generics on derived methods not supported"); + let darling::ast::Data::Struct(fields) = &input.data else { + unreachable!("We parsed struct above"); + }; + + if !input.generics.params.is_empty() { + bail!(input.generics, "Generics on derived methods not supported"); } - let derived_ffi_fns = - util::gen_derived_methods(&item.ident, &item.attrs, &item.fields)? - .map(|fn_| ffi_fn::gen_definition(&fn_, None)); + let derived_ffi_fns = getset_gen::gen_derived_methods( + &input.ident, + &input.derive_attr, + &input.getset_attr, + fields, + )? + .map(|fn_| ffi_fn::gen_definition(&fn_, None)); quote! { #item @@ -301,7 +317,7 @@ pub fn ffi_export(attr: TokenStream, item: TokenStream) -> Result { #[manyhow] #[proc_macro_attribute] pub fn ffi_import(attr: TokenStream, item: TokenStream) -> Result { - Ok(match syn::parse2(item)? { + Ok(match syn2::parse2(item)? { Item::Impl(item) => { if !attr.is_empty() { bail!(item, "Unknown tokens in the attribute"); @@ -354,45 +370,3 @@ pub fn ffi_import(attr: TokenStream, item: TokenStream) -> Result { item => bail!(item, "Item not supported"), }) } - -fn is_opaque_struct(input: &syn::ItemStruct) -> bool { - if is_opaque_attr(&input.attrs) { - return true; - } - - without_repr(&find_attr(&input.attrs, "repr")) -} - -fn is_opaque(input: &syn::DeriveInput) -> bool { - if is_opaque_attr(&input.attrs) { - return true; - } - - let repr = find_attr(&input.attrs, "repr"); - - // NOTE: Enums without defined representation, by default, are not opaque - !matches!(&input.data, syn::Data::Enum(_)) && without_repr(&repr) -} - -fn is_opaque_attr(attrs: &[syn::Attribute]) -> bool { - let opaque_attr = parse_quote! {#[ffi_type(opaque)]}; - attrs.iter().any(|a| *a == opaque_attr) -} - -fn without_repr(repr: &[NestedMeta]) -> bool { - repr.is_empty() -} - -fn find_attr(attrs: &[syn::Attribute], name: &str) -> syn::AttributeArgs { - attrs - .iter() - .filter_map(|attr| { - if let Ok(syn::Meta::List(meta_list)) = attr.parse_meta() { - return meta_list.path.is_ident(name).then_some(meta_list.nested); - } - - None - }) - .flatten() - .collect() -} diff --git a/ffi/derive/src/util.rs b/ffi/derive/src/util.rs deleted file mode 100644 index c00a8366ea6..00000000000 --- a/ffi/derive/src/util.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::default::Default; - -use manyhow::{bail, Result}; -use proc_macro2::TokenStream; -use quote::quote; -use rustc_hash::{FxHashMap, FxHashSet}; -use syn::{parse_quote, visit::Visit, Fields, Ident}; - -use crate::impl_visitor::{is_doc_attr, unwrap_result_type, Arg, FnDescriptor}; - -/// Type of accessor method derived for a structure -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -enum Derive { - Setter, - Getter, - MutGetter, -} - -/// Generate FFI function equivalents of derived methods -pub fn gen_derived_methods<'a>( - name: &Ident, - attrs: &[syn::Attribute], - fields: &'a syn::Fields, -) -> Result>> { - let struct_derives = parse_derives(attrs)?.unwrap_or_default(); - let mut ffi_derives = FxHashMap::default(); - - match fields { - Fields::Named(syn::FieldsNamed { named, .. }) => { - for field in named.iter() { - if let Some(mut field_derives) = parse_derives(&field.attrs)? { - field_derives.extend(struct_derives.clone()); - - for derive in field_derives { - let fn_ = gen_derived_method(name, field, derive); - ffi_derives.insert(fn_.sig.ident.clone(), fn_); - } - } - } - } - Fields::Unnamed(_) | Fields::Unit => { - bail!(name, "Only named structs supported") - } - } - - Ok(ffi_derives.into_values()) -} - -pub fn gen_resolve_type(arg: &Arg) -> TokenStream { - let (arg_name, src_type) = (arg.name(), arg.src_type()); - - if unwrap_result_type(src_type).is_some() { - return quote! { - let #arg_name = if let Ok(ok) = #arg_name { - ok - } else { - // TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252) - return Err(iroha_ffi::FfiReturn::ExecutionFail); - }; - }; - } - - let mut type_resolver = FfiTypeResolver(arg_name, quote! {}); - type_resolver.visit_type(src_type); - type_resolver.1 -} - -/// Parse `getset` attributes to find out which methods it derives -fn parse_derives(attrs: &[syn::Attribute]) -> Result>> { - let mut result = FxHashSet::default(); - - for nested in attrs - .iter() - .filter_map(|attr| { - if let Ok(syn::Meta::List(meta_list)) = attr.parse_meta() { - return meta_list - .path - .is_ident("getset") - .then_some(meta_list.nested); - } - - None - }) - .flatten() - { - if let syn::NestedMeta::Meta(item) = nested { - match item { - syn::Meta::NameValue(item) => { - if item.lit == parse_quote! {"pub"} { - if item.path.is_ident("set") { - result.insert(Derive::Setter); - } else if item.path.is_ident("get") { - result.insert(Derive::Getter); - } else if item.path.is_ident("get_mut") { - result.insert(Derive::MutGetter); - } - } - } - syn::Meta::Path(path) => { - if path.is_ident("skip") { - return Ok(None); - } - } - _ => bail!(item, "Unsupported getset attribute"), - } - } - } - - Ok(Some(result)) -} - -fn gen_derived_method<'ast>( - item_name: &Ident, - field: &'ast syn::Field, - derive: Derive, -) -> FnDescriptor<'ast> { - let handle_name = Ident::new("__handle", proc_macro2::Span::call_site()); - let field_name = field.ident.as_ref().expect("Defined").clone(); - let sig = gen_derived_method_sig(field, derive); - let self_ty = Some(parse_quote! {#item_name}); - - let mut doc = Vec::new(); - for attr in &field.attrs { - if is_doc_attr(attr) { - doc.push(attr); - } - } - - let field_ty = &field.ty; - let field_ty = match derive { - Derive::Setter => field_ty.clone(), - Derive::Getter => parse_quote! {&#field_ty}, - Derive::MutGetter => parse_quote! {&mut #field_ty}, - }; - - let (receiver, input_args, output_arg) = match derive { - Derive::Setter => ( - Arg::new(self_ty.clone(), handle_name, parse_quote! {&mut Self}), - vec![Arg::new(self_ty.clone(), field_name, field_ty)], - None, - ), - Derive::Getter => ( - Arg::new(self_ty.clone(), handle_name, parse_quote! {&Self}), - Vec::new(), - Some(Arg::new(self_ty.clone(), field_name, field_ty)), - ), - Derive::MutGetter => ( - Arg::new(self_ty.clone(), handle_name, parse_quote! {&mut Self}), - Vec::new(), - Some(Arg::new(self_ty.clone(), field_name, field_ty)), - ), - }; - - FnDescriptor { - attrs: Vec::new(), - self_ty, - doc, - sig, - receiver: Some(receiver), - input_args, - output_arg, - } -} - -fn gen_derived_method_sig(field: &syn::Field, derive: Derive) -> syn::Signature { - let field_name = field.ident.as_ref().expect("Field name not defined"); - let field_ty = &field.ty; - - let method_name = Ident::new( - &match derive { - Derive::Setter => format!("set_{field_name}"), - Derive::Getter => format!("{field_name}"), - Derive::MutGetter => format!("{field_name}_mut"), - }, - proc_macro2::Span::call_site(), - ); - - match derive { - Derive::Setter => parse_quote! { - fn #method_name(&mut self, #field_name: #field_ty) - }, - Derive::Getter => parse_quote! { - fn #method_name(&self) -> &#field_ty - }, - Derive::MutGetter => parse_quote! { - fn #method_name(&mut self) -> &mut #field_ty - }, - } -} - -pub fn gen_store_name(arg_name: &Ident) -> Ident { - Ident::new(&format!("{arg_name}_store"), proc_macro2::Span::call_site()) -} - -struct FfiTypeResolver<'itm>(&'itm Ident, TokenStream); -impl<'itm> Visit<'itm> for FfiTypeResolver<'itm> { - fn visit_trait_bound(&mut self, i: &'itm syn::TraitBound) { - let trait_ = i.path.segments.last().expect("Defined"); - - let arg_name = self.0; - if trait_.ident == "IntoIterator" || trait_.ident == "ExactSizeIterator" { - self.1 = quote! { let #arg_name: Vec<_> = #arg_name.into_iter().collect(); }; - } else if trait_.ident == "Into" { - self.1 = quote! { let #arg_name = #arg_name.into(); }; - } else if trait_.ident == "AsRef" { - self.1 = quote! { let #arg_name = #arg_name.as_ref(); }; - } - } -} diff --git a/ffi/derive/src/wrapper.rs b/ffi/derive/src/wrapper.rs index af8cbc9a752..93acac57f20 100644 --- a/ffi/derive/src/wrapper.rs +++ b/ffi/derive/src/wrapper.rs @@ -1,12 +1,15 @@ -use manyhow::{bail, Result}; +use manyhow::{emit, Result}; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{parse_quote, visit_mut::VisitMut, Ident, Type}; +use syn2::{parse_quote, visit_mut::VisitMut, Attribute, Ident, Type}; use crate::{ - ffi_fn, find_attr, + attr_parse::derive::{Derive, RustcDerive}, + convert::FfiTypeInput, + emitter::Emitter, + ffi_fn, + getset_gen::{gen_resolve_type, gen_store_name}, impl_visitor::{unwrap_result_type, Arg, FnDescriptor, ImplDescriptor, TypeImplTraitResolver}, - util::{gen_resolve_type, gen_store_name}, }; fn gen_lifetime_name_for_opaque() -> TokenStream { @@ -19,7 +22,7 @@ fn gen_ref_mut_name(name: &Ident) -> Ident { Ident::new(&format!("RefMut{name}"), Span::call_site()) } -fn add_handle_bound(name: &Ident, generics: &mut syn::Generics) { +fn add_handle_bound(name: &Ident, generics: &mut syn2::Generics) { let cloned_generics = generics.clone(); let (_, ty_generics, _) = cloned_generics.split_for_impl(); @@ -29,7 +32,7 @@ fn add_handle_bound(name: &Ident, generics: &mut syn::Generics) { .push(parse_quote! {#name #ty_generics: iroha_ffi::Handle}); } -fn impl_clone_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream { +fn impl_clone_for_opaque(name: &Ident, generics: &syn2::Generics) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -50,7 +53,7 @@ fn impl_clone_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream } } -fn impl_default_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream { +fn impl_default_for_opaque(name: &Ident, generics: &syn2::Generics) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -71,11 +74,11 @@ fn impl_default_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStrea } } -fn impl_eq_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream { +fn impl_eq_for_opaque(name: &Ident, generics: &syn2::Generics) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { impl #impl_generics Eq for #name #ty_generics #where_clause {} } } -fn impl_partial_eq_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream { +fn impl_partial_eq_for_opaque(name: &Ident, generics: &syn2::Generics) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -96,7 +99,7 @@ fn impl_partial_eq_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenSt } } -fn impl_partial_ord_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream { +fn impl_partial_ord_for_opaque(name: &Ident, generics: &syn2::Generics) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -107,7 +110,7 @@ fn impl_partial_ord_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenS } } } -fn impl_ord_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream { +fn impl_ord_for_opaque(name: &Ident, generics: &syn2::Generics) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); quote! { @@ -128,73 +131,67 @@ fn impl_ord_for_opaque(name: &Ident, generics: &syn::Generics) -> TokenStream { } } -fn gen_shared_fns(input: &syn::DeriveInput) -> Result> { +fn gen_shared_fns(input: &FfiTypeInput) -> Result> { + let mut emitter = Emitter::new(); + let name = &input.ident; let mut shared_fn_impls = Vec::new(); - for derive in find_attr(&input.attrs, "derive") { - if let syn::NestedMeta::Meta(meta) = &derive { - if let syn::Meta::Path(path) = meta { - let first_segment = &path.segments.first().expect("Must have one segment").ident; - - if path.is_ident("Copy") { - bail!(path, "Opaque type should not implement `Copy` trait"); - } else if path.is_ident("Clone") { + for derive in &input.derive_attr.derives { + match derive { + Derive::Rustc(derive) => match derive { + RustcDerive::Copy => { + emit!( + emitter, + name, + "Opaque type should not implement `Copy` trait" + ) + } + RustcDerive::Clone => { shared_fn_impls.push(impl_clone_for_opaque(name, &input.generics)); - } else if path.is_ident("Default") { + } + RustcDerive::Default => { shared_fn_impls.push(impl_default_for_opaque(name, &input.generics)); - } else if path.is_ident("PartialEq") { + } + RustcDerive::PartialEq => { shared_fn_impls.push(impl_partial_eq_for_opaque(name, &input.generics)); - } else if path.is_ident("Eq") { + } + RustcDerive::Eq => { shared_fn_impls.push(impl_eq_for_opaque(name, &input.generics)); - } else if path.is_ident("PartialOrd") { + } + RustcDerive::PartialOrd => { shared_fn_impls.push(impl_partial_ord_for_opaque(name, &input.generics)); - } else if path.is_ident("Ord") { + } + RustcDerive::Ord => { shared_fn_impls.push(impl_ord_for_opaque(name, &input.generics)); - } else if first_segment == "getset" - || path.is_ident("Setters") - || path.is_ident("Getters") - || path.is_ident("MutGetters") - { - // NOTE: Already expanded - } else { - bail!(path, "Unsupported derive for opaque type"); } + RustcDerive::Hash | RustcDerive::Debug => { + emit!( + emitter, + name, + "Opaque type should not implement `{:?}` trait", + derive + ) + } + }, + Derive::GetSet(_) => { + // handled by `getset_gen` module } - } else { - unreachable!() - } - } - - Ok(shared_fn_impls) -} - -fn wrapper_attributes(attrs: Vec) -> Vec { - let mut pruned = Vec::new(); - - for attr in attrs { - if attr.path.is_ident("derive") { - continue; - } - if attr == parse_quote! {#[ffi_type(opaque)]} { - continue; - } - if let Ok(syn::Meta::List(meta_list)) = attr.parse_meta() { - let attr_name = &meta_list.path; - - // TODO: consider disallowing getset - if attr_name.is_ident("repr") || attr_name.is_ident("getset") { - continue; + Derive::Other(derive) => { + emit!( + emitter, + name, + "Opaque type should not implement `{}` trait", + derive + ) } } - - pruned.push(attr); } - pruned + emitter.finish_with(shared_fn_impls) } -pub fn wrap_as_opaque(mut input: syn::DeriveInput) -> Result { +pub fn wrap_as_opaque(mut input: FfiTypeInput) -> Result { let name = &input.ident; let vis = &input.vis; @@ -226,7 +223,11 @@ pub fn wrap_as_opaque(mut input: syn::DeriveInput) -> Result { let ref_mut_inner = quote!(*mut iroha_ffi::Extern); let shared_fns = gen_shared_fns(&input)?; - let attrs = wrapper_attributes(input.attrs); + // TODO: which attributes do we need to keep? + // in darling there is mechanism to forwards attrs, but it needs to be an whitelist + // it seems that as of now no such forwarding needs to take place + // so we just drop all attributes + let attrs = Vec::::new(); Ok(quote! { #(#attrs)* @@ -295,7 +296,7 @@ pub fn wrap_as_opaque(mut input: syn::DeriveInput) -> Result { } #[allow(clippy::too_many_lines)] -fn gen_impl_ffi(name: &Ident, generics: &syn::Generics) -> TokenStream { +fn gen_impl_ffi(name: &Ident, generics: &syn2::Generics) -> TokenStream { let mut ref_generics = generics.clone(); let ref_name = gen_ref_name(name); @@ -309,7 +310,7 @@ fn gen_impl_ffi(name: &Ident, generics: &syn::Generics) -> TokenStream { let lifetime_bounded_where_clause = generics .type_params() .map(|param| parse_quote! {#param: #lifetime}) - .collect::>(); + .collect::>(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (ref_impl_generics, ref_ty_generics, _) = ref_generics.split_for_impl(); @@ -518,7 +519,7 @@ pub fn wrap_impl_items(impl_desc: &ImplDescriptor) -> TokenStream { quote! { #(#result)* } } -fn gen_ref_wrapper_signature(fn_descriptor: &FnDescriptor) -> syn::Signature { +fn gen_ref_wrapper_signature(fn_descriptor: &FnDescriptor) -> syn2::Signature { let mut signature = gen_wrapper_signature(fn_descriptor); let add_lifetime = fn_descriptor @@ -535,7 +536,7 @@ fn gen_ref_wrapper_signature(fn_descriptor: &FnDescriptor) -> syn::Signature { signature } -fn gen_wrapper_signature(fn_descriptor: &FnDescriptor) -> syn::Signature { +fn gen_wrapper_signature(fn_descriptor: &FnDescriptor) -> syn2::Signature { let mut signature = fn_descriptor.sig.clone(); let mut type_impl_trait_resolver = TypeImplTraitResolver; @@ -585,7 +586,7 @@ fn gen_self_ref_method( let fn_name = &fn_descriptor.sig.ident; let args = fn_descriptor.sig.inputs.iter().filter_map(|input| { - if let syn::FnArg::Typed(arg) = input { + if let syn2::FnArg::Typed(arg) = input { return Some(&arg.pat); } @@ -619,7 +620,7 @@ pub fn wrap_method(fn_descriptor: &FnDescriptor, trait_name: Option<&Ident>) -> fn wrap_method_with_signature( fn_descriptor: &FnDescriptor, trait_name: Option<&Ident>, - signature: &syn::Signature, + signature: &syn2::Signature, ) -> TokenStream { let ffi_fn_name = ffi_fn::gen_fn_name(fn_descriptor, trait_name); let method_body = gen_wrapper_method_body(fn_descriptor, &ffi_fn_name); @@ -629,7 +630,7 @@ fn wrap_method_with_signature( fn wrap_method_with_signature_and_body( fn_descriptor: &FnDescriptor, trait_name: Option<&Ident>, - signature: &syn::Signature, + signature: &syn2::Signature, method_body: &TokenStream, ) -> TokenStream { let ffi_fn_attrs = &fn_descriptor.attrs; @@ -789,15 +790,18 @@ impl WrapperTypeResolver { } } impl VisitMut for WrapperTypeResolver { - fn visit_receiver_mut(&mut self, i: &mut syn::Receiver) { + fn visit_receiver_mut(&mut self, i: &mut syn2::Receiver) { if i.reference.is_none() { i.mutability = None; } - syn::visit_mut::visit_receiver_mut(self, i); + // we do NOT want to visit the type in the receiver: + // 1. what can actually go in there is severely limited + // 2. in syn 2.0 even &self has a reconstructed type &Self, which, when patched, leads to an incorrect rust syntax + // syn2::visit_mut::visit_receiver_mut(self, i); } - fn visit_type_mut(&mut self, i: &mut syn::Type) { + fn visit_type_mut(&mut self, i: &mut syn2::Type) { if self.0 { // Patch return type to facilitate returning types referencing local store *i = parse_quote! {<#i as iroha_ffi::FfiWrapperType>::ReturnType}; @@ -806,10 +810,10 @@ impl VisitMut for WrapperTypeResolver { *i = parse_quote! {<#i as iroha_ffi::FfiWrapperType>::InputType}; } } - fn visit_return_type_mut(&mut self, i: &mut syn::ReturnType) { + fn visit_return_type_mut(&mut self, i: &mut syn2::ReturnType) { self.0 = true; - if let syn::ReturnType::Type(_, output) = i { + if let syn2::ReturnType::Type(_, output) = i { if let Some((ok, err)) = unwrap_result_type(output) { let mut ok = ok.clone(); self.visit_type_mut(&mut ok); @@ -830,7 +834,7 @@ impl WrapperLifetimeResolver { } impl VisitMut for WrapperLifetimeResolver { - fn visit_type_reference_mut(&mut self, i: &mut syn::TypeReference) { + fn visit_type_reference_mut(&mut self, i: &mut syn2::TypeReference) { let lifetime = gen_lifetime_name_for_opaque(); if !self.0 || i.lifetime.is_some() { @@ -839,8 +843,8 @@ impl VisitMut for WrapperLifetimeResolver { i.lifetime = parse_quote! {#lifetime}; } - fn visit_return_type_mut(&mut self, i: &mut syn::ReturnType) { + fn visit_return_type_mut(&mut self, i: &mut syn2::ReturnType) { self.0 = true; - syn::visit_mut::visit_return_type_mut(self, i); + syn2::visit_mut::visit_return_type_mut(self, i); } } diff --git a/ffi/derive/tests/ui_fail/derive_skip_struct.rs b/ffi/derive/tests/ui_fail/derive_skip_struct.rs index 70fc0db1a9d..810c127a58c 100644 --- a/ffi/derive/tests/ui_fail/derive_skip_struct.rs +++ b/ffi/derive/tests/ui_fail/derive_skip_struct.rs @@ -6,7 +6,11 @@ use iroha_ffi::{ffi_export, FfiConvert, FfiType}; /// FfiStruct #[ffi_export] #[derive(Clone, Setters, MutGetters, FfiType)] -#[getset(skip)] +// TODO: I am not really sure what is the purpose of this test +// getset allows `#[getset(skip)]` to be placed on a struct, but it doesn't seem to have any effect at all +// Due to it being potentially error-prone, iroha_ffi_derive disallows such placement +// hence it's commented out here +// #[getset(skip)] pub struct FfiStruct { /// a #[getset(set = "pub", get_mut = "pub")] diff --git a/ffi/derive/tests/ui_fail/derive_skip_struct.stderr b/ffi/derive/tests/ui_fail/derive_skip_struct.stderr index 6d8e6d6fed3..3f1fe4552d5 100644 --- a/ffi/derive/tests/ui_fail/derive_skip_struct.stderr +++ b/ffi/derive/tests/ui_fail/derive_skip_struct.stderr @@ -1,17 +1,17 @@ error[E0425]: cannot find function, tuple struct or tuple variant `FfiStruct__b_mut` in this scope - --> tests/ui_fail/derive_skip_struct.rs:31:9 + --> tests/ui_fail/derive_skip_struct.rs:35:9 | 7 | #[ffi_export] | ------------- similarly named function `FfiStruct__a_mut` defined here ... -31 | FfiStruct__b_mut(FfiConvert::into_ffi(&s, &mut ()), b.as_mut_ptr()); +35 | FfiStruct__b_mut(FfiConvert::into_ffi(&s, &mut ()), b.as_mut_ptr()); | ^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `FfiStruct__a_mut` error[E0425]: cannot find function, tuple struct or tuple variant `FfiStruct__set_b` in this scope - --> tests/ui_fail/derive_skip_struct.rs:33:9 + --> tests/ui_fail/derive_skip_struct.rs:37:9 | 7 | #[ffi_export] | ------------- similarly named function `FfiStruct__set_a` defined here ... -33 | FfiStruct__set_b( +37 | FfiStruct__set_b( | ^^^^^^^^^^^^^^^^ help: a function with a similar name exists: `FfiStruct__set_a`