diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 91d9135..59be2b8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install nightly - run: rustup toolchain install nightly && rustup toolchain install stable + run: rustup toolchain install nightly && rustup toolchain install stable && rustup toolchain install 1.74 - name: Miri (nightly) run: rustup component add --toolchain nightly miri && cd savefile-test && cargo +nightly miri test - name: Build (nightly) @@ -34,4 +34,6 @@ jobs: run: cargo +stable build -p savefile-min-build - name: compile_tests (stable) run: cd compile_tests && cargo +stable test + - name: Build (1.74) + run: cargo +1.74 build --workspace diff --git a/Cargo.lock b/Cargo.lock index bbca116..3fbf31a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,7 @@ dependencies = [ [[package]] name = "savefile" -version = "0.17.3" +version = "0.17.4" dependencies = [ "arrayvec", "bit-set", @@ -471,10 +471,11 @@ dependencies = [ [[package]] name = "savefile-abi" -version = "0.17.3" +version = "0.17.4" dependencies = [ "byteorder", "libloading", + "rustc_version 0.2.3", "savefile", "savefile-derive", ] @@ -511,7 +512,7 @@ dependencies = [ [[package]] name = "savefile-derive" -version = "0.17.3" +version = "0.17.4" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/savefile-abi-min/Cargo.toml b/savefile-abi-min/Cargo.toml index e8756d0..8b91efa 100644 --- a/savefile-abi-min/Cargo.toml +++ b/savefile-abi-min/Cargo.toml @@ -2,6 +2,7 @@ name = "savefile-abi-min" version = "0.1.0" edition = "2021" +rust-version = "1.74" diff --git a/savefile-abi-min/src/main.rs b/savefile-abi-min/src/main.rs index 8d19b8b..4e114de 100644 --- a/savefile-abi-min/src/main.rs +++ b/savefile-abi-min/src/main.rs @@ -33,7 +33,7 @@ pub extern "C" fn call_do_nothing(adder: &AbiConnection) { fn main() { let connection = AbiConnection::::load_shared_library( - "c:/savefile/target/debug/savefile_abi_min_lib_impl.dll", //Change this to the proper path on your machine + "../target/debug/libsavefile_abi_min_lib_impl.so", //Change this to the proper path on your machine ) .unwrap(); diff --git a/savefile-abi/CHANGELOG.md b/savefile-abi/CHANGELOG.md index 1c334ad..57cd15a 100644 --- a/savefile-abi/CHANGELOG.md +++ b/savefile-abi/CHANGELOG.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.4](https://github.com/avl/savefile/compare/savefile-abi-v0.17.3...savefile-abi-v0.17.4) - 2024-05-12 + +### Added +- Better diagnostics when correct traits not implemented by user, and also explicit minimum rustc version specified + +### Other +- Merge branch 'minor_v19' of github.com:avl/savefile into minor_v19 +- format +- release ([#54](https://github.com/avl/savefile/pull/54)) + ## [0.17.3](https://github.com/avl/savefile/compare/savefile-abi-v0.17.2...savefile-abi-v0.17.3) - 2024-05-09 ### Other diff --git a/savefile-abi/Cargo.toml b/savefile-abi/Cargo.toml index d9d2ea6..165837b 100644 --- a/savefile-abi/Cargo.toml +++ b/savefile-abi/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "savefile-abi" -version = "0.17.3" +version = "0.17.4" edition = "2021" authors = ["Anders Musikka "] documentation = "https://docs.rs/savefile-abi/" homepage = "https://github.com/avl/savefile/blob/master/savefile-abi/README.md" repository = "https://github.com/avl/savefile" +rust-version = "1.74" description = "Easy to use, simple, stable ABI for Rust-libraries. Allows creating dynamically loadable plugins written in rust." @@ -16,7 +17,16 @@ keywords = ["dylib", "dlopen", "ffi"] license = "MIT/Apache-2.0" [dependencies] -savefile = { path="../savefile", version = "=0.17.3" } -savefile-derive = { path="../savefile-derive", version = "=0.17.3" } +savefile = { path="../savefile", version = "=0.17.4" } +savefile-derive = { path="../savefile-derive", version = "=0.17.4" } byteorder = "1.4" libloading = "0.8" + +[features] +# Rust version is > 1.78 +# Automatically set by build.rs +rust1_78=[] + + +[build-dependencies] +rustc_version="0.2" diff --git a/savefile-abi/build.rs b/savefile-abi/build.rs new file mode 100644 index 0000000..4a28536 --- /dev/null +++ b/savefile-abi/build.rs @@ -0,0 +1,8 @@ +extern crate rustc_version; +use rustc_version::{version_meta, Channel, Version}; +fn main() { + let version = version_meta().unwrap(); + if version.semver >= Version::new(1,78,0) { + println!("cargo:rustc-cfg=feature=\"rust1_78\""); + } +} diff --git a/savefile-abi/src/lib.rs b/savefile-abi/src/lib.rs index caca641..0a84c29 100644 --- a/savefile-abi/src/lib.rs +++ b/savefile-abi/src/lib.rs @@ -328,6 +328,11 @@ use libloading::{Library, Symbol}; /// If trait `MyExampleTrait` is to be exportable, the trait `AbiExportable` must /// be implemented for `dyn MyExampleTrait`. /// +/// NOTE! +/// This trait is not meant to be implemented manually. It is mostly an implementation +/// detail of SavefileAbi, it is only ever meant to be implemented by the savefile-derive +/// proc macro. +/// /// # Safety /// The implementor must: /// * Make sure that the ABI_ENTRY function implements all parts of AbiProtocol @@ -335,6 +340,11 @@ use libloading::{Library, Symbol}; /// * Has a correct 'get_definition' function, which must return a AbiTraitDefinition instance /// that is truthful. /// * Implement 'call' correctly +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot be used across an ABI-boundary. Try adding a `#[savefile_abi_exportable(version=X)]` attribute to the declaration of the relevant trait.", + label = "`{Self}` cannot be called across an ABI-boundary", + note = "This error probably occurred because `{Self}` occurred as a return-value or argument to a method in a trait marked with `#[savefile_abi_exportable(version=X)]`, or because savefile_abi_export!-macro was used to export `{Self}`.", +))] pub unsafe trait AbiExportable { /// A function which implements the savefile-abi contract. const ABI_ENTRY: unsafe extern "C" fn(AbiProtocol); @@ -381,6 +391,11 @@ pub unsafe trait AbiExportable { /// * ABI_ENTRY must be a valid function, implementing the AbiProtocol-protocol. /// * AbiInterface must be 'dyn SomeTrait', where 'SomeTrait' is an exported trait. /// +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot be the concrete type of an AbiExportable dyn trait.", + label = "Does not implement `AbiExportableImplementation`", + note = "You should not be using this trait directly, and should never see this error.", +))] pub unsafe trait AbiExportableImplementation { /// An entry point which implements the AbiProtocol protocol const ABI_ENTRY: unsafe extern "C" fn(AbiProtocol); diff --git a/savefile-derive/CHANGELOG.md b/savefile-derive/CHANGELOG.md index 87a95f1..9443096 100644 --- a/savefile-derive/CHANGELOG.md +++ b/savefile-derive/CHANGELOG.md @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.4](https://github.com/avl/savefile/compare/savefile-derive-v0.17.3...savefile-derive-v0.17.4) - 2024-05-12 + +### Added +- Better diagnostics when correct traits not implemented by user, and also explicit minimum rustc version specified + +### Other +- Merge branch 'minor_v19' of github.com:avl/savefile into minor_v19 +- Merge remote-tracking branch 'origin/master' into minor_v19 + ## [0.17.3](https://github.com/avl/savefile/compare/savefile-derive-v0.17.2...savefile-derive-v0.17.3) - 2024-05-09 ### Other diff --git a/savefile-derive/Cargo.toml b/savefile-derive/Cargo.toml index 28a51cc..d88e828 100644 --- a/savefile-derive/Cargo.toml +++ b/savefile-derive/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "savefile-derive" -version = "0.17.3" +version = "0.17.4" authors = ["Anders Musikka "] repository = "https://github.com/avl/savefile" - +rust-version = "1.74" description = "Custom derive macros for savefile crate - simple, convenient, fast, versioned, binary serialization/deserialization library." readme = "../README.md" diff --git a/savefile-test/Cargo.toml b/savefile-test/Cargo.toml index a8127a9..8c62e7f 100644 --- a/savefile-test/Cargo.toml +++ b/savefile-test/Cargo.toml @@ -12,7 +12,7 @@ nightly=["savefile/nightly"] [dependencies] savefile = { path = "../savefile", features = ["size_sanity_checks", "encryption", "compression","bit-set","bit-vec","rustc-hash","serde_derive", "quickcheck"]} -savefile-derive = { path = "../savefile-derive", version = "=0.17.3" } +savefile-derive = { path = "../savefile-derive", version = "=0.17.4" } savefile-abi = { path = "../savefile-abi" } bit-vec = "0.6" arrayvec="0.7" diff --git a/savefile/CHANGELOG.md b/savefile/CHANGELOG.md index 38d46ce..dd4d9ce 100644 --- a/savefile/CHANGELOG.md +++ b/savefile/CHANGELOG.md @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.4](https://github.com/avl/savefile/compare/savefile-v0.17.3...savefile-v0.17.4) - 2024-05-12 + +### Added +- Better diagnostics when correct traits not implemented by user, and also explicit minimum rustc version specified + +### Other +- Merge branch 'minor_v19' of github.com:avl/savefile into minor_v19 +- Merge remote-tracking branch 'origin/master' into minor_v19 + ## [0.17.3](https://github.com/avl/savefile/compare/savefile-v0.17.2...savefile-v0.17.3) - 2024-05-09 ### Fixed diff --git a/savefile/Cargo.toml b/savefile/Cargo.toml index e9b2d17..33b35c9 100644 --- a/savefile/Cargo.toml +++ b/savefile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "savefile" -version = "0.17.3" +version = "0.17.4" authors = ["Anders Musikka "] documentation = "https://docs.rs/savefile/" homepage = "https://github.com/avl/savefile/" @@ -32,8 +32,14 @@ serde_derive = ["dep:serde_derive","serde"] size_sanity_checks = [] # Use features only available on the nightly rust-compiler. # Enabling this provides slightly better introspection support. +# Automatically set by build.rs for nightly compilers nightly=[] +# Rust version is > 1.78 +# Automatically set by build.rs +rust1_78=[] + + compression = ["bzip2"] encryption = ["ring", "rand"] @@ -53,13 +59,13 @@ bit-set = {version = "0.5", optional = true} rustc-hash = {version = "1.1", optional = true} memoffset = "0.9" byteorder = "1.4" -savefile-derive = {path="../savefile-derive", version = "=0.17.3", optional = true } +savefile-derive = {path="../savefile-derive", version = "=0.17.4", optional = true } serde_derive = {version= "1.0", optional = true} serde = {version= "1.0", optional = true} quickcheck = {version= "1.0", optional = true} [dev-dependencies] -savefile-derive = { path="../savefile-derive", version = "=0.17.3" } +savefile-derive = { path="../savefile-derive", version = "=0.17.4" } [build-dependencies] rustc_version="0.2" diff --git a/savefile/build.rs b/savefile/build.rs index ff9d5a4..b48258d 100644 --- a/savefile/build.rs +++ b/savefile/build.rs @@ -1,7 +1,11 @@ extern crate rustc_version; -use rustc_version::{version_meta, Channel}; +use rustc_version::{version_meta, Channel, Version}; fn main() { - if version_meta().unwrap().channel == Channel::Nightly { + let version = version_meta().unwrap(); + if version.channel == Channel::Nightly { println!("cargo:rustc-cfg=feature=\"nightly\""); } + if version.semver >= Version::new(1,78,0) { + println!("cargo:rustc-cfg=feature=\"rust1_78\""); + } } diff --git a/savefile/src/lib.rs b/savefile/src/lib.rs index b441971..29a89aa 100644 --- a/savefile/src/lib.rs +++ b/savefile/src/lib.rs @@ -1203,6 +1203,12 @@ impl IsPacked { /// See method repr_c_optimization_safe. /// Note! The name Packed is a little misleading. A better name would be /// 'packed' +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot be serialized or deserialized by Savefile, since it doesn't implement trait `savefile::Packed`", + label = "This cannot be serialized or deserialized", + note = "You can implement it by adding `#[derive(Savefile)]` before the declaration of `{Self}`", + note = "Or you can manually implement the `savefile::Packed` trait." +))] pub trait Packed { /// This method returns true if the optimization is allowed /// for the protocol version given as an argument. @@ -1218,7 +1224,7 @@ pub trait Packed { /// /// Rules to allow returning true: /// - /// * The type must be copy + /// * The type must "be Copy" (i.e, implement the `Copy`-trait) /// * The type must not contain any padding (if there is padding, backward compatibility will fail, since in fallback mode regular savefile-deserialize will be used, and it will not use padding) /// * The type must have a strictly deterministic memory layout (no field order randomization). This typically means repr(C) /// * All the constituent types of the type must also implement `Packed` (correctly). @@ -1226,7 +1232,8 @@ pub trait Packed { /// Constructing an instance of 'IsPacked' with value 'true' is not safe. See /// documentation of 'IsPacked'. The idea is that the Packed-trait itself /// can still be safe to implement, it just won't be possible to get hold of an - /// instance of IsPacked(true). To make it impossible to just + /// instance of IsPacked(true). That is, a safe implementation of `Packed` can't return + /// IsPacked(true), if everything else follows safety rules. To make it impossible to just /// 'steal' such a value from some other thing implementing 'Packed', /// this method is marked unsafe (however, it can be left unimplemented, /// making it still possible to safely implement Packed). @@ -1249,6 +1256,11 @@ pub struct DeliberatelyUnimplementable { #[deprecated(since = "0.17", note = "The 'ReprC' trait has been renamed to 'Packed'.")] #[doc(hidden)] +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "ReprC has been deprecated and must not be used. Use trait `savefile::Packed` instead!", + label = "ReprC was erroneously required here", + note = "Please change any `ReprC` bounds into `Packed` bounds.", +))] pub trait ReprC { #[deprecated(since = "0.17", note = "The 'ReprC' trait has been renamed to 'Packed'.")] #[doc(hidden)] @@ -2331,6 +2343,13 @@ impl WithSchemaContext { /// This is only for increased safety, the file format does not in fact use the schema for any other /// purpose, the design is schema-less at the core, the schema is just an added layer of safety (which /// can be disabled). +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` does not have a defined schema for savefile, since it doesn't implement the trait `savefile::WithSchema`", + label = "This cannot be serialized or deserialized with a schema", + note = "You can implement it by adding `#[derive(Savefile)]` before the declaration of `{Self}`", + note = "Or you can manually implement the `savefile::WithSchema` trait.", + note = "You can also use one of the `*_noschema` functions to save/load without a schema." +))] pub trait WithSchema { /// Returns a representation of the schema used by this Serialize implementation for the given version. /// The WithSchemaContext can be used to guard against recursive data structures. @@ -2354,6 +2373,12 @@ pub fn get_schema(version: u32) -> Schema { /// extern crate savefile-derive;` /// /// and the use #\[derive(Serialize)] +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot be serialized by Savefile, since it doesn't implement the trait `savefile::Serialize`", + label = "This cannot be serialized", + note = "You can implement it by adding `#[derive(Savefile)]` before the declaration of `{Self}`", + note = "Or you can manually implement the `savefile::Serialize` trait." +))] pub trait Serialize: WithSchema { /// Serialize self into the given serializer. /// In versions prior to 0.15, 'Serializer' did not accept a type parameter. @@ -2365,6 +2390,11 @@ pub trait Serialize: WithSchema { /// simply (String, &dyn Introspect) is that Mutex wouldn't be introspectable in that case. /// Mutex needs something like `(String, MutexGuard)`. By having this a trait, /// different types can have whatever reference holder needed (MutexGuard, RefMut etc). +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot be an introspected value used by Savefile, since it doesn't implement the trait `savefile::IntrospectItem`", + label = "This cannot be the type of an introspected field value", + note = "You can possibly implement IntrospectItem manually for the type `{Self}`, or try to use `String` instead of `{Self}`." +))] pub trait IntrospectItem<'a> { /// Should return a descriptive string for the given child. For structures, /// this would be the field name, for instance. @@ -2417,6 +2447,12 @@ impl<'a> IntrospectItem<'a> for String { pub const MAX_CHILDREN: usize = 10000; /// Gives the ability to look into an object, inspecting any children (fields). +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot be introspected by Savefile, since it doesn't implement trait `savefile::Introspect`", + label = "This cannot be introspected", + note = "You can implement it by adding `#[derive(Savefile)]` or `#[derive(SavefileIntrospectOnly)]` before the declaration of `{Self}`", + note = "Or you can manually implement the `savefile::Introspect` trait." +))] pub trait Introspect { /// Returns the value of the object, excluding children, as a string. /// Exactly what the value returned here is depends on the type. @@ -2458,6 +2494,12 @@ pub trait Introspect { /// extern crate savefile-derive;` /// /// and the use #\[derive(Deserialize)] +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot be deserialized by Savefile, since it doesn't implement the trait `savefile::Deserialize`", + label = "This cannot be deserialized", + note = "You can implement it by adding `#[derive(Savefile)]` before the declaration of `{Self}`", + note = "Or you can manually implement the `savefile::Deserialize` trait." +))] pub trait Deserialize: WithSchema + Sized { /// Deserialize and return an instance of Self from the given deserializer. fn deserialize(deserializer: &mut Deserializer) -> Result; //TODO: Do error handling @@ -4884,7 +4926,31 @@ impl Deserialize for IndexSet { } } -/// Something that can construct a value of type T +/// Something that can construct a value of type T. +/// Used when a field has been removed using the `AbiRemoved` type. +/// Usage: +/// ```rust +/// use savefile::{AbiRemoved, ValueConstructor}; +/// use savefile_derive::Savefile; +/// #[derive(Savefile)] +/// struct MyStruct { +/// my_field: String, +/// #[savefile_versions="..0"] +/// my_removed_field: AbiRemoved, +/// } +/// struct MyStructMyRemovedFieldFactory; +/// impl ValueConstructor for MyStructMyRemovedFieldFactory { +/// fn make_value() -> String { +/// "Default value for when values of version 0 are to be serialized".to_string() +/// } +/// } +/// ``` +#[cfg_attr(feature = "rust1_78", diagnostic::on_unimplemented( + message = "`{Self}` cannot serve as a factory generating default values of type {T}, since it doesn't implement the trait `savefile::ValueConstructor<{T}>`-", + label = "`{Self}` cannot produce values of type `{T}`", + note = "Check that any type used as 2nd type parameter to AbiRemoved implements `savefile::ValueConstructor<{T}>`.", + note = "Alternatively, skip the 2nd parameter entirely, and ensure that `{T}` implements `Default`.", +))] pub trait ValueConstructor { /// Create a value of type T. /// This is used by the AbiRemoved trait to be able to invent @@ -4892,16 +4958,10 @@ pub trait ValueConstructor { fn make_value() -> T; } -impl T + Default> ValueConstructor for R { - fn make_value() -> T { - let r: R = Default::default(); - r() - } -} - -/// +/// A value constructor that delegates to the 'Default' trait. +/// Requires that type `T` implements `Default`. #[derive(Debug, PartialEq, Eq)] -pub struct DefaultValueConstructor { +pub struct DefaultValueConstructor { phantom: PhantomData<*const T>, } @@ -4912,6 +4972,13 @@ impl ValueConstructor for DefaultValueConstructor { } /// Helper struct which represents a field which has been removed +/// In contrast to AbiRemoved, this type only supports deserialization. +/// It is thus not recommended for use when SavefileAbi is to be used, and +/// forward compatibility is desired. +/// +/// The difference is that Removed does not require T to implement Default, +/// or any other factory trait, since we never need to serialize dummy +/// values of Removed (we never serialize using a schema where a field i Removed). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Removed { phantom: std::marker::PhantomData<*const T>, @@ -4969,11 +5036,14 @@ impl Deserialize for Removed { } /// Helper struct which represents a field which has been removed. -/// This, in contrast to 'AbiRemoved', -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct AbiRemoved> -where - D: ValueConstructor, +/// In contrast to `Removed`, this type supports both serialization and +/// deserialization, and is preferred when SavefileAbi is to be used. +/// Regular Savefile does not support serializing older versions, whereas +/// SavefileAbi does. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AbiRemoved> + where + D: ValueConstructor, { phantom: std::marker::PhantomData<(*const T, *const D)>, } @@ -4981,14 +5051,14 @@ where /// Removed is a zero-sized type. It contains a PhantomData<*const T>, which means /// it doesn't implement Send or Sync per default. However, implementing these /// is actually safe, so implement it manually. -unsafe impl> Send for AbiRemoved {} +unsafe impl> Send for AbiRemoved {} /// Removed is a zero-sized type. It contains a PhantomData<*const T>, which means /// it doesn't implement Send or Sync per default. However, implementing these /// is actually safe, so implement it manually. -unsafe impl> Sync for AbiRemoved {} +unsafe impl> Sync for AbiRemoved {} -impl> AbiRemoved { - /// Helper to create an instance of `Removed`. `Removed` has no data. +impl> AbiRemoved { + /// Helper to create an instance of `AbiRemoved`. `AbiRemoved` has no data. pub fn new() -> AbiRemoved { AbiRemoved { phantom: std::marker::PhantomData, @@ -5004,7 +5074,7 @@ impl> WithSchema for AbiRemoved { impl> Introspect for AbiRemoved { fn introspect_value(&self) -> String { - format!("Removed<{}>", std::any::type_name::()) + format!("AbiRemoved<{}>", std::any::type_name::()) } fn introspect_child(&self, _index: usize) -> Option> {