Generate deserialize impls for identifiers

This commit is contained in:
David Tolnay
2017-04-14 16:09:00 -07:00
parent 30e8c84d01
commit cdfd445528
17 changed files with 745 additions and 103 deletions
+1
View File
@@ -36,6 +36,7 @@ pub struct Field<'a> {
pub ty: &'a syn::Ty,
}
#[derive(Copy, Clone)]
pub enum Style {
Struct,
Tuple,
+73
View File
@@ -111,6 +111,7 @@ pub struct Container {
from_type: Option<syn::Ty>,
into_type: Option<syn::Ty>,
remote: Option<syn::Path>,
identifier: Identifier,
}
/// Styles of representing an enum.
@@ -145,6 +146,23 @@ pub enum EnumTag {
None,
}
/// Whether this enum represents the fields of a struct or the variants of an
/// enum.
#[derive(Copy, Clone, Debug)]
pub enum Identifier {
/// It does not.
No,
/// This enum represents the fields of a struct. All of the variants must be
/// unit variants, except possibly one which is annotated with
/// `#[serde(other)]` and is a newtype variant.
Field,
/// This enum represents the variants of an enum. All of the variants must
/// be unit variants.
Variant,
}
impl Container {
/// Extract out the `#[serde(...)]` attributes from an item.
pub fn from_ast(cx: &Ctxt, item: &syn::MacroInput) -> Self {
@@ -161,6 +179,8 @@ impl Container {
let mut from_type = Attr::none(cx, "from");
let mut into_type = Attr::none(cx, "into");
let mut remote = Attr::none(cx, "remote");
let mut field_identifier = BoolAttr::none(cx, "field_identifier");
let mut variant_identifier = BoolAttr::none(cx, "variant_identifier");
for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) {
for meta_item in meta_items {
@@ -307,6 +327,16 @@ impl Container {
}
}
// Parse `#[serde(field_identifier)]`
MetaItem(Word(ref name)) if name == "field_identifier" => {
field_identifier.set_true();
}
// Parse `#[serde(variant_identifier)]`
MetaItem(Word(ref name)) if name == "variant_identifier" => {
variant_identifier.set_true();
}
MetaItem(ref meta_item) => {
cx.error(format!("unknown serde container attribute `{}`",
meta_item.name()));
@@ -333,6 +363,7 @@ impl Container {
from_type: from_type.get(),
into_type: into_type.get(),
remote: remote.get(),
identifier: decide_identifier(cx, item, field_identifier, variant_identifier),
}
}
@@ -375,6 +406,10 @@ impl Container {
pub fn remote(&self) -> Option<&syn::Path> {
self.remote.as_ref()
}
pub fn identifier(&self) -> Identifier {
self.identifier
}
}
fn decide_tag(
@@ -431,6 +466,31 @@ fn decide_tag(
}
}
fn decide_identifier(
cx: &Ctxt,
item: &syn::MacroInput,
field_identifier: BoolAttr,
variant_identifier: BoolAttr,
) -> Identifier {
match (&item.body, field_identifier.get(), variant_identifier.get()) {
(_, false, false) => Identifier::No,
(_, true, true) => {
cx.error("`field_identifier` and `variant_identifier` cannot both be set");
Identifier::No
}
(&syn::Body::Struct(_), true, false) => {
cx.error("`field_identifier` can only be used on an enum");
Identifier::No
}
(&syn::Body::Struct(_), false, true) => {
cx.error("`variant_identifier` can only be used on an enum");
Identifier::No
}
(&syn::Body::Enum(_), true, false) => Identifier::Field,
(&syn::Body::Enum(_), false, true) => Identifier::Variant,
}
}
/// Represents variant attribute information
#[derive(Debug)]
pub struct Variant {
@@ -440,6 +500,7 @@ pub struct Variant {
rename_all: RenameRule,
skip_deserializing: bool,
skip_serializing: bool,
other: bool,
}
impl Variant {
@@ -449,6 +510,7 @@ impl Variant {
let mut skip_deserializing = BoolAttr::none(cx, "skip_deserializing");
let mut skip_serializing = BoolAttr::none(cx, "skip_serializing");
let mut rename_all = Attr::none(cx, "rename_all");
let mut other = BoolAttr::none(cx, "other");
for meta_items in variant.attrs.iter().filter_map(get_serde_meta_items) {
for meta_item in meta_items {
@@ -487,11 +549,17 @@ impl Variant {
MetaItem(Word(ref name)) if name == "skip_deserializing" => {
skip_deserializing.set_true();
}
// Parse `#[serde(skip_serializing)]`
MetaItem(Word(ref name)) if name == "skip_serializing" => {
skip_serializing.set_true();
}
// Parse `#[serde(other)]`
MetaItem(Word(ref name)) if name == "other" => {
other.set_true();
}
MetaItem(ref meta_item) => {
cx.error(format!("unknown serde variant attribute `{}`", meta_item.name()));
}
@@ -517,6 +585,7 @@ impl Variant {
rename_all: rename_all.get().unwrap_or(RenameRule::None),
skip_deserializing: skip_deserializing.get(),
skip_serializing: skip_serializing.get(),
other: other.get(),
}
}
@@ -544,6 +613,10 @@ impl Variant {
pub fn skip_serializing(&self) -> bool {
self.skip_serializing
}
pub fn other(&self) -> bool {
self.other
}
}
/// Represents field attribute information
+70 -5
View File
@@ -6,23 +6,88 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use ast::{Body, Container};
use ast::{Body, Container, Style};
use attr::Identifier;
use Ctxt;
/// 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, item: &Container) {
match item.body {
pub fn check(cx: &Ctxt, cont: &Container) {
check_getter(cx, cont);
check_identifier(cx, cont);
}
/// Getters are only allowed inside structs (not enums) with the `remote`
/// attribute.
fn check_getter(cx: &Ctxt, cont: &Container) {
match cont.body {
Body::Enum(_) => {
if item.body.has_getter() {
if cont.body.has_getter() {
cx.error("#[serde(getter = \"...\")] is not allowed in an enum");
}
}
Body::Struct(_, _) => {
if item.body.has_getter() && item.attrs.remote().is_none() {
if cont.body.has_getter() && cont.attrs.remote().is_none() {
cx.error("#[serde(getter = \"...\")] can only be used in structs \
that have #[serde(remote = \"...\")]");
}
}
}
}
/// The `other` attribute must be used at most once and it must be the last
/// variant of an enum that has the `field_identifier` attribute.
///
/// Inside a `variant_identifier` all variants must be unit variants. Inside a
/// `field_identifier` all but possibly one variant must be unit variants. The
/// last variant may be a newtype variant which is an implicit "other" case.
fn check_identifier(cx: &Ctxt, cont: &Container) {
let variants = match cont.body {
Body::Enum(ref variants) => variants,
Body::Struct(_, _) => {
return;
}
};
for (i, variant) in variants.iter().enumerate() {
match (variant.style, cont.attrs.identifier(), variant.attrs.other()) {
// The `other` attribute may only be used in a field_identifier.
(_, Identifier::Variant, true) | (_, Identifier::No, true) => {
cx.error("#[serde(other)] may only be used inside a field_identifier");
}
// Variant with `other` attribute must be the last one.
(Style::Unit, Identifier::Field, true) => {
if i < variants.len() - 1 {
cx.error("#[serde(other)] must be the last variant");
}
}
// Variant with `other` attribute must be a unit variant.
(_, Identifier::Field, true) => {
cx.error("#[serde(other)] must be on a unit variant");
}
// Any sort of variant is allowed if this is not an identifier.
(_, Identifier::No, false) => {}
// Unit variant without `other` attribute is always fine.
(Style::Unit, _, false) => {}
// The last field is allowed to be a newtype catch-all.
(Style::Newtype, Identifier::Field, false) => {
if i < variants.len() - 1 {
cx.error(format!("`{}` must be the last variant", variant.ident));
}
}
(_, Identifier::Field, false) => {
cx.error("field_identifier may only contain unit variants");
}
(_, Identifier::Variant, false) => {
cx.error("variant_identifier may only contain unit variants");
}
}
}
}