Skip to content
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ assert!("oh no, an error: muahaha i am an error" == &format!("{}", error));
- Two optional attributes can be added to your types next to the derive:

- `#[ignore_extra_doc_attributes]` makes the macro ignore any doc
comment attributes (or `///` lines) after the first. Multi-line
comments using `///` are otherwise treated as an error, so use this
comment attributes (or `///` lines) after the last non-empty line. Multi-line
comments with line breaks using `///` are otherwise treated as an error, so use this
attribute or consider switching to block doc comments (`/** */`).

- `#[prefix_enum_doc_attributes]` combines the doc comment message on
Expand Down
13 changes: 13 additions & 0 deletions examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pub enum DataStoreError {
Redaction(String),
/// invalid header (expected {expected:?}, found {found:?})
InvalidHeader { expected: String, found: String },
/// connection refused:
/// please try again later
ConnectionRefused,
/// unknown data store error
Unknown,
}
Expand All @@ -33,4 +36,14 @@ fn main() {
"Enum value `InvalidHeader` should be printed as:\n\t{}",
invalid_header
);

println!(
"Enum value `ConnectionRefused` should be printed as:\n\t{}",
DataStoreError::ConnectionRefused
);

println!(
"Enum value `Unknown` should be printed as:\n\t{}",
DataStoreError::Unknown
);
}
82 changes: 43 additions & 39 deletions src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,50 +69,54 @@ impl AttrsHelper {
return Ok(Some(display));
}

let num_doc_attrs = attrs
let literals = attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"))
.count();
.map(|attr| match &attr.meta {
Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}),
..
}) => lit,
_ => unimplemented!(),
});

let span = match literals.clone().next() {
Some(lit) => lit.span(),
None => return Ok(None),
};

if !self.ignore_extra_doc_attributes && num_doc_attrs > 1 {
panic!("Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive.");
}
let strs = literals.map(|lit| {
// Make an attempt at cleaning up multiline doc comments.
let doc_str = lit
.value()
.lines()
.map(|line| line.trim().trim_start_matches('*').trim())
.collect::<Vec<&str>>()
.join("\n")
.trim()
.to_string();
(!doc_str.is_empty()).then(|| doc_str)
});

for attr in attrs {
if attr.path().is_ident("doc") {
let lit = match &attr.meta {
Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}),
..
}) => lit,
_ => unimplemented!(),
};

// Make an attempt at cleaning up multiline doc comments.
let doc_str = lit
.value()
.lines()
.map(|line| line.trim().trim_start_matches('*').trim())
.collect::<Vec<&str>>()
.join("\n");

let lit = LitStr::new(doc_str.trim(), lit.span());

let mut display = Display {
fmt: lit,
args: TokenStream::new(),
};

display.expand_shorthand();
return Ok(Some(display));
}
}
let joined = if self.ignore_extra_doc_attributes {
strs.take_while(|x| x.is_some()).collect::<Option<Vec<_>>>()
} else {
strs.collect::<Option<Vec<_>>>()
}.unwrap_or_else(|| {
panic!("Line breaks in multi-line doc comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive");
}).join(" ");

let mut display = Display {
fmt: LitStr::new(&joined, span),
args: TokenStream::new(),
};

Ok(None)
display.expand_shorthand();
Ok(Some(display))
}

