diff --git a/serde_codegen/src/ser.rs b/serde_codegen/src/ser.rs index 1c87a9cf..58ed820e 100644 --- a/serde_codegen/src/ser.rs +++ b/serde_codegen/src/ser.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use aster; use syntax::ast::{ @@ -10,6 +12,7 @@ use syntax::codemap::Span; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ext::build::AstBuilder; use syntax::ptr::P; +use syntax::visit; use attr; use error::Error; @@ -85,8 +88,8 @@ fn serialize_item( ).unwrap()) } -// All the generics in the input, plus a bound `T: Serialize` for each field -// type that will be serialized by us. +// All the generics in the input, plus a bound `T: Serialize` for each generic +// field type that will be serialized by us. fn build_impl_generics( cx: &ExtCtxt, builder: &aster::AstBuilder, @@ -104,6 +107,8 @@ fn build_impl_generics( .flat_map(|variant_data| all_struct_fields(variant_data)) .filter(|field| serialized_by_us(field)) .map(|field| &field.node.ty) + // TODO this filter can be removed later, see comment on function + .filter(|ty| contains_generic(ty, generics)) .map(|ty| strip_reference(ty)) .map(|ty| builder.where_predicate() // the type that is being bounded i.e. T @@ -144,8 +149,8 @@ fn all_struct_fields(variant_data: &ast::VariantData) -> &[ast::StructField] { } // Fields with a `skip_serializing` or `serialize_with` attribute are not -// serialized by us. All other fields will receive a `T: Serialize` bound where -// T is the type of the field. +// serialized by us. All other fields may need a `T: Serialize` bound where T is +// the type of the field. fn serialized_by_us(field: &ast::StructField) -> bool { for meta_items in field.node.attrs.iter().filter_map(attr::get_serde_meta_items) { for meta_item in meta_items { @@ -163,6 +168,44 @@ fn serialized_by_us(field: &ast::StructField) -> bool { true } +// Rust <1.7 enforces that `where` clauses involve generic type parameters. The +// corresponding compiler error is E0193. It is no longer enforced in Rust >=1.7 +// so this filtering can be removed in the future when we stop supporting <1.7. +// +// E0193 means we must not generate a `where` clause like `i32: Serialize` +// because even though i32 implements Serialize, i32 is not a generic type +// parameter. Clauses like `T: Serialize` and `Option: Serialize` are okay. +// This function decides whether a given type references any of the generic type +// parameters in the input `Generics`. +fn contains_generic(ty: &ast::Ty, generics: &ast::Generics) -> bool { + struct FindGeneric<'a> { + generic_names: &'a HashSet, + found_generic: bool, + } + impl<'a, 'v> visit::Visitor<'v> for FindGeneric<'a> { + fn visit_path(&mut self, path: &'v ast::Path, _id: ast::NodeId) { + if !path.global + && path.segments.len() == 1 + && self.generic_names.contains(&path.segments[0].identifier.name) { + self.found_generic = true; + } else { + visit::walk_path(self, path); + } + } + } + + let generic_names: HashSet<_> = generics.ty_params.iter() + .map(|ty_param| ty_param.ident.name) + .collect(); + + let mut visitor = FindGeneric { + generic_names: &generic_names, + found_generic: false, + }; + visit::walk_ty(&mut visitor, ty); + visitor.found_generic +} + // This is required to handle types that use both a reference and a value of // the same type, as in: //