Skip to content
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

Support for doc comments in icrate generation #435

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
126 changes: 126 additions & 0 deletions crates/header-translator/src/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! Utilities for manipulating C/C++ comments.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I copied this file straight out of bindgen and then modified it for some of the extra gross objective-c comments.

Copy link
Owner

Choose a reason for hiding this comment

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

Could you leave attribution in the file? I think that should alleviate most licensing issues (they have a different license than us), but IANAL.


/// The type of a comment.
#[derive(Debug, PartialEq, Eq)]
enum Kind {
/// A `///` comment, or something of the like.
/// All lines in a comment should start with the same symbol.
SingleLines,
/// A `/**` comment, where each other line can start with `*` and the
/// entire block ends with `*/`.
MultiLine,
}

/// Preprocesses a C/C++ comment so that it is a valid Rust comment.
pub(crate) fn preprocess(comment: &str) -> String {
match self::kind(comment) {
Some(Kind::SingleLines) => preprocess_single_lines(comment),
Some(Kind::MultiLine) => preprocess_multi_line(comment),
None => comment.to_owned(),
}
}

/// Gets the kind of the doc comment, if it is one.
fn kind(comment: &str) -> Option<Kind> {
if comment.starts_with("/*") {
Some(Kind::MultiLine)
} else if comment.starts_with("//") {
Some(Kind::SingleLines)
} else {
None
}
}

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

let lines: Vec<_> = comment
.lines()
.map(|l| {
l.trim()
.trim_end_matches('/')
.trim_end_matches('*')
.trim_start_matches('/')
})
.collect();
lines.join("\n")
}

fn preprocess_multi_line(comment: &str) -> String {
let comment = comment
.trim_start_matches('/')
.trim_end_matches('/')
.trim_end_matches('*');

// Strip any potential `*` characters preceding each line.
let mut lines: Vec<_> = comment
.lines()
.map(|line| {
line.trim_start_matches('/')
.trim_end_matches('/')
.trim_end_matches('*')
.trim()
.trim_start_matches('*')
.trim_start_matches('!')
})
.skip_while(|line| line.trim().is_empty()) // Skip the first empty lines.
.collect();

// Remove the trailing line corresponding to the `*/`.
if lines.last().map_or(false, |l| l.trim().is_empty()) {
lines.pop();
}

lines.join("\n")
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn picks_up_single_and_multi_line_doc_comments() {
assert_eq!(kind("/// hello"), Some(Kind::SingleLines));
assert_eq!(kind("/** world */"), Some(Kind::MultiLine));
}

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

#[test]
fn processes_multi_lines_correctly() {
assert_eq!(preprocess("/**/"), "");

assert_eq!(
preprocess("/** hello \n * world \n * foo \n */"),
" hello\n world\n foo"
);

assert_eq!(
preprocess("/**\nhello\n*world\n*foo\n*/"),
"hello\nworld\nfoo"
);
assert_eq!(
preprocess(
r#"/************************ Deprecated ************************/
// Following NSStringDrawing methods are soft deprecated starting with OS X 10.11. It will be officially deprecated in a future release. Use corresponding API with NSStringDrawingContext instead"#
),
r#" Deprecated
Following NSStringDrawing methods are soft deprecated starting with OS X 10.11. It will be officially deprecated in a future release. Use corresponding API with NSStringDrawingContext instead"#
);
assert_eq!(
preprocess(
r#"/// The \c contentLayoutRect will return the area inside the window that is for non-obscured content. Typically, this is the same thing as the `contentView`'s frame. However, for windows with the \c NSFullSizeContentViewWindowMask set, there needs to be a way to determine the portion that is not under the toolbar. The \c contentLayoutRect returns the portion of the layout that is not obscured under the toolbar. \c contentLayoutRect is in window coordinates. It is KVO compliant. */"#
),
r#" The \c contentLayoutRect will return the area inside the window that is for non-obscured content. Typically, this is the same thing as the `contentView`'s frame. However, for windows with the \c NSFullSizeContentViewWindowMask set, there needs to be a way to determine the portion that is not under the toolbar. The \c contentLayoutRect returns the portion of the layout that is not obscured under the toolbar. \c contentLayoutRect is in window coordinates. It is KVO compliant. "#
);
}
}
1 change: 1 addition & 0 deletions crates/header-translator/src/global_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn update_module(module: &mut Module) {
kind: _,
variants: _,
sendable: _,
comment: _,
}) = iter.peek_mut()
{
if enum_ty.is_typedef_to(&id.name) {
Expand Down
1 change: 1 addition & 0 deletions crates/header-translator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use tracing::span::EnteredSpan;

mod availability;
mod cfgs;
mod comment;
mod config;
mod context;
mod display_helper;
Expand Down
4 changes: 2 additions & 2 deletions crates/header-translator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,8 @@ fn get_translation_unit<'i: 'tu, 'tu>(
"-fobjc-arc",
"-fobjc-arc-exceptions",
"-fobjc-abi-version=2", // 3??
// "-fparse-all-comments",
// TODO: "-fretain-comments-from-system-headers"
"-fparse-all-comments",
"-fretain-comments-from-system-headers",
"-fapinotes",
"-isysroot",
sdk.path.to_str().unwrap(),
Expand Down
11 changes: 11 additions & 0 deletions crates/header-translator/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ pub struct Method {
is_error: bool,
safe: bool,
mutating: bool,
comment: Option<String>,
is_pub: bool,
// Thread-safe, even on main-thread only (@MainActor/@UIActor) classes
non_isolated: bool,
Expand Down Expand Up @@ -465,6 +466,7 @@ impl Method {
}

let result_type = entity.get_result_type().expect("method return type");
//let comment = entity.get_comment();
let default_nonnull = (selector == "init" && !is_class) || (selector == "new" && is_class);
let mut result_type = Ty::parse_method_return(result_type, default_nonnull, context);

Expand Down Expand Up @@ -509,6 +511,7 @@ impl Method {
// since immutable methods are usually either declared on an
// immutable subclass, or as a property.
mutating: data.mutating.unwrap_or(parent_is_mutable),
comment: None,
is_pub,
non_isolated: modifiers.non_isolated,
mainthreadonly,
Expand Down Expand Up @@ -544,6 +547,7 @@ impl Method {
}

let availability = Availability::parse(&entity, context);
let comment = entity.get_comment();

let modifiers = MethodModifiers::parse(&entity, context);

Expand Down Expand Up @@ -586,6 +590,7 @@ impl Method {
// is, so let's default to immutable.
mutating: getter_data.mutating.unwrap_or(false),
is_pub,
comment,
non_isolated: modifiers.non_isolated,
mainthreadonly,
})
Expand All @@ -605,6 +610,7 @@ impl Method {
);

let fn_name = selector.strip_suffix(':').unwrap().to_string();
let comment = entity.get_comment();
let memory_management =
MemoryManagement::new(is_class, &selector, &result_type, modifiers);

Expand All @@ -629,6 +635,7 @@ impl Method {
safe: !setter_data.unsafe_,
// Setters are usually mutable if the class itself is.
mutating: setter_data.mutating.unwrap_or(parent_is_mutable),
comment,
is_pub,
non_isolated: modifiers.non_isolated,
mainthreadonly,
Expand Down Expand Up @@ -683,6 +690,10 @@ impl fmt::Display for Method {
// Attributes
//

if let Some(ref comment) = self.comment {
let comment = crate::comment::preprocess(comment);
writeln!(f, "/**\n {comment} \n*/")?;
}
write!(f, "{}", self.availability)?;

if self.is_optional {
Expand Down
Loading
Loading