pub(crate) fn display_with_input(
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
//! - Two optional attributes can be added to your types next to the derive:
//!
//! - `#[ignore_extra_doc_attributes]` makes the macro ignore any doc
//! comment attributes (or `///` lines) after the first. Multi-line
//! comments using `///` are otherwise treated as an error, so use this
//! comment attributes (or `///` lines) after the last non-empty line. Multi-line
//! comments with line breaks using `///` are otherwise treated as an error, so use this
//! attribute or consider switching to block doc comments (`/** */`).
//!
//! - `#[prefix_enum_doc_attributes]` combines the doc comment message on
Expand Down
4 changes: 3 additions & 1 deletion tests/compile_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
fn no_std() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/without.rs");
t.compile_fail("tests/ui/multi_line.rs");
t.compile_fail("tests/ui/multi_line_line_break.rs");
t.compile_fail("tests/ui/multi_line_line_break_block.rs");
t.pass("tests/ui/multi_line.rs");
t.pass("tests/ui/multi_line_allow.rs");
t.compile_fail("tests/ui/enum_prefix_missing.rs");
t.pass("tests/ui/enum_prefix.rs");
Expand Down
69 changes: 67 additions & 2 deletions tests/happy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,53 @@ struct HappyStruct {
#[derive(Display)]
#[ignore_extra_doc_attributes]
/// Just a basic struct {thing}
/// and this line should get ignored
/// and this line should not get ignored
struct HappyStruct2 {
thing: &'static str,
}

#[derive(Display)]
/// Really fancy first line with thing: {thing}
/// Really cool second line
struct HappyMultiLine {
Copy link
Collaborator

Choose a reason for hiding this comment

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

issue: please include a test for multiline attributes with ignore_extra_doc_attributes

thing: &'static str,
}

#[derive(Display)]
#[ignore_extra_doc_attributes]
/// multi
/// line
///
/// new paragraph should be ignored
struct HappyMultilineWithIgnore;
Comment on lines +27 to +33
Copy link
Contributor Author

@20jasper 20jasper Dec 19, 2025

Choose a reason for hiding this comment

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

issue: please include a test for multiline attributes with ignore_extra_doc_attributes

Good callout! Here's a new test covering that case

Output is "multi line"


#[derive(Display)]
enum MixedBlockAndLineComments {
/**
* hello
* block
* comment
*/
/// line comment
BlockFirst,
/// line comment
/**
* hello
* block
* comment
*/
LineFirst,
/**
* block
* comment
*/
/**
* block
* comment2
*/
DoubleBlock,
Comment on lines +51 to +59
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 just added test cases for several other edge cases

Question—is it appropriate to concatenate sequential block comments with spaces?

This outputs "block\ncomment block\ncomment2"

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I think so.

}

#[derive(Display)]
enum Happy {
/// I really like Variant1
Expand Down Expand Up @@ -109,7 +151,30 @@ fn does_it_print() {
);
assert_display(HappyStruct { thing: "hi" }, "Just a basic struct hi");

assert_display(HappyStruct2 { thing: "hi2" }, "Just a basic struct hi2");
assert_display(
HappyStruct2 { thing: "hi2" },
"Just a basic struct hi2 and this line should not get ignored",
);

assert_display(
HappyMultiLine { thing: "rust" },
"Really fancy first line with thing: rust Really cool second line",
);

assert_display(HappyMultilineWithIgnore, "multi line");

assert_display(
MixedBlockAndLineComments::BlockFirst,
"hello\nblock\ncomment line comment",
);
assert_display(
MixedBlockAndLineComments::LineFirst,
"line comment hello\nblock\ncomment",
);
assert_display(
MixedBlockAndLineComments::DoubleBlock,
"block\ncomment block\ncomment2",
);

assert_display(inner_mod::InnerHappy::Variant1, "I really like Variant1");
assert_display(
Expand Down
5 changes: 4 additions & 1 deletion tests/ui/multi_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ enum TestType {

/// Multi
/// line
/// doc.
/// doc
/// is
/// pretty
/// swell
Variant2,
}

Expand Down
11 changes: 10 additions & 1 deletion tests/ui/multi_line_allow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ enum TestType {
/// line
/// doc.
Variant2,

/// Multi
/// line
/// doc
///
/// with
/// line
/// breaks
Variant3,
}

static_assertions::assert_impl_all!(TestType: core::fmt::Display);

fn main() {}
fn main() {}
20 changes: 20 additions & 0 deletions tests/ui/multi_line_line_break.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use displaydoc::Display;

#[derive(Display)]
/// Multi
/// line
/// doc
/// with
/// line
/// break
///
/// is
/// pretty
/// not
/// swell
/// 😞👊
struct TestType;

static_assertions::assert_impl_all!(TestType: core::fmt::Display);

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
error: proc-macro derive panicked
--> tests/ui/multi_line.rs:4:10
--> tests/ui/multi_line_line_break.rs:3:10
|
4 | #[derive(Display)]
3 | #[derive(Display)]
| ^^^^^^^
|
= help: message: Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive.
= help: message: Line breaks in multi-line doc comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive

error[E0277]: `TestType` doesn't implement `std::fmt::Display`
--> tests/ui/multi_line.rs:15:37
--> tests/ui/multi_line_line_break.rs:18:37
|
15 | static_assertions::assert_impl_all!(TestType: core::fmt::Display);
18 | static_assertions::assert_impl_all!(TestType: core::fmt::Display);
| ^^^^^^^^ unsatisfied trait bound
|
help: the trait `std::fmt::Display` is not implemented for `TestType`
--> tests/ui/multi_line.rs:5:1
--> tests/ui/multi_line_line_break.rs:16:1
|
5 | enum TestType {
| ^^^^^^^^^^^^^
16 | struct TestType;
| ^^^^^^^^^^^^^^^
note: required by a bound in `assert_impl_all`
--> tests/ui/multi_line.rs:15:1
--> tests/ui/multi_line_line_break.rs:18:1
|
15 | static_assertions::assert_impl_all!(TestType: core::fmt::Display);
18 | static_assertions::assert_impl_all!(TestType: core::fmt::Display);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all`
= note: this error originates in the macro `static_assertions::assert_impl_all` (in Nightly builds, run with -Z macro-backtrace for more info)
14 changes: 14 additions & 0 deletions tests/ui/multi_line_line_break_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use displaydoc::Display;

#[derive(Display)]
/**
* block
*/
/**
*
*/
struct TestType;

static_assertions::assert_impl_all!(TestType: core::fmt::Display);

fn main() {}
25 changes: 25 additions & 0 deletions tests/ui/multi_line_line_break_block.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error: proc-macro derive panicked
--> tests/ui/multi_line_line_break_block.rs:3:10
|
3 | #[derive(Display)]
| ^^^^^^^
|
= help: message: Line breaks in multi-line doc comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive

error[E0277]: `TestType` doesn't implement `std::fmt::Display`
--> tests/ui/multi_line_line_break_block.rs:12:37
|
12 | static_assertions::assert_impl_all!(TestType: core::fmt::Display);
| ^^^^^^^^ unsatisfied trait bound
|
help: the trait `std::fmt::Display` is not implemented for `TestType`
--> tests/ui/multi_line_line_break_block.rs:10:1
|
10 | struct TestType;
| ^^^^^^^^^^^^^^^
note: required by a bound in `assert_impl_all`
--> tests/ui/multi_line_line_break_block.rs:12:1
|
12 | static_assertions::assert_impl_all!(TestType: core::fmt::Display);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `assert_impl_all`
= note: this error originates in the macro `static_assertions::assert_impl_all` (in Nightly builds, run with -Z macro-backtrace for more info)
Loading