Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ns/versionable unsupported #1691

Merged
merged 2 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions utils/tfhe-versionable-derive/src/associated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
};

/// Generates an impl block for the From trait. This will be:
/// ```
/// ```ignore
/// impl From<Src> for Dest {
/// fn from(value: Src) -> Self {
/// ...[constructor]...
Expand All @@ -39,7 +39,7 @@ pub(crate) fn generate_from_trait_impl(
}

/// Generates an impl block for the TryFrom trait. This will be:
/// ```
/// ```ignore
/// impl TryFrom<Src> for Dest {
/// type Error = ErrorType;
/// fn from(value: Src) -> Self {
Expand Down
7 changes: 4 additions & 3 deletions utils/tfhe-versionable-derive/src/dispatch_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ impl DispatchType {
fn generate_conversion_constructor_owned(&self, arg_name: &str) -> syn::Result<TokenStream> {
let arg_ident = Ident::new(arg_name, Span::call_site());
let error_ty: Type = parse_const_str(UNVERSIONIZE_ERROR_NAME);
let upgrade_trait: Path = parse_const_str(UPGRADE_TRAIT_NAME);

let match_cases =
self.orig_type
Expand All @@ -354,12 +355,12 @@ impl DispatchType {
// Add chained calls to the upgrade method, with error handling
let upgrades_chain = (0..upgrades_needed).map(|upgrade_idx| {
// Here we can unwrap because src_idx + upgrade_idx < version_count or we wouldn't need to upgrade
let src_type = self.version_type_at(src_idx + upgrade_idx).unwrap();
let src_variant = self.variant_at(src_idx + upgrade_idx).unwrap().ident.to_string();
let dest_variant = self.variant_at(src_idx + upgrade_idx + 1).unwrap().ident.to_string();
quote! {
.and_then(|value| {
value
.upgrade()
.and_then(|value: #src_type| {
#upgrade_trait::upgrade(value)
.map_err(|e|
#error_ty::upgrade(#src_variant, #dest_variant, e)
)
Expand Down
43 changes: 43 additions & 0 deletions utils/tfhe-versionable-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,49 @@ pub fn derive_versions_dispatch(input: TokenStream) -> TokenStream {
///
/// This macro has a mandatory attribute parameter, which is the name of the versioned enum for this
/// type. This enum can be anywhere in the code but should be in scope.
///
/// Example:
/// ```ignore
/// // The structure that should be versioned, as defined in your code
/// #[derive(Versionize)]
/// // We have to link to the enum type that will holds all the versions of this
/// // type. This can also be written `#[versionize(dispatch = MyStructVersions)]`.
/// #[versionize(MyStructVersions)]
/// struct MyStruct<T> {
/// attr: T,
/// builtin: u32,
/// }
///
/// // To avoid polluting your code, the old versions can be defined in another module/file, along with
/// // the dispatch enum
/// #[derive(Version)] // Used to mark an old version of the type
/// struct MyStructV0 {
/// builtin: u32,
/// }
///
/// // The Upgrade trait tells how to go from the first version to the last. During unversioning, the
/// // upgrade method will be called on the deserialized value enough times to go to the last variant.
/// 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,
/// })
/// }
/// }
///
/// // This is the dispatch enum, that holds one variant for each version of your type.
/// #[derive(VersionsDispatch)]
/// // This enum is not directly used but serves as a template to generate a new enum that will be
/// // serialized. This allows recursive versioning.
/// #[allow(unused)]
/// enum MyStructVersions<T> {
/// V0(MyStructV0),
/// V1(MyStruct<T>),
/// }
/// ```
#[proc_macro_derive(Versionize, attributes(versionize))]
pub fn derive_versionize(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
Expand Down
192 changes: 192 additions & 0 deletions utils/tfhe-versionable/examples/deprecation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Example of a version deprecation, to remove support for types up to a chosen point.
//!
//! In this example, we have an application with 3 versions: v0, v1, v2. We know that v0 and v1 are
//! not used in the wild, so we want to remove backward compatibility with them to be able to
//! clean-up some code. We can use this feature to create a v3 version that will be compatible with
//! v2 but remove support for the previous ones.
use tfhe_versionable::{Unversionize, Versionize};

// The newer version of the app, where you want to cut compatibility with versions that are too old
mod v3 {
use serde::{Deserialize, Serialize};
use tfhe_versionable::Versionize;

use backward_compat::MyStructVersions;

#[derive(Serialize, Deserialize, Versionize)]
#[versionize(MyStructVersions)]
pub struct MyStruct<T> {
pub count: u32,
pub attr: T,
}

mod backward_compat {
use tfhe_versionable::deprecation::{Deprecable, Deprecated};
use tfhe_versionable::VersionsDispatch;

use super::MyStruct;

// The `Deprecation` trait will be used to give meaningful error messages to you users
impl<T> Deprecable for MyStruct<T> {
// The name of the type, as seen by the user
const TYPE_NAME: &'static str = "MyStruct";

// The minimum version of the application/library that we still support. You can include
// the name of your app/library.
const MIN_SUPPORTED_APP_VERSION: &'static str = "app v2";
}

// Replace the deprecation versions with the `Deprecated` type in the dispatch enum
#[derive(VersionsDispatch)]
#[allow(unused)]
pub enum MyStructVersions<T> {
V0(Deprecated<MyStruct<T>>),
V1(Deprecated<MyStruct<T>>),
V2(MyStruct<T>),
}
}
}

fn main() {
// A version that will be deprecated
let v0 = v0::MyStruct(37);

let serialized = bincode::serialize(&v0.versionize()).unwrap();

// We can upgrade it until the last supported version
let v2 = v2::MyStruct::<u64>::unversionize(bincode::deserialize(&serialized).unwrap()).unwrap();

assert_eq!(v0.0, v2.count);
assert_eq!(v2.attr, u64::default());

// But trying to upgrade it into the newer version with dropped support will fail.
let v3_deser: Result<v3::MyStruct<u64>, _> = bincode::deserialize(&serialized);

assert!(v3_deser.is_err());

// However you can still update from the last supported version
let _serialized_v2 = bincode::serialize(&v2.versionize()).unwrap();
}

// Older versions of the application

mod v0 {
use serde::{Deserialize, Serialize};
use tfhe_versionable::Versionize;

use backward_compat::MyStructVersions;

#[derive(Serialize, Deserialize, Versionize)]
#[versionize(MyStructVersions)]
pub struct MyStruct(pub u32);

mod backward_compat {
use tfhe_versionable::VersionsDispatch;

use super::MyStruct;

#[derive(VersionsDispatch)]
#[allow(unused)]
pub enum MyStructVersions {
V0(MyStruct),
}
}
}

mod v1 {
use serde::{Deserialize, Serialize};
use tfhe_versionable::Versionize;

use backward_compat::MyStructVersions;

#[derive(Serialize, Deserialize, Versionize)]
#[versionize(MyStructVersions)]
pub struct MyStruct<T>(pub u32, pub T);

mod backward_compat {
use std::convert::Infallible;

use tfhe_versionable::{Upgrade, Version, VersionsDispatch};

use super::MyStruct;

#[derive(Version)]
pub struct MyStructV0(pub u32);

impl<T: Default> Upgrade<MyStruct<T>> for MyStructV0 {
type Error = Infallible;

fn upgrade(self) -> Result<MyStruct<T>, Self::Error> {
Ok(MyStruct(self.0, T::default()))
}
}

#[derive(VersionsDispatch)]
#[allow(unused)]
pub enum MyStructVersions<T> {
V0(MyStructV0),
V1(MyStruct<T>),
}
}
}

mod v2 {
use serde::{Deserialize, Serialize};
use tfhe_versionable::Versionize;

use backward_compat::MyStructVersions;

#[derive(Serialize, Deserialize, Versionize)]
#[versionize(MyStructVersions)]
pub struct MyStruct<T> {
pub count: u32,
pub attr: T,
}

mod backward_compat {
use std::convert::Infallible;

use tfhe_versionable::{Upgrade, Version, VersionsDispatch};

use super::MyStruct;

#[derive(Version)]
pub struct MyStructV0(pub u32);

impl<T: Default> Upgrade<MyStructV1<T>> for MyStructV0 {
type Error = Infallible;

fn upgrade(self) -> Result<MyStructV1<T>, Self::Error> {
Ok(MyStructV1(self.0, T::default()))
}
}

#[derive(Version)]
pub struct MyStructV1<T>(pub u32, pub T);

impl<T: Default> Upgrade<MyStruct<T>> for MyStructV1<T> {
type Error = Infallible;

fn upgrade(self) -> Result<MyStruct<T>, Self::Error> {
Ok(MyStruct {
count: self.0,
attr: T::default(),
})
}
}

#[derive(VersionsDispatch)]
#[allow(unused)]
pub enum MyStructVersions<T> {
V0(MyStructV0),
V1(MyStructV1<T>),
V2(MyStruct<T>),
}
}
}

#[test]
fn test() {
main()
}
Loading
Loading