mirror of
https://github.com/pezkuwichain/serde.git
synced 2026-04-23 03:38:00 +00:00
Attribute for handwritten where clauses
This commit is contained in:
+121
-26
@@ -4,7 +4,7 @@ use syntax::attr;
|
||||
use syntax::codemap::Span;
|
||||
use syntax::ext::base::ExtCtxt;
|
||||
use syntax::fold::Folder;
|
||||
use syntax::parse::parser::PathStyle;
|
||||
use syntax::parse::parser::{Parser, PathStyle};
|
||||
use syntax::parse::token::{self, InternedString};
|
||||
use syntax::parse;
|
||||
use syntax::print::pprust::{lit_to_string, meta_item_to_string};
|
||||
@@ -62,6 +62,8 @@ impl Name {
|
||||
pub struct ContainerAttrs {
|
||||
name: Name,
|
||||
deny_unknown_fields: bool,
|
||||
ser_where: Option<Vec<ast::WherePredicate>>,
|
||||
de_where: Option<Vec<ast::WherePredicate>>,
|
||||
}
|
||||
|
||||
impl ContainerAttrs {
|
||||
@@ -70,6 +72,8 @@ impl ContainerAttrs {
|
||||
let mut container_attrs = ContainerAttrs {
|
||||
name: Name::new(item.ident),
|
||||
deny_unknown_fields: false,
|
||||
ser_where: None,
|
||||
de_where: None,
|
||||
};
|
||||
|
||||
for meta_items in item.attrs().iter().filter_map(get_serde_meta_items) {
|
||||
@@ -96,6 +100,20 @@ impl ContainerAttrs {
|
||||
container_attrs.deny_unknown_fields = true;
|
||||
}
|
||||
|
||||
// Parse `#[serde(where="D: Serialize")]`
|
||||
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"where" => {
|
||||
let where_predicates = try!(parse_lit_into_where(cx, name, lit));
|
||||
container_attrs.ser_where = Some(where_predicates.clone());
|
||||
container_attrs.de_where = Some(where_predicates.clone());
|
||||
}
|
||||
|
||||
// Parse `#[serde(where(serialize="D: Serialize", deserialize="D: Deserialize"))]`
|
||||
ast::MetaItemKind::List(ref name, ref meta_items) if name == &"where" => {
|
||||
let (ser_where, de_where) = try!(get_where_predicates(cx, meta_items));
|
||||
container_attrs.ser_where = ser_where;
|
||||
container_attrs.de_where = de_where;
|
||||
}
|
||||
|
||||
_ => {
|
||||
cx.span_err(
|
||||
meta_item.span,
|
||||
@@ -118,6 +136,14 @@ impl ContainerAttrs {
|
||||
pub fn deny_unknown_fields(&self) -> bool {
|
||||
self.deny_unknown_fields
|
||||
}
|
||||
|
||||
pub fn ser_where(&self) -> Option<&Vec<ast::WherePredicate>> {
|
||||
self.ser_where.as_ref()
|
||||
}
|
||||
|
||||
pub fn de_where(&self) -> Option<&Vec<ast::WherePredicate>> {
|
||||
self.de_where.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents variant attribute information
|
||||
@@ -181,6 +207,8 @@ pub struct FieldAttrs {
|
||||
default_expr_if_missing: Option<P<ast::Expr>>,
|
||||
serialize_with: Option<ast::Path>,
|
||||
deserialize_with: Option<ast::Path>,
|
||||
ser_where: Option<Vec<ast::WherePredicate>>,
|
||||
de_where: Option<Vec<ast::WherePredicate>>,
|
||||
}
|
||||
|
||||
impl FieldAttrs {
|
||||
@@ -203,6 +231,8 @@ impl FieldAttrs {
|
||||
default_expr_if_missing: None,
|
||||
serialize_with: None,
|
||||
deserialize_with: None,
|
||||
ser_where: None,
|
||||
de_where: None,
|
||||
};
|
||||
|
||||
for meta_items in field.attrs.iter().filter_map(get_serde_meta_items) {
|
||||
@@ -274,6 +304,20 @@ impl FieldAttrs {
|
||||
field_attrs.deserialize_with = Some(path);
|
||||
}
|
||||
|
||||
// Parse `#[serde(where="D: Serialize")]`
|
||||
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"where" => {
|
||||
let where_predicates = try!(parse_lit_into_where(cx, name, lit));
|
||||
field_attrs.ser_where = Some(where_predicates.clone());
|
||||
field_attrs.de_where = Some(where_predicates.clone());
|
||||
}
|
||||
|
||||
// Parse `#[serde(where(serialize="D: Serialize", deserialize="D: Deserialize"))]`
|
||||
ast::MetaItemKind::List(ref name, ref meta_items) if name == &"where" => {
|
||||
let (ser_where, de_where) = try!(get_where_predicates(cx, meta_items));
|
||||
field_attrs.ser_where = ser_where;
|
||||
field_attrs.de_where = de_where;
|
||||
}
|
||||
|
||||
_ => {
|
||||
cx.span_err(
|
||||
meta_item.span,
|
||||
@@ -316,45 +360,59 @@ impl FieldAttrs {
|
||||
pub fn deserialize_with(&self) -> Option<&ast::Path> {
|
||||
self.deserialize_with.as_ref()
|
||||
}
|
||||
|
||||
pub fn ser_where(&self) -> Option<&Vec<ast::WherePredicate>> {
|
||||
self.ser_where.as_ref()
|
||||
}
|
||||
|
||||
pub fn de_where(&self) -> Option<&Vec<ast::WherePredicate>> {
|
||||
self.de_where.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Zip together fields and `#[serde(...)]` attributes on those fields.
|
||||
pub fn fields_with_attrs<'a>(
|
||||
pub fn fields_with_attrs(
|
||||
cx: &ExtCtxt,
|
||||
fields: &'a [ast::StructField],
|
||||
) -> Result<Vec<(&'a ast::StructField, FieldAttrs)>, Error> {
|
||||
fields: &[ast::StructField],
|
||||
) -> Result<Vec<(ast::StructField, FieldAttrs)>, Error> {
|
||||
fields.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
let attrs = try!(FieldAttrs::from_field(cx, i, field));
|
||||
Ok((field, attrs))
|
||||
Ok((field.clone(), attrs))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_renames(cx: &ExtCtxt,
|
||||
items: &[P<ast::MetaItem>],
|
||||
)-> Result<(Option<InternedString>, Option<InternedString>), Error> {
|
||||
let mut ser_name = None;
|
||||
let mut de_name = None;
|
||||
fn get_ser_and_de<T, F>(
|
||||
cx: &ExtCtxt,
|
||||
attribute: &str,
|
||||
items: &[P<ast::MetaItem>],
|
||||
f: F
|
||||
) -> Result<(Option<T>, Option<T>), Error>
|
||||
where F: Fn(&ExtCtxt, &str, &ast::Lit) -> Result<T, Error>,
|
||||
{
|
||||
let mut ser_item = None;
|
||||
let mut de_item = None;
|
||||
|
||||
for item in items {
|
||||
match item.node {
|
||||
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"serialize" => {
|
||||
let s = try!(get_str_from_lit(cx, name, lit));
|
||||
ser_name = Some(s);
|
||||
let s = try!(f(cx, name, lit));
|
||||
ser_item = Some(s);
|
||||
}
|
||||
|
||||
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"deserialize" => {
|
||||
let s = try!(get_str_from_lit(cx, name, lit));
|
||||
de_name = Some(s);
|
||||
let s = try!(f(cx, name, lit));
|
||||
de_item = Some(s);
|
||||
}
|
||||
|
||||
_ => {
|
||||
cx.span_err(
|
||||
item.span,
|
||||
&format!("unknown rename attribute `{}`",
|
||||
&format!("unknown {} attribute `{}`",
|
||||
attribute,
|
||||
meta_item_to_string(item)));
|
||||
|
||||
return Err(Error);
|
||||
@@ -362,7 +420,21 @@ fn get_renames(cx: &ExtCtxt,
|
||||
}
|
||||
}
|
||||
|
||||
Ok((ser_name, de_name))
|
||||
Ok((ser_item, de_item))
|
||||
}
|
||||
|
||||
fn get_renames(
|
||||
cx: &ExtCtxt,
|
||||
items: &[P<ast::MetaItem>],
|
||||
) -> Result<(Option<InternedString>, Option<InternedString>), Error> {
|
||||
get_ser_and_de(cx, "rename", items, get_str_from_lit)
|
||||
}
|
||||
|
||||
fn get_where_predicates(
|
||||
cx: &ExtCtxt,
|
||||
items: &[P<ast::MetaItem>],
|
||||
) -> Result<(Option<Vec<ast::WherePredicate>>, Option<Vec<ast::WherePredicate>>), Error> {
|
||||
get_ser_and_de(cx, "where", items, parse_lit_into_where)
|
||||
}
|
||||
|
||||
pub fn get_serde_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
|
||||
@@ -437,17 +509,18 @@ fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<Interned
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_lit_into_path(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<ast::Path, Error> {
|
||||
let source = try!(get_str_from_lit(cx, name, lit));
|
||||
|
||||
// If we just parse the string into an expression, any syntax errors in the source will only
|
||||
// have spans that point inside the string, and not back to the attribute. So to have better
|
||||
// error reporting, we'll first parse the string into a token tree. Then we'll update those
|
||||
// spans to say they're coming from a macro context that originally came from the attribute,
|
||||
// and then finally parse them into an expression.
|
||||
// If we just parse a string literal from an attibute, any syntax errors in the
|
||||
// source will only have spans that point inside the string and not back to the
|
||||
// attribute. So to have better error reporting, we'll first parse the string
|
||||
// into a token tree. Then we'll update those spans to say they're coming from a
|
||||
// macro context that originally came from the attribnute, and then finally
|
||||
// parse them into an expression or where-clause.
|
||||
fn parse_string_via_tts<T, F>(cx: &ExtCtxt, name: &str, string: String, action: F) -> Result<T, Error>
|
||||
where F: for<'a> Fn(&'a mut Parser) -> parse::PResult<'a, T>,
|
||||
{
|
||||
let tts = panictry!(parse::parse_tts_from_source_str(
|
||||
format!("<serde {} expansion>", name),
|
||||
(*source).to_owned(),
|
||||
string,
|
||||
cx.cfg(),
|
||||
cx.parse_sess()));
|
||||
|
||||
@@ -456,7 +529,7 @@ fn parse_lit_into_path(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<ast::
|
||||
|
||||
let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg(), tts);
|
||||
|
||||
let path = match parser.parse_path(PathStyle::Type) {
|
||||
let path = match action(&mut parser) {
|
||||
Ok(path) => path,
|
||||
Err(mut e) => {
|
||||
e.emit();
|
||||
@@ -476,6 +549,28 @@ fn parse_lit_into_path(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<ast::
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn parse_lit_into_path(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<ast::Path, Error> {
|
||||
let string = try!(get_str_from_lit(cx, name, lit)).to_string();
|
||||
|
||||
parse_string_via_tts(cx, name, string, |parser| {
|
||||
parser.parse_path(PathStyle::Type)
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_lit_into_where(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Result<Vec<ast::WherePredicate>, Error> {
|
||||
let string = try!(get_str_from_lit(cx, name, lit));
|
||||
if string.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let where_string = format!("where {}", string);
|
||||
|
||||
parse_string_via_tts(cx, name, where_string, |parser| {
|
||||
let where_clause = try!(parser.parse_where_clause());
|
||||
Ok(where_clause.predicates)
|
||||
})
|
||||
}
|
||||
|
||||
/// This function wraps the expression in `#[serde(default="...")]` in a function to prevent it
|
||||
/// from accessing the internal `Deserialize` state.
|
||||
fn wrap_default(path: ast::Path) -> P<ast::Expr> {
|
||||
|
||||
Reference in New Issue
Block a user