zerovec-derive-0.11.0/.cargo_vcs_info.json0000644000000001620000000000100140430ustar { "git": { "sha1": "6bd4893cc44c2ca2718de47a119a31cc40045fe5" }, "path_in_vcs": "utils/zerovec/derive" }zerovec-derive-0.11.0/Cargo.lock0000644000000051620000000000100120230ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "itoa" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "zerovec-derive" version = "0.11.0" dependencies = [ "bincode", "proc-macro2", "quote", "serde", "serde_json", "syn", ] zerovec-derive-0.11.0/Cargo.toml0000644000000033250000000000100120450ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "zerovec-derive" version = "0.11.0" authors = ["Manish Goregaokar "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Custom derive for the zerovec crate" readme = "README.md" keywords = [ "zerocopy", "serialization", "zero-copy", "serde", ] categories = [ "rust-patterns", "memory-management", "caching", "no-std", "data-structures", ] license = "Unicode-3.0" repository = "https://github.com/unicode-org/icu4x" [package.metadata.workspaces] independent = true [lib] name = "zerovec_derive" path = "src/lib.rs" proc-macro = true [[example]] name = "derives" path = "examples/derives.rs" test = true harness = false [[example]] name = "make" path = "examples/make.rs" test = true harness = false [[example]] name = "make_var" path = "examples/make_var.rs" test = true harness = false [dependencies.proc-macro2] version = "1.0.61" [dependencies.quote] version = "1.0.28" [dependencies.syn] version = "2.0.21" features = ["extra-traits"] [dev-dependencies.bincode] version = "1.3.1" [dev-dependencies.serde] version = "1.0.110" features = ["derive"] default-features = false [dev-dependencies.serde_json] version = "1.0.45" zerovec-derive-0.11.0/Cargo.toml.orig000064400000000000000000000023301046102023000155210ustar 00000000000000# This file is part of ICU4X. For terms of use, please see the file # called LICENSE at the top level of the ICU4X source tree # (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). [package] name = "zerovec-derive" description = "Custom derive for the zerovec crate" version = "0.11.0" authors = ["Manish Goregaokar "] categories = ["rust-patterns", "memory-management", "caching", "no-std", "data-structures"] keywords = ["zerocopy", "serialization", "zero-copy", "serde"] edition.workspace = true license.workspace = true repository.workspace = true [package.metadata.workspaces] independent = true [lib] proc-macro = true path = "src/lib.rs" [dependencies] proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true, features = ["extra-traits"]} [dev-dependencies] zerovec = { path = "..", features = ["serde", "derive"] } serde = { workspace = true, features = ["derive"] } zerofrom = { path = "../../../utils/zerofrom" } bincode = { workspace = true } serde_json = { workspace = true } [[example]] name = "derives" harness = false test = true [[example]] name = "make" harness = false test = true [[example]] name = "make_var" harness = false test = true zerovec-derive-0.11.0/LICENSE000064400000000000000000000042231046102023000136420ustar 00000000000000UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE Copyright © 2020-2024 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. Permission is hereby granted, free of charge, to any person obtaining a copy of data files and any associated documentation (the "Data Files") or software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that either (a) this copyright and permission notice appear with all copies of the Data Files or Software, or (b) this copyright and permission notice appear in associated Documentation. THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. SPDX-License-Identifier: Unicode-3.0 — Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. zerovec-derive-0.11.0/README.md000064400000000000000000000006421046102023000141150ustar 00000000000000# zerovec-derive [![crates.io](https://img.shields.io/crates/v/zerovec-derive)](https://crates.io/crates/zerovec-derive) Proc macros for generating `ULE`, `VarULE` impls and types for the `zerovec` crate ## More Information For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x). zerovec-derive-0.11.0/examples/derives.rs000064400000000000000000000072121046102023000164630ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use zerovec::ule::AsULE; use zerovec::ule::EncodeAsVarULE; use zerovec::*; #[repr(C, packed)] #[derive(ule::ULE, Copy, Clone)] pub struct FooULE { a: u8, b: ::ULE, c: ::ULE, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct Foo { a: u8, b: u32, c: char, } impl AsULE for Foo { type ULE = FooULE; fn to_unaligned(self) -> FooULE { FooULE { a: self.a, b: self.b.to_unaligned(), c: self.c.to_unaligned(), } } fn from_unaligned(other: FooULE) -> Self { Self { a: other.a, b: AsULE::from_unaligned(other.b), c: AsULE::from_unaligned(other.c), } } } #[repr(C, packed)] #[derive(ule::VarULE)] pub struct RelationULE { /// This maps to (AndOr, Polarity, Operand), /// with the first bit mapping to AndOr (1 == And), the second bit /// to Polarity (1 == Positive), and the remaining bits to Operand /// encoded via Operand::encode. It is unsound for the Operand bits to /// not be a valid encoded Operand. andor_polarity_operand: u8, modulo: ::ULE, range_list: ZeroSlice, } #[derive(Clone, PartialEq, Debug)] pub struct Relation<'a> { andor_polarity_operand: u8, modulo: u32, range_list: ZeroVec<'a, Foo>, } unsafe impl EncodeAsVarULE for Relation<'_> { fn encode_var_ule_as_slices(&self, cb: impl FnOnce(&[&[u8]]) -> R) -> R { cb(&[ &[self.andor_polarity_operand], ule::ULE::slice_as_bytes(&[self.modulo.to_unaligned()]), self.range_list.as_bytes(), ]) } } impl RelationULE { pub fn as_relation(&self) -> Relation { Relation { andor_polarity_operand: self.andor_polarity_operand, modulo: u32::from_unaligned(self.modulo), range_list: self.range_list.as_zerovec(), } } } const TEST_SLICE: &[Foo] = &[ Foo { a: 101, b: 924, c: '⸘', }, Foo { a: 217, b: 4228, c: 'ə', }, Foo { a: 117, b: 9090, c: 'ø', }, ]; const TEST_SLICE2: &[Foo] = &[ Foo { a: 92, b: 4, c: 'å', }, Foo { a: 9, b: 49993, c: '±', }, ]; fn test_zerovec() { let zerovec: ZeroVec = TEST_SLICE.iter().copied().collect(); assert_eq!(zerovec, TEST_SLICE); let bytes = zerovec.as_bytes(); let reparsed: ZeroVec = ZeroVec::parse_bytes(bytes).expect("Parsing should succeed"); assert_eq!(reparsed, TEST_SLICE); } fn test_varzerovec() { let relation1 = Relation { andor_polarity_operand: 1, modulo: 5004, range_list: TEST_SLICE.iter().copied().collect(), }; let relation2 = Relation { andor_polarity_operand: 5, modulo: 909, range_list: TEST_SLICE2.iter().copied().collect(), }; let relations = &[relation1, relation2]; let vzv = VarZeroVec::<_>::from(relations); for (ule, stack) in vzv.iter().zip(relations.iter()) { assert_eq!(*stack, ule.as_relation()); } let bytes = vzv.as_bytes(); let recovered: VarZeroVec = VarZeroVec::parse_bytes(bytes).expect("Parsing should succeed"); for (ule, stack) in recovered.iter().zip(relations.iter()) { assert_eq!(*stack, ule.as_relation()); } } fn main() { test_zerovec(); test_varzerovec(); } zerovec-derive-0.11.0/examples/make.rs000064400000000000000000000057741046102023000157520ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use std::fmt::Debug; use ule::ULE; use zerovec::*; #[make_ule(StructULE)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] struct Struct { a: u8, b: u32, c: Option, } #[make_ule(HashedStructULE)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[zerovec::derive(Debug, Hash)] struct HashedStruct { a: u64, b: i16, c: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[make_ule(TupleStructULE)] struct TupleStruct(u8, char); #[make_ule(EnumULE)] #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] #[zerovec::derive(Debug, Hash)] enum Enum { A = 0, B = 1, D = 2, E = 3, FooBar = 4, F = 5, } #[make_ule(OutOfOrderMissingZeroEnumULE)] #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] #[allow(unused)] enum OutOfOrderMissingZeroEnum { E = 3, B = 1, FooBar = 4, D = 2, F = 5, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] #[make_ule(NoKVULE)] #[zerovec::skip_derive(ZeroMapKV)] struct NoKV(u8, char); #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[make_ule(NoOrdULE)] #[zerovec::skip_derive(ZeroMapKV, Ord)] struct NoOrd(u8, char); fn test_zerovec(slice: &[T]) { let zerovec: ZeroVec = slice.iter().copied().collect(); assert_eq!(zerovec, slice); let bytes = zerovec.as_bytes(); let name = std::any::type_name::(); let reparsed: ZeroVec = ZeroVec::parse_bytes(bytes).unwrap_or_else(|_| panic!("Parsing {name} should succeed")); assert_eq!(reparsed, slice); } fn main() { test_zerovec(TEST_SLICE_STRUCT); test_zerovec(TEST_SLICE_TUPLESTRUCT); test_zerovec(TEST_SLICE_ENUM); assert!(EnumULE::parse_bytes_to_slice(&[0]).is_ok()); assert!(EnumULE::parse_bytes_to_slice(&[1]).is_ok()); assert!(EnumULE::parse_bytes_to_slice(&[5]).is_ok()); assert!(EnumULE::parse_bytes_to_slice(&[6]).is_err()); assert!(OutOfOrderMissingZeroEnumULE::parse_bytes_to_slice(&[0]).is_err()); assert!(OutOfOrderMissingZeroEnumULE::parse_bytes_to_slice(&[1]).is_ok()); assert!(OutOfOrderMissingZeroEnumULE::parse_bytes_to_slice(&[5]).is_ok()); assert!(OutOfOrderMissingZeroEnumULE::parse_bytes_to_slice(&[6]).is_err()); } const TEST_SLICE_STRUCT: &[Struct] = &[ Struct { a: 101, b: 924, c: Some('⸘'), }, Struct { a: 217, b: 4228, c: Some('ə'), }, Struct { a: 117, b: 9090, c: Some('ø'), }, ]; const TEST_SLICE_TUPLESTRUCT: &[TupleStruct] = &[ TupleStruct(101, 'ř'), TupleStruct(76, '°'), TupleStruct(15, 'a'), ]; const TEST_SLICE_ENUM: &[Enum] = &[ Enum::A, Enum::FooBar, Enum::F, Enum::D, Enum::B, Enum::FooBar, Enum::E, ]; zerovec-derive-0.11.0/examples/make_var.rs000064400000000000000000000205761046102023000166170ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use std::borrow::Cow; use std::fmt::Debug; use zerofrom::ZeroFrom; use zerovec::{ule::AsULE, *}; #[make_varule(VarStructULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::derive(Serialize, Deserialize, Debug)] struct VarStruct<'a> { a: u32, b: char, #[serde(borrow)] c: Cow<'a, str>, } #[make_varule(VarStructOutOfOrderULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::derive(Serialize, Deserialize, Debug)] struct VarStructOutOfOrder<'a> { a: u32, #[serde(borrow)] b: Cow<'a, str>, c: char, d: u8, } #[make_varule(VarTupleStructULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::derive(Serialize, Deserialize, Debug)] struct VarTupleStruct<'a>(u32, char, #[serde(borrow)] VarZeroVec<'a, str>); #[make_varule(NoKVULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::skip_derive(ZeroMapKV)] #[zerovec::derive(Serialize, Deserialize, Debug)] struct NoKV<'a>(u32, char, #[serde(borrow)] VarZeroVec<'a, str>); #[make_varule(NoOrdULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::skip_derive(ZeroMapKV, Ord)] #[zerovec::derive(Serialize, Deserialize, Debug)] struct NoOrd<'a>(u32, char, #[serde(borrow)] VarZeroVec<'a, str>); #[make_varule(MultiFieldStructULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::derive(Serialize, Deserialize, Debug)] struct MultiFieldStruct<'a> { a: u32, b: char, #[serde(borrow)] c: Cow<'a, str>, d: u8, #[serde(borrow)] e: Cow<'a, str>, f: char, } #[make_varule(MultiFieldConsecutiveStructULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::derive(Serialize, Deserialize, Debug)] #[zerovec::format(zerovec::vecs::Index8)] struct MultiFieldConsecutiveStruct<'a> { #[serde(borrow)] a: Cow<'a, str>, #[serde(borrow)] b: Cow<'a, str>, #[serde(borrow)] c: Cow<'a, str>, #[serde(borrow)] d: Cow<'a, str>, } #[make_varule(CustomVarFieldULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::derive(Serialize, Deserialize, Debug)] struct CustomVarField<'a> { #[zerovec::varule(MultiFieldStructULE)] #[serde(borrow)] a: MultiFieldStruct<'a>, b: u32, } #[make_varule(MultiFieldTupleULE)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)] #[zerovec::derive(Serialize, Deserialize, Debug)] #[zerovec::format(zerovec::vecs::Index32)] struct MultiFieldTuple<'a>( u8, char, #[serde(borrow)] VarZeroVec<'a, str>, #[serde(borrow)] VarZeroVec<'a, [u8]>, #[serde(borrow)] Cow<'a, str>, ); /// The `assert` function should have the body `|(stack, zero)| assert_eq!(stack, &U::zero_from(&zero))` /// /// We cannot do this internally because we technically need a different `U` with a shorter lifetime here /// which would require some gnarly lifetime bounds and perhaps a Yoke dependency. This is just a test, so it's /// not important to get this 100% perfect fn assert_zerovec(slice: &[U], assert: F) where T: ule::VarULE + ?Sized + serde::Serialize, U: ule::EncodeAsVarULE + serde::Serialize, F: Fn(&U, &T), for<'a> Box: serde::Deserialize<'a>, for<'a> &'a T: serde::Deserialize<'a>, T: PartialEq + Debug, { let varzerovec: VarZeroVec = slice.into(); assert_eq!(varzerovec.len(), slice.len()); for (stack, zero) in slice.iter().zip(varzerovec.iter()) { assert(stack, zero) } let bytes = varzerovec.as_bytes(); let name = std::any::type_name::(); let reparsed: VarZeroVec = VarZeroVec::parse_bytes(bytes) .unwrap_or_else(|_| panic!("Parsing VarZeroVec<{name}> should succeed")); assert_eq!(reparsed.len(), slice.len()); for (stack, zero) in slice.iter().zip(reparsed.iter()) { assert(stack, zero) } let bincode = bincode::serialize(&varzerovec).unwrap(); let deserialized: VarZeroVec = bincode::deserialize(&bincode).unwrap(); for (stack, zero) in slice.iter().zip(deserialized.iter()) { assert(stack, zero) } let json_slice = serde_json::to_string(&slice).unwrap(); let json_vzv = serde_json::to_string(&varzerovec).unwrap(); assert_eq!(json_slice, json_vzv); let deserialized: VarZeroVec = serde_json::from_str(&json_vzv).unwrap(); for (stack, zero) in slice.iter().zip(deserialized.iter()) { assert(stack, zero) } if let Some(first) = varzerovec.get(0) { let bincode = bincode::serialize(first).unwrap(); let deserialized: &T = bincode::deserialize(&bincode).unwrap(); let deserialized_box: Box = bincode::deserialize(&bincode).unwrap(); assert_eq!( first, deserialized, "Single element roundtrips with bincode" ); assert_eq!( first, &*deserialized_box, "Single element roundtrips with bincode" ); let json = serde_json::to_string(first).unwrap(); let deserialized: Box = serde_json::from_str(&json).unwrap(); assert_eq!( first, &*deserialized, "Single element roundtrips with serde" ); } } fn main() { assert_zerovec::(TEST_VARSTRUCTS, |stack, zero| { assert_eq!(stack, &VarStruct::zero_from(zero)) }); assert_zerovec::(TEST_MULTIFIELD, |stack, zero| { assert_eq!(stack, &MultiFieldStruct::zero_from(zero)) }); assert_zerovec::( TEST_MULTICONSECUTIVE, |stack, zero| assert_eq!(stack, &MultiFieldConsecutiveStruct::zero_from(zero)), ); let vartuples = &[ VarTupleStruct(101, 'ø', TEST_STRINGS1.into()), VarTupleStruct(9499, '⸘', TEST_STRINGS2.into()), VarTupleStruct(3478, '月', TEST_STRINGS3.into()), ]; assert_zerovec::(vartuples, |stack, zero| { assert_eq!(stack, &VarTupleStruct::zero_from(zero)) }); // Test that all fields are accessible on a type using multifieldule let multi_ule = ule::encode_varule_to_box(&TEST_MULTIFIELD[0]); assert_eq!(multi_ule.a, TEST_MULTIFIELD[0].a.to_unaligned()); assert_eq!(multi_ule.b, TEST_MULTIFIELD[0].b.to_unaligned()); assert_eq!(multi_ule.c(), TEST_MULTIFIELD[0].c); assert_eq!(multi_ule.d, TEST_MULTIFIELD[0].d); assert_eq!(multi_ule.e(), TEST_MULTIFIELD[0].e); assert_eq!(multi_ule.f, TEST_MULTIFIELD[0].f.to_unaligned()); } const TEST_VARSTRUCTS: &[VarStruct<'static>] = &[ VarStruct { a: 101, b: 'ø', c: Cow::Borrowed("testīng strīng"), }, VarStruct { a: 9499, b: '⸘', c: Cow::Borrowed("a diffərənt ştring"), }, VarStruct { a: 3478, b: '月', c: Cow::Borrowed("好多嘅 string"), }, ]; const TEST_STRINGS1: &[&str] = &["foo", "bar", "baz"]; const TEST_STRINGS2: &[&str] = &["hellø", "wørłd"]; const TEST_STRINGS3: &[&str] = &["łořem", "ɨpsu₥"]; const TEST_MULTIFIELD: &[MultiFieldStruct<'static>] = &[ MultiFieldStruct { a: 101, b: 'ø', c: Cow::Borrowed("testīng strīng"), d: 8, e: Cow::Borrowed("another testīng strīng"), f: 'å', }, MultiFieldStruct { a: 9499, b: '⸘', c: Cow::Borrowed("a diffərənt ştring"), d: 120, e: Cow::Borrowed("a diffərənt testing ştring"), f: 'ł', }, MultiFieldStruct { a: 3478, b: '月', c: Cow::Borrowed("好多嘅 string"), d: 89, e: Cow::Borrowed("many 好多嘅 string"), f: 'ə', }, ]; const TEST_MULTICONSECUTIVE: &[MultiFieldConsecutiveStruct<'static>] = &[MultiFieldConsecutiveStruct { a: Cow::Borrowed("one"), b: Cow::Borrowed("2"), c: Cow::Borrowed("three"), d: Cow::Borrowed("four"), }]; zerovec-derive-0.11.0/src/lib.rs000064400000000000000000000033121046102023000145360ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). //! Proc macros for generating `ULE`, `VarULE` impls and types for the `zerovec` crate use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput, Ident}; mod make_ule; mod make_varule; pub(crate) mod ule; mod utils; mod varule; /// Full docs for this proc macro can be found on the [`zerovec`](docs.rs/zerovec) crate. #[proc_macro_derive(ULE)] pub fn ule_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); TokenStream::from(ule::derive_impl(&input)) } /// Full docs for this proc macro can be found on the [`zerovec`](docs.rs/zerovec) crate. #[proc_macro_derive(VarULE)] pub fn varule_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); TokenStream::from(varule::derive_impl(&input, None)) } /// Full docs for this proc macro can be found on the [`zerovec`](docs.rs/zerovec) crate. #[proc_macro_attribute] pub fn make_ule(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as DeriveInput); let attr = parse_macro_input!(attr as Ident); TokenStream::from(make_ule::make_ule_impl(attr, input)) } /// Full docs for this proc macro can be found on the [`zerovec`](docs.rs/zerovec) crate. #[proc_macro_attribute] pub fn make_varule(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as DeriveInput); let attr = parse_macro_input!(attr as Ident); TokenStream::from(make_varule::make_varule_impl(attr, input)) } zerovec-derive-0.11.0/src/make_ule.rs000064400000000000000000000306201046102023000155540ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use proc_macro2::TokenStream as TokenStream2; use quote::quote; use crate::utils::{self, FieldInfo, ZeroVecAttrs}; use std::collections::HashSet; use syn::spanned::Spanned; use syn::{parse_quote, Data, DataEnum, DataStruct, DeriveInput, Error, Expr, Fields, Ident, Lit}; pub fn make_ule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream2 { if input.generics.type_params().next().is_some() || input.generics.lifetimes().next().is_some() || input.generics.const_params().next().is_some() { return Error::new( input.generics.span(), "#[make_ule] must be applied to a struct without any generics", ) .to_compile_error(); } let sp = input.span(); let attrs = match utils::extract_attributes_common(&mut input.attrs, sp, false) { Ok(val) => val, Err(e) => return e.to_compile_error(), }; let name = &input.ident; let ule_stuff = match input.data { Data::Struct(ref s) => make_ule_struct_impl(name, &ule_name, &input, s, &attrs), Data::Enum(ref e) => make_ule_enum_impl(name, &ule_name, &input, e, &attrs), _ => { return Error::new(input.span(), "#[make_ule] must be applied to a struct") .to_compile_error(); } }; let zmkv = if attrs.skip_kv { quote!() } else { quote!( impl<'a> zerovec::maps::ZeroMapKV<'a> for #name { type Container = zerovec::ZeroVec<'a, #name>; type Slice = zerovec::ZeroSlice<#name>; type GetType = #ule_name; type OwnedType = #name; } ) }; let maybe_debug = if attrs.debug { quote!( impl core::fmt::Debug for #ule_name { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self); <#name as core::fmt::Debug>::fmt(&this, f) } } ) } else { quote!() }; quote!( #input #ule_stuff #maybe_debug #zmkv ) } fn make_ule_enum_impl( name: &Ident, ule_name: &Ident, input: &DeriveInput, enu: &DataEnum, attrs: &ZeroVecAttrs, ) -> TokenStream2 { // We could support more int reprs in the future if needed if !utils::ReprInfo::compute(&input.attrs).u8 { return Error::new( input.span(), "#[make_ule] can only be applied to #[repr(u8)] enums", ) .to_compile_error(); } if enu.variants.is_empty() { return Error::new(input.span(), "#[make_ule] cannot be applied to empty enums") .to_compile_error(); } // the smallest discriminant seen let mut min = None; // the largest discriminant seen let mut max = None; // Discriminants that have not been found in series (we might find them later) let mut not_found = HashSet::new(); for (i, variant) in enu.variants.iter().enumerate() { if !matches!(variant.fields, Fields::Unit) { // This can be supported in the future, see zerovec/design_doc.md return Error::new( variant.span(), "#[make_ule] can only be applied to enums with dataless variants", ) .to_compile_error(); } if let Some((_, ref discr)) = variant.discriminant { if let Some(n) = get_expr_int(discr) { let n = match u8::try_from(n) { Ok(n) => n, Err(_) => { return Error::new( variant.span(), "#[make_ule] only supports discriminants from 0 to 255", ) .to_compile_error(); } }; match min { Some(x) if x < n => {} _ => { min = Some(n); } } match max { Some(x) if x >= n => {} _ => { let old_max = max.unwrap_or(0u8); for missing in (old_max + 1)..n { not_found.insert(missing); } max = Some(n); } } not_found.remove(&n); // We require explicit discriminants so that it is clear that reordering // fields would be a breaking change. Furthermore, using explicit discriminants helps ensure that // platform-specific C ABI choices do not matter. // We could potentially add in explicit discriminants on the user's behalf in the future, or support // more complicated sets of explicit discriminant values. if n as usize != i {} } else { return Error::new( discr.span(), "#[make_ule] must be applied to enums with explicit integer discriminants", ) .to_compile_error(); } } else { return Error::new( variant.span(), "#[make_ule] must be applied to enums with explicit discriminants", ) .to_compile_error(); } } let not_found = not_found.iter().collect::>(); let min = min.unwrap(); let max = max.unwrap(); if not_found.len() > min as usize { return Error::new(input.span(), format!("#[make_ule] must be applied to enums with discriminants \ filling the range from a minimum to a maximum; could not find {not_found:?}")) .to_compile_error(); } let maybe_ord_derives = if attrs.skip_ord { quote!() } else { quote!(#[derive(Ord, PartialOrd)]) }; let vis = &input.vis; let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}"); // Safety (based on the safety checklist on the ULE trait): // 1. ULE type does not include any uninitialized or padding bytes. // (achieved by `#[repr(transparent)]` on a type that satisfies this invariant // 2. ULE type is aligned to 1 byte. // (achieved by `#[repr(transparent)]` on a type that satisfies this invariant) // 3. The impl of validate_bytes() returns an error if any byte is not valid. // (Guarantees that the byte is in range of the corresponding enum.) // 4. The impl of validate_bytes() returns an error if there are extra bytes. // (This does not happen since we are backed by 1 byte.) // 5. The other ULE methods use the default impl. // 6. ULE type byte equality is semantic equality quote!( #[repr(transparent)] #[derive(Copy, Clone, PartialEq, Eq)] #maybe_ord_derives #[doc = #doc] #vis struct #ule_name(u8); unsafe impl zerovec::ule::ULE for #ule_name { #[inline] fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> { for byte in bytes { if *byte < #min || *byte > #max { return Err(zerovec::ule::UleError::parse::()) } } Ok(()) } } impl zerovec::ule::AsULE for #name { type ULE = #ule_name; fn to_unaligned(self) -> Self::ULE { // safety: the enum is repr(u8) and can be cast to a u8 unsafe { ::core::mem::transmute(self) } } fn from_unaligned(other: Self::ULE) -> Self { // safety: the enum is repr(u8) and can be cast from a u8, // and `#ule_name` guarantees a valid value for this enum. unsafe { ::core::mem::transmute(other) } } } impl #name { /// Attempt to construct the value from its corresponding integer, /// returning `None` if not possible pub(crate) fn new_from_u8(value: u8) -> Option { if value <= #max { unsafe { Some(::core::mem::transmute(value)) } } else { None } } } ) } fn get_expr_int(e: &Expr) -> Option { if let Ok(Lit::Int(ref i)) = syn::parse2(quote!(#e)) { return i.base10_parse().ok(); } None } fn make_ule_struct_impl( name: &Ident, ule_name: &Ident, input: &DeriveInput, struc: &DataStruct, attrs: &ZeroVecAttrs, ) -> TokenStream2 { if struc.fields.iter().next().is_none() { return Error::new( input.span(), "#[make_ule] must be applied to a non-empty struct", ) .to_compile_error(); } let sized_fields = FieldInfo::make_list(struc.fields.iter()); let field_inits = crate::ule::make_ule_fields(&sized_fields); let field_inits = utils::wrap_field_inits(&field_inits, &struc.fields); let semi = utils::semi_for(&struc.fields); let repr_attr = utils::repr_for(&struc.fields); let vis = &input.vis; let doc = format!("[`ULE`](zerovec::ule::ULE) type for [`{name}`]"); let ule_struct: DeriveInput = parse_quote!( #[repr(#repr_attr)] #[derive(Copy, Clone, PartialEq, Eq)] #[doc = #doc] // We suppress the `missing_docs` lint for the fields of the struct. #[allow(missing_docs)] #vis struct #ule_name #field_inits #semi ); let derived = crate::ule::derive_impl(&ule_struct); let mut as_ule_conversions = vec![]; let mut from_ule_conversions = vec![]; for (i, field) in struc.fields.iter().enumerate() { let ty = &field.ty; let i = syn::Index::from(i); if let Some(ref ident) = field.ident { as_ule_conversions .push(quote!(#ident: <#ty as zerovec::ule::AsULE>::to_unaligned(self.#ident))); from_ule_conversions.push( quote!(#ident: <#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#ident)), ); } else { as_ule_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::to_unaligned(self.#i))); from_ule_conversions .push(quote!(<#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#i))); }; } let as_ule_conversions = utils::wrap_field_inits(&as_ule_conversions, &struc.fields); let from_ule_conversions = utils::wrap_field_inits(&from_ule_conversions, &struc.fields); let asule_impl = quote!( impl zerovec::ule::AsULE for #name { type ULE = #ule_name; fn to_unaligned(self) -> Self::ULE { #ule_name #as_ule_conversions } fn from_unaligned(unaligned: Self::ULE) -> Self { Self #from_ule_conversions } } ); let maybe_ord_impls = if attrs.skip_ord { quote!() } else { quote!( impl core::cmp::PartialOrd for #ule_name { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl core::cmp::Ord for #ule_name { fn cmp(&self, other: &Self) -> core::cmp::Ordering { let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self); let other = <#name as zerovec::ule::AsULE>::from_unaligned(*other); <#name as core::cmp::Ord>::cmp(&this, &other) } } ) }; let maybe_hash = if attrs.hash { quote!( #[allow(clippy::derive_hash_xor_eq)] impl core::hash::Hash for #ule_name { fn hash(&self, state: &mut H) where H: core::hash::Hasher { state.write(<#ule_name as zerovec::ule::ULE>::slice_as_bytes(&[*self])); } } ) } else { quote!() }; quote!( #asule_impl #ule_struct #derived #maybe_ord_impls #maybe_hash ) } zerovec-derive-0.11.0/src/make_varule.rs000064400000000000000000000761471046102023000163030ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::utils::{self, FieldInfo}; use proc_macro2::Span; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; use syn::spanned::Spanned; use syn::{ parse_quote, Data, DeriveInput, Error, Field, Fields, GenericArgument, Ident, Lifetime, PathArguments, Type, TypePath, }; pub fn make_varule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream2 { if input.generics.type_params().next().is_some() || input.generics.const_params().next().is_some() || input.generics.lifetimes().count() > 1 { return Error::new( input.generics.span(), "#[make_varule] must be applied to a struct without any type or const parameters and at most one lifetime", ) .to_compile_error(); } let sp = input.span(); let attrs = match utils::extract_attributes_common(&mut input.attrs, sp, true) { Ok(val) => val, Err(e) => return e.to_compile_error(), }; let lt = input.generics.lifetimes().next(); if let Some(lt) = lt { if lt.colon_token.is_some() || !lt.bounds.is_empty() { return Error::new( input.generics.span(), "#[make_varule] must be applied to a struct without lifetime bounds", ) .to_compile_error(); } } let lt = lt.map(|l| &l.lifetime); let name = &input.ident; let input_span = input.span(); let fields = match input.data { Data::Struct(ref mut s) => &mut s.fields, _ => { return Error::new(input.span(), "#[make_varule] must be applied to a struct") .to_compile_error(); } }; if fields.is_empty() { return Error::new( input.span(), "#[make_varule] must be applied to a struct with at least one field", ) .to_compile_error(); } let mut sized_fields = vec![]; let mut unsized_fields = vec![]; let mut custom_varule_idents = vec![]; for field in fields.iter_mut() { match utils::extract_field_attributes(&mut field.attrs) { Ok(i) => custom_varule_idents.push(i), Err(e) => return e.to_compile_error(), } } for (i, field) in fields.iter().enumerate() { match UnsizedField::new(field, i, custom_varule_idents[i].clone()) { Ok(o) => unsized_fields.push(o), Err(_) => sized_fields.push(FieldInfo::new_for_field(field, i)), } } if unsized_fields.is_empty() { let last_field_index = fields.len() - 1; let last_field = fields.iter().next_back().unwrap(); let e = UnsizedField::new( last_field, last_field_index, custom_varule_idents[last_field_index].clone(), ) .unwrap_err(); return Error::new(last_field.span(), e).to_compile_error(); } if unsized_fields[0].field.index != fields.len() - unsized_fields.len() && unsized_fields[0].field.field.ident.is_none() { return Error::new( unsized_fields.first().unwrap().field.field.span(), "#[make_varule] requires its unsized fields to be at the end for tuple structs", ) .to_compile_error(); } let unsized_field_info = UnsizedFields::new(unsized_fields, attrs.vzv_format); let mut field_inits = crate::ule::make_ule_fields(&sized_fields); let last_field_ule = unsized_field_info.varule_ty(); let setter = unsized_field_info.varule_setter(); let vis = &unsized_field_info.varule_vis(); field_inits.push(quote!(#vis #setter #last_field_ule)); let semi = utils::semi_for(fields); let repr_attr = utils::repr_for(fields); let field_inits = utils::wrap_field_inits(&field_inits, fields); let vis = &input.vis; let doc = format!( "[`VarULE`](zerovec::ule::VarULE) type for [`{name}`]. See [`{name}`] for documentation." ); let varule_struct: DeriveInput = parse_quote!( #[repr(#repr_attr)] #[doc = #doc] #[allow(missing_docs)] #vis struct #ule_name #field_inits #semi ); let derived = crate::varule::derive_impl(&varule_struct, unsized_field_info.varule_validator()); let maybe_lt_bound = lt.as_ref().map(|lt| quote!(<#lt>)); let encode_impl = make_encode_impl( &sized_fields, &unsized_field_info, name, &ule_name, &maybe_lt_bound, ); let zf_and_from_impl = make_zf_and_from_impl( &sized_fields, &unsized_field_info, fields, name, &ule_name, lt, input_span, attrs.skip_from, ); let eq_impl = quote!( impl core::cmp::PartialEq for #ule_name { fn eq(&self, other: &Self) -> bool { // The VarULE invariants allow us to assume that equality is byte equality // in non-safety-critical contexts ::as_bytes(&self) == ::as_bytes(&other) } } impl core::cmp::Eq for #ule_name {} ); let zerofrom_fq_path = quote!(<#name as zerovec::__zerovec_internal_reexport::ZeroFrom<#ule_name>>); let maybe_ord_impls = if attrs.skip_ord { quote!() } else { quote!( impl core::cmp::PartialOrd for #ule_name { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl core::cmp::Ord for #ule_name { fn cmp(&self, other: &Self) -> core::cmp::Ordering { let this = #zerofrom_fq_path::zero_from(self); let other = #zerofrom_fq_path::zero_from(other); <#name as core::cmp::Ord>::cmp(&this, &other) } } ) }; let maybe_debug = if attrs.debug { quote!( impl core::fmt::Debug for #ule_name { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let this = #zerofrom_fq_path::zero_from(self); <#name as core::fmt::Debug>::fmt(&this, f) } } ) } else { quote!() }; let maybe_toowned = if !attrs.skip_toowned { quote!( impl zerovec::__zerovec_internal_reexport::borrow::ToOwned for #ule_name { type Owned = zerovec::__zerovec_internal_reexport::boxed::Box; fn to_owned(&self) -> Self::Owned { zerovec::ule::encode_varule_to_box(self) } } ) } else { quote!() }; let zmkv = if attrs.skip_kv { quote!() } else { quote!( impl<'a> zerovec::maps::ZeroMapKV<'a> for #ule_name { type Container = zerovec::VarZeroVec<'a, #ule_name>; type Slice = zerovec::VarZeroSlice<#ule_name>; type GetType = #ule_name; type OwnedType = zerovec::__zerovec_internal_reexport::boxed::Box<#ule_name>; } ) }; let serde_path = quote!(zerovec::__zerovec_internal_reexport::serde); let maybe_ser = if attrs.serialize { quote!( impl #serde_path::Serialize for #ule_name { fn serialize(&self, serializer: S) -> Result where S: #serde_path::Serializer { if serializer.is_human_readable() { let this = #zerofrom_fq_path::zero_from(self); <#name as #serde_path::Serialize>::serialize(&this, serializer) } else { serializer.serialize_bytes(zerovec::ule::VarULE::as_bytes(self)) } } } ) } else { quote!() }; let deserialize_error = format!("&{ule_name} can only deserialize in zero-copy ways"); let maybe_de = if attrs.deserialize { quote!( impl<'de> #serde_path::Deserialize<'de> for zerovec::__zerovec_internal_reexport::boxed::Box<#ule_name> { fn deserialize(deserializer: D) -> Result where D: #serde_path::Deserializer<'de> { if deserializer.is_human_readable() { let this = <#name as #serde_path::Deserialize>::deserialize(deserializer)?; Ok(zerovec::ule::encode_varule_to_box(&this)) } else { // This branch should usually not be hit, since Cow-like use cases will hit the Deserialize impl for &'a ULEType instead. let deserialized = <& #ule_name>::deserialize(deserializer)?; Ok(zerovec::ule::VarULE::to_boxed(deserialized)) } } } impl<'a, 'de: 'a> #serde_path::Deserialize<'de> for &'a #ule_name { fn deserialize(deserializer: D) -> Result where D: #serde_path::Deserializer<'de> { if !deserializer.is_human_readable() { let bytes = <&[u8]>::deserialize(deserializer)?; <#ule_name as zerovec::ule::VarULE>::parse_bytes(bytes).map_err(#serde_path::de::Error::custom) } else { Err(#serde_path::de::Error::custom(#deserialize_error)) } } } ) } else { quote!() }; let maybe_hash = if attrs.hash { quote!( #[allow(clippy::derive_hash_xor_eq)] impl core::hash::Hash for #ule_name { fn hash(&self, state: &mut H) where H: core::hash::Hasher { state.write(<#ule_name as zerovec::ule::VarULE>::as_bytes(&self)); } } ) } else { quote!() }; let maybe_multi_getters = if let Some(getters) = unsized_field_info.maybe_multi_getters() { quote! { impl #ule_name { #getters } } } else { quote!() }; quote!( #input #varule_struct #maybe_multi_getters #encode_impl #zf_and_from_impl #derived #maybe_ord_impls #eq_impl #zmkv #maybe_ser #maybe_de #maybe_debug #maybe_toowned #maybe_hash ) } #[allow(clippy::too_many_arguments)] // Internal function. Could refactor later to use some kind of context type. fn make_zf_and_from_impl( sized_fields: &[FieldInfo], unsized_field_info: &UnsizedFields, fields: &Fields, name: &Ident, ule_name: &Ident, maybe_lt: Option<&Lifetime>, span: Span, skip_from: bool, ) -> TokenStream2 { if !unsized_field_info.has_zf() { return quote!(); } let lt = if let Some(ref lt) = maybe_lt { lt } else { return Error::new( span, "Can only generate ZeroFrom impls for types with lifetimes", ) .to_compile_error(); }; let mut field_inits = sized_fields .iter() .map(|f| { let ty = &f.field.ty; let accessor = &f.accessor; let setter = f.setter(); quote!(#setter <#ty as zerovec::ule::AsULE>::from_unaligned(other.#accessor)) }) .collect::>(); unsized_field_info.push_zf_setters(lt, &mut field_inits); let field_inits = utils::wrap_field_inits(&field_inits, fields); let zerofrom_trait = quote!(zerovec::__zerovec_internal_reexport::ZeroFrom); let maybe_from = if skip_from { quote!() } else { quote!( impl<#lt> From<&#lt #ule_name> for #name<#lt> { fn from(other: &#lt #ule_name) -> Self { >::zero_from(other) } } ) }; quote!( impl <#lt> #zerofrom_trait <#lt, #ule_name> for #name <#lt> { fn zero_from(other: &#lt #ule_name) -> Self { Self #field_inits } } #maybe_from ) } fn make_encode_impl( sized_fields: &[FieldInfo], unsized_field_info: &UnsizedFields, name: &Ident, ule_name: &Ident, maybe_lt_bound: &Option, ) -> TokenStream2 { let mut lengths = vec![]; for field in sized_fields { let ty = &field.field.ty; lengths.push(quote!(::core::mem::size_of::<<#ty as zerovec::ule::AsULE>::ULE>())); } let (encoders, remaining_offset) = utils::generate_per_field_offsets( sized_fields, true, |field, prev_offset_ident, size_ident| { let ty = &field.field.ty; let accessor = &field.accessor; quote!( #[allow(clippy::indexing_slicing)] // generate_per_field_offsets produces valid indices let out = &mut dst[#prev_offset_ident .. #prev_offset_ident + #size_ident]; let unaligned = zerovec::ule::AsULE::to_unaligned(self.#accessor); let unaligned_slice = &[unaligned]; let src = <<#ty as zerovec::ule::AsULE>::ULE as zerovec::ule::ULE>::slice_as_bytes(unaligned_slice); out.copy_from_slice(src); ) }, ); let last_encode_len = unsized_field_info.encode_len(); let last_encode_write = unsized_field_info.encode_write(quote!(out)); quote!( unsafe impl #maybe_lt_bound zerovec::ule::EncodeAsVarULE<#ule_name> for #name #maybe_lt_bound { // Safety: unimplemented as the other two are implemented fn encode_var_ule_as_slices(&self, cb: impl FnOnce(&[&[u8]]) -> R) -> R { unreachable!("other two methods implemented") } // Safety: returns the total length of the ULE form by adding up the lengths of each element's ULE forms fn encode_var_ule_len(&self) -> usize { #(#lengths +)* #last_encode_len } // Safety: converts each element to ULE form and writes them in sequence fn encode_var_ule_write(&self, mut dst: &mut [u8]) { debug_assert_eq!(self.encode_var_ule_len(), dst.len()); #encoders #[allow(clippy::indexing_slicing)] // generate_per_field_offsets produces valid remainder let out = &mut dst[#remaining_offset..]; #last_encode_write } } // This second impl exists to allow for using EncodeAsVarULE without cloning // // A blanket impl cannot exist without coherence issues unsafe impl #maybe_lt_bound zerovec::ule::EncodeAsVarULE<#ule_name> for &'_ #name #maybe_lt_bound { // Safety: unimplemented as the other two are implemented fn encode_var_ule_as_slices(&self, cb: impl FnOnce(&[&[u8]]) -> R) -> R { unreachable!("other two methods implemented") } // Safety: returns the total length of the ULE form by adding up the lengths of each element's ULE forms fn encode_var_ule_len(&self) -> usize { (**self).encode_var_ule_len() } // Safety: converts each element to ULE form and writes them in sequence fn encode_var_ule_write(&self, mut dst: &mut [u8]) { (**self).encode_var_ule_write(dst) } } ) } /// Represents a VarULE-compatible type that would typically /// be found behind a `Cow<'a, _>` in the last field, and is represented /// roughly the same in owned and borrowed versions #[derive(Copy, Clone, Debug)] enum OwnULETy<'a> { /// [T] where T: AsULE Slice(&'a Type), /// str Str, } /// Represents the type of the last field of the struct #[derive(Clone, Debug)] enum UnsizedFieldKind<'a> { Cow(OwnULETy<'a>), ZeroVec(&'a Type), VarZeroVec(&'a Type), /// Custom VarULE type, and the identifier corresponding to the VarULE type Custom(&'a TypePath, Ident), // Generally you should be using the above ones for maximum zero-copy, but these will still work Growable(OwnULETy<'a>), Boxed(OwnULETy<'a>), Ref(OwnULETy<'a>), } #[derive(Clone, Debug)] struct UnsizedField<'a> { kind: UnsizedFieldKind<'a>, field: FieldInfo<'a>, } struct UnsizedFields<'a> { fields: Vec>, format_param: TokenStream2, } impl<'a> UnsizedFields<'a> { /// The format_param is an optional tokenstream describing a VZVFormat argument needed by MultiFieldsULE fn new(fields: Vec>, format_param: Option) -> Self { assert!(!fields.is_empty(), "Must have at least one unsized field"); let format_param = format_param.unwrap_or_else(|| quote!(zerovec::vecs::Index16)); Self { fields, format_param, } } // Get the corresponding VarULE type that can store all of these fn varule_ty(&self) -> TokenStream2 { let len = self.fields.len(); let format_param = &self.format_param; if len == 1 { self.fields[0].kind.varule_ty() } else { quote!(zerovec::ule::MultiFieldsULE::<#len, #format_param>) } } // Get the accessor field name in the VarULE type fn varule_accessor(&self) -> TokenStream2 { if self.fields.len() == 1 { self.fields[0].field.accessor.clone() } else if self.fields[0].field.field.ident.is_some() { quote!(unsized_fields) } else { // first unsized field self.fields[0].field.accessor.clone() } } // Get the setter for this type for use in struct definition/creation syntax fn varule_setter(&self) -> TokenStream2 { if self.fields.len() == 1 { self.fields[0].field.setter() } else if self.fields[0].field.field.ident.is_some() { quote!(unsized_fields: ) } else { quote!() } } fn varule_vis(&self) -> TokenStream2 { if self.fields.len() == 1 { self.fields[0].field.field.vis.to_token_stream() } else { // Always private quote!() } } // Check if the type has a ZeroFrom impl fn has_zf(&self) -> bool { self.fields.iter().all(|f| f.kind.has_zf()) } // Takes all unsized fields on self and encodes them into a byte slice `out` fn encode_write(&self, out: TokenStream2) -> TokenStream2 { let len = self.fields.len(); let format_param = &self.format_param; if len == 1 { self.fields[0].encode_func(quote!(encode_var_ule_write), quote!(#out)) } else { let mut lengths = vec![]; let mut writers = vec![]; for (i, field) in self.fields.iter().enumerate() { lengths.push(field.encode_func(quote!(encode_var_ule_len), quote!())); let (encodeable_ty, encodeable) = field.encodeable_tokens(); let varule_ty = field.kind.varule_ty(); writers .push(quote!(multi.set_field_at::<#varule_ty, #encodeable_ty>(#i, #encodeable))) } quote!( let lengths = [#(#lengths),*]; // Todo: index type should be settable by attribute let mut multi = zerovec::ule::MultiFieldsULE::<#len, #format_param>::new_from_lengths_partially_initialized(lengths, #out); unsafe { #(#writers;)* } ) } } // Takes all unsized fields on self and returns the length needed for encoding into a byte slice fn encode_len(&self) -> TokenStream2 { let len = self.fields.len(); let format_param = &self.format_param; if len == 1 { self.fields[0].encode_func(quote!(encode_var_ule_len), quote!()) } else { let mut lengths = vec![]; for field in self.fields.iter() { lengths.push(field.encode_func(quote!(encode_var_ule_len), quote!())); } // Todo: index type should be settable by attribute quote!(zerovec::ule::MultiFieldsULE::<#len, #format_param>::compute_encoded_len_for([#(#lengths),*])) } } /// Constructs ZeroFrom setters for each field of the stack type fn push_zf_setters(&self, lt: &Lifetime, field_inits: &mut Vec) { let zerofrom_trait = quote!(zerovec::__zerovec_internal_reexport::ZeroFrom); if self.fields.len() == 1 { let accessor = self.fields[0].field.accessor.clone(); let setter = self.fields[0].field.setter(); let last_field_ty = &self.fields[0].field.field.ty; let last_field_ule_ty = self.fields[0].kind.varule_ty(); field_inits.push(quote!(#setter <#last_field_ty as #zerofrom_trait <#lt, #last_field_ule_ty>>::zero_from(&other.#accessor) )); } else { for field in self.fields.iter() { let setter = field.field.setter(); let getter = field.field.getter(); let field_ty = &field.field.field.ty; let field_ule_ty = field.kind.varule_ty(); field_inits.push(quote!(#setter <#field_ty as #zerofrom_trait <#lt, #field_ule_ty>>::zero_from(&other.#getter()) )); } } } fn maybe_multi_getters(&self) -> Option { if self.fields.len() == 1 { None } else { let multi_accessor = self.varule_accessor(); let field_getters = self.fields.iter().enumerate().map(|(i, field)| { let getter = field.field.getter(); let field_ule_ty = field.kind.varule_ty(); let doc_name = field.field.getter_doc_name(); let doc = format!("Access the VarULE type behind {doc_name}"); quote!( #[doc = #doc] pub fn #getter<'a>(&'a self) -> &'a #field_ule_ty { unsafe { self.#multi_accessor.get_field::<#field_ule_ty>(#i) } } ) }); Some(quote!(#(#field_getters)*)) } } /// In case this needs custom validation code, return it /// /// The code will validate a variable known as `last_field_bytes` fn varule_validator(&self) -> Option { let len = self.fields.len(); let format_param = &self.format_param; if len == 1 { None } else { let mut validators = vec![]; for (i, field) in self.fields.iter().enumerate() { let varule_ty = field.kind.varule_ty(); validators.push(quote!(multi.validate_field::<#varule_ty>(#i)?;)); } Some(quote!( let multi = zerovec::ule::MultiFieldsULE::<#len, #format_param>::parse_bytes(last_field_bytes)?; unsafe { #(#validators)* } )) } } } impl<'a> UnsizedField<'a> { fn new( field: &'a Field, index: usize, custom_varule_ident: Option, ) -> Result { Ok(UnsizedField { kind: UnsizedFieldKind::new(&field.ty, custom_varule_ident)?, field: FieldInfo::new_for_field(field, index), }) } /// Call `>::#method(self.accessor #additional_args)` after adjusting /// Self and self.accessor to be the right types fn encode_func(&self, method: TokenStream2, additional_args: TokenStream2) -> TokenStream2 { let encodeas_trait = quote!(zerovec::ule::EncodeAsVarULE); let (encodeable_ty, encodeable) = self.encodeable_tokens(); let varule_ty = self.kind.varule_ty(); quote!(<#encodeable_ty as #encodeas_trait<#varule_ty>>::#method(#encodeable, #additional_args)) } /// Returns (encodeable_ty, encodeable) fn encodeable_tokens(&self) -> (TokenStream2, TokenStream2) { let accessor = self.field.accessor.clone(); let value = quote!(self.#accessor); let encodeable = self.kind.encodeable_value(value); let encodeable_ty = self.kind.encodeable_ty(); (encodeable_ty, encodeable) } } impl<'a> UnsizedFieldKind<'a> { /// Construct a UnsizedFieldKind for the type of a UnsizedFieldKind if possible fn new( ty: &'a Type, custom_varule_ident: Option, ) -> Result, String> { static PATH_TYPE_IDENTITY_ERROR: &str = "Can only automatically detect corresponding VarULE types for path types \ that are Cow, ZeroVec, VarZeroVec, Box, String, or Vec"; static PATH_TYPE_GENERICS_ERROR: &str = "Can only automatically detect corresponding VarULE types for path \ types with at most one lifetime and at most one generic parameter. VarZeroVecFormat types are not currently supported"; match *ty { Type::Reference(ref tyref) => OwnULETy::new(&tyref.elem, "reference").map(UnsizedFieldKind::Ref), Type::Path(ref typath) => { if let Some(custom_varule_ident) = custom_varule_ident { return Ok(UnsizedFieldKind::Custom(typath, custom_varule_ident)); } if typath.path.segments.len() != 1 { return Err("Can only automatically detect corresponding VarULE types for \ path types with a single path segment".into()); } let segment = typath.path.segments.first().unwrap(); match segment.arguments { PathArguments::None => { if segment.ident == "String" { Ok(UnsizedFieldKind::Growable(OwnULETy::Str)) } else { Err(PATH_TYPE_IDENTITY_ERROR.into()) } } PathArguments::AngleBracketed(ref params) => { // At most one lifetime and exactly one generic parameter let mut lifetime = None; let mut generic = None; for param in ¶ms.args { match param { GenericArgument::Lifetime(ref lt) if lifetime.is_none() => { lifetime = Some(lt) } GenericArgument::Type(ref ty) if generic.is_none() => { generic = Some(ty) } _ => return Err(PATH_TYPE_GENERICS_ERROR.into()), } } // Must be exactly one generic parameter // (we've handled the zero generics case already) let generic = if let Some(g) = generic { g } else { return Err(PATH_TYPE_GENERICS_ERROR.into()); }; let ident = segment.ident.to_string(); if lifetime.is_some() { match &*ident { "ZeroVec" => Ok(UnsizedFieldKind::ZeroVec(generic)), "VarZeroVec" => Ok(UnsizedFieldKind::VarZeroVec(generic)), "Cow" => OwnULETy::new(generic, "Cow").map(UnsizedFieldKind::Cow), _ => Err(PATH_TYPE_IDENTITY_ERROR.into()), } } else { match &*ident { "Vec" => Ok(UnsizedFieldKind::Growable(OwnULETy::Slice(generic))), "Box" => OwnULETy::new(generic, "Box").map(UnsizedFieldKind::Boxed), _ => Err(PATH_TYPE_IDENTITY_ERROR.into()), } } } _ => Err("Can only automatically detect corresponding VarULE types for path types \ with none or angle bracketed generics".into()), } } _ => Err("Can only automatically detect corresponding VarULE types for path and reference types".into()), } } /// Get the tokens for the corresponding VarULE type fn varule_ty(&self) -> TokenStream2 { match *self { Self::Ref(ref inner) | Self::Cow(ref inner) | Self::Boxed(ref inner) | Self::Growable(ref inner) => { let inner_ule = inner.varule_ty(); quote!(#inner_ule) } Self::Custom(_, ref name) => quote!(#name), Self::ZeroVec(ref inner) => quote!(zerovec::ZeroSlice<#inner>), Self::VarZeroVec(ref inner) => quote!(zerovec::VarZeroSlice<#inner>), } } // Takes expr `value` and returns it as a value that can be encoded via EncodeAsVarULE fn encodeable_value(&self, value: TokenStream2) -> TokenStream2 { match *self { Self::Ref(_) | Self::Cow(_) | Self::Growable(_) | Self::Boxed(_) => quote!(&*#value), Self::Custom(..) => quote!(&#value), Self::ZeroVec(_) | Self::VarZeroVec(_) => quote!(&*#value), } } /// Returns the EncodeAsVarULE type this can be represented as, the same returned by encodeable_value() fn encodeable_ty(&self) -> TokenStream2 { match *self { Self::Ref(ref inner) | Self::Cow(ref inner) | Self::Growable(ref inner) | Self::Boxed(ref inner) => inner.varule_ty(), Self::Custom(ref path, _) => quote!(#path), Self::ZeroVec(ref ty) => quote!(zerovec::ZeroSlice<#ty>), Self::VarZeroVec(ref ty) => quote!(zerovec::VarZeroSlice<#ty>), } } fn has_zf(&self) -> bool { matches!( *self, Self::Ref(_) | Self::Cow(_) | Self::ZeroVec(_) | Self::VarZeroVec(_) | Self::Custom(..) ) } } impl<'a> OwnULETy<'a> { fn new(ty: &'a Type, context: &str) -> Result { match *ty { Type::Slice(ref slice) => Ok(OwnULETy::Slice(&slice.elem)), Type::Path(ref typath) => { if typath.path.is_ident("str") { Ok(OwnULETy::Str) } else { Err(format!("Cannot automatically detect corresponding VarULE type for non-str path type inside a {context}")) } } _ => Err(format!("Cannot automatically detect corresponding VarULE type for non-slice/path type inside a {context}")), } } /// Get the tokens for the corresponding VarULE type fn varule_ty(&self) -> TokenStream2 { match *self { OwnULETy::Slice(s) => quote!([#s]), OwnULETy::Str => quote!(str), } } } zerovec-derive-0.11.0/src/ule.rs000064400000000000000000000105451046102023000145630ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use proc_macro2::TokenStream as TokenStream2; use quote::quote; use crate::utils::{self, FieldInfo}; use syn::spanned::Spanned; use syn::{Data, DeriveInput, Error}; pub fn derive_impl(input: &DeriveInput) -> TokenStream2 { if !utils::ReprInfo::compute(&input.attrs).cpacked_or_transparent() { return Error::new( input.span(), "derive(ULE) must be applied to a #[repr(C, packed)] or #[repr(transparent)] type", ) .to_compile_error(); } if input.generics.type_params().next().is_some() || input.generics.lifetimes().next().is_some() || input.generics.const_params().next().is_some() { return Error::new( input.generics.span(), "derive(ULE) must be applied to a struct without any generics", ) .to_compile_error(); } let struc = if let Data::Struct(ref s) = input.data { if s.fields.iter().next().is_none() { return Error::new( input.span(), "derive(ULE) must be applied to a non-empty struct", ) .to_compile_error(); } s } else { return Error::new(input.span(), "derive(ULE) must be applied to a struct") .to_compile_error(); }; let fields = FieldInfo::make_list(struc.fields.iter()); let (validators, remaining_offset) = generate_ule_validators(&fields); let name = &input.ident; // Safety (based on the safety checklist on the ULE trait): // 1. #name does not include any uninitialized or padding bytes. // (achieved by enforcing #[repr(transparent)] or #[repr(C, packed)] on a struct of only ULE types) // 2. #name is aligned to 1 byte. // (achieved by enforcing #[repr(transparent)] or #[repr(C, packed)] on a struct of only ULE types) // 3. The impl of validate_bytes() returns an error if any byte is not valid. // 4. The impl of validate_bytes() returns an error if there are extra bytes. // 5. The other ULE methods use the default impl. // 6. [This impl does not enforce the non-safety equality constraint, it is up to the user to do so, ideally via a custom derive] quote! { unsafe impl zerovec::ule::ULE for #name { #[inline] fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> { const SIZE: usize = ::core::mem::size_of::<#name>(); #[allow(clippy::modulo_one)] if bytes.len() % SIZE != 0 { return Err(zerovec::ule::UleError::length::(bytes.len())); } // Validate the bytes #[allow(clippy::indexing_slicing)] // We're slicing a chunk of known size for chunk in bytes.chunks_exact(SIZE) { #validators debug_assert_eq!(#remaining_offset, SIZE); } Ok(()) } } } } /// Given an slice over ULE struct fields, returns code validating that a slice variable `bytes` contains valid instances of those ULE types /// in order, plus the byte offset of any remaining unvalidated bytes. ULE types should not have any remaining bytes, but VarULE types will since /// the last field is the unsized one. pub(crate) fn generate_ule_validators( fields: &[FieldInfo], // (validators, remaining_offset) ) -> (TokenStream2, syn::Ident) { utils::generate_per_field_offsets(fields, false, |field, prev_offset_ident, size_ident| { let ty = &field.field.ty; quote! { #[allow(clippy::indexing_slicing)] // generate_per_field_offsets produces valid indices <#ty as zerovec::ule::ULE>::validate_bytes(&bytes[#prev_offset_ident .. #prev_offset_ident + #size_ident])?; } }) } /// Make corresponding ULE fields for each field pub(crate) fn make_ule_fields(fields: &[FieldInfo]) -> Vec { fields .iter() .map(|f| { let ty = &f.field.ty; let ty = quote!(<#ty as zerovec::ule::AsULE>::ULE); let setter = f.setter(); let vis = &f.field.vis; quote!(#vis #setter #ty) }) .collect::>() } zerovec-derive-0.11.0/src/utils.rs000064400000000000000000000303161046102023000151340ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use quote::{quote, ToTokens}; use proc_macro2::Span; use proc_macro2::TokenStream as TokenStream2; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{Attribute, Error, Field, Fields, Ident, Index, Result, Token}; #[derive(Default)] pub struct ReprInfo { pub c: bool, pub transparent: bool, pub u8: bool, pub packed: bool, } impl ReprInfo { pub fn compute(attrs: &[Attribute]) -> Self { let mut info = ReprInfo::default(); for attr in attrs.iter().filter(|a| a.path().is_ident("repr")) { if let Ok(pieces) = attr.parse_args::() { for piece in pieces.idents.iter() { if piece == "C" || piece == "c" { info.c = true; } else if piece == "transparent" { info.transparent = true; } else if piece == "packed" { info.packed = true; } else if piece == "u8" { info.u8 = true; } } } } info } pub fn cpacked_or_transparent(self) -> bool { (self.c && self.packed) || self.transparent } } // An attribute that is a list of idents struct IdentListAttribute { idents: Punctuated, } impl Parse for IdentListAttribute { fn parse(input: ParseStream) -> Result { Ok(IdentListAttribute { idents: input.parse_terminated(Ident::parse, Token![,])?, }) } } /// Given a set of entries for struct field definitions to go inside a `struct {}` definition, /// wrap in a () or {} based on the type of field pub fn wrap_field_inits(streams: &[TokenStream2], fields: &Fields) -> TokenStream2 { match *fields { Fields::Named(_) => quote!( { #(#streams),* } ), Fields::Unnamed(_) => quote!( ( #(#streams),* ) ), Fields::Unit => { unreachable!("#[make_(var)ule] should have already checked that there are fields") } } } /// Return a semicolon token if necessary after the struct definition pub fn semi_for(f: &Fields) -> TokenStream2 { if let Fields::Unnamed(..) = *f { quote!(;) } else { quote!() } } /// Returns the repr attribute to be applied to the resultant ULE or VarULE type pub fn repr_for(f: &Fields) -> TokenStream2 { if f.len() == 1 { quote!(transparent) } else { quote!(C, packed) } } fn suffixed_ident(name: &str, suffix: usize, s: Span) -> Ident { Ident::new(&format!("{name}_{suffix}"), s) } /// Given an iterator over ULE or AsULE struct fields, returns code that calculates field sizes and generates a line /// of code per field based on the per_field_code function (whose parameters are the field, the identifier of the const /// for the previous offset, the identifier for the const for the next offset, and the field index) pub(crate) fn generate_per_field_offsets<'a>( fields: &[FieldInfo<'a>], // Whether the fields are ULE types or AsULE (and need conversion) fields_are_asule: bool, // (field, prev_offset_ident, size_ident) mut per_field_code: impl FnMut(&FieldInfo<'a>, &Ident, &Ident) -> TokenStream2, /* (code, remaining_offset) */ ) -> (TokenStream2, syn::Ident) { let mut prev_offset_ident = Ident::new("ZERO", Span::call_site()); let mut code = quote!( const ZERO: usize = 0; ); for (i, field_info) in fields.iter().enumerate() { let field = &field_info.field; let ty = &field.ty; let ty = if fields_are_asule { quote!(<#ty as zerovec::ule::AsULE>::ULE) } else { quote!(#ty) }; let new_offset_ident = suffixed_ident("OFFSET", i, field.span()); let size_ident = suffixed_ident("SIZE", i, field.span()); let pf_code = per_field_code(field_info, &prev_offset_ident, &size_ident); code = quote! { #code; const #size_ident: usize = ::core::mem::size_of::<#ty>(); const #new_offset_ident: usize = #prev_offset_ident + #size_ident; #pf_code; }; prev_offset_ident = new_offset_ident; } (code, prev_offset_ident) } #[derive(Clone, Debug)] pub(crate) struct FieldInfo<'a> { pub accessor: TokenStream2, pub field: &'a Field, pub index: usize, } impl<'a> FieldInfo<'a> { pub fn make_list(iter: impl Iterator) -> Vec { iter.enumerate() .map(|(i, field)| Self::new_for_field(field, i)) .collect() } pub fn new_for_field(f: &'a Field, index: usize) -> Self { if let Some(ref i) = f.ident { FieldInfo { accessor: quote!(#i), field: f, index, } } else { let idx = Index::from(index); FieldInfo { accessor: quote!(#idx), field: f, index, } } } /// Get the code for setting this field in struct decl/brace syntax /// /// Use self.accessor for dot-notation accesses pub fn setter(&self) -> TokenStream2 { if let Some(ref i) = self.field.ident { quote!(#i: ) } else { quote!() } } /// Produce a name for a getter for the field pub fn getter(&self) -> TokenStream2 { if let Some(ref i) = self.field.ident { quote!(#i) } else { suffixed_ident("field", self.index, self.field.span()).into_token_stream() } } /// Produce a prose name for the field for use in docs pub fn getter_doc_name(&self) -> String { if let Some(ref i) = self.field.ident { format!("the unsized `{i}` field") } else { format!("tuple struct field #{}", self.index) } } } /// Extracts all `zerovec::name(..)` attribute pub fn extract_parenthetical_zerovec_attrs( attrs: &mut Vec, name: &str, ) -> Result> { let mut ret = vec![]; let mut error = None; attrs.retain(|a| { // skip the "zerovec" part let second_segment = a.path().segments.iter().nth(1); if let Some(second) = second_segment { if second.ident == name { let list = match a.parse_args::() { Ok(l) => l, Err(_) => { error = Some(Error::new( a.span(), format!("#[zerovec::{name}(..)] takes in a comma separated list of identifiers"), )); return false; } }; ret.extend(list.idents.iter().cloned()); return false; } } true }); if let Some(error) = error { return Err(error); } Ok(ret) } pub fn extract_single_tt_attr( attrs: &mut Vec, name: &str, ) -> Result> { let mut ret = None; let mut error = None; attrs.retain(|a| { // skip the "zerovec" part let second_segment = a.path().segments.iter().nth(1); if let Some(second) = second_segment { if second.ident == name { if ret.is_some() { error = Some(Error::new( a.span(), "Can only specify a single VarZeroVecFormat via #[zerovec::format(..)]", )); return false } ret = match a.parse_args::() { Ok(l) => Some(l), Err(_) => { error = Some(Error::new( a.span(), format!("#[zerovec::{name}(..)] takes in a comma separated list of identifiers"), )); return false; } }; return false; } } true }); if let Some(error) = error { return Err(error); } Ok(ret) } /// Removes all attributes with `zerovec` in the name and places them in a separate vector pub fn extract_zerovec_attributes(attrs: &mut Vec) -> Vec { let mut ret = vec![]; attrs.retain(|a| { if a.path().segments.len() == 2 && a.path().segments[0].ident == "zerovec" { ret.push(a.clone()); return false; } true }); ret } /// Extract attributes from field, and return them /// /// Only current field attribute is `zerovec::varule(VarUleType)` pub fn extract_field_attributes(attrs: &mut Vec) -> Result> { let mut zerovec_attrs = extract_zerovec_attributes(attrs); let varule = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "varule")?; if varule.len() > 1 { return Err(Error::new( varule[1].span(), "Found multiple #[zerovec::varule()] on one field", )); } if !zerovec_attrs.is_empty() { return Err(Error::new( zerovec_attrs[1].span(), "Found unusable #[zerovec::] attrs on field, only #[zerovec::varule()] supported", )); } Ok(varule.first().cloned()) } #[derive(Default, Clone)] pub struct ZeroVecAttrs { pub skip_kv: bool, pub skip_ord: bool, pub skip_toowned: bool, pub skip_from: bool, pub serialize: bool, pub deserialize: bool, pub debug: bool, pub hash: bool, pub vzv_format: Option, } /// Removes all known zerovec:: attributes from struct attrs and validates them pub fn extract_attributes_common( attrs: &mut Vec, span: Span, is_var: bool, ) -> Result { let mut zerovec_attrs = extract_zerovec_attributes(attrs); let derive = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "derive")?; let skip = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "skip_derive")?; let format = extract_single_tt_attr(&mut zerovec_attrs, "format")?; let name = if is_var { "make_varule" } else { "make_ule" }; if let Some(attr) = zerovec_attrs.first() { return Err(Error::new( attr.span(), format!("Found unknown or duplicate attribute for #[{name}]"), )); } let mut attrs = ZeroVecAttrs::default(); for ident in derive { if ident == "Serialize" { attrs.serialize = true; } else if ident == "Deserialize" { attrs.deserialize = true; } else if ident == "Debug" { attrs.debug = true; } else if ident == "Hash" { attrs.hash = true; } else { return Err(Error::new( ident.span(), format!( "Found unknown derive attribute for #[{name}]: #[zerovec::derive({ident})]" ), )); } } for ident in skip { if ident == "ZeroMapKV" { attrs.skip_kv = true; } else if ident == "Ord" { attrs.skip_ord = true; } else if ident == "ToOwned" && is_var { attrs.skip_toowned = true; } else if ident == "From" && is_var { attrs.skip_from = true; } else { return Err(Error::new( ident.span(), format!("Found unknown derive attribute for #[{name}]: #[zerovec::skip_derive({ident})]"), )); } } if let Some(ref format) = format { if !is_var { return Err(Error::new( format.span(), format!( "Found unknown derive attribute for #[{name}]: #[zerovec::format({format})]" ), )); } } attrs.vzv_format = format; if (attrs.serialize || attrs.deserialize) && !is_var { return Err(Error::new( span, "#[make_ule] does not support #[zerovec::derive(Serialize, Deserialize)]", )); } Ok(attrs) } zerovec-derive-0.11.0/src/varule.rs000064400000000000000000000124151046102023000152720ustar 00000000000000// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::utils::{self, FieldInfo}; use proc_macro2::Span; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::spanned::Spanned; use syn::{Data, DeriveInput, Error, Ident}; /// Implementation for derive(VarULE). `custom_varule_validator` validates the last field bytes `last_field_bytes` /// if specified, if not, the VarULE implementation will be used. pub fn derive_impl( input: &DeriveInput, custom_varule_validator: Option, ) -> TokenStream2 { if !utils::ReprInfo::compute(&input.attrs).cpacked_or_transparent() { return Error::new( input.span(), "derive(VarULE) must be applied to a #[repr(C, packed)] or #[repr(transparent)] type", ) .to_compile_error(); } if input.generics.type_params().next().is_some() || input.generics.lifetimes().next().is_some() || input.generics.const_params().next().is_some() { return Error::new( input.generics.span(), "derive(VarULE) must be applied to a struct without any generics", ) .to_compile_error(); } let struc = if let Data::Struct(ref s) = input.data { if s.fields.iter().next().is_none() { return Error::new( input.span(), "derive(VarULE) must be applied to a non-empty struct", ) .to_compile_error(); } s } else { return Error::new(input.span(), "derive(VarULE) must be applied to a struct") .to_compile_error(); }; let n_fields = struc.fields.len(); let ule_fields = FieldInfo::make_list(struc.fields.iter().take(n_fields - 1)); let sizes = ule_fields.iter().map(|f| { let ty = &f.field.ty; quote!(::core::mem::size_of::<#ty>()) }); let (validators, remaining_offset) = if n_fields > 1 { // generate ULE validators crate::ule::generate_ule_validators(&ule_fields) } else { // no ULE subfields ( quote!( const ZERO: usize = 0; ), Ident::new("ZERO", Span::call_site()), ) }; let unsized_field = &struc .fields .iter() .next_back() .expect("Already verified that struct is not empty") .ty; let name = &input.ident; let ule_size = Ident::new( &format!("__IMPL_VarULE_FOR_{name}_ULE_SIZE"), Span::call_site(), ); let last_field_validator = if let Some(custom_varule_validator) = custom_varule_validator { custom_varule_validator } else { quote!(<#unsized_field as zerovec::ule::VarULE>::validate_bytes(last_field_bytes)?;) }; // Safety (based on the safety checklist on the ULE trait): // 1. #name does not include any uninitialized or padding bytes // (achieved by enforcing #[repr(transparent)] or #[repr(C, packed)] on a struct of only ULE types) // 2. #name is aligned to 1 byte. // (achieved by enforcing #[repr(transparent)] or #[repr(C, packed)] on a struct of only ULE types) // 3. The impl of `validate_bytes()` returns an error if any byte is not valid. // 4. The impl of `validate_bytes()` returns an error if the slice cannot be used in its entirety // 5. The impl of `from_bytes_unchecked()` returns a reference to the same data. // 6. The other VarULE methods use the default impl // 7. [This impl does not enforce the non-safety equality constraint, it is up to the user to do so, ideally via a custom derive] quote! { // The size of the ULE section of this type const #ule_size: usize = 0 #(+ #sizes)*; unsafe impl zerovec::ule::VarULE for #name { #[inline] fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> { if bytes.len() < #ule_size { return Err(zerovec::ule::UleError::parse::()); } #validators debug_assert_eq!(#remaining_offset, #ule_size); #[allow(clippy::indexing_slicing)] // TODO explain let last_field_bytes = &bytes[#remaining_offset..]; #last_field_validator Ok(()) } #[inline] unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { // just the unsized part #[allow(clippy::indexing_slicing)] // TODO explain let unsized_bytes = &bytes[#ule_size..]; let unsized_ref = <#unsized_field as zerovec::ule::VarULE>::from_bytes_unchecked(unsized_bytes); // We should use the pointer metadata APIs here when they are stable: https://github.com/rust-lang/rust/issues/81513 // For now we rely on all DST metadata being a usize to extract it via a fake slice pointer let (_ptr, metadata): (usize, usize) = ::core::mem::transmute(unsized_ref); let entire_struct_as_slice: *const [u8] = ::core::slice::from_raw_parts(bytes.as_ptr(), metadata); &*(entire_struct_as_slice as *const Self) } } } }