Skip to content

codegen: Make comments indentation-aware. #799

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

Merged
merged 2 commits into from
Jul 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ name = "bindgen"
readme = "README.md"
repository = "https://github.com/servo/rust-bindgen"
documentation = "https://docs.rs/bindgen"
version = "0.26.3"
version = "0.27.0"
build = "build.rs"

exclude = [
Expand Down
4 changes: 2 additions & 2 deletions src/codegen/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub mod attributes {
aster::AstBuilder::new().attr().word("inline")
}

pub fn doc(comment: &str) -> ast::Attribute {
aster::AstBuilder::new().attr().doc(comment)
pub fn doc(comment: String) -> ast::Attribute {
aster::AstBuilder::new().attr().doc(&*comment)
}

pub fn link_name(name: &str) -> ast::Attribute {
Expand Down
55 changes: 24 additions & 31 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ use aster::struct_field::StructFieldBuilder;
use ir::annotations::FieldAccessorKind;
use ir::comp::{Base, BitfieldUnit, Bitfield, CompInfo, CompKind, Field,
FieldData, FieldMethods, Method, MethodKind};
use ir::comment;
use ir::context::{BindgenContext, ItemId};
use ir::derive::{CanDeriveCopy, CanDeriveDebug, CanDeriveDefault};
use ir::dot;
use ir::enum_ty::{Enum, EnumVariant, EnumVariantValue};
use ir::function::{Abi, Function, FunctionSig};
use ir::int::IntKind;
use ir::item::{IsOpaque, Item, ItemAncestors, ItemCanonicalName,
ItemCanonicalPath, ItemSet};
use ir::item::{IsOpaque, Item, ItemCanonicalName, ItemCanonicalPath, ItemSet};
use ir::item_kind::ItemKind;
use ir::layout::Layout;
use ir::module::Module;
Expand All @@ -42,23 +42,13 @@ use syntax::ptr::P;
// Name of type defined in constified enum module
pub static CONSTIFIED_ENUM_MODULE_REPR_NAME: &'static str = "Type";

fn root_import_depth(ctx: &BindgenContext, item: &Item) -> usize {
if !ctx.options().enable_cxx_namespaces {
return 0;
}

item.ancestors(ctx)
.filter(|id| ctx.resolve_item(*id).is_module())
.fold(1, |i, _| i + 1)
}

fn top_level_path(ctx: &BindgenContext, item: &Item) -> Vec<ast::Ident> {
let mut path = vec![ctx.rust_ident_raw("self")];

if ctx.options().enable_cxx_namespaces {
let super_ = ctx.rust_ident_raw("super");

for _ in 0..root_import_depth(ctx, item) {
for _ in 0..item.codegen_depth(ctx) {
path.push(super_.clone());
}
}
Expand Down Expand Up @@ -616,10 +606,8 @@ impl CodeGenerator for Type {
let rust_name = ctx.rust_ident(&name);
let mut typedef = aster::AstBuilder::new().item().pub_();

if ctx.options().generate_comments {
if let Some(comment) = item.comment() {
typedef = typedef.attr().doc(comment);
}
if let Some(comment) = item.comment(ctx) {
typedef = typedef.with_attr(attributes::doc(comment));
}

// We prefer using `pub use` over `pub type` because of:
Expand Down Expand Up @@ -839,6 +827,7 @@ trait FieldCodegen<'a> {
fn codegen<F, M>(&self,
ctx: &BindgenContext,
fields_should_be_private: bool,
codegen_depth: usize,
accessor_kind: FieldAccessorKind,
parent: &CompInfo,
anon_field_names: &mut AnonFieldNames,
Expand All @@ -857,6 +846,7 @@ impl<'a> FieldCodegen<'a> for Field {
fn codegen<F, M>(&self,
ctx: &BindgenContext,
fields_should_be_private: bool,
codegen_depth: usize,
accessor_kind: FieldAccessorKind,
parent: &CompInfo,
anon_field_names: &mut AnonFieldNames,
Expand All @@ -872,6 +862,7 @@ impl<'a> FieldCodegen<'a> for Field {
Field::DataMember(ref data) => {
data.codegen(ctx,
fields_should_be_private,
codegen_depth,
accessor_kind,
parent,
anon_field_names,
Expand All @@ -884,6 +875,7 @@ impl<'a> FieldCodegen<'a> for Field {
Field::Bitfields(ref unit) => {
unit.codegen(ctx,
fields_should_be_private,
codegen_depth,
accessor_kind,
parent,
anon_field_names,
Expand All @@ -903,6 +895,7 @@ impl<'a> FieldCodegen<'a> for FieldData {
fn codegen<F, M>(&self,
ctx: &BindgenContext,
fields_should_be_private: bool,
codegen_depth: usize,
accessor_kind: FieldAccessorKind,
parent: &CompInfo,
anon_field_names: &mut AnonFieldNames,
Expand Down Expand Up @@ -945,8 +938,9 @@ impl<'a> FieldCodegen<'a> for FieldData {

let mut attrs = vec![];
if ctx.options().generate_comments {
if let Some(comment) = self.comment() {
attrs.push(attributes::doc(comment));
if let Some(raw_comment) = self.comment() {
let comment = comment::preprocess(raw_comment, codegen_depth + 1);
attrs.push(attributes::doc(comment))
}
}

Expand Down Expand Up @@ -1153,6 +1147,7 @@ impl<'a> FieldCodegen<'a> for BitfieldUnit {
fn codegen<F, M>(&self,
ctx: &BindgenContext,
fields_should_be_private: bool,
codegen_depth: usize,
accessor_kind: FieldAccessorKind,
parent: &CompInfo,
anon_field_names: &mut AnonFieldNames,
Expand Down Expand Up @@ -1197,6 +1192,7 @@ impl<'a> FieldCodegen<'a> for BitfieldUnit {
for bf in self.bitfields() {
bf.codegen(ctx,
fields_should_be_private,
codegen_depth,
accessor_kind,
parent,
anon_field_names,
Expand Down Expand Up @@ -1277,6 +1273,7 @@ impl<'a> FieldCodegen<'a> for Bitfield {
fn codegen<F, M>(&self,
ctx: &BindgenContext,
_fields_should_be_private: bool,
_codegen_depth: usize,
_accessor_kind: FieldAccessorKind,
parent: &CompInfo,
_anon_field_names: &mut AnonFieldNames,
Expand Down Expand Up @@ -1409,10 +1406,8 @@ impl CodeGenerator for CompInfo {
let mut attributes = vec![];
let mut needs_clone_impl = false;
let mut needs_default_impl = false;
if ctx.options().generate_comments {
if let Some(comment) = item.comment() {
attributes.push(attributes::doc(comment));
}
if let Some(comment) = item.comment(ctx) {
attributes.push(attributes::doc(comment));
}
if self.packed() {
attributes.push(attributes::repr_list(&["C", "packed"]));
Expand Down Expand Up @@ -1545,9 +1540,11 @@ impl CodeGenerator for CompInfo {

let mut methods = vec![];
let mut anon_field_names = AnonFieldNames::default();
let codegen_depth = item.codegen_depth(ctx);
for field in self.fields() {
field.codegen(ctx,
fields_should_be_private,
codegen_depth,
struct_accessor_kind,
self,
&mut anon_field_names,
Expand Down Expand Up @@ -2367,10 +2364,8 @@ impl CodeGenerator for Enum {
builder = builder.with_attr(attributes::repr("C"));
}

if ctx.options().generate_comments {
if let Some(comment) = item.comment() {
builder = builder.with_attr(attributes::doc(comment));
}
if let Some(comment) = item.comment(ctx) {
builder = builder.with_attr(attributes::doc(comment));
}

if !is_constified_enum {
Expand Down Expand Up @@ -3069,10 +3064,8 @@ impl CodeGenerator for Function {

let mut attributes = vec![];

if ctx.options().generate_comments {
if let Some(comment) = item.comment() {
attributes.push(attributes::doc(comment));
}
if let Some(comment) = item.comment(ctx) {
attributes.push(attributes::doc(comment));
}

if let Some(mangled) = mangled_name {
Expand Down
64 changes: 44 additions & 20 deletions src/ir/comment.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Utilities for manipulating C/C++ comments.
use std::iter;

/// The type of a comment.
#[derive(Debug, PartialEq, Eq)]
enum Kind {
Expand All @@ -10,11 +12,12 @@ enum Kind {
/// entire block ends with `*/`.
MultiLine,
}

/// Preprocesses a C/C++ comment so that it is a valid Rust comment.
pub fn preprocess(comment: String) -> String {
pub fn preprocess(comment: &str, indent: usize) -> String {
match self::kind(&comment) {
Some(Kind::SingleLines) => preprocess_single_lines(&comment),
Some(Kind::MultiLine) => preprocess_multi_line(&comment),
Some(Kind::SingleLines) => preprocess_single_lines(comment, indent),
Some(Kind::MultiLine) => preprocess_multi_line(comment, indent),
None => comment.to_owned(),
}
}
Expand All @@ -30,38 +33,59 @@ fn kind(comment: &str) -> Option<Kind> {
}
}

fn make_indent(indent: usize) -> String {
const RUST_INDENTATION: usize = 4;

iter::repeat(' ').take(indent * RUST_INDENTATION).collect()
}

/// Preprocesses mulitple single line comments.
///
/// Handles lines starting with both `//` and `///`.
fn preprocess_single_lines(comment: &str) -> String {
assert!(comment.starts_with("//"), "comment is not single line");
fn preprocess_single_lines(comment: &str, indent: usize) -> String {
debug_assert!(comment.starts_with("//"), "comment is not single line");

let indent = make_indent(indent);
let mut is_first = true;
let lines: Vec<_> = comment.lines()
.map(|l| l.trim_left_matches('/').trim())
.map(|l| format!("/// {}", l).trim().to_owned())
.collect();
.map(|l| l.trim_left_matches('/').trim())
.map(|l| {
let indent = if is_first { "" } else { &*indent };
is_first = false;
let maybe_space = if l.is_empty() { "" } else { " " };
format!("{}///{}{}", indent, maybe_space, l)
})
.collect();
lines.join("\n")
}

fn preprocess_multi_line(comment: &str) -> String {
fn preprocess_multi_line(comment: &str, indent: usize) -> String {
let comment = comment.trim_left_matches('/')
.trim_left_matches("*")
.trim_left_matches("!")
.trim_left_matches('*')
.trim_left_matches('!')
.trim_right_matches('/')
.trim_right_matches('*')
.trim();

let indent = make_indent(indent);
// Strip any potential `*` characters preceding each line.
let mut is_first = true;
let mut lines: Vec<_> = comment.lines()
.map(|line| line.trim().trim_left_matches('*').trim())
.skip_while(|line| line.is_empty()) // Skip the first empty lines.
.map(|line| format!("/// {}", line).trim().to_owned())
.map(|line| {
let indent = if is_first { "" } else { &*indent };
is_first = false;
let maybe_space = if line.is_empty() { "" } else { " " };
format!("{}///{}{}", indent, maybe_space, line)
})
.collect();

// Remove the trailing `*/`.
let last_idx = lines.len() - 1;
if lines[last_idx].is_empty() {
lines.remove(last_idx);
// Remove the trailing line corresponding to the `*/`.
let last_line_is_empty = lines.last().map_or(false, |l| l.is_empty());

if last_line_is_empty {
lines.pop();
}

lines.join("\n")
Expand All @@ -79,16 +103,16 @@ mod test {

#[test]
fn processes_single_lines_correctly() {
assert_eq!(preprocess("/// hello".to_owned()), "/// hello");
assert_eq!(preprocess("// hello".to_owned()), "/// hello");
assert_eq!(preprocess("/// hello", 0), "/// hello");
assert_eq!(preprocess("// hello", 0), "/// hello");
}

#[test]
fn processes_multi_lines_correctly() {
assert_eq!(preprocess("/** hello \n * world \n * foo \n */".to_owned()),
assert_eq!(preprocess("/** hello \n * world \n * foo \n */", 0),
"/// hello\n/// world\n/// foo");

assert_eq!(preprocess("/**\nhello\n*world\n*foo\n*/".to_owned()),
assert_eq!(preprocess("/**\nhello\n*world\n*foo\n*/", 0),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have a unit test with nonzero indent for comment preprocessing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a super-fan of unit tests, but will do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(err, for stuff that can be integration-tested, I mean)

"/// hello\n/// world\n/// foo");
}
}
3 changes: 1 addition & 2 deletions src/ir/comp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Compound types (unions and structs) in our intermediate representation.
use super::annotations::Annotations;
use super::comment;
use super::context::{BindgenContext, ItemId};
use super::derive::{CanDeriveCopy, CanDeriveDebug, CanDeriveDefault};
use super::dot::DotAttributes;
Expand Down Expand Up @@ -1102,7 +1101,7 @@ impl CompInfo {
Some(potential_id),
ctx);

let comment = cur.raw_comment().map(comment::preprocess);
let comment = cur.raw_comment();
let annotations = Annotations::new(&cur);
let name = cur.spelling();
let is_mutable = cursor.is_mutable_field();
Expand Down
3 changes: 1 addition & 2 deletions src/ir/enum_ty.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Intermediate representation for C/C++ enumerations.
use super::comment;
use super::context::{BindgenContext, ItemId};
use super::item::Item;
use super::ty::TypeKind;
Expand Down Expand Up @@ -114,7 +113,7 @@ impl Enum {
})
});

let comment = cursor.raw_comment().map(comment::preprocess);
let comment = cursor.raw_comment();
variants.push(EnumVariant::new(name,
comment,
val,
Expand Down
3 changes: 1 addition & 2 deletions src/ir/function.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Intermediate representation for C/C++ functions and methods.
use super::comment;
use super::context::{BindgenContext, ItemId};
use super::dot::DotAttributes;
use super::item::Item;
Expand Down Expand Up @@ -406,7 +405,7 @@ impl ClangSubItemParser for Function {
mangled_name = None;
}

let comment = cursor.raw_comment().map(comment::preprocess);
let comment = cursor.raw_comment();

let function = Self::new(name, mangled_name, sig, comment);
Ok(ParseResult::New(function, Some(cursor)))
Expand Down
Loading