]> git.proxmox.com Git - rustc.git/blobdiff - src/vendor/serde_derive_internals/src/check.rs
New upstream version 1.26.0+dfsg1
[rustc.git] / src / vendor / serde_derive_internals / src / check.rs
index 2dae42f54106e4b0e36f8ea06ddd4ff0c9d49b39..31980a398eb8b3b0e9a26633622f7c0d6664390e 100644 (file)
@@ -6,29 +6,32 @@
 // 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 = \"...\")]",
@@ -38,6 +41,38 @@ fn check_getter(cx: &Ctxt, cont: &Container) {
     }
 }
 
+/// 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.
 ///
@@ -45,9 +80,9 @@ fn check_getter(cx: &Ctxt, cont: &Container) {
 /// `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;
         }
     };
@@ -102,9 +137,9 @@ fn check_identifier(cx: &Ctxt, cont: &Container) {
 /// 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;
         }
     };
@@ -169,3 +204,67 @@ fn check_variant_skip_attrs(cx: &Ctxt, cont: &Container) {
         }
     }
 }
+
+/// 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);
+    }
+}