mirror of
https://github.com/pezkuwichain/serde.git
synced 2026-04-29 05:57:55 +00:00
Generate deserialize impls for identifiers
This commit is contained in:
@@ -36,6 +36,7 @@ pub struct Field<'a> {
|
||||
pub ty: &'a syn::Ty,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Style {
|
||||
Struct,
|
||||
Tuple,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user