mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-09 14:47:56 -05:00
feat(versionable): add transparent mode for newtype structs
This commit is contained in:
committed by
Nicolas Sarlin
parent
543b39951b
commit
51da8fe735
@@ -7,8 +7,10 @@
|
||||
|
||||
mod associated;
|
||||
mod dispatch_type;
|
||||
mod transparent;
|
||||
mod version_type;
|
||||
mod versionize_attribute;
|
||||
mod versionize_impl;
|
||||
|
||||
use dispatch_type::DispatchType;
|
||||
use proc_macro::TokenStream;
|
||||
@@ -51,6 +53,7 @@ pub(crate) const SEND_TRAIT_NAME: &str = "::core::marker::Send";
|
||||
pub(crate) const STATIC_LIFETIME_NAME: &str = "'static";
|
||||
|
||||
use associated::AssociatingTrait;
|
||||
use versionize_impl::VersionizeImplementor;
|
||||
|
||||
use crate::version_type::VersionType;
|
||||
use crate::versionize_attribute::VersionizeAttribute;
|
||||
@@ -131,21 +134,24 @@ pub fn derive_versions_dispatch(input: TokenStream) -> TokenStream {
|
||||
pub fn derive_versionize(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let attributes = syn_unwrap!(
|
||||
VersionizeAttribute::parse_from_attributes_list(&input.attrs).and_then(|attr_opt| attr_opt
|
||||
.ok_or_else(|| syn::Error::new(
|
||||
Span::call_site(),
|
||||
"Missing `versionize` attribute for `Versionize`",
|
||||
)))
|
||||
);
|
||||
let attributes = syn_unwrap!(VersionizeAttribute::parse_from_attributes_list(
|
||||
&input.attrs
|
||||
));
|
||||
|
||||
let implementor = syn_unwrap!(VersionizeImplementor::new(
|
||||
attributes,
|
||||
&input.data,
|
||||
Span::call_site()
|
||||
));
|
||||
|
||||
// If we apply a type conversion before the call to versionize, the type that implements
|
||||
// the `Version` trait is the target type and not Self
|
||||
let version_trait_impl: Option<proc_macro2::TokenStream> = if attributes.needs_conversion() {
|
||||
None
|
||||
} else {
|
||||
Some(impl_version_trait(&input))
|
||||
};
|
||||
let version_trait_impl: Option<proc_macro2::TokenStream> =
|
||||
if implementor.is_directly_versioned() {
|
||||
Some(impl_version_trait(&input))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Parse the name of the traits that we will implement
|
||||
let versionize_trait: Path = parse_const_str(VERSIONIZE_TRAIT_NAME);
|
||||
@@ -162,28 +168,28 @@ pub fn derive_versionize(input: TokenStream) -> TokenStream {
|
||||
let (_, ty_generics, _) = input.generics.split_for_impl();
|
||||
|
||||
// Generates the associated types required by the traits
|
||||
let versioned_type = attributes.versioned_type(&lifetime, &input.generics);
|
||||
let versioned_owned_type = attributes.versioned_owned_type(&input.generics);
|
||||
let versioned_type = implementor.versioned_type(&lifetime, &input.generics);
|
||||
let versioned_owned_type = implementor.versioned_owned_type(&input.generics);
|
||||
let versioned_type_where_clause =
|
||||
attributes.versioned_type_where_clause(&lifetime, &input.generics);
|
||||
implementor.versioned_type_where_clause(&lifetime, &input.generics);
|
||||
let versioned_owned_type_where_clause =
|
||||
attributes.versioned_owned_type_where_clause(&input.generics);
|
||||
implementor.versioned_owned_type_where_clause(&input.generics);
|
||||
|
||||
// If the original type has some generics, we need to add bounds on them for
|
||||
// the traits impl
|
||||
let versionize_trait_where_clause =
|
||||
syn_unwrap!(attributes.versionize_trait_where_clause(&input.generics));
|
||||
syn_unwrap!(implementor.versionize_trait_where_clause(&input.generics));
|
||||
let versionize_owned_trait_where_clause =
|
||||
syn_unwrap!(attributes.versionize_owned_trait_where_clause(&input.generics));
|
||||
syn_unwrap!(implementor.versionize_owned_trait_where_clause(&input.generics));
|
||||
let unversionize_trait_where_clause =
|
||||
syn_unwrap!(attributes.unversionize_trait_where_clause(&input.generics));
|
||||
syn_unwrap!(implementor.unversionize_trait_where_clause(&input.generics));
|
||||
|
||||
let trait_impl_generics = input.generics.split_for_impl().0;
|
||||
|
||||
let versionize_body = attributes.versionize_method_body();
|
||||
let versionize_owned_body = attributes.versionize_owned_method_body();
|
||||
let versionize_body = implementor.versionize_method_body();
|
||||
let versionize_owned_body = implementor.versionize_owned_method_body();
|
||||
let unversionize_arg_name = Ident::new("versioned", Span::call_site());
|
||||
let unversionize_body = attributes.unversionize_method_body(&unversionize_arg_name);
|
||||
let unversionize_body = implementor.unversionize_method_body(&unversionize_arg_name);
|
||||
let unversionize_error: Path = parse_const_str(UNVERSIONIZE_ERROR_NAME);
|
||||
|
||||
quote! {
|
||||
|
||||
58
utils/tfhe-versionable-derive/src/transparent.rs
Normal file
58
utils/tfhe-versionable-derive/src/transparent.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use proc_macro2::Span;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Data, Field, Ident, Token, Type};
|
||||
|
||||
/// A transparent struct that is versioned using its inner field
|
||||
pub(crate) struct TransparentStruct {
|
||||
pub(crate) inner_type: Type,
|
||||
pub(crate) kind: TransparentStructKind,
|
||||
}
|
||||
|
||||
/// Transparent struct can be either newtypes or regular structs with a single field.
|
||||
pub(crate) enum TransparentStructKind {
|
||||
NewType,
|
||||
SingleField(Ident),
|
||||
}
|
||||
|
||||
impl TransparentStruct {
|
||||
/// Parse the type declaration to find the target versioned type when the `transparent`
|
||||
/// attribute is used.
|
||||
pub(crate) fn new(decla: &Data, base_span: Span) -> syn::Result<Self> {
|
||||
let error = || {
|
||||
syn::Error::new(
|
||||
base_span,
|
||||
"'transparent' attribute is only supported for single field structs",
|
||||
)
|
||||
};
|
||||
|
||||
match decla {
|
||||
Data::Struct(stru) => match &stru.fields {
|
||||
syn::Fields::Named(named_fields) => {
|
||||
Self::from_fields(&named_fields.named).map_err(|_| error())
|
||||
}
|
||||
syn::Fields::Unnamed(unnamed_fields) => {
|
||||
Self::from_fields(&unnamed_fields.unnamed).map_err(|_| error())
|
||||
}
|
||||
syn::Fields::Unit => Err(error()),
|
||||
},
|
||||
Data::Enum(_) => Err(error()),
|
||||
Data::Union(_) => Err(error()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the single element inside a transparent struct
|
||||
fn from_fields(fields: &Punctuated<Field, Token![,]>) -> Result<Self, ()> {
|
||||
if fields.len() != 1 {
|
||||
Err(())
|
||||
} else {
|
||||
let field = fields.first().unwrap();
|
||||
let inner_type = field.ty.clone();
|
||||
let kind = match &field.ident {
|
||||
Some(ident) => TransparentStructKind::SingleField(ident.clone()),
|
||||
None => TransparentStructKind::NewType,
|
||||
};
|
||||
|
||||
Ok(Self { inner_type, kind })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,41 @@
|
||||
//! This module handles the parsing of the parameters of the proc macro, found in the
|
||||
//! `#[versionize(...)]` attribute
|
||||
|
||||
use proc_macro2::Span;
|
||||
use quote::{quote, ToTokens};
|
||||
use quote::ToTokens;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
parse_quote, Attribute, Expr, GenericArgument, GenericParam, Generics, Ident, Lifetime, Lit,
|
||||
Meta, Path, PathArguments, Token, TraitBound, Type, TypeParam, WhereClause,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
add_lifetime_where_clause, add_trait_where_clause, add_where_lifetime_bound_to_generics,
|
||||
parse_const_str, DISPATCH_TRAIT_NAME, ERROR_TRAIT_NAME, FROM_TRAIT_NAME, INTO_TRAIT_NAME,
|
||||
SEND_TRAIT_NAME, STATIC_LIFETIME_NAME, SYNC_TRAIT_NAME, TRY_INTO_TRAIT_NAME,
|
||||
UNVERSIONIZE_ERROR_NAME, UNVERSIONIZE_TRAIT_NAME, VERSIONIZE_OWNED_TRAIT_NAME,
|
||||
};
|
||||
use syn::{Attribute, Expr, Lit, Meta, Path, Token};
|
||||
|
||||
/// Name of the attribute used to give arguments to the `Versionize` macro
|
||||
const VERSIONIZE_ATTR_NAME: &str = "versionize";
|
||||
|
||||
pub(crate) struct ClassicVersionizeAttribute {
|
||||
dispatch_enum: Path,
|
||||
}
|
||||
|
||||
pub(crate) enum ConversionType {
|
||||
Direct,
|
||||
Try,
|
||||
}
|
||||
|
||||
pub(crate) struct ConvertVersionizeAttribute {
|
||||
conversion_target: Path,
|
||||
conversion_type: ConversionType,
|
||||
}
|
||||
/// Transparent mode can also be activated using `#[repr(transparent)]`
|
||||
const REPR_ATTR_NAME: &str = "repr";
|
||||
|
||||
/// Represent the parsed `#[versionize(...)]` attribute
|
||||
pub(crate) enum VersionizeAttribute {
|
||||
Classic(ClassicVersionizeAttribute),
|
||||
Convert(ConvertVersionizeAttribute),
|
||||
Transparent,
|
||||
}
|
||||
|
||||
/// The "classic" variant of the versionize attribute: `#[versionize(MyTypeVersions)]`
|
||||
pub(crate) struct ClassicVersionizeAttribute {
|
||||
pub(crate) dispatch_enum: Path,
|
||||
}
|
||||
|
||||
/// A versionize attribute with a type conversion: `#[versionize(convert = "SerializableMyType")]`
|
||||
/// or `#[versionize(from = "SerializableMyType", into = "SerializableMyType")]`
|
||||
pub(crate) struct ConvertVersionizeAttribute {
|
||||
pub(crate) conversion_target: Path,
|
||||
pub(crate) conversion_type: ConversionType,
|
||||
}
|
||||
|
||||
/// Tell if the conversion can fail or not
|
||||
pub(crate) enum ConversionType {
|
||||
Direct,
|
||||
Try,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -44,10 +46,27 @@ struct VersionizeAttributeBuilder {
|
||||
from: Option<Path>,
|
||||
try_from: Option<Path>,
|
||||
into: Option<Path>,
|
||||
transparent: bool,
|
||||
}
|
||||
|
||||
impl VersionizeAttributeBuilder {
|
||||
fn build(self, base_span: &Span) -> syn::Result<VersionizeAttribute> {
|
||||
fn build(self, base_span: Span) -> syn::Result<VersionizeAttribute> {
|
||||
if self.transparent {
|
||||
if self.dispatch_enum.is_some()
|
||||
|| self.convert.is_some()
|
||||
|| self.try_convert.is_some()
|
||||
|| self.from.is_some()
|
||||
|| self.into.is_some()
|
||||
{
|
||||
return Err(syn::Error::new(
|
||||
base_span,
|
||||
"'transparent' does not accept any other parameters",
|
||||
));
|
||||
} else {
|
||||
return Ok(VersionizeAttribute::Transparent);
|
||||
}
|
||||
}
|
||||
|
||||
let convert_is_try = self.try_convert.is_some() || self.try_from.is_some();
|
||||
// User should not use `from` and `try_from` at the same time
|
||||
let from_target = match (self.from, self.try_from) {
|
||||
@@ -129,10 +148,9 @@ should have the same value",
|
||||
}))
|
||||
} else {
|
||||
Ok(VersionizeAttribute::Classic(ClassicVersionizeAttribute {
|
||||
dispatch_enum: self.dispatch_enum.ok_or(syn::Error::new(
|
||||
*base_span,
|
||||
"Missing dispatch enum argument",
|
||||
))?,
|
||||
dispatch_enum: self
|
||||
.dispatch_enum
|
||||
.ok_or(syn::Error::new(base_span, "Missing dispatch enum argument"))?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -143,17 +161,30 @@ impl VersionizeAttribute {
|
||||
/// `DispatchType` is the name of the type holding the dispatch enum.
|
||||
/// Return an error if no `versionize` attribute has been found, if multiple attributes are
|
||||
/// present on the same struct or if the attribute is malformed.
|
||||
pub(crate) fn parse_from_attributes_list(
|
||||
attributes: &[Attribute],
|
||||
) -> syn::Result<Option<Self>> {
|
||||
pub(crate) fn parse_from_attributes_list(attributes: &[Attribute]) -> syn::Result<Self> {
|
||||
let version_attributes: Vec<&Attribute> = attributes
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident(VERSIONIZE_ATTR_NAME))
|
||||
.collect();
|
||||
|
||||
let repr_attributes: Vec<&Attribute> = attributes
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident(REPR_ATTR_NAME))
|
||||
.collect();
|
||||
|
||||
match version_attributes.as_slice() {
|
||||
[] => Ok(None),
|
||||
[attr] => Self::parse_from_attribute(attr).map(Some),
|
||||
[] => {
|
||||
// transparent mode can also be enabled via `#[repr(transparent)]`
|
||||
if let Some(attr) = repr_attributes.first() {
|
||||
Self::parse_from_attribute(attr)
|
||||
} else {
|
||||
Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
"Missing `versionize` attribute for `Versionize`",
|
||||
))
|
||||
}
|
||||
}
|
||||
[attr] => Self::parse_from_attribute(attr),
|
||||
[_, attr2, ..] => Err(syn::Error::new(
|
||||
attr2.span(),
|
||||
"Multiple `versionize` attributes found",
|
||||
@@ -173,11 +204,19 @@ impl VersionizeAttribute {
|
||||
let mut attribute_builder = VersionizeAttributeBuilder::default();
|
||||
for meta in nested.iter() {
|
||||
match meta {
|
||||
Meta::Path(dispatch_enum) => {
|
||||
if attribute_builder.dispatch_enum.is_some() {
|
||||
Meta::Path(path) => {
|
||||
// parse versionize(transparent)
|
||||
if path.is_ident("transparent") {
|
||||
if attribute_builder.transparent {
|
||||
return Err(Self::default_error(meta.span()));
|
||||
} else {
|
||||
attribute_builder.transparent = true;
|
||||
}
|
||||
// parse versionize(MyTypeVersions)
|
||||
} else if attribute_builder.dispatch_enum.is_some() {
|
||||
return Err(Self::default_error(meta.span()));
|
||||
} else {
|
||||
attribute_builder.dispatch_enum = Some(dispatch_enum.clone());
|
||||
attribute_builder.dispatch_enum = Some(path.clone());
|
||||
}
|
||||
}
|
||||
Meta::NameValue(name_value) => {
|
||||
@@ -221,7 +260,7 @@ impl VersionizeAttribute {
|
||||
attribute_builder.into =
|
||||
Some(parse_path_ignore_quotes(&name_value.value)?);
|
||||
}
|
||||
// parse versionize(dispatch = "Type")
|
||||
// parse versionize(dispatch = "MyTypeVersions")
|
||||
} else if name_value.path.is_ident("dispatch") {
|
||||
if attribute_builder.dispatch_enum.is_some() {
|
||||
return Err(Self::default_error(meta.span()));
|
||||
@@ -237,284 +276,7 @@ impl VersionizeAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
attribute_builder.build(&attribute.span())
|
||||
}
|
||||
|
||||
pub(crate) fn needs_conversion(&self) -> bool {
|
||||
match self {
|
||||
VersionizeAttribute::Classic(_) => false,
|
||||
VersionizeAttribute::Convert(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the associated type used in the `Versionize` trait: `MyType::Versioned<'vers>`
|
||||
///
|
||||
/// If the type is directly versioned, this will be a type generated by the `VersionDispatch`.
|
||||
///
|
||||
/// If we have a conversion before the versioning, we re-use the versioned_owned type of the
|
||||
/// conversion target. The versioned_owned is needed because the conversion will create a new
|
||||
/// value, so we can't just use a reference.
|
||||
pub(crate) fn versioned_type(
|
||||
&self,
|
||||
lifetime: &Lifetime,
|
||||
input_generics: &Generics,
|
||||
) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
VersionizeAttribute::Classic(attr) => {
|
||||
let (_, ty_generics, _) = input_generics.split_for_impl();
|
||||
|
||||
let dispatch_trait: Path = parse_const_str(DISPATCH_TRAIT_NAME);
|
||||
let dispatch_enum_path = &attr.dispatch_enum;
|
||||
quote! {
|
||||
<#dispatch_enum_path #ty_generics as
|
||||
#dispatch_trait<Self>>::Ref<#lifetime>
|
||||
}
|
||||
}
|
||||
VersionizeAttribute::Convert(_) => {
|
||||
// If we want to apply a conversion before the call to versionize we need to use the
|
||||
// "owned" alternative of the dispatch enum to be able to store the
|
||||
// conversion result.
|
||||
self.versioned_owned_type(input_generics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the where clause for `MyType::Versioned<'vers>`. if `MyType` has generics, this means
|
||||
/// adding a 'vers lifetime bound on them.
|
||||
pub(crate) fn versioned_type_where_clause(
|
||||
&self,
|
||||
lifetime: &Lifetime,
|
||||
input_generics: &Generics,
|
||||
) -> Option<WhereClause> {
|
||||
let mut generics = input_generics.clone();
|
||||
|
||||
add_where_lifetime_bound_to_generics(&mut generics, lifetime);
|
||||
let (_, _, where_clause) = generics.split_for_impl();
|
||||
where_clause.cloned()
|
||||
}
|
||||
|
||||
/// Return the associated type used in the `VersionizeOwned` trait: `MyType::VersionedOwned`
|
||||
///
|
||||
/// If the type is directly versioned, this will be a type generated by the `VersionDispatch`.
|
||||
///
|
||||
/// If we have a conversion before the versioning, we re-use the versioned_owned type of the
|
||||
/// conversion target.
|
||||
pub(crate) fn versioned_owned_type(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let (_, ty_generics, _) = input_generics.split_for_impl();
|
||||
match self {
|
||||
VersionizeAttribute::Classic(attr) => {
|
||||
let dispatch_trait: Path = parse_const_str(DISPATCH_TRAIT_NAME);
|
||||
let dispatch_enum_path = &attr.dispatch_enum;
|
||||
quote! {
|
||||
<#dispatch_enum_path #ty_generics as
|
||||
#dispatch_trait<Self>>::Owned
|
||||
}
|
||||
}
|
||||
VersionizeAttribute::Convert(convert_attr) => {
|
||||
let convert_type_path = &convert_attr.conversion_target;
|
||||
let versionize_owned_trait: Path = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
|
||||
|
||||
quote! {
|
||||
<#convert_type_path as #versionize_owned_trait>::VersionedOwned
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the where clause for `MyType::VersionedOwned`.
|
||||
///
|
||||
/// This is simply the where clause of the input type.
|
||||
pub(crate) fn versioned_owned_type_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> Option<WhereClause> {
|
||||
match self {
|
||||
VersionizeAttribute::Classic(_) => input_generics.split_for_impl().2.cloned(),
|
||||
VersionizeAttribute::Convert(convert_attr) => {
|
||||
extract_generics(&convert_attr.conversion_target)
|
||||
.split_for_impl()
|
||||
.2
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the where clause needed to implement the Versionize trait.
|
||||
///
|
||||
/// This is the same as the one for the VersionizeOwned, with an additional "Clone" bound in the
|
||||
/// case where we need to perform a conversion before the versioning.
|
||||
pub(crate) fn versionize_trait_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> syn::Result<Option<WhereClause>> {
|
||||
// The base bounds for the owned traits are also used for the ref traits
|
||||
let mut generics = input_generics.clone();
|
||||
if self.needs_conversion() {
|
||||
// The versionize method takes a ref. We need to own the input type in the conversion
|
||||
// case to apply `From<Input> for Target`. This adds a `Clone` bound to have
|
||||
// a better error message if the input type is not Clone.
|
||||
add_trait_where_clause(&mut generics, [&parse_quote! { Self }], &["Clone"])?;
|
||||
}
|
||||
|
||||
self.versionize_owned_trait_where_clause(&generics)
|
||||
}
|
||||
|
||||
/// Return the where clause needed to implement the VersionizeOwned trait.
|
||||
///
|
||||
/// If the type is directly versioned, the bound states that the argument points to a valid
|
||||
/// DispatchEnum for this type. This is done by adding a bound on this argument to
|
||||
/// `VersionsDisaptch<Self>`.
|
||||
///
|
||||
/// If there is a conversion, the target of the conversion should implement `VersionizeOwned`
|
||||
/// and `From<Self>`.
|
||||
pub(crate) fn versionize_owned_trait_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> syn::Result<Option<WhereClause>> {
|
||||
let mut generics = input_generics.clone();
|
||||
match self {
|
||||
VersionizeAttribute::Classic(attr) => {
|
||||
let dispatch_generics = generics.clone();
|
||||
let dispatch_ty_generics = dispatch_generics.split_for_impl().1;
|
||||
let dispatch_enum_path = &attr.dispatch_enum;
|
||||
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(#dispatch_enum_path #dispatch_ty_generics)],
|
||||
&[format!("{}<Self>", DISPATCH_TRAIT_NAME,)],
|
||||
)?;
|
||||
}
|
||||
VersionizeAttribute::Convert(convert_attr) => {
|
||||
let convert_type_path = &convert_attr.conversion_target;
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(#convert_type_path)],
|
||||
&[
|
||||
VERSIONIZE_OWNED_TRAIT_NAME,
|
||||
&format!("{}<Self>", FROM_TRAIT_NAME),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(generics.split_for_impl().2.cloned())
|
||||
}
|
||||
|
||||
/// Return the where clause for the `Unversionize` trait.
|
||||
///
|
||||
/// If the versioning is direct, this is the same bound as the one used for `VersionizeOwned`.
|
||||
///
|
||||
/// If there is a conversion, the target of the conversion need to implement `Unversionize` and
|
||||
/// `Into` or `TryInto<T, E>`, with `E: Error + Send + Sync + 'static`
|
||||
pub(crate) fn unversionize_trait_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> syn::Result<Option<WhereClause>> {
|
||||
match self {
|
||||
VersionizeAttribute::Classic(_) => {
|
||||
self.versionize_owned_trait_where_clause(input_generics)
|
||||
}
|
||||
VersionizeAttribute::Convert(convert_attr) => {
|
||||
let mut generics = input_generics.clone();
|
||||
let convert_type_path = &convert_attr.conversion_target;
|
||||
let into_trait = match convert_attr.conversion_type {
|
||||
ConversionType::Direct => format!("{}<Self>", INTO_TRAIT_NAME),
|
||||
ConversionType::Try => {
|
||||
// Doing a TryFrom requires that the error
|
||||
// impl Error + Send + Sync + 'static
|
||||
let try_into_trait: Path = parse_const_str(TRY_INTO_TRAIT_NAME);
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(<#convert_type_path as #try_into_trait<Self>>::Error)],
|
||||
&[ERROR_TRAIT_NAME, SYNC_TRAIT_NAME, SEND_TRAIT_NAME],
|
||||
)?;
|
||||
add_lifetime_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(<#convert_type_path as #try_into_trait<Self>>::Error)],
|
||||
&[STATIC_LIFETIME_NAME],
|
||||
)?;
|
||||
|
||||
format!("{}<Self>", TRY_INTO_TRAIT_NAME)
|
||||
}
|
||||
};
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(#convert_type_path)],
|
||||
&[
|
||||
UNVERSIONIZE_TRAIT_NAME,
|
||||
&format!("{}<Self>", FROM_TRAIT_NAME),
|
||||
&into_trait,
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(generics.split_for_impl().2.cloned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the body of the versionize method.
|
||||
pub(crate) fn versionize_method_body(&self) -> proc_macro2::TokenStream {
|
||||
let versionize_owned_trait: TraitBound = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
|
||||
|
||||
match self {
|
||||
VersionizeAttribute::Classic(_) => {
|
||||
quote! {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
VersionizeAttribute::Convert(convert_attr) => {
|
||||
let convert_type_path = with_turbofish(&convert_attr.conversion_target);
|
||||
quote! {
|
||||
#versionize_owned_trait::versionize_owned(#convert_type_path::from(self.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the body of the versionize_owned method.
|
||||
pub(crate) fn versionize_owned_method_body(&self) -> proc_macro2::TokenStream {
|
||||
let versionize_owned_trait: TraitBound = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
|
||||
|
||||
match self {
|
||||
VersionizeAttribute::Classic(_) => {
|
||||
quote! {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
VersionizeAttribute::Convert(convert_attr) => {
|
||||
let convert_type_path = with_turbofish(&convert_attr.conversion_target);
|
||||
quote! {
|
||||
#versionize_owned_trait::versionize_owned(#convert_type_path::from(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the body of the unversionize method.
|
||||
pub(crate) fn unversionize_method_body(&self, arg_name: &Ident) -> proc_macro2::TokenStream {
|
||||
let error: Type = parse_const_str(UNVERSIONIZE_ERROR_NAME);
|
||||
match self {
|
||||
VersionizeAttribute::Classic(_) => {
|
||||
quote! { #arg_name.try_into() }
|
||||
}
|
||||
VersionizeAttribute::Convert(convert_attr) => {
|
||||
let target = with_turbofish(&convert_attr.conversion_target);
|
||||
match convert_attr.conversion_type {
|
||||
ConversionType::Direct => {
|
||||
quote! { #target::unversionize(#arg_name).map(|value| value.into()) }
|
||||
}
|
||||
ConversionType::Try => {
|
||||
let target_name = format!("{}", target.to_token_stream());
|
||||
quote! { #target::unversionize(#arg_name).and_then(|value| TryInto::<Self>::try_into(value)
|
||||
.map_err(|e| #error::conversion(#target_name, e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
attribute_builder.build(attribute.span())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,37 +298,3 @@ fn parse_path_ignore_quotes(value: &Expr) -> syn::Result<Path> {
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the same type but with generics that use the turbofish syntax. Converts
|
||||
/// `MyStruct<T>` into `MyStruct::<T>`
|
||||
fn with_turbofish(path: &Path) -> Path {
|
||||
let mut with_turbo = path.clone();
|
||||
|
||||
for segment in with_turbo.segments.iter_mut() {
|
||||
if let PathArguments::AngleBracketed(generics) = &mut segment.arguments {
|
||||
generics.colon2_token = Some(Token));
|
||||
}
|
||||
}
|
||||
|
||||
with_turbo
|
||||
}
|
||||
|
||||
/// Extract the generics inside a type
|
||||
fn extract_generics(path: &Path) -> Generics {
|
||||
let mut generics = Generics::default();
|
||||
|
||||
if let Some(last_segment) = path.segments.last() {
|
||||
if let PathArguments::AngleBracketed(args) = &last_segment.arguments {
|
||||
for arg in &args.args {
|
||||
if let GenericArgument::Type(Type::Path(type_path)) = arg {
|
||||
if let Some(ident) = type_path.path.get_ident() {
|
||||
let param = TypeParam::from(ident.clone());
|
||||
generics.params.push(GenericParam::Type(param));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generics
|
||||
}
|
||||
|
||||
440
utils/tfhe-versionable-derive/src/versionize_impl.rs
Normal file
440
utils/tfhe-versionable-derive/src/versionize_impl.rs
Normal file
@@ -0,0 +1,440 @@
|
||||
use proc_macro2::Span;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
parse_quote, Data, GenericArgument, GenericParam, Generics, Ident, Lifetime, Path,
|
||||
PathArguments, Token, TraitBound, Type, TypeParam, TypePath, WhereClause,
|
||||
};
|
||||
|
||||
use crate::transparent::{TransparentStruct, TransparentStructKind};
|
||||
use crate::versionize_attribute::{
|
||||
ClassicVersionizeAttribute, ConversionType, ConvertVersionizeAttribute, VersionizeAttribute,
|
||||
};
|
||||
use crate::{
|
||||
add_lifetime_where_clause, add_trait_where_clause, add_where_lifetime_bound_to_generics,
|
||||
parse_const_str, DISPATCH_TRAIT_NAME, ERROR_TRAIT_NAME, FROM_TRAIT_NAME, INTO_TRAIT_NAME,
|
||||
SEND_TRAIT_NAME, STATIC_LIFETIME_NAME, SYNC_TRAIT_NAME, TRY_INTO_TRAIT_NAME,
|
||||
UNVERSIONIZE_ERROR_NAME, UNVERSIONIZE_TRAIT_NAME, VERSIONIZE_OWNED_TRAIT_NAME,
|
||||
VERSIONIZE_TRAIT_NAME,
|
||||
};
|
||||
|
||||
pub(crate) enum VersionizeImplementor {
|
||||
Classic(ClassicVersionizeAttribute),
|
||||
Convert(ConvertVersionizeAttribute),
|
||||
Transparent(TransparentStruct),
|
||||
}
|
||||
|
||||
impl VersionizeImplementor {
|
||||
pub(crate) fn new(
|
||||
attributes: VersionizeAttribute,
|
||||
decla: &Data,
|
||||
base_span: Span,
|
||||
) -> syn::Result<Self> {
|
||||
match attributes {
|
||||
VersionizeAttribute::Classic(classic) => Ok(Self::Classic(classic)),
|
||||
VersionizeAttribute::Convert(convert) => Ok(Self::Convert(convert)),
|
||||
VersionizeAttribute::Transparent => {
|
||||
Ok(Self::Transparent(TransparentStruct::new(decla, base_span)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the type should have a "true" Versionize implementation or if the implementation
|
||||
/// is delegated to another type
|
||||
pub(crate) fn is_directly_versioned(&self) -> bool {
|
||||
match self {
|
||||
Self::Classic(_) => true,
|
||||
Self::Convert(_) => false,
|
||||
Self::Transparent(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the associated type used in the `Versionize` trait: `MyType::Versioned<'vers>`
|
||||
///
|
||||
/// If the type is directly versioned, this will be a type generated by the `VersionDispatch`.
|
||||
///
|
||||
/// If we have a conversion before the versioning, we re-use the versioned_owned type of the
|
||||
/// conversion target. The versioned_owned is needed because the conversion will create a new
|
||||
/// value, so we can't just use a reference.
|
||||
pub(crate) fn versioned_type(
|
||||
&self,
|
||||
lifetime: &Lifetime,
|
||||
input_generics: &Generics,
|
||||
) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
Self::Classic(attr) => {
|
||||
let (_, ty_generics, _) = input_generics.split_for_impl();
|
||||
|
||||
let dispatch_trait: Path = parse_const_str(DISPATCH_TRAIT_NAME);
|
||||
let dispatch_enum_path = &attr.dispatch_enum;
|
||||
quote! {
|
||||
<#dispatch_enum_path #ty_generics as
|
||||
#dispatch_trait<Self>>::Ref<#lifetime>
|
||||
}
|
||||
}
|
||||
Self::Convert(_) => {
|
||||
// If we want to apply a conversion before the call to versionize we need to use the
|
||||
// "owned" alternative of the dispatch enum to be able to store the
|
||||
// conversion result.
|
||||
self.versioned_owned_type(input_generics)
|
||||
}
|
||||
Self::Transparent(transparent) => {
|
||||
let versionize_trait: Path = parse_const_str(VERSIONIZE_TRAIT_NAME);
|
||||
let inner_type = &transparent.inner_type;
|
||||
quote! { <#inner_type as #versionize_trait>::Versioned<#lifetime>}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the where clause for `MyType::Versioned<'vers>`. if `MyType` has generics, this means
|
||||
/// adding a 'vers lifetime bound on them.
|
||||
pub(crate) fn versioned_type_where_clause(
|
||||
&self,
|
||||
lifetime: &Lifetime,
|
||||
input_generics: &Generics,
|
||||
) -> Option<WhereClause> {
|
||||
let mut generics = input_generics.clone();
|
||||
|
||||
add_where_lifetime_bound_to_generics(&mut generics, lifetime);
|
||||
let (_, _, where_clause) = generics.split_for_impl();
|
||||
where_clause.cloned()
|
||||
}
|
||||
|
||||
/// Return the associated type used in the `VersionizeOwned` trait: `MyType::VersionedOwned`
|
||||
///
|
||||
/// If the type is directly versioned, this will be a type generated by the `VersionDispatch`.
|
||||
///
|
||||
/// If we have a conversion before the versioning, we re-use the versioned_owned type of the
|
||||
/// conversion target.
|
||||
pub(crate) fn versioned_owned_type(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let (_, ty_generics, _) = input_generics.split_for_impl();
|
||||
match self {
|
||||
Self::Classic(attr) => {
|
||||
let dispatch_trait: Path = parse_const_str(DISPATCH_TRAIT_NAME);
|
||||
let dispatch_enum_path = &attr.dispatch_enum;
|
||||
quote! {
|
||||
<#dispatch_enum_path #ty_generics as
|
||||
#dispatch_trait<Self>>::Owned
|
||||
}
|
||||
}
|
||||
Self::Convert(convert_attr) => {
|
||||
let convert_type_path = &convert_attr.conversion_target;
|
||||
let versionize_owned_trait: Path = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
|
||||
|
||||
quote! {
|
||||
<#convert_type_path as #versionize_owned_trait>::VersionedOwned
|
||||
}
|
||||
}
|
||||
Self::Transparent(transparent) => {
|
||||
let versionize_owned_trait: Path = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
|
||||
let inner_type = &transparent.inner_type;
|
||||
|
||||
quote! { <#inner_type as #versionize_owned_trait>::VersionedOwned }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the where clause for `MyType::VersionedOwned`.
|
||||
///
|
||||
/// This is simply the where clause of the input type.
|
||||
pub(crate) fn versioned_owned_type_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> Option<WhereClause> {
|
||||
match self {
|
||||
Self::Classic(_) => input_generics.split_for_impl().2.cloned(),
|
||||
Self::Convert(convert_attr) => extract_generics(&convert_attr.conversion_target)
|
||||
.split_for_impl()
|
||||
.2
|
||||
.cloned(),
|
||||
Self::Transparent(_) => input_generics.split_for_impl().2.cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the where clause needed to implement the Versionize trait.
|
||||
///
|
||||
/// This is the same as the one for the VersionizeOwned, with an additional "Clone" bound in the
|
||||
/// case where we need to perform a conversion before the versioning.
|
||||
pub(crate) fn versionize_trait_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> syn::Result<Option<WhereClause>> {
|
||||
// The base bounds for the owned traits are also used for the ref traits
|
||||
let mut generics = input_generics.clone();
|
||||
|
||||
match self {
|
||||
VersionizeImplementor::Classic(_) => {
|
||||
self.versionize_owned_trait_where_clause(&generics)
|
||||
}
|
||||
VersionizeImplementor::Convert(_) => {
|
||||
// The versionize method takes a ref. We need to own the input type in the
|
||||
// conversion case to apply `From<Input> for Target`. This adds a
|
||||
// `Clone` bound to have a better error message if the input type is
|
||||
// not Clone.
|
||||
add_trait_where_clause(&mut generics, [&parse_quote! { Self }], &["Clone"])?;
|
||||
self.versionize_owned_trait_where_clause(&generics)
|
||||
}
|
||||
VersionizeImplementor::Transparent(transparent) => {
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&transparent.inner_type],
|
||||
&[VERSIONIZE_TRAIT_NAME],
|
||||
)?;
|
||||
Ok(generics.split_for_impl().2.cloned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the where clause needed to implement the VersionizeOwned trait.
|
||||
///
|
||||
/// If the type is directly versioned, the bound states that the argument points to a valid
|
||||
/// DispatchEnum for this type. This is done by adding a bound on this argument to
|
||||
/// `VersionsDisaptch<Self>`.
|
||||
///
|
||||
/// If there is a conversion, the target of the conversion should implement `VersionizeOwned`
|
||||
/// and `From<Self>`.
|
||||
pub(crate) fn versionize_owned_trait_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> syn::Result<Option<WhereClause>> {
|
||||
let mut generics = input_generics.clone();
|
||||
match self {
|
||||
Self::Classic(attr) => {
|
||||
let dispatch_generics = generics.clone();
|
||||
let dispatch_ty_generics = dispatch_generics.split_for_impl().1;
|
||||
let dispatch_enum_path = &attr.dispatch_enum;
|
||||
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(#dispatch_enum_path #dispatch_ty_generics)],
|
||||
&[format!("{}<Self>", DISPATCH_TRAIT_NAME,)],
|
||||
)?;
|
||||
}
|
||||
Self::Convert(convert_attr) => {
|
||||
let convert_type_path = &convert_attr.conversion_target;
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(#convert_type_path)],
|
||||
&[
|
||||
VERSIONIZE_OWNED_TRAIT_NAME,
|
||||
&format!("{}<Self>", FROM_TRAIT_NAME),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
Self::Transparent(transparent) => {
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&transparent.inner_type],
|
||||
&[VERSIONIZE_OWNED_TRAIT_NAME],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(generics.split_for_impl().2.cloned())
|
||||
}
|
||||
|
||||
/// Return the where clause for the `Unversionize` trait.
|
||||
///
|
||||
/// If the versioning is direct, this is the same bound as the one used for `VersionizeOwned`.
|
||||
///
|
||||
/// If there is a conversion, the target of the conversion need to implement `Unversionize` and
|
||||
/// `Into` or `TryInto<T, E>`, with `E: Error + Send + Sync + 'static`
|
||||
pub(crate) fn unversionize_trait_where_clause(
|
||||
&self,
|
||||
input_generics: &Generics,
|
||||
) -> syn::Result<Option<WhereClause>> {
|
||||
match self {
|
||||
Self::Classic(_) => self.versionize_owned_trait_where_clause(input_generics),
|
||||
Self::Convert(convert_attr) => {
|
||||
let mut generics = input_generics.clone();
|
||||
let convert_type_path = &convert_attr.conversion_target;
|
||||
let into_trait = match convert_attr.conversion_type {
|
||||
ConversionType::Direct => format!("{}<Self>", INTO_TRAIT_NAME),
|
||||
ConversionType::Try => {
|
||||
// Doing a TryFrom requires that the error
|
||||
// impl Error + Send + Sync + 'static
|
||||
let try_into_trait: Path = parse_const_str(TRY_INTO_TRAIT_NAME);
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(<#convert_type_path as #try_into_trait<Self>>::Error)],
|
||||
&[ERROR_TRAIT_NAME, SYNC_TRAIT_NAME, SEND_TRAIT_NAME],
|
||||
)?;
|
||||
add_lifetime_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(<#convert_type_path as #try_into_trait<Self>>::Error)],
|
||||
&[STATIC_LIFETIME_NAME],
|
||||
)?;
|
||||
|
||||
format!("{}<Self>", TRY_INTO_TRAIT_NAME)
|
||||
}
|
||||
};
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&parse_quote!(#convert_type_path)],
|
||||
&[
|
||||
UNVERSIONIZE_TRAIT_NAME,
|
||||
&format!("{}<Self>", FROM_TRAIT_NAME),
|
||||
&into_trait,
|
||||
],
|
||||
)?;
|
||||
|
||||
Ok(generics.split_for_impl().2.cloned())
|
||||
}
|
||||
Self::Transparent(transparent) => {
|
||||
let mut generics = input_generics.clone();
|
||||
|
||||
add_trait_where_clause(
|
||||
&mut generics,
|
||||
[&transparent.inner_type],
|
||||
&[UNVERSIONIZE_TRAIT_NAME],
|
||||
)?;
|
||||
Ok(generics.split_for_impl().2.cloned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the body of the versionize method.
|
||||
pub(crate) fn versionize_method_body(&self) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
Self::Classic(_) => {
|
||||
quote! {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
Self::Convert(convert_attr) => {
|
||||
let versionize_owned_trait: TraitBound =
|
||||
parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
|
||||
let convert_type_path = with_turbofish(&convert_attr.conversion_target);
|
||||
quote! {
|
||||
#versionize_owned_trait::versionize_owned(#convert_type_path::from(self.to_owned()))
|
||||
}
|
||||
}
|
||||
Self::Transparent(transparent) => match &transparent.kind {
|
||||
TransparentStructKind::NewType => {
|
||||
quote! {
|
||||
self.0.versionize()
|
||||
}
|
||||
}
|
||||
TransparentStructKind::SingleField(field_name) => {
|
||||
quote! {
|
||||
self.#field_name.versionize()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the body of the versionize_owned method.
|
||||
pub(crate) fn versionize_owned_method_body(&self) -> proc_macro2::TokenStream {
|
||||
let versionize_owned_trait: TraitBound = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
|
||||
|
||||
match self {
|
||||
Self::Classic(_) => {
|
||||
quote! {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
Self::Convert(convert_attr) => {
|
||||
let convert_type_path = with_turbofish(&convert_attr.conversion_target);
|
||||
quote! {
|
||||
#versionize_owned_trait::versionize_owned(#convert_type_path::from(self))
|
||||
}
|
||||
}
|
||||
Self::Transparent(transparent) => match &transparent.kind {
|
||||
TransparentStructKind::NewType => {
|
||||
quote! {
|
||||
self.0.versionize_owned()
|
||||
}
|
||||
}
|
||||
TransparentStructKind::SingleField(field_name) => {
|
||||
quote! {
|
||||
self.#field_name.versionize_owned()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the body of the unversionize method.
|
||||
pub(crate) fn unversionize_method_body(&self, arg_name: &Ident) -> proc_macro2::TokenStream {
|
||||
let error: Type = parse_const_str(UNVERSIONIZE_ERROR_NAME);
|
||||
match self {
|
||||
Self::Classic(_) => {
|
||||
quote! { #arg_name.try_into() }
|
||||
}
|
||||
Self::Convert(convert_attr) => {
|
||||
let target = with_turbofish(&convert_attr.conversion_target);
|
||||
match convert_attr.conversion_type {
|
||||
ConversionType::Direct => {
|
||||
quote! { #target::unversionize(#arg_name).map(|value| value.into()) }
|
||||
}
|
||||
ConversionType::Try => {
|
||||
let target_name = format!("{}", target.to_token_stream());
|
||||
quote! { #target::unversionize(#arg_name).and_then(|value| TryInto::<Self>::try_into(value)
|
||||
.map_err(|e| #error::conversion(#target_name, e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Transparent(transparent) => {
|
||||
let inner = match &transparent.inner_type {
|
||||
Type::Path(path) => Type::Path(TypePath {
|
||||
qself: path.qself.clone(),
|
||||
path: with_turbofish(&path.path),
|
||||
}),
|
||||
inner => inner.clone(),
|
||||
};
|
||||
|
||||
match &transparent.kind {
|
||||
TransparentStructKind::NewType => {
|
||||
quote! {
|
||||
#inner::unversionize(#arg_name).map(Self)
|
||||
}
|
||||
}
|
||||
TransparentStructKind::SingleField(field_name) => {
|
||||
quote! {
|
||||
Ok(Self { #field_name: #inner::unversionize(#arg_name)? })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the same type but with generics that use the turbofish syntax. Converts
|
||||
/// `MyStruct<T>` into `MyStruct::<T>`
|
||||
fn with_turbofish(path: &Path) -> Path {
|
||||
let mut with_turbo = path.clone();
|
||||
|
||||
for segment in with_turbo.segments.iter_mut() {
|
||||
if let PathArguments::AngleBracketed(generics) = &mut segment.arguments {
|
||||
generics.colon2_token = Some(Token));
|
||||
}
|
||||
}
|
||||
|
||||
with_turbo
|
||||
}
|
||||
|
||||
/// Extract the generics inside a type
|
||||
fn extract_generics(path: &Path) -> Generics {
|
||||
let mut generics = Generics::default();
|
||||
|
||||
if let Some(last_segment) = path.segments.last() {
|
||||
if let PathArguments::AngleBracketed(args) = &last_segment.arguments {
|
||||
for arg in &args.args {
|
||||
if let GenericArgument::Type(Type::Path(type_path)) = arg {
|
||||
if let Some(ident) = type_path.path.get_ident() {
|
||||
let param = TypeParam::from(ident.clone());
|
||||
generics.params.push(GenericParam::Type(param));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generics
|
||||
}
|
||||
86
utils/tfhe-versionable/examples/transparent.rs
Normal file
86
utils/tfhe-versionable/examples/transparent.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
//! In this example, we use the `transparent` attribute so that the versioning of the outer struct
|
||||
//! will simply defer to the inner type. This is useful for wrapper types that shouldn't be
|
||||
//! represented in serialized data. This only works for "newtype" struct or structs with only one
|
||||
//! field.
|
||||
|
||||
use std::convert::Infallible;
|
||||
|
||||
use tfhe_versionable::{Unversionize, Upgrade, Version, Versionize, VersionsDispatch};
|
||||
|
||||
// The Wrapper that should be skipped. Also work with a single field regular struct:
|
||||
//
|
||||
// struct MyStructWrapper<T> { inner: MyStruct<T> };
|
||||
#[derive(Versionize)]
|
||||
#[versionize(transparent)] // Also works with `#[repr(transparent)]`
|
||||
struct MyStructWrapper<T>(MyStruct<T>);
|
||||
|
||||
// The inner struct that is versioned.
|
||||
#[derive(Versionize)]
|
||||
#[versionize(MyStructVersions)]
|
||||
struct MyStruct<T> {
|
||||
attr: T,
|
||||
builtin: u32,
|
||||
}
|
||||
|
||||
#[derive(Version)]
|
||||
struct MyStructV0 {
|
||||
builtin: u32,
|
||||
}
|
||||
|
||||
impl<T: Default> Upgrade<MyStruct<T>> for MyStructV0 {
|
||||
type Error = Infallible;
|
||||
|
||||
fn upgrade(self) -> Result<MyStruct<T>, Self::Error> {
|
||||
Ok(MyStruct {
|
||||
attr: T::default(),
|
||||
builtin: self.builtin,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(VersionsDispatch)]
|
||||
#[allow(unused)]
|
||||
enum MyStructVersions<T> {
|
||||
V0(MyStructV0),
|
||||
V1(MyStruct<T>),
|
||||
}
|
||||
|
||||
mod v0 {
|
||||
use tfhe_versionable::{Versionize, VersionsDispatch};
|
||||
|
||||
// This struct cannot change as it is not itself versioned. If you ever make a change that
|
||||
// should impact the serialized layout of the data, you need to update all the types that use
|
||||
// it.
|
||||
#[derive(Versionize)]
|
||||
#[versionize(transparent)]
|
||||
pub(super) struct MyStructWrapper(pub(super) MyStruct);
|
||||
|
||||
#[derive(Versionize)]
|
||||
#[versionize(MyStructVersions)]
|
||||
pub(super) struct MyStruct {
|
||||
pub(super) builtin: u32,
|
||||
}
|
||||
|
||||
#[derive(VersionsDispatch)]
|
||||
#[allow(unused)]
|
||||
pub(super) enum MyStructVersions {
|
||||
V0(MyStruct),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let value = 1234;
|
||||
let ms = v0::MyStructWrapper(v0::MyStruct { builtin: value });
|
||||
|
||||
let serialized = bincode::serialize(&ms.versionize()).unwrap();
|
||||
|
||||
let unserialized =
|
||||
MyStructWrapper::<u64>::unversionize(bincode::deserialize(&serialized).unwrap()).unwrap();
|
||||
|
||||
assert_eq!(unserialized.0.builtin, value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
main()
|
||||
}
|
||||
Reference in New Issue
Block a user