#![allow(unused_imports)]
use std::mem::transmute;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::print::pprust::{stmt_to_string};
use syntax::ast::{ItemKind, Expr, MetaItem, Mutability, VariantData, Ident};
use syntax::codemap::Span;
use syntax::ext::build::AstBuilder;
use syntax::ptr::P;
use syntax_ext::deriving::generic::MethodDef;
use syntax_ext::deriving::generic::{StaticStruct, Substructure, TraitDef, ty};
use syntax_ext::deriving::generic::combine_substructure as c_s;
use utils::strip_ty_lifetimes;
static ONLY_STRUCTS_ERR: &'static str = "`FromForm` can only be derived for \
structures with named fields.";
static PRIVATE_LIFETIME: &'static str = "'rocket";
fn get_struct_lifetime(ecx: &mut ExtCtxt, item: &Annotatable, span: Span)
-> Option<String> {
match *item {
Annotatable::Item(ref item) => match item.node {
ItemKind::Struct(_, ref generics) => {
match generics.lifetimes.len() {
0 => None,
1 => {
let lifetime = generics.lifetimes[0].lifetime;
Some(lifetime.ident.to_string())
}
_ => {
ecx.span_err(item.span, "cannot have more than one \
lifetime parameter when deriving `FromForm`.");
None
}
}
},
_ => ecx.span_fatal(span, ONLY_STRUCTS_ERR)
},
_ => ecx.span_fatal(span, ONLY_STRUCTS_ERR)
}
}
pub fn from_form_derive(ecx: &mut ExtCtxt, span: Span, meta_item: &MetaItem,
annotated: &Annotatable, push: &mut FnMut(Annotatable)) {
let struct_lifetime = get_struct_lifetime(ecx, annotated, span);
let (lifetime_var, trait_generics) = match struct_lifetime {
Some(ref lifetime) => (Some(lifetime.as_str()), ty::LifetimeBounds::empty()),
None => (Some(PRIVATE_LIFETIME), ty::LifetimeBounds {
lifetimes: vec![(PRIVATE_LIFETIME, vec![])],
bounds: vec![]
})
};
let error_type = ty::Ty::Literal(ty::Path::new(vec!["rocket", "Error"]));
let trait_def = TraitDef {
is_unsafe: false,
supports_unions: false,
span: span,
attributes: Vec::new(),
path: ty::Path {
path: vec!["rocket", "request", "FromForm"],
lifetime: lifetime_var,
params: vec![],
global: true,
},
additional_bounds: Vec::new(),
generics: trait_generics,
methods: vec![
MethodDef {
name: "from_form_items",
generics: ty::LifetimeBounds::empty(),
explicit_self: None,
args: vec![
ty::Ptr(
Box::new(ty::Literal(ty::Path {
path: vec!["rocket", "request", "FormItems"],
lifetime: lifetime_var,
params: vec![],
global: true
})),
ty::Borrowed(None, Mutability::Mutable)
)
],
ret_ty: ty::Ty::Literal(
ty::Path {
path: vec!["std", "result", "Result"],
lifetime: None,
params: vec![
Box::new(ty::Ty::Self_),
Box::new(error_type.clone())
],
global: true,
}
),
attributes: vec![],
is_unsafe: false,
combine_substructure: c_s(Box::new(from_form_substructure)),
unify_fieldless_variants: false,
}
],
associated_types: vec![
(Ident::from_str("Error"), error_type.clone())
],
};
trait_def.expand(ecx, meta_item, annotated, push);
}
fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P<Expr> {
const EXPECTED_ARGS: usize = 1;
let arg = if substr.nonself_args.len() == EXPECTED_ARGS {
&substr.nonself_args[0]
} else {
let msg = format!("incorrect number of arguments in `from_form_string`: \
expected {}, found {}", EXPECTED_ARGS, substr.nonself_args.len());
cx.span_bug(trait_span, msg.as_str());
};
debug!("argument is: {:?}", arg);
let fields = match *substr.fields {
StaticStruct(var_data, _) => match *var_data {
VariantData::Struct(ref fields, _) => fields,
_ => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR)
},
_ => cx.span_bug(trait_span, "impossible substructure in `from_form`")
};
let mut fields_and_types = vec![];
for field in fields {
let ident = match field.ident {
Some(ident) => ident,
None => cx.span_fatal(trait_span, ONLY_STRUCTS_ERR)
};
let stripped_ty = strip_ty_lifetimes(field.ty.clone());
fields_and_types.push((ident, stripped_ty));
}
debug!("Fields and types: {:?}", fields_and_types);
let mut stmts = Vec::new();
let return_err_stmt = quote_tokens!(cx,
return Err(::rocket::Error::BadParse)
);
for &(ref ident, ref ty) in &fields_and_types {
stmts.push(quote_stmt!(cx,
let mut $ident: ::std::option::Option<$ty> = None;
).unwrap());
}
let mut arms = vec![];
for &(ref ident, _) in &fields_and_types {
let ident_string = ident.to_string();
let id_str = ident_string.as_str();
arms.push(quote_tokens!(cx,
$id_str => {
$ident = match ::rocket::request::FromFormValue::from_form_value(__v) {
Ok(__v) => Some(__v),
Err(__e) => {
println!(" => Error parsing form val '{}': {:?}",
$id_str, __e);
$return_err_stmt
}
};
},
));
}
stmts.push(quote_stmt!(cx,
for (__k, __v) in $arg {
match __k {
$arms
__field if __field == "_method" => {
}
_ => {
println!(" => {}={} has no matching field in struct.",
__k, __v);
$return_err_stmt
}
};
}
).unwrap());
let mut failure_conditions = vec![];
failure_conditions.push(quote_tokens!(cx, false));
for &(ref ident, ref ty) in (&fields_and_types).iter() {
failure_conditions.push(quote_tokens!(cx, ||));
failure_conditions.push(quote_tokens!(cx,
if $ident.is_none() &&
<$ty as ::rocket::request::FromFormValue>::default().is_none() {
println!(" => '{}' did not parse.", stringify!($ident));
true
} else { false }
));
}
let mut result_fields = vec![];
for &(ref ident, ref ty) in &fields_and_types {
result_fields.push(quote_tokens!(cx,
$ident: $ident.unwrap_or_else(||
<$ty as ::rocket::request::FromFormValue>::default().unwrap()
),
));
}
let self_ident = substr.type_ident;
let final_block = quote_block!(cx, {
if $failure_conditions {
$return_err_stmt;
}
Ok($self_ident { $result_fields })
});
stmts.extend(final_block.unwrap().stmts);
debug!("Form statements:");
for stmt in &stmts {
debug!("{:?}", stmt_to_string(stmt));
}
cx.expr_block(cx.block(trait_span, stmts))
}