-
Notifications
You must be signed in to change notification settings - Fork 693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implements Debug trait for types which do not support derive Debug #899
Changes from 1 commit
b541f4c
dc95fe9
ed487d3
f7345fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
use ir::comp::{BitfieldUnit, CompKind, Field, FieldData, FieldMethods}; | ||
use ir::context::BindgenContext; | ||
use ir::derive::CanTriviallyDeriveDebug; | ||
use ir::item::{IsOpaque, Item, ItemCanonicalName}; | ||
use ir::ty::{TypeKind, RUST_DERIVE_IN_ARRAY_LIMIT}; | ||
use syntax::ast; | ||
use syntax::codemap::DUMMY_SP; | ||
use syntax::parse::token::Token; | ||
|
||
use syntax::tokenstream::TokenTree; | ||
|
||
pub fn gen_debug_impl( | ||
ctx: &BindgenContext, | ||
fields: &[Field], | ||
item: &Item, | ||
kind: CompKind, | ||
) -> Vec<ast::ImplItem> { | ||
let struct_name = item.canonical_name(ctx); | ||
let mut format_string = format!("{} {{{{ ", struct_name); | ||
let mut tokens: Vec<TokenTree> = Vec::new(); | ||
|
||
if item.is_opaque(ctx, &()) { | ||
format_string.push_str("opaque"); | ||
} else { | ||
match kind { | ||
CompKind::Union => { | ||
format_string.push_str("union"); | ||
} | ||
CompKind::Struct => { | ||
let processed_fields = fields.iter().filter_map(|f| match f { | ||
&Field::DataMember(ref fd) => { | ||
gen_field_data_debug_impl(ctx, fd) | ||
} | ||
&Field::Bitfields(ref bu) => { | ||
gen_bitfield_unit_debug_impl(ctx, bu) | ||
} | ||
}); | ||
|
||
|
||
for (i, (fstring, token)) in processed_fields.enumerate() { | ||
if i > 0 { | ||
format_string.push_str(", "); | ||
} | ||
if !token.is_empty() { | ||
tokens.push(TokenTree::Token(DUMMY_SP, Token::Comma)); | ||
tokens.extend(token); | ||
} | ||
format_string.push_str(&fstring); | ||
} | ||
} | ||
} | ||
} | ||
|
||
format_string.push_str(" }}"); | ||
|
||
let impl_ = quote_item!(ctx.ext_cx(), | ||
impl X { | ||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { | ||
write!(f, $format_string $tokens) | ||
} | ||
}); | ||
|
||
match impl_.unwrap().node { | ||
ast::ItemKind::Impl(_, _, _, _, _, ref items) => items.clone(), | ||
_ => unreachable!(), | ||
} | ||
} | ||
|
||
fn gen_field_data_debug_impl( | ||
ctx: &BindgenContext, | ||
data: &FieldData, | ||
) -> Option<(String, Vec<TokenTree>)> { | ||
if let Some(name) = data.name() { | ||
gen_item_debug_impl(ctx, ctx.resolve_item(data.ty()), name) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn gen_bitfield_unit_debug_impl( | ||
ctx: &BindgenContext, | ||
data: &BitfieldUnit, | ||
) -> Option<(String, Vec<TokenTree>)> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of these methods have pretty much the same signature: fn (&BindgenContext, &T) -> Option<(String, Vec<TokenTree>)>;
// or sometimes
fn (&BindgenContext, &T, &U) -> Option<(String, Vec<TokenTree)>; Let's formalize this pattern with a trait: /// A trait for the things which we can codegen tokens that contribute towards a
/// generated `impl Debug`.
pub trait ImplDebug {
/// Any extra parameter required by this a particular `ImplDebug` implementation. This
/// is the `U` from the above snippet, or `()` if its not needed.
type Extra;
/// Generate a format string snippet to be included in the larger `impl Debug`
/// format string, and the code to get the format string's interpolation values.
fn impl_debug(&self, ctx: &BindgenContext, extra: &Self::Extra) -> Option<(String, Vec<TokenTree>)>;
} And then each of these functions should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, will do it :) I also thought about creating a Trait, but that seemed to be to much overengineering for me. |
||
let mut format_string = String::new(); | ||
let mut tokens = Vec::new(); | ||
for (i, bu) in data.bitfields().iter().enumerate() { | ||
if i > 0 { | ||
format_string.push_str(", "); | ||
tokens.push(TokenTree::Token(DUMMY_SP, Token::Comma)); | ||
} | ||
format_string.push_str(&format!("{} : {{:?}}", bu.name())); | ||
let name_ident = ctx.rust_ident_raw(bu.name()); | ||
tokens.extend(quote_tokens!(ctx.ext_cx(), self.$name_ident())); | ||
} | ||
|
||
Some((format_string, tokens)) | ||
} | ||
|
||
fn gen_item_debug_impl( | ||
ctx: &BindgenContext, | ||
item: &Item, | ||
name: &str, | ||
) -> Option<(String, Vec<TokenTree>)> { | ||
let name_ident = ctx.rust_ident_raw(name); | ||
|
||
let ty = match item.as_type() { | ||
Some(ty) => ty, | ||
None => { | ||
return None; | ||
} | ||
}; | ||
|
||
fn debug_print( | ||
ctx: &BindgenContext, | ||
name: &str, | ||
name_ident: ast::Ident, | ||
) -> Option<(String, Vec<TokenTree>)> { | ||
Some(( | ||
format!("{}: {{:?}}", name), | ||
quote_tokens!(ctx.ext_cx(), self.$name_ident), | ||
)) | ||
} | ||
|
||
match *ty.kind() { | ||
// Handle the simple cases. | ||
TypeKind::Void | | ||
TypeKind::NullPtr | | ||
TypeKind::Int(..) | | ||
TypeKind::Float(..) | | ||
TypeKind::Complex(..) | | ||
TypeKind::Function(..) | | ||
TypeKind::Enum(..) | | ||
TypeKind::Reference(..) | | ||
TypeKind::BlockPointer | | ||
TypeKind::UnresolvedTypeRef(..) | | ||
TypeKind::ObjCInterface(..) | | ||
TypeKind::ObjCId | | ||
TypeKind::Comp(..) | | ||
TypeKind::ObjCSel => debug_print(ctx, name, name_ident), | ||
|
||
TypeKind::TemplateInstantiation(ref inst) => { | ||
if inst.is_opaque(ctx, item) { | ||
Some((format!("{}: opaque", name), vec![])) | ||
} else { | ||
debug_print(ctx, name, name_ident) | ||
} | ||
} | ||
|
||
// The generic is not required to implement Debug, so we can not debug print that type | ||
TypeKind::Named => { | ||
Some((format!("{}: Non-debuggable generic", name), vec![])) | ||
} | ||
|
||
TypeKind::Array(_, len) => { | ||
// Generics are not required to implement Debug | ||
if ctx.lookup_item_id_has_type_param_in_array(&item.id()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be: if item.has_type_param_in_array(ctx) {
...
} You may have to add |
||
Some((format!("{}: Array with length {}", name, len), vec![])) | ||
} else if len < RUST_DERIVE_IN_ARRAY_LIMIT { | ||
// The simple case | ||
debug_print(ctx, name, name_ident) | ||
} else { | ||
// Let's implement our own print function | ||
Some(( | ||
format!("{}: [{{}}]", name), | ||
quote_tokens!( | ||
ctx.ext_cx(), | ||
self.$name_ident | ||
.iter() | ||
.enumerate() | ||
.map(|(i, v)| format!("{}{:?}", if i > 0 { ", " } else { "" }, v)) | ||
.collect::<String>()), | ||
)) | ||
} | ||
} | ||
|
||
TypeKind::ResolvedTypeRef(t) | | ||
TypeKind::TemplateAlias(t, _) | | ||
TypeKind::Alias(t) => { | ||
// We follow the aliases | ||
gen_item_debug_impl(ctx, ctx.resolve_item(t), name) | ||
} | ||
|
||
TypeKind::Pointer(inner) => { | ||
let inner_type = ctx.resolve_type(inner).canonical_type(ctx); | ||
match *inner_type.kind() { | ||
TypeKind::Function(ref sig) | ||
if !sig.can_trivially_derive_debug() => | ||
{ | ||
Some((format!("{}: FunctionPointer", name), vec![])) | ||
} | ||
_ => debug_print(ctx, name, name_ident), | ||
} | ||
} | ||
|
||
TypeKind::Opaque => None, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So. Many. Mustaches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could remove some mustaches by doing format_string.push_str("{{"). Or just leave it as it is?