Skip to content

Commit

Permalink
Merge pull request #1270 from serde-rs/transparent
Browse files Browse the repository at this point in the history
Transparent attribute to specify that representation is the same as its only field
  • Loading branch information
dtolnay committed May 21, 2018
2 parents 3208976 + 6bbc415 commit 922fadf
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 15 deletions.
47 changes: 43 additions & 4 deletions serde_derive/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ use syn::{self, Ident, Index, Member};
use bound;
use fragment::{Expr, Fragment, Match, Stmts};
use internals::ast::{Container, Data, Field, Style, Variant};
use internals::{attr, Ctxt};
use internals::{attr, Ctxt, Derive};
use pretend;
use try;

use std::collections::BTreeSet;

pub fn expand_derive_deserialize(input: &syn::DeriveInput) -> Result<TokenStream, String> {
let ctxt = Ctxt::new();
let cont = Container::from_ast(&ctxt, input);
let cont = Container::from_ast(&ctxt, input, Derive::Deserialize);
precondition(&ctxt, &cont);
try!(ctxt.check());

Expand Down Expand Up @@ -269,7 +269,9 @@ fn borrowed_lifetimes(cont: &Container) -> BorrowedLifetimes {
}

fn deserialize_body(cont: &Container, params: &Parameters) -> Fragment {
if let Some(type_from) = cont.attrs.type_from() {
if cont.attrs.transparent() {
deserialize_transparent(cont, params)
} else if let Some(type_from) = cont.attrs.type_from() {
deserialize_from(type_from)
} else if let attr::Identifier::No = cont.attrs.identifier() {
match cont.data {
Expand Down Expand Up @@ -298,7 +300,9 @@ fn deserialize_in_place_body(cont: &Container, params: &Parameters) -> Option<St
// deserialize_in_place for remote derives.
assert!(!params.has_getter);

if cont.attrs.type_from().is_some() || cont.attrs.identifier().is_some()
if cont.attrs.transparent()
|| cont.attrs.type_from().is_some()
|| cont.attrs.identifier().is_some()
|| cont
.data
.all_fields()
Expand Down Expand Up @@ -344,6 +348,41 @@ fn deserialize_in_place_body(_cont: &Container, _params: &Parameters) -> Option<
None
}

fn deserialize_transparent(cont: &Container, params: &Parameters) -> Fragment {
let fields = match cont.data {
Data::Struct(_, ref fields) => fields,
Data::Enum(_) => unreachable!(),
};

let this = &params.this;
let transparent_field = fields.iter().find(|f| f.attrs.transparent()).unwrap();

let path = match transparent_field.attrs.deserialize_with() {
Some(path) => quote!(#path),
None => quote!(_serde::Deserialize::deserialize),
};

let assign = fields.iter().map(|field| {
let member = &field.member;
if field as *const Field == transparent_field as *const Field {
quote!(#member: __transparent)
} else {
let value = match *field.attrs.default() {
attr::Default::Default => quote!(_serde::export::Default::default()),
attr::Default::Path(ref path) => quote!(#path()),
attr::Default::None => quote!(_serde::export::PhantomData),
};
quote!(#member: #value)
}
});

quote_block! {
_serde::export::Result::map(
#path(__deserializer),
|__transparent| #this { #(#assign),* })
}
}

fn deserialize_from(type_from: &syn::Type) -> Fragment {
quote_block! {
_serde::export::Result::map(
Expand Down
8 changes: 4 additions & 4 deletions serde_derive/src/internals/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use internals::attr;
use internals::check;
use internals::Ctxt;
use internals::{Ctxt, Derive};
use syn;
use syn::punctuated::Punctuated;

Expand Down Expand Up @@ -47,7 +47,7 @@ pub enum Style {
}

impl<'a> Container<'a> {
pub fn from_ast(cx: &Ctxt, item: &'a syn::DeriveInput) -> Container<'a> {
pub fn from_ast(cx: &Ctxt, item: &'a syn::DeriveInput, derive: Derive) -> Container<'a> {
let mut attrs = attr::Container::from_ast(cx, item);

let mut data = match item.data {
Expand Down Expand Up @@ -86,13 +86,13 @@ impl<'a> Container<'a> {
attrs.mark_has_flatten();
}

let item = Container {
let mut item = Container {
ident: item.ident.clone(),
attrs: attrs,
data: data,
generics: &item.generics,
};
check::check(cx, &item);
check::check(cx, &mut item, derive);
item
}
}
Expand Down
23 changes: 22 additions & 1 deletion serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ impl Name {
/// Represents container (e.g. struct) attribute information
pub struct Container {
name: Name,
transparent: bool,
deny_unknown_fields: bool,
default: Default,
rename_all: RenameRule,
Expand Down Expand Up @@ -181,6 +182,7 @@ impl Container {
pub fn from_ast(cx: &Ctxt, item: &syn::DeriveInput) -> Self {
let mut ser_name = Attr::none(cx, "rename");
let mut de_name = Attr::none(cx, "rename");
let mut transparent = BoolAttr::none(cx, "transparent");
let mut deny_unknown_fields = BoolAttr::none(cx, "deny_unknown_fields");
let mut default = Attr::none(cx, "default");
let mut rename_all = Attr::none(cx, "rename_all");
Expand Down Expand Up @@ -228,6 +230,11 @@ impl Container {
}
}

// Parse `#[serde(transparent)]`
Meta(Word(ref word)) if word == "transparent" => {
transparent.set_true();
}

// Parse `#[serde(deny_unknown_fields)]`
Meta(Word(ref word)) if word == "deny_unknown_fields" => {
deny_unknown_fields.set_true();
Expand Down Expand Up @@ -376,6 +383,7 @@ impl Container {
serialize: ser_name.get().unwrap_or_else(|| item.ident.to_string()),
deserialize: de_name.get().unwrap_or_else(|| item.ident.to_string()),
},
transparent: transparent.get(),
deny_unknown_fields: deny_unknown_fields.get(),
default: default.get().unwrap_or(Default::None),
rename_all: rename_all.get().unwrap_or(RenameRule::None),
Expand All @@ -398,6 +406,10 @@ impl Container {
&self.rename_all
}

pub fn transparent(&self) -> bool {
self.transparent
}

pub fn deny_unknown_fields(&self) -> bool {
self.deny_unknown_fields
}
Expand Down Expand Up @@ -764,6 +776,7 @@ pub struct Field {
borrowed_lifetimes: BTreeSet<syn::Lifetime>,
getter: Option<syn::ExprPath>,
flatten: bool,
transparent: bool,
}

/// Represents the default to use for a field when deserializing.
Expand All @@ -777,7 +790,6 @@ pub enum Default {
}

impl Default {
#[cfg(feature = "deserialize_in_place")]
pub fn is_none(&self) -> bool {
match *self {
Default::None => true,
Expand Down Expand Up @@ -1066,6 +1078,7 @@ impl Field {
borrowed_lifetimes: borrowed_lifetimes,
getter: getter.get(),
flatten: flatten.get(),
transparent: false,
}
}

Expand Down Expand Up @@ -1125,6 +1138,14 @@ impl Field {
pub fn flatten(&self) -> bool {
self.flatten
}

pub fn transparent(&self) -> bool {
self.transparent
}

pub fn mark_transparent(&mut self) {
self.transparent = true;
}
}

type SerAndDe<T> = (Option<T>, Option<T>);
Expand Down
73 changes: 70 additions & 3 deletions serde_derive/src/internals/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@

use internals::ast::{Container, Data, Field, Style};
use internals::attr::{EnumTag, Identifier};
use internals::Ctxt;
use syn::Member;
use internals::{Ctxt, Derive};
use syn::{Member, Type};

/// Cross-cutting checks that require looking at more than a single attrs
/// object. Simpler checks should happen when parsing and building the attrs.
pub fn check(cx: &Ctxt, cont: &Container) {
pub fn check(cx: &Ctxt, cont: &mut Container, derive: Derive) {
check_getter(cx, cont);
check_flatten(cx, cont);
check_identifier(cx, cont);
check_variant_skip_attrs(cx, cont);
check_internal_tag_field_name_conflict(cx, cont);
check_adjacent_tag_conflict(cx, cont);
check_transparent(cx, cont, derive);
}

/// Getters are only allowed inside structs (not enums) with the `remote`
Expand Down Expand Up @@ -278,9 +279,75 @@ fn check_adjacent_tag_conflict(cx: &Ctxt, cont: &Container) {
}
}

/// Enums and unit structs cannot be transparent.
fn check_transparent(cx: &Ctxt, cont: &mut Container, derive: Derive) {
if !cont.attrs.transparent() {
return;
}

if cont.attrs.type_from().is_some() {
cx.error("#[serde(transparent)] is not allowed with #[serde(from = \"...\")]");
}

if cont.attrs.type_into().is_some() {
cx.error("#[serde(transparent)] is not allowed with #[serde(into = \"...\")]");
}

let fields = match cont.data {
Data::Enum(_) => {
cx.error("#[serde(transparent)] is not allowed on an enum");
return;
}
Data::Struct(Style::Unit, _) => {
cx.error("#[serde(transparent)] is not allowed on a unit struct");
return;
}
Data::Struct(_, ref mut fields) => fields,
};

let mut transparent_field = None;

for field in fields {
if allow_transparent(field, derive) {
if transparent_field.is_some() {
cx.error("#[serde(transparent)] requires struct to have at most one transparent field");
return;
}
transparent_field = Some(field);
}
}

match transparent_field {
Some(transparent_field) => transparent_field.attrs.mark_transparent(),
None => match derive {
Derive::Serialize => {
cx.error("#[serde(transparent)] requires at least one field that is not skipped");
}
Derive::Deserialize => {
cx.error("#[serde(transparent)] requires at least one field that is neither skipped nor has a default");
}
}
}
}

fn member_message(member: &Member) -> String {
match *member {
Member::Named(ref ident) => format!("`{}`", ident),
Member::Unnamed(ref i) => i.index.to_string(),
}
}

fn allow_transparent(field: &Field, derive: Derive) -> bool {
if let Type::Path(ref ty) = *field.ty {
if let Some(seg) = ty.path.segments.last() {
if seg.into_value().ident == "PhantomData" {
return false;
}
}
}

match derive {
Derive::Serialize => !field.attrs.skip_serializing(),
Derive::Deserialize => !field.attrs.skip_deserializing() && field.attrs.default().is_none(),
}
}
6 changes: 6 additions & 0 deletions serde_derive/src/internals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ pub use self::ctxt::Ctxt;

mod case;
mod check;

#[derive(Copy, Clone)]
pub enum Derive {
Serialize,
Deserialize,
}
28 changes: 25 additions & 3 deletions serde_derive/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ use syn::{self, Ident, Index, Member};
use bound;
use fragment::{Fragment, Match, Stmts};
use internals::ast::{Container, Data, Field, Style, Variant};
use internals::{attr, Ctxt};
use internals::{attr, Ctxt, Derive};
use pretend;
use try;

pub fn expand_derive_serialize(input: &syn::DeriveInput) -> Result<TokenStream, String> {
let ctxt = Ctxt::new();
let cont = Container::from_ast(&ctxt, input);
let cont = Container::from_ast(&ctxt, input, Derive::Serialize);
precondition(&ctxt, &cont);
try!(ctxt.check());

Expand Down Expand Up @@ -166,7 +166,9 @@ fn needs_serialize_bound(field: &attr::Field, variant: Option<&attr::Variant>) -
}

fn serialize_body(cont: &Container, params: &Parameters) -> Fragment {
if let Some(type_into) = cont.attrs.type_into() {
if cont.attrs.transparent() {
serialize_transparent(cont, params)
} else if let Some(type_into) = cont.attrs.type_into() {
serialize_into(params, type_into)
} else {
match cont.data {
Expand All @@ -185,6 +187,26 @@ fn serialize_body(cont: &Container, params: &Parameters) -> Fragment {
}
}

fn serialize_transparent(cont: &Container, params: &Parameters) -> Fragment {
let fields = match cont.data {
Data::Struct(_, ref fields) => fields,
Data::Enum(_) => unreachable!(),
};

let self_var = &params.self_var;
let transparent_field = fields.iter().find(|f| f.attrs.transparent()).unwrap();
let member = &transparent_field.member;

let path = match transparent_field.attrs.serialize_with() {
Some(path) => quote!(#path),
None => quote!(_serde::Serialize::serialize),
};

quote_block! {
#path(&#self_var.#member, __serializer)
}
}

fn serialize_into(params: &Parameters, type_into: &syn::Type) -> Fragment {
let self_var = &params.self_var;
quote_block! {
Expand Down
20 changes: 20 additions & 0 deletions test_suite/tests/compile-fail/transparent/at_most_one.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2018 Serde Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[macro_use]
extern crate serde_derive;

#[derive(Serialize)] //~ ERROR: proc-macro derive panicked
#[serde(transparent)]
struct S {
//~^^^ HELP: #[serde(transparent)] requires struct to have at most one transparent field
a: u8,
b: u8,
}

fn main() {}

0 comments on commit 922fadf

Please sign in to comment.