// option. This file may not be copied, modified, or distributed
// except according to those terms.
-use ast::{Body, Container, Style};
-use attr::Identifier;
+use ast::{Data, Container, Style};
+use attr::{Identifier, EnumTag};
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, cont: &Container) {
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);
}
/// 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 cont.body.has_getter() {
+ match cont.data {
+ Data::Enum(_) => {
+ if cont.data.has_getter() {
cx.error("#[serde(getter = \"...\")] is not allowed in an enum");
}
}
- Body::Struct(_, _) => {
- if cont.body.has_getter() && cont.attrs.remote().is_none() {
+ Data::Struct(_, _) => {
+ if cont.data.has_getter() && cont.attrs.remote().is_none() {
cx.error(
"#[serde(getter = \"...\")] can only be used in structs \
that have #[serde(remote = \"...\")]",
}
}
+/// Flattening has some restrictions we can test.
+fn check_flatten(cx: &Ctxt, cont: &Container) {
+ match cont.data {
+ Data::Enum(_) => {
+ assert!(!cont.attrs.has_flatten());
+ }
+ Data::Struct(_, _) => {
+ for field in cont.data.all_fields() {
+ if !field.attrs.flatten() {
+ continue;
+ }
+ if field.attrs.skip_serializing() {
+ cx.error(
+ "#[serde(flatten] can not be combined with \
+ #[serde(skip_serializing)]"
+ );
+ } else if field.attrs.skip_serializing_if().is_some() {
+ cx.error(
+ "#[serde(flatten] can not be combined with \
+ #[serde(skip_serializing_if = \"...\")]"
+ );
+ } else if field.attrs.skip_deserializing() {
+ cx.error(
+ "#[serde(flatten] can not be combined with \
+ #[serde(skip_deserializing)]"
+ );
+ }
+ }
+ }
+ }
+}
+
/// 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.
///
/// `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(_, _) => {
+ let variants = match cont.data {
+ Data::Enum(ref variants) => variants,
+ Data::Struct(_, _) => {
return;
}
};
/// Skip-(de)serializing attributes are not allowed on variants marked
/// (de)serialize_with.
fn check_variant_skip_attrs(cx: &Ctxt, cont: &Container) {
- let variants = match cont.body {
- Body::Enum(ref variants) => variants,
- Body::Struct(_, _) => {
+ let variants = match cont.data {
+ Data::Enum(ref variants) => variants,
+ Data::Struct(_, _) => {
return;
}
};
}
}
}
+
+/// The tag of an internally-tagged struct variant must not be
+/// the same as either one of its fields, as this would result in
+/// duplicate keys in the serialized output and/or ambiguity in
+/// the to-be-deserialized input.
+fn check_internal_tag_field_name_conflict(
+ cx: &Ctxt,
+ cont: &Container,
+) {
+ let variants = match cont.data {
+ Data::Enum(ref variants) => variants,
+ Data::Struct(_, _) => return,
+ };
+
+ let tag = match *cont.attrs.tag() {
+ EnumTag::Internal { ref tag } => tag.as_str(),
+ EnumTag::External | EnumTag::Adjacent { .. } | EnumTag::None => return,
+ };
+
+ let diagnose_conflict = || {
+ let message = format!(
+ "variant field name `{}` conflicts with internal tag",
+ tag
+ );
+ cx.error(message);
+ };
+
+ for variant in variants {
+ match variant.style {
+ Style::Struct => {
+ for field in &variant.fields {
+ let check_ser = !field.attrs.skip_serializing();
+ let check_de = !field.attrs.skip_deserializing();
+ let name = field.attrs.name();
+ let ser_name = name.serialize_name();
+ let de_name = name.deserialize_name();
+
+ if check_ser && ser_name == tag || check_de && de_name == tag {
+ diagnose_conflict();
+ return;
+ }
+ }
+ },
+ Style::Unit | Style::Newtype | Style::Tuple => {},
+ }
+ }
+}
+
+/// In the case of adjacently-tagged enums, the type and the
+/// contents tag must differ, for the same reason.
+fn check_adjacent_tag_conflict(cx: &Ctxt, cont: &Container) {
+ let (type_tag, content_tag) = match *cont.attrs.tag() {
+ EnumTag::Adjacent { ref tag, ref content } => (tag, content),
+ EnumTag::Internal { .. } | EnumTag::External | EnumTag::None => return,
+ };
+
+ if type_tag == content_tag {
+ let message = format!(
+ "enum tags `{}` for type and content conflict with each other",
+ type_tag
+ );
+ cx.error(message);
+ }
+}