Merge pull request #3748 from rtfeldman/serde

Add serde serializers/deserializers to roc_std
This commit is contained in:
Richard Feldman 2022-08-11 17:46:44 -04:00 committed by GitHub
commit 07eed2c4a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 219 additions and 6 deletions

View file

@ -64,7 +64,7 @@ build-rust-test:
RUN apt -y install gcc-10 g++-10 && rm /usr/bin/gcc && ln -s /usr/bin/gcc-10 /usr/bin/gcc # gcc-9 maybe causes segfault
RUN gcc --version
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --features with_sound --workspace --no-run && sccache --show-stats
cargo test --locked --release --features with_sound serde --workspace --no-run && sccache --show-stats
check-typos:
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
@ -82,7 +82,7 @@ test-rust:
RUN gcc --version
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --features with_sound --workspace && sccache --show-stats
cargo test --locked --release --features with_sound serde --workspace && sccache --show-stats
# test the dev and wasm backend: they require an explicit feature flag.
RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
@ -99,7 +99,7 @@ test-rust:
# NOTE: disabled until zig 0.9
# RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
# RUN --mount=type=cache,target=$SCCACHE_DIR \
# cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
# cargo test --locked --release --features with_sound serde --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
# make sure website deployment works (that is, make sure build.sh returns status code 0)
ENV REPL_DEBUG=1
RUN bash www/build.sh
@ -126,7 +126,7 @@ build-nightly-release:
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
# version.txt is used by the CLI: roc --version
RUN ./ci/write_version.sh
RUN RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release
RUN RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound serde --release
RUN ./ci/package_release.sh roc_linux_x86_64.tar.gz
SAVE ARTIFACT ./roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz

View file

@ -78,6 +78,12 @@ dependencies = [
"unindent",
]
[[package]]
name = "itoa"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "libc"
version = "0.2.119"
@ -197,7 +203,7 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "roc_std"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"arrayvec",
"indoc",
@ -205,9 +211,34 @@ dependencies = [
"pretty_assertions",
"quickcheck",
"quickcheck_macros",
"serde",
"serde_json",
"static_assertions",
]
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "serde"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
[[package]]
name = "serde_json"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "static_assertions"
version = "1.1.0"

View file

@ -11,13 +11,16 @@ version = "0.0.1"
[dependencies]
static_assertions = "1.1.0"
arrayvec = "0.7.2"
serde = { version = "1", optional = true }
[dev-dependencies]
indoc = "1.0.3"
libc = "0.2.106"
pretty_assertions = "1.0.0"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
libc = "0.2.106"
serde_json = "1.0.83"
[features]
std = []
serde = ["dep:serde"]

View file

@ -15,6 +15,15 @@ use core::{
use crate::{roc_alloc, roc_dealloc, roc_realloc, storage::Storage};
#[cfg(feature = "serde")]
use core::marker::PhantomData;
#[cfg(feature = "serde")]
use serde::{
de::{Deserializer, Visitor},
ser::{SerializeSeq, Serializer},
Deserialize, Serialize,
};
#[repr(C)]
pub struct RocList<T> {
elements: Option<NonNull<ManuallyDrop<T>>>,
@ -586,3 +595,76 @@ impl<T: Clone> FromIterator<T> for RocList<T> {
list
}
}
#[cfg(feature = "serde")]
impl<T: Serialize> Serialize for RocList<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.len()))?;
for item in self {
seq.serialize_element(item)?;
}
seq.end()
}
}
#[cfg(feature = "serde")]
impl<'de, T> Deserialize<'de> for RocList<T>
where
// TODO: I'm not sure about requiring clone here. Is that fine? Is that
// gonna mean lots of extra allocations?
T: Deserialize<'de> + core::clone::Clone,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(RocListVisitor::new())
}
}
#[cfg(feature = "serde")]
struct RocListVisitor<T> {
marker: PhantomData<T>,
}
#[cfg(feature = "serde")]
impl<T> RocListVisitor<T> {
fn new() -> Self {
RocListVisitor {
marker: PhantomData,
}
}
}
#[cfg(feature = "serde")]
impl<'de, T> Visitor<'de> for RocListVisitor<T>
where
T: Deserialize<'de> + core::clone::Clone,
{
type Value = RocList<T>;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(formatter, "a list of strings")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut out = match seq.size_hint() {
Some(hint) => RocList::with_capacity(hint),
None => RocList::empty(),
};
while let Some(next) = seq.next_element()? {
// TODO: it would be ideal to call `out.push` here, but we haven't
// implemented that yet! I think this is also why we need Clone.
out.extend_from_slice(&[next])
}
Ok(out)
}
}

View file

@ -1,5 +1,12 @@
#![deny(unsafe_op_in_unsafe_fn)]
#[cfg(feature = "serde")]
use serde::{
de::{Deserializer, Visitor},
ser::Serializer,
Deserialize, Serialize,
};
use core::{
cmp,
convert::TryFrom,
@ -708,3 +715,45 @@ impl Hash for RocStr {
self.as_str().hash(state)
}
}
#[cfg(feature = "serde")]
impl Serialize for RocStr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for RocStr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// TODO: using deserialize_string here instead of deserialize_str here
// because I think we'd "benefit from taking ownership of buffered data
// owned by the Deserializer." is that correct?
deserializer.deserialize_string(RocStrVisitor {})
}
}
#[cfg(feature = "serde")]
struct RocStrVisitor {}
#[cfg(feature = "serde")]
impl<'de> Visitor<'de> for RocStrVisitor {
type Value = RocStr;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(formatter, "a string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(RocStr::from(value))
}
}

View file

@ -138,6 +138,29 @@ mod test_roc_std {
assert_eq!(roc_str.capacity(), 5000);
}
#[test]
#[cfg(feature = "serde")]
fn str_short_serde_roundtrip() {
let orig = RocStr::from("x");
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
let deserialized = serde_json::from_str(&serialized).expect("failed to deserialize string");
assert_eq!(orig, deserialized);
}
#[test]
#[cfg(feature = "serde")]
fn str_long_serde_roundtrip() {
// How about a little philosophy to accompany test failures?
let orig = RocStr::from("If there's a remedy when trouble strikes, what reason is there for dejection? And if there is no help for it, what use is there in being glum? -- Shantideva, The Way of the Bodhisattva");
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
let deserialized = serde_json::from_str(&serialized).expect("failed to deserialize string");
assert_eq!(orig, deserialized);
}
#[test]
fn reserve_small_list() {
let mut roc_list = RocList::<RocStr>::empty();
@ -156,6 +179,31 @@ mod test_roc_std {
assert_eq!(roc_list.capacity(), 5000);
}
#[test]
#[cfg(feature = "serde")]
fn short_list_roundtrip() {
let items: [u8; 4] = [1, 3, 3, 7];
let orig = RocList::from_slice(&items);
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
let deserialized =
serde_json::from_str::<RocList<u8>>(&serialized).expect("failed to deserialize string");
assert_eq!(orig, deserialized);
}
#[test]
#[cfg(feature = "serde")]
fn long_list_roundtrip() {
let orig = RocList::from_iter(1..100);
let serialized = serde_json::to_string(&orig).expect("failed to serialize string");
let deserialized =
serde_json::from_str::<RocList<u8>>(&serialized).expect("failed to deserialize string");
assert_eq!(orig, deserialized);
}
#[test]
fn list_from_iter() {
let elems: [i64; 5] = [1, 2, 3, 4, 5];