mirror of
https://github.com/pezkuwichain/serde.git
synced 2026-06-09 20:11:01 +00:00
Improve overall quality of compile_error! errors
Also updates UI tests.
This commit is contained in:
+290
-152
@@ -1,5 +1,6 @@
|
||||
use internals::Ctxt;
|
||||
use proc_macro2::{Group, Span, TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use std::collections::BTreeSet;
|
||||
use std::str::FromStr;
|
||||
use syn;
|
||||
@@ -19,10 +20,11 @@ use syn::NestedMeta::{Literal, Meta};
|
||||
|
||||
pub use internals::case::RenameRule;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Clone)]
|
||||
struct Attr<'c, T> {
|
||||
cx: &'c Ctxt,
|
||||
name: &'static str,
|
||||
tokens: TokenStream,
|
||||
value: Option<T>,
|
||||
}
|
||||
|
||||
@@ -31,22 +33,28 @@ impl<'c, T> Attr<'c, T> {
|
||||
Attr {
|
||||
cx: cx,
|
||||
name: name,
|
||||
tokens: TokenStream::new(),
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&mut self, value: T) {
|
||||
fn set<A: ToTokens>(&mut self, obj: A, value: T) {
|
||||
let tokens = obj.into_token_stream();
|
||||
|
||||
if self.value.is_some() {
|
||||
self.cx
|
||||
.error(format!("duplicate serde attribute `{}`", self.name));
|
||||
self.cx.error_spanned_by(
|
||||
tokens,
|
||||
format!("duplicate serde attribute `{}`", self.name),
|
||||
);
|
||||
} else {
|
||||
self.tokens = tokens;
|
||||
self.value = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_opt(&mut self, value: Option<T>) {
|
||||
fn set_opt<A: ToTokens>(&mut self, obj: A, value: Option<T>) {
|
||||
if let Some(value) = value {
|
||||
self.set(value);
|
||||
self.set(obj, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +67,13 @@ impl<'c, T> Attr<'c, T> {
|
||||
fn get(self) -> Option<T> {
|
||||
self.value
|
||||
}
|
||||
|
||||
fn get_with_tokens(self) -> Option<(TokenStream, T)> {
|
||||
match self.value {
|
||||
Some(v) => Some((self.tokens, v)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BoolAttr<'c>(Attr<'c, ()>);
|
||||
@@ -68,8 +83,8 @@ impl<'c> BoolAttr<'c> {
|
||||
BoolAttr(Attr::none(cx, name))
|
||||
}
|
||||
|
||||
fn set_true(&mut self) {
|
||||
self.0.set(());
|
||||
fn set_true<A: ToTokens>(&mut self, obj: A) {
|
||||
self.0.set(obj, ());
|
||||
}
|
||||
|
||||
fn get(&self) -> bool {
|
||||
@@ -199,16 +214,16 @@ impl Container {
|
||||
// Parse `#[serde(rename = "foo")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "rename" => {
|
||||
if let Ok(s) = get_lit_str(cx, &m.ident, &m.ident, &m.lit) {
|
||||
ser_name.set(s.value());
|
||||
de_name.set(s.value());
|
||||
ser_name.set(&m.ident, s.value());
|
||||
de_name.set(&m.ident, s.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(rename(serialize = "foo", deserialize = "bar"))]`
|
||||
Meta(List(ref m)) if m.ident == "rename" => {
|
||||
if let Ok((ser, de)) = get_renames(cx, &m.nested) {
|
||||
ser_name.set_opt(ser.map(syn::LitStr::value));
|
||||
de_name.set_opt(de.map(syn::LitStr::value));
|
||||
ser_name.set_opt(&m.ident, ser.map(syn::LitStr::value));
|
||||
de_name.set_opt(&m.ident, de.map(syn::LitStr::value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,11 +231,11 @@ impl Container {
|
||||
Meta(NameValue(ref m)) if m.ident == "rename_all" => {
|
||||
if let Ok(s) = get_lit_str(cx, &m.ident, &m.ident, &m.lit) {
|
||||
match RenameRule::from_str(&s.value()) {
|
||||
Ok(rename_rule) => rename_all.set(rename_rule),
|
||||
Err(()) => cx.error(format!(
|
||||
Ok(rename_rule) => rename_all.set(&m.ident, rename_rule),
|
||||
Err(()) => cx.error_spanned_by(s, format!(
|
||||
"unknown rename rule for #[serde(rename_all \
|
||||
= {:?})]",
|
||||
s.value()
|
||||
s.value(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -228,23 +243,33 @@ impl Container {
|
||||
|
||||
// Parse `#[serde(transparent)]`
|
||||
Meta(Word(ref word)) if word == "transparent" => {
|
||||
transparent.set_true();
|
||||
transparent.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(deny_unknown_fields)]`
|
||||
Meta(Word(ref word)) if word == "deny_unknown_fields" => {
|
||||
deny_unknown_fields.set_true();
|
||||
deny_unknown_fields.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(default)]`
|
||||
Meta(Word(ref word)) if word == "default" => match item.data {
|
||||
syn::Data::Struct(syn::DataStruct {
|
||||
fields: syn::Fields::Named(_),
|
||||
..
|
||||
}) => {
|
||||
default.set(Default::Default);
|
||||
}
|
||||
_ => cx.error(
|
||||
syn::Data::Struct(syn::DataStruct { ref fields, .. }) => match *fields {
|
||||
syn::Fields::Named(_) => {
|
||||
default.set(word, Default::Default);
|
||||
}
|
||||
syn::Fields::Unnamed(_) | syn::Fields::Unit => cx.error_spanned_by(
|
||||
fields,
|
||||
"#[serde(default)] can only be used on structs \
|
||||
with named fields",
|
||||
)
|
||||
},
|
||||
syn::Data::Enum(syn::DataEnum { ref enum_token, .. }) => cx.error_spanned_by(
|
||||
enum_token,
|
||||
"#[serde(default)] can only be used on structs \
|
||||
with named fields",
|
||||
),
|
||||
syn::Data::Union(syn::DataUnion { ref union_token, .. }) => cx.error_spanned_by(
|
||||
union_token,
|
||||
"#[serde(default)] can only be used on structs \
|
||||
with named fields",
|
||||
),
|
||||
@@ -254,13 +279,23 @@ impl Container {
|
||||
Meta(NameValue(ref m)) if m.ident == "default" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
match item.data {
|
||||
syn::Data::Struct(syn::DataStruct {
|
||||
fields: syn::Fields::Named(_),
|
||||
..
|
||||
}) => {
|
||||
default.set(Default::Path(path));
|
||||
}
|
||||
_ => cx.error(
|
||||
syn::Data::Struct(syn::DataStruct { ref fields, .. }) => match *fields {
|
||||
syn::Fields::Named(_) => {
|
||||
default.set(&m.ident, Default::Path(path));
|
||||
}
|
||||
syn::Fields::Unnamed(_) | syn::Fields::Unit => cx.error_spanned_by(
|
||||
fields,
|
||||
"#[serde(default = \"...\")] can only be used \
|
||||
on structs with named fields",
|
||||
)
|
||||
},
|
||||
syn::Data::Enum(syn::DataEnum { ref enum_token, .. }) => cx.error_spanned_by(
|
||||
enum_token,
|
||||
"#[serde(default = \"...\")] can only be used \
|
||||
on structs with named fields",
|
||||
),
|
||||
syn::Data::Union(syn::DataUnion { ref union_token, .. }) => cx.error_spanned_by(
|
||||
union_token,
|
||||
"#[serde(default = \"...\")] can only be used \
|
||||
on structs with named fields",
|
||||
),
|
||||
@@ -273,26 +308,35 @@ impl Container {
|
||||
if let Ok(where_predicates) =
|
||||
parse_lit_into_where(cx, &m.ident, &m.ident, &m.lit)
|
||||
{
|
||||
ser_bound.set(where_predicates.clone());
|
||||
de_bound.set(where_predicates);
|
||||
ser_bound.set(&m.ident, where_predicates.clone());
|
||||
de_bound.set(&m.ident, where_predicates);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(bound(serialize = "...", deserialize = "..."))]`
|
||||
Meta(List(ref m)) if m.ident == "bound" => {
|
||||
if let Ok((ser, de)) = get_where_predicates(cx, &m.nested) {
|
||||
ser_bound.set_opt(ser);
|
||||
de_bound.set_opt(de);
|
||||
ser_bound.set_opt(&m.ident, ser);
|
||||
de_bound.set_opt(&m.ident, de);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(untagged)]`
|
||||
Meta(Word(ref word)) if word == "untagged" => match item.data {
|
||||
syn::Data::Enum(_) => {
|
||||
untagged.set_true();
|
||||
untagged.set_true(word);
|
||||
}
|
||||
syn::Data::Struct(_) | syn::Data::Union(_) => {
|
||||
cx.error("#[serde(untagged)] can only be used on enums")
|
||||
syn::Data::Struct(syn::DataStruct { ref struct_token, .. }) => {
|
||||
cx.error_spanned_by(
|
||||
struct_token,
|
||||
"#[serde(untagged)] can only be used on enums",
|
||||
);
|
||||
}
|
||||
syn::Data::Union(syn::DataUnion { ref union_token, .. }) => {
|
||||
cx.error_spanned_by(
|
||||
union_token,
|
||||
"#[serde(untagged)] can only be used on enums",
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -301,11 +345,20 @@ impl Container {
|
||||
if let Ok(s) = get_lit_str(cx, &m.ident, &m.ident, &m.lit) {
|
||||
match item.data {
|
||||
syn::Data::Enum(_) => {
|
||||
internal_tag.set(s.value());
|
||||
}
|
||||
syn::Data::Struct(_) | syn::Data::Union(_) => {
|
||||
cx.error("#[serde(tag = \"...\")] can only be used on enums")
|
||||
internal_tag.set(&m.ident, s.value());
|
||||
}
|
||||
syn::Data::Struct(syn::DataStruct { ref struct_token, .. }) => {
|
||||
cx.error_spanned_by(
|
||||
struct_token,
|
||||
"#[serde(tag = \"...\")] can only be used on enums",
|
||||
);
|
||||
},
|
||||
syn::Data::Union(syn::DataUnion { ref union_token, .. }) => {
|
||||
cx.error_spanned_by(
|
||||
union_token,
|
||||
"#[serde(tag = \"...\")] can only be used on enums",
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,12 +368,20 @@ impl Container {
|
||||
if let Ok(s) = get_lit_str(cx, &m.ident, &m.ident, &m.lit) {
|
||||
match item.data {
|
||||
syn::Data::Enum(_) => {
|
||||
content.set(s.value());
|
||||
content.set(&m.ident, s.value());
|
||||
}
|
||||
syn::Data::Struct(_) | syn::Data::Union(_) => cx.error(
|
||||
"#[serde(content = \"...\")] can only be used on \
|
||||
enums",
|
||||
),
|
||||
syn::Data::Struct(syn::DataStruct { ref struct_token, .. }) => {
|
||||
cx.error_spanned_by(
|
||||
struct_token,
|
||||
"#[serde(content = \"...\")] can only be used on enums",
|
||||
);
|
||||
},
|
||||
syn::Data::Union(syn::DataUnion { ref union_token, .. }) => {
|
||||
cx.error_spanned_by(
|
||||
union_token,
|
||||
"#[serde(content = \"...\")] can only be used on enums",
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,14 +389,14 @@ impl Container {
|
||||
// Parse `#[serde(from = "Type")]
|
||||
Meta(NameValue(ref m)) if m.ident == "from" => {
|
||||
if let Ok(from_ty) = parse_lit_into_ty(cx, &m.ident, &m.lit) {
|
||||
type_from.set_opt(Some(from_ty));
|
||||
type_from.set_opt(&m.ident, Some(from_ty));
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(into = "Type")]
|
||||
Meta(NameValue(ref m)) if m.ident == "into" => {
|
||||
if let Ok(into_ty) = parse_lit_into_ty(cx, &m.ident, &m.lit) {
|
||||
type_into.set_opt(Some(into_ty));
|
||||
type_into.set_opt(&m.ident, Some(into_ty));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,32 +404,32 @@ impl Container {
|
||||
Meta(NameValue(ref m)) if m.ident == "remote" => {
|
||||
if let Ok(path) = parse_lit_into_path(cx, &m.ident, &m.lit) {
|
||||
if is_primitive_path(&path, "Self") {
|
||||
remote.set(item.ident.clone().into());
|
||||
remote.set(&m.ident, item.ident.clone().into());
|
||||
} else {
|
||||
remote.set(path);
|
||||
remote.set(&m.ident, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(field_identifier)]`
|
||||
Meta(Word(ref word)) if word == "field_identifier" => {
|
||||
field_identifier.set_true();
|
||||
field_identifier.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(variant_identifier)]`
|
||||
Meta(Word(ref word)) if word == "variant_identifier" => {
|
||||
variant_identifier.set_true();
|
||||
variant_identifier.set_true(word);
|
||||
}
|
||||
|
||||
Meta(ref meta_item) => {
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(meta_item.name(), format!(
|
||||
"unknown serde container attribute `{}`",
|
||||
meta_item.name()
|
||||
));
|
||||
}
|
||||
|
||||
Literal(_) => {
|
||||
cx.error("unexpected literal in serde container attribute");
|
||||
Literal(ref lit) => {
|
||||
cx.error_spanned_by(lit, "unexpected literal in serde container attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,11 +446,11 @@ impl Container {
|
||||
rename_all: rename_all.get().unwrap_or(RenameRule::None),
|
||||
ser_bound: ser_bound.get(),
|
||||
de_bound: de_bound.get(),
|
||||
tag: decide_tag(cx, item, &untagged, internal_tag, content),
|
||||
tag: decide_tag(cx, item, untagged, internal_tag, content),
|
||||
type_from: type_from.get(),
|
||||
type_into: type_into.get(),
|
||||
remote: remote.get(),
|
||||
identifier: decide_identifier(cx, item, &field_identifier, &variant_identifier),
|
||||
identifier: decide_identifier(cx, item, field_identifier, variant_identifier),
|
||||
has_flatten: false,
|
||||
}
|
||||
}
|
||||
@@ -454,14 +515,14 @@ impl Container {
|
||||
fn decide_tag(
|
||||
cx: &Ctxt,
|
||||
item: &syn::DeriveInput,
|
||||
untagged: &BoolAttr,
|
||||
untagged: BoolAttr,
|
||||
internal_tag: Attr<String>,
|
||||
content: Attr<String>,
|
||||
) -> EnumTag {
|
||||
match (untagged.get(), internal_tag.get(), content.get()) {
|
||||
(false, None, None) => EnumTag::External,
|
||||
(true, None, None) => EnumTag::None,
|
||||
(false, Some(tag), None) => {
|
||||
match (untagged.0.get_with_tokens(), internal_tag.get_with_tokens(), content.get_with_tokens()) {
|
||||
(None, None, None) => EnumTag::External,
|
||||
(Some(_), None, None) => EnumTag::None,
|
||||
(None, Some((_, tag)), None) => {
|
||||
// Check that there are no tuple variants.
|
||||
if let syn::Data::Enum(ref data) = item.data {
|
||||
for variant in &data.variants {
|
||||
@@ -469,7 +530,8 @@ fn decide_tag(
|
||||
syn::Fields::Named(_) | syn::Fields::Unit => {}
|
||||
syn::Fields::Unnamed(ref fields) => {
|
||||
if fields.unnamed.len() != 1 {
|
||||
cx.error(
|
||||
cx.error_spanned_by(
|
||||
variant,
|
||||
"#[serde(tag = \"...\")] cannot be used with tuple \
|
||||
variants",
|
||||
);
|
||||
@@ -481,24 +543,52 @@ fn decide_tag(
|
||||
}
|
||||
EnumTag::Internal { tag: tag }
|
||||
}
|
||||
(true, Some(_), None) => {
|
||||
cx.error("enum cannot be both untagged and internally tagged");
|
||||
(Some((untagged_tokens, _)), Some((tag_tokens, _)), None) => {
|
||||
cx.error_spanned_by(
|
||||
untagged_tokens,
|
||||
"enum cannot be both untagged and internally tagged",
|
||||
);
|
||||
cx.error_spanned_by(
|
||||
tag_tokens,
|
||||
"enum cannot be both untagged and internally tagged",
|
||||
);
|
||||
EnumTag::External // doesn't matter, will error
|
||||
}
|
||||
(false, None, Some(_)) => {
|
||||
cx.error("#[serde(tag = \"...\", content = \"...\")] must be used together");
|
||||
(None, None, Some((content_tokens, _))) => {
|
||||
cx.error_spanned_by(
|
||||
content_tokens,
|
||||
"#[serde(tag = \"...\", content = \"...\")] must be used together",
|
||||
);
|
||||
EnumTag::External
|
||||
}
|
||||
(true, None, Some(_)) => {
|
||||
cx.error("untagged enum cannot have #[serde(content = \"...\")]");
|
||||
(Some((untagged_tokens, _)), None, Some((content_tokens, _))) => {
|
||||
cx.error_spanned_by(
|
||||
untagged_tokens,
|
||||
"untagged enum cannot have #[serde(content = \"...\")]",
|
||||
);
|
||||
cx.error_spanned_by(
|
||||
content_tokens,
|
||||
"untagged enum cannot have #[serde(content = \"...\")]",
|
||||
);
|
||||
EnumTag::External
|
||||
}
|
||||
(false, Some(tag), Some(content)) => EnumTag::Adjacent {
|
||||
(None, Some((_, tag)), Some((_, content))) => EnumTag::Adjacent {
|
||||
tag: tag,
|
||||
content: content,
|
||||
},
|
||||
(true, Some(_), Some(_)) => {
|
||||
cx.error("untagged enum cannot have #[serde(tag = \"...\", content = \"...\")]");
|
||||
(Some((untagged_tokens, _)), Some((tag_tokens, _)), Some((content_tokens, _))) => {
|
||||
cx.error_spanned_by(
|
||||
untagged_tokens,
|
||||
"untagged enum cannot have #[serde(tag = \"...\", content = \"...\")]",
|
||||
);
|
||||
cx.error_spanned_by(
|
||||
tag_tokens,
|
||||
"untagged enum cannot have #[serde(tag = \"...\", content = \"...\")]",
|
||||
);
|
||||
cx.error_spanned_by(
|
||||
content_tokens,
|
||||
"untagged enum cannot have #[serde(tag = \"...\", content = \"...\")]",
|
||||
);
|
||||
EnumTag::External
|
||||
}
|
||||
}
|
||||
@@ -507,23 +597,50 @@ fn decide_tag(
|
||||
fn decide_identifier(
|
||||
cx: &Ctxt,
|
||||
item: &syn::DeriveInput,
|
||||
field_identifier: &BoolAttr,
|
||||
variant_identifier: &BoolAttr,
|
||||
field_identifier: BoolAttr,
|
||||
variant_identifier: BoolAttr,
|
||||
) -> Identifier {
|
||||
match (&item.data, field_identifier.get(), variant_identifier.get()) {
|
||||
(_, false, false) => Identifier::No,
|
||||
(_, true, true) => {
|
||||
cx.error("`field_identifier` and `variant_identifier` cannot both be set");
|
||||
match (&item.data, field_identifier.0.get_with_tokens(), variant_identifier.0.get_with_tokens()) {
|
||||
(_, None, None) => Identifier::No,
|
||||
(_, Some((field_identifier_tokens, _)), Some((variant_identifier_tokens, _))) => {
|
||||
cx.error_spanned_by(
|
||||
field_identifier_tokens,
|
||||
"#[serde(field_identifier)] and #[serde(variant_identifier)] cannot both be set",
|
||||
);
|
||||
cx.error_spanned_by(
|
||||
variant_identifier_tokens,
|
||||
"#[serde(field_identifier)] and #[serde(variant_identifier)] cannot both be set",
|
||||
);
|
||||
Identifier::No
|
||||
}
|
||||
(&syn::Data::Enum(_), true, false) => Identifier::Field,
|
||||
(&syn::Data::Enum(_), false, true) => Identifier::Variant,
|
||||
(&syn::Data::Struct(_), true, false) | (&syn::Data::Union(_), true, false) => {
|
||||
cx.error("`field_identifier` can only be used on an enum");
|
||||
(&syn::Data::Enum(_), Some(_), None) => Identifier::Field,
|
||||
(&syn::Data::Enum(_), None, Some(_)) => Identifier::Variant,
|
||||
(&syn::Data::Struct(syn::DataStruct { ref struct_token, .. }), Some(_), None) => {
|
||||
cx.error_spanned_by(
|
||||
struct_token,
|
||||
"#[serde(field_identifier)] can only be used on an enum",
|
||||
);
|
||||
Identifier::No
|
||||
}
|
||||
(&syn::Data::Struct(_), false, true) | (&syn::Data::Union(_), false, true) => {
|
||||
cx.error("`variant_identifier` can only be used on an enum");
|
||||
(&syn::Data::Union(syn::DataUnion { ref union_token, .. }), Some(_), None) => {
|
||||
cx.error_spanned_by(
|
||||
union_token,
|
||||
"#[serde(field_identifier)] can only be used on an enum",
|
||||
);
|
||||
Identifier::No
|
||||
}
|
||||
(&syn::Data::Struct(syn::DataStruct { ref struct_token, .. }), None, Some(_)) => {
|
||||
cx.error_spanned_by(
|
||||
struct_token,
|
||||
"#[serde(variant_identifier)] can only be used on an enum",
|
||||
);
|
||||
Identifier::No
|
||||
}
|
||||
(&syn::Data::Union(syn::DataUnion { ref union_token, .. }), None, Some(_)) => {
|
||||
cx.error_spanned_by(
|
||||
union_token,
|
||||
"#[serde(variant_identifier)] can only be used on an enum",
|
||||
);
|
||||
Identifier::No
|
||||
}
|
||||
}
|
||||
@@ -565,16 +682,16 @@ impl Variant {
|
||||
// Parse `#[serde(rename = "foo")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "rename" => {
|
||||
if let Ok(s) = get_lit_str(cx, &m.ident, &m.ident, &m.lit) {
|
||||
ser_name.set(s.value());
|
||||
de_name.set(s.value());
|
||||
ser_name.set(&m.ident, s.value());
|
||||
de_name.set(&m.ident, s.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(rename(serialize = "foo", deserialize = "bar"))]`
|
||||
Meta(List(ref m)) if m.ident == "rename" => {
|
||||
if let Ok((ser, de)) = get_renames(cx, &m.nested) {
|
||||
ser_name.set_opt(ser.map(syn::LitStr::value));
|
||||
de_name.set_opt(de.map(syn::LitStr::value));
|
||||
ser_name.set_opt(&m.ident, ser.map(syn::LitStr::value));
|
||||
de_name.set_opt(&m.ident, de.map(syn::LitStr::value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,8 +699,8 @@ impl Variant {
|
||||
Meta(NameValue(ref m)) if m.ident == "rename_all" => {
|
||||
if let Ok(s) = get_lit_str(cx, &m.ident, &m.ident, &m.lit) {
|
||||
match RenameRule::from_str(&s.value()) {
|
||||
Ok(rename_rule) => rename_all.set(rename_rule),
|
||||
Err(()) => cx.error(format!(
|
||||
Ok(rename_rule) => rename_all.set(&m.ident, rename_rule),
|
||||
Err(()) => cx.error_spanned_by(s, format!(
|
||||
"unknown rename rule for #[serde(rename_all \
|
||||
= {:?})]",
|
||||
s.value()
|
||||
@@ -594,23 +711,23 @@ impl Variant {
|
||||
|
||||
// Parse `#[serde(skip)]`
|
||||
Meta(Word(ref word)) if word == "skip" => {
|
||||
skip_serializing.set_true();
|
||||
skip_deserializing.set_true();
|
||||
skip_serializing.set_true(word);
|
||||
skip_deserializing.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_deserializing)]`
|
||||
Meta(Word(ref word)) if word == "skip_deserializing" => {
|
||||
skip_deserializing.set_true();
|
||||
skip_deserializing.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_serializing)]`
|
||||
Meta(Word(ref word)) if word == "skip_serializing" => {
|
||||
skip_serializing.set_true();
|
||||
skip_serializing.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(other)]`
|
||||
Meta(Word(ref word)) if word == "other" => {
|
||||
other.set_true();
|
||||
other.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(bound = "T: SomeBound")]`
|
||||
@@ -618,16 +735,16 @@ impl Variant {
|
||||
if let Ok(where_predicates) =
|
||||
parse_lit_into_where(cx, &m.ident, &m.ident, &m.lit)
|
||||
{
|
||||
ser_bound.set(where_predicates.clone());
|
||||
de_bound.set(where_predicates);
|
||||
ser_bound.set(&m.ident, where_predicates.clone());
|
||||
de_bound.set(&m.ident, where_predicates);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(bound(serialize = "...", deserialize = "..."))]`
|
||||
Meta(List(ref m)) if m.ident == "bound" => {
|
||||
if let Ok((ser, de)) = get_where_predicates(cx, &m.nested) {
|
||||
ser_bound.set_opt(ser);
|
||||
de_bound.set_opt(de);
|
||||
ser_bound.set_opt(&m.ident, ser);
|
||||
de_bound.set_opt(&m.ident, de);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,49 +756,55 @@ impl Variant {
|
||||
.path
|
||||
.segments
|
||||
.push(Ident::new("serialize", Span::call_site()).into());
|
||||
serialize_with.set(ser_path);
|
||||
serialize_with.set(&m.ident, ser_path);
|
||||
let mut de_path = path;
|
||||
de_path
|
||||
.path
|
||||
.segments
|
||||
.push(Ident::new("deserialize", Span::call_site()).into());
|
||||
deserialize_with.set(de_path);
|
||||
deserialize_with.set(&m.ident, de_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(serialize_with = "...")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "serialize_with" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
serialize_with.set(path);
|
||||
serialize_with.set(&m.ident, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(deserialize_with = "...")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "deserialize_with" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
deserialize_with.set(path);
|
||||
deserialize_with.set(&m.ident, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Defer `#[serde(borrow)]` and `#[serde(borrow = "'a + 'b")]`
|
||||
Meta(ref m) if m.name() == "borrow" => match variant.fields {
|
||||
syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
||||
borrow.set(m.clone());
|
||||
borrow.set(m.name(), m.clone());
|
||||
}
|
||||
_ => {
|
||||
cx.error("#[serde(borrow)] may only be used on newtype variants");
|
||||
cx.error_spanned_by(
|
||||
variant,
|
||||
"#[serde(borrow)] may only be used on newtype variants",
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
Meta(ref meta_item) => {
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(meta_item.name(), format!(
|
||||
"unknown serde variant attribute `{}`",
|
||||
meta_item.name()
|
||||
));
|
||||
}
|
||||
|
||||
Literal(_) => {
|
||||
cx.error("unexpected literal in serde variant attribute");
|
||||
Literal(ref lit) => {
|
||||
cx.error_spanned_by(
|
||||
lit,
|
||||
"unexpected literal in serde variant attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -837,65 +960,65 @@ impl Field {
|
||||
// Parse `#[serde(rename = "foo")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "rename" => {
|
||||
if let Ok(s) = get_lit_str(cx, &m.ident, &m.ident, &m.lit) {
|
||||
ser_name.set(s.value());
|
||||
de_name.set(s.value());
|
||||
ser_name.set(&m.ident, s.value());
|
||||
de_name.set(&m.ident, s.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(rename(serialize = "foo", deserialize = "bar"))]`
|
||||
Meta(List(ref m)) if m.ident == "rename" => {
|
||||
if let Ok((ser, de)) = get_renames(cx, &m.nested) {
|
||||
ser_name.set_opt(ser.map(syn::LitStr::value));
|
||||
de_name.set_opt(de.map(syn::LitStr::value));
|
||||
ser_name.set_opt(&m.ident, ser.map(syn::LitStr::value));
|
||||
de_name.set_opt(&m.ident, de.map(syn::LitStr::value));
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(default)]`
|
||||
Meta(Word(ref word)) if word == "default" => {
|
||||
default.set(Default::Default);
|
||||
default.set(word, Default::Default);
|
||||
}
|
||||
|
||||
// Parse `#[serde(default = "...")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "default" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
default.set(Default::Path(path));
|
||||
default.set(&m.ident, Default::Path(path));
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_serializing)]`
|
||||
Meta(Word(ref word)) if word == "skip_serializing" => {
|
||||
skip_serializing.set_true();
|
||||
skip_serializing.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_deserializing)]`
|
||||
Meta(Word(ref word)) if word == "skip_deserializing" => {
|
||||
skip_deserializing.set_true();
|
||||
skip_deserializing.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip)]`
|
||||
Meta(Word(ref word)) if word == "skip" => {
|
||||
skip_serializing.set_true();
|
||||
skip_deserializing.set_true();
|
||||
skip_serializing.set_true(word);
|
||||
skip_deserializing.set_true(word);
|
||||
}
|
||||
|
||||
// Parse `#[serde(skip_serializing_if = "...")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "skip_serializing_if" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
skip_serializing_if.set(path);
|
||||
skip_serializing_if.set(&m.ident, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(serialize_with = "...")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "serialize_with" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
serialize_with.set(path);
|
||||
serialize_with.set(&m.ident, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(deserialize_with = "...")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "deserialize_with" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
deserialize_with.set(path);
|
||||
deserialize_with.set(&m.ident, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,13 +1030,13 @@ impl Field {
|
||||
.path
|
||||
.segments
|
||||
.push(Ident::new("serialize", Span::call_site()).into());
|
||||
serialize_with.set(ser_path);
|
||||
serialize_with.set(&m.ident, ser_path);
|
||||
let mut de_path = path;
|
||||
de_path
|
||||
.path
|
||||
.segments
|
||||
.push(Ident::new("deserialize", Span::call_site()).into());
|
||||
deserialize_with.set(de_path);
|
||||
deserialize_with.set(&m.ident, de_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,39 +1045,39 @@ impl Field {
|
||||
if let Ok(where_predicates) =
|
||||
parse_lit_into_where(cx, &m.ident, &m.ident, &m.lit)
|
||||
{
|
||||
ser_bound.set(where_predicates.clone());
|
||||
de_bound.set(where_predicates);
|
||||
ser_bound.set(&m.ident, where_predicates.clone());
|
||||
de_bound.set(&m.ident, where_predicates);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(bound(serialize = "...", deserialize = "..."))]`
|
||||
Meta(List(ref m)) if m.ident == "bound" => {
|
||||
if let Ok((ser, de)) = get_where_predicates(cx, &m.nested) {
|
||||
ser_bound.set_opt(ser);
|
||||
de_bound.set_opt(de);
|
||||
ser_bound.set_opt(&m.ident, ser);
|
||||
de_bound.set_opt(&m.ident, de);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(borrow)]`
|
||||
Meta(Word(ref word)) if word == "borrow" => {
|
||||
if let Ok(borrowable) = borrowable_lifetimes(cx, &ident, &field.ty) {
|
||||
borrowed_lifetimes.set(borrowable);
|
||||
if let Ok(borrowable) = borrowable_lifetimes(cx, &ident, field) {
|
||||
borrowed_lifetimes.set(word, borrowable);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(borrow = "'a + 'b")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "borrow" => {
|
||||
if let Ok(lifetimes) = parse_lit_into_lifetimes(cx, &m.ident, &m.lit) {
|
||||
if let Ok(borrowable) = borrowable_lifetimes(cx, &ident, &field.ty) {
|
||||
if let Ok(borrowable) = borrowable_lifetimes(cx, &ident, field) {
|
||||
for lifetime in &lifetimes {
|
||||
if !borrowable.contains(lifetime) {
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(field, format!(
|
||||
"field `{}` does not have lifetime {}",
|
||||
ident, lifetime
|
||||
));
|
||||
}
|
||||
}
|
||||
borrowed_lifetimes.set(lifetimes);
|
||||
borrowed_lifetimes.set(&m.ident, lifetimes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -962,24 +1085,27 @@ impl Field {
|
||||
// Parse `#[serde(getter = "...")]`
|
||||
Meta(NameValue(ref m)) if m.ident == "getter" => {
|
||||
if let Ok(path) = parse_lit_into_expr_path(cx, &m.ident, &m.lit) {
|
||||
getter.set(path);
|
||||
getter.set(&m.ident, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `#[serde(flatten)]`
|
||||
Meta(Word(ref word)) if word == "flatten" => {
|
||||
flatten.set_true();
|
||||
flatten.set_true(word);
|
||||
}
|
||||
|
||||
Meta(ref meta_item) => {
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(meta_item.name(), format!(
|
||||
"unknown serde field attribute `{}`",
|
||||
meta_item.name()
|
||||
));
|
||||
}
|
||||
|
||||
Literal(_) => {
|
||||
cx.error("unexpected literal in serde field attribute");
|
||||
Literal(ref lit) => {
|
||||
cx.error_spanned_by(
|
||||
lit,
|
||||
"unexpected literal in serde field attribute",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1162,18 +1288,18 @@ where
|
||||
match *meta {
|
||||
Meta(NameValue(ref meta)) if meta.ident == "serialize" => {
|
||||
if let Ok(v) = f(cx, &attr_name, &meta.ident, &meta.lit) {
|
||||
ser_meta.set(v);
|
||||
ser_meta.set(&meta.ident, v);
|
||||
}
|
||||
}
|
||||
|
||||
Meta(NameValue(ref meta)) if meta.ident == "deserialize" => {
|
||||
if let Ok(v) = f(cx, &attr_name, &meta.ident, &meta.lit) {
|
||||
de_meta.set(v);
|
||||
de_meta.set(&meta.ident, v);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(meta, format!(
|
||||
"malformed {0} attribute, expected `{0}(serialize = ..., \
|
||||
deserialize = ...)`",
|
||||
attr_name
|
||||
@@ -1223,7 +1349,7 @@ fn get_lit_str<'a>(
|
||||
if let syn::Lit::Str(ref lit) = *lit {
|
||||
Ok(lit)
|
||||
} else {
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(lit, format!(
|
||||
"expected serde {} attribute to be a string: `{} = \"...\"`",
|
||||
attr_name, meta_item_name
|
||||
));
|
||||
@@ -1234,7 +1360,10 @@ fn get_lit_str<'a>(
|
||||
fn parse_lit_into_path(cx: &Ctxt, attr_name: &Ident, lit: &syn::Lit) -> Result<syn::Path, ()> {
|
||||
let string = try!(get_lit_str(cx, attr_name, attr_name, lit));
|
||||
parse_lit_str(string)
|
||||
.map_err(|_| cx.error(format!("failed to parse path: {:?}", string.value())))
|
||||
.map_err(|_| cx.error_spanned_by(
|
||||
lit,
|
||||
format!("failed to parse path: {:?}", string.value()),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_lit_into_expr_path(
|
||||
@@ -1244,7 +1373,10 @@ fn parse_lit_into_expr_path(
|
||||
) -> Result<syn::ExprPath, ()> {
|
||||
let string = try!(get_lit_str(cx, attr_name, attr_name, lit));
|
||||
parse_lit_str(string)
|
||||
.map_err(|_| cx.error(format!("failed to parse path: {:?}", string.value())))
|
||||
.map_err(|_| cx.error_spanned_by(
|
||||
lit,
|
||||
format!("failed to parse path: {:?}", string.value()),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_lit_into_where(
|
||||
@@ -1262,14 +1394,14 @@ fn parse_lit_into_where(
|
||||
|
||||
parse_lit_str::<syn::WhereClause>(&where_string)
|
||||
.map(|wh| wh.predicates.into_iter().collect())
|
||||
.map_err(|err| cx.error(err))
|
||||
.map_err(|err| cx.error_spanned_by(lit, err))
|
||||
}
|
||||
|
||||
fn parse_lit_into_ty(cx: &Ctxt, attr_name: &Ident, lit: &syn::Lit) -> Result<syn::Type, ()> {
|
||||
let string = try!(get_lit_str(cx, attr_name, attr_name, lit));
|
||||
|
||||
parse_lit_str(string).map_err(|_| {
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(lit, format!(
|
||||
"failed to parse type: {} = {:?}",
|
||||
attr_name,
|
||||
string.value()
|
||||
@@ -1286,7 +1418,7 @@ fn parse_lit_into_lifetimes(
|
||||
) -> Result<BTreeSet<syn::Lifetime>, ()> {
|
||||
let string = try!(get_lit_str(cx, attr_name, attr_name, lit));
|
||||
if string.value().is_empty() {
|
||||
cx.error("at least one lifetime must be borrowed");
|
||||
cx.error_spanned_by(lit, "at least one lifetime must be borrowed");
|
||||
return Err(());
|
||||
}
|
||||
|
||||
@@ -1302,13 +1434,16 @@ fn parse_lit_into_lifetimes(
|
||||
let mut set = BTreeSet::new();
|
||||
for lifetime in lifetimes {
|
||||
if !set.insert(lifetime.clone()) {
|
||||
cx.error(format!("duplicate borrowed lifetime `{}`", lifetime));
|
||||
cx.error_spanned_by(
|
||||
lit,
|
||||
format!("duplicate borrowed lifetime `{}`", lifetime),
|
||||
);
|
||||
}
|
||||
}
|
||||
return Ok(set);
|
||||
}
|
||||
|
||||
cx.error(format!(
|
||||
cx.error_spanned_by(lit, format!(
|
||||
"failed to parse borrowed lifetimes: {:?}",
|
||||
string.value()
|
||||
));
|
||||
@@ -1461,12 +1596,15 @@ fn is_primitive_path(path: &syn::Path, primitive: &str) -> bool {
|
||||
fn borrowable_lifetimes(
|
||||
cx: &Ctxt,
|
||||
name: &str,
|
||||
ty: &syn::Type,
|
||||
field: &syn::Field,
|
||||
) -> Result<BTreeSet<syn::Lifetime>, ()> {
|
||||
let mut lifetimes = BTreeSet::new();
|
||||
collect_lifetimes(ty, &mut lifetimes);
|
||||
collect_lifetimes(&field.ty, &mut lifetimes);
|
||||
if lifetimes.is_empty() {
|
||||
cx.error(format!("field `{}` has no lifetimes to borrow", name));
|
||||
cx.error_spanned_by(
|
||||
field,
|
||||
format!("field `{}` has no lifetimes to borrow", name),
|
||||
);
|
||||
Err(())
|
||||
} else {
|
||||
Ok(lifetimes)
|
||||
|
||||
Reference in New Issue
Block a user