Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added experimental support for deserializer state #1327

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
89e93a6
Added experimental support for deserializer state
mitsuhiko Jun 29, 2018
983bab8
Remove unused file
mitsuhiko Jun 29, 2018
cc0abce
Put state feature behind feature flag
mitsuhiko Jun 29, 2018
4f0ae2d
Disable accidentally enabled nightly setting
mitsuhiko Jun 29, 2018
ae7c745
Added way to check for state feature
mitsuhiko Jun 29, 2018
a973d5d
Some small fixes
mitsuhiko Jun 29, 2018
a9bd06b
Correctly forward state in content deserializer
mitsuhiko Jun 29, 2018
3a3c5d6
Restore support with older rusts in one area
mitsuhiko Jun 29, 2018
368229f
Restore support for older rust versions
mitsuhiko Jun 30, 2018
48f6f26
with -> get
mitsuhiko Jun 30, 2018
4d134ca
Improved state map
mitsuhiko Jun 30, 2018
bd9b89c
Optimized state remove slightly
mitsuhiko Jun 30, 2018
825254d
Use cfg syntax for older rusts
mitsuhiko Jun 30, 2018
a56d41d
Stub out state implementation for old rusts
mitsuhiko Jun 30, 2018
cd2d724
Fixed a pedantic clippy
mitsuhiko Jun 30, 2018
4ba7885
Fixed another pedantic clippy error
mitsuhiko Jun 30, 2018
6a93c5f
map and unwrap_or -> map_or
mitsuhiko Jun 30, 2018
7854fef
Pass some more state through
mitsuhiko Jun 30, 2018
bd3e526
Carry some more state through
mitsuhiko Jun 30, 2018
27191b8
Pass state to flat map deserialzer
mitsuhiko Jun 30, 2018
0183d78
Optionally roundtrip state through the value deserializers
mitsuhiko Jul 1, 2018
9799908
Remove the replace_state interface
mitsuhiko Jul 1, 2018
e184daa
Add Deserializer::replace_state
mitsuhiko Jul 1, 2018
af6a439
Carry state through de::value deserializers
mitsuhiko Jul 1, 2018
b5db8c8
Refactor state once more
mitsuhiko Jul 1, 2018
fa6a6f5
Always import state
mitsuhiko Jul 1, 2018
dd60622
Explicitly clone in a test
mitsuhiko Jul 1, 2018
7e4f519
Remove broken state support from NeverDeserializer
mitsuhiko Jul 1, 2018
716ffb6
Use explicit default calls
mitsuhiko Jul 1, 2018
d73cc93
Merge branch 'master' into feature/state
jan-auer Jul 4, 2018
e4eb330
Expose ContentRepr from Content
jan-auer Jul 4, 2018
880a8c0
Ensure we have a higher version than serde
jan-auer Jul 23, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions serde/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serde"
version = "1.0.69" # remember to update html_root_url
version = "1.0.999" # remember to update html_root_url
authors = ["Erick Tryzelaar <[email protected]>", "David Tolnay <[email protected]>"]
license = "MIT/Apache-2.0"
description = "A generic serialization/deserialization framework"
Expand All @@ -27,7 +27,7 @@ serde_derive = { version = "1.0", path = "../serde_derive" }
### FEATURES #################################################################

[features]
default = ["std"]
default = ["std", "state"]

# Re-export the derive(Serialize, Deserialize) macros. This is intended for
# library crates that provide optional Serde impls behind a Cargo cfg of their
Expand Down Expand Up @@ -78,3 +78,9 @@ alloc = ["unstable"]
# does not preserve identity and may result in multiple copies of the same data.
# Be sure that this is what you want before enabling this feature.
rc = []

# Enables the state support. This feature is on by default but can be removed
# to disable states. If states are enabled serializers can carry state information
# around. If this feature is disabled any attempt to store data in the state will
# silently do nothing.
state = []
6 changes: 6 additions & 0 deletions serde/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ fn main() {
println!("cargo:rustc-cfg=de_rc_dst");
}

// drop_types_in_const was stabilized in 1.22:
// https://blog.rust-lang.org/2017/11/22/Rust-1.22.html
if minor >= 22 {
println!("cargo:rustc-cfg=de_state");
}

// Duration available in core since Rust 1.25:
// https://blog.rust-lang.org/2018/03/29/Rust-1.25.html#library-stabilizations
if minor >= 25 {
Expand Down
18 changes: 18 additions & 0 deletions serde/src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ mod from_primitive;
mod ignored_any;
mod impls;
mod utf8;
mod state;

pub use self::ignored_any::IgnoredAny;
pub use self::state::State;

////////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -1203,6 +1205,12 @@ pub trait Deserializer<'de>: Sized {
fn is_human_readable(&self) -> bool {
true
}

/// Returns the current state.
#[inline]
fn state(&self) -> &State {
State::empty()
}
}

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -2235,6 +2243,16 @@ pub trait IntoDeserializer<'de, E: Error = value::Error> {

/// Convert this value into a deserializer.
fn into_deserializer(self) -> Self::Deserializer;

/// Convert this value into a deserializer and attach state.
fn into_deserializer_with_state(self, state: State) -> Self::Deserializer
where Self: Sized
{
if !state.is_empty() {
panic!("This deserializer does not support state");
}
IntoDeserializer::into_deserializer(self)
}
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
136 changes: 136 additions & 0 deletions serde/src/de/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use lib::*;

/// A copy on write structure for deserializer state.
///
/// Deserializers can hold arbitrary state that the `Deserializer` trait
/// can access. From the outside state can only be read not modified
/// (not counting interior mutability). Any static type can be added
/// to the state.
///
/// This for instance can be used to notify types about location
/// information or associated metadata. If the `state` feature is
/// disabled in serde this becomes a zero sized type that does not
/// do anything instead.
///
/// Internally the structure is a reasonably efficient copy on write
/// structure. It can be cheaply cloned which effectively just bumps
/// some refcounts. Internally it is implemented as a vector.
///
/// This requires Rust 1.22 or later and the `state` feature to be
/// enabled. Otherwise the state type is read-only and does not
/// provide the `set` and `remove` methods.
#[derive(Clone, Default)]
pub struct State {
#[cfg(all(feature = "state", de_state))]
map: Option<Rc<Vec<(TypeId, Rc<Box<Any>>)>>>,
}

impl State {
/// Returns the static reference to the empty state.
///
/// The state is normally non `Send` but the read only empty state
/// can be safely accessed from multiple threads. To modify the
/// state it needs to be cloned first.
///
/// ```
/// # use serde::de::State;
/// struct MyInfo(i32);
/// let mut state = State::empty().clone();
/// state.set(MyInfo(42));
/// ```
#[inline]
pub fn empty() -> &'static State {
// we could use `const EMPTY_STATE: State` here for newer rust
// versions which would avoid the unsafe. The end result is
// about the same though.
#[cfg(all(feature = "state", de_state))] {
const EMPTY_STATE: State = State {
map: None
};
&EMPTY_STATE
}
#[cfg(not(all(feature = "state", de_state)))] {
static mut EMPTY_STATE: State = State {};
unsafe { &EMPTY_STATE }
}
}

/// Checks if the state is empty.
pub fn is_empty(&self) -> bool {
#[cfg(all(feature = "state", de_state))] {
match self.map {
Some(ref map) => map.is_empty(),
None => true,
}
}
#[cfg(not(all(feature = "state", de_state)))] {
true
}
}

/// Looks up an item.
///
/// This function is always available even if the state feature is
/// disabled. In that case the state just always returns `None`.
pub fn get<T: 'static>(&self) -> Option<&T> {
#[cfg(all(feature = "state", de_state))] {
if let Some(ref map) = self.map {
for &(type_id, ref boxed_rc) in map.iter() {
if type_id == TypeId::of::<T>() {
return (&***boxed_rc as &(Any + 'static)).downcast_ref();
}
}
}
}
None
}

/// Inserts or replaces a type in the state map.
#[cfg(all(feature = "state", de_state))]
pub fn set<T: 'static>(&mut self, val: T) {
self.map = Some(Rc::new(self.map
.as_ref()
.map_or(&[][..], |x| &x[..])
.iter()
.filter_map(|&(type_id, ref boxed_rc)| {
if type_id == TypeId::of::<T>() {
None
} else {
Some((type_id, boxed_rc.clone()))
}
})
.chain(iter::once((TypeId::of::<T>(), Rc::new(Box::new(val) as Box<Any>))))
.collect()));
}

/// Removes a type from the state map.
#[cfg(all(feature = "state", de_state))]
pub fn remove<T: 'static>(&mut self) {
let new_map = {
let mut iter = self.map
.as_ref()
.map_or(&[][..], |x| &x[..])
.iter()
.filter_map(|&(type_id, ref boxed_rc)| {
if type_id == TypeId::of::<T>() {
None
} else {
Some((type_id, boxed_rc.clone()))
}
})
.peekable();
if iter.peek().is_some() {
Some(iter.collect())
} else {
None
}
};
self.map = new_map.map(Rc::new);
}
}

impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("State").finish()
}
}
Loading