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
39 changes: 39 additions & 0 deletions changelog/dmd.ddoc-behavior-flags.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Ddoc now emits behavior-flag badges for function guarantees

Documented functions now display badge-like $(I behavior flags) under their
title, similar to the build-status badges seen in project READMEs. When any are
present, the badges are introduced by a `Current Behaviors:` label. Four flags are
available, each shown only when it applies and each in a distinct color:

$(UL
$(LI `$(DOLLAR)(NOGC)` (rendered $(I No GC)) $(MDASH) emitted automatically for `@nogc` functions)
$(LI `$(DOLLAR)(NOTHROW)` (rendered $(I No Exceptions Thrown)) $(MDASH) emitted automatically for `nothrow` functions)
$(LI `$(DOLLAR)(PURE)` (rendered $(I Is Pure)) $(MDASH) emitted automatically for `pure` functions)
$(LI `$(DOLLAR)(NOALLOC)` (rendered $(I No Allocations)) $(MDASH) a manual-only
flag with no corresponding language attribute)
)

The `$(DOLLAR)(NOGC)`, `$(DOLLAR)(NOTHROW)` and `$(DOLLAR)(PURE)` flags appear
automatically whenever the matching attribute is present on the documented
declaration. Any flag may also be written manually anywhere in a documentation
comment; manually-written flags are moved to appear under the declaration's
title, and duplicate flags $(MDASH) whether written manually or added
automatically $(MDASH) are collapsed into a single badge.

-------
/++
Copy `n` bytes.

$(DOLLAR)(NOALLOC)
+/
void* fastCopy(void* dst, const(void)* src, size_t n) @nogc nothrow pure;
-------

The example above renders a $(I No Allocations) badge (written manually) next to
the $(I No GC), $(I No Exceptions Thrown) and $(I Is Pure) badges (added
automatically from the attributes), all shown after a `Current Behaviors:` label.

The badges and their grouping container are produced by the new `NOALLOC`,
`NOGC`, `NOTHROW`, `PURE` and `DDOC_FLAGS` macros, which can be redefined like
any other Ddoc macro. See $(LINK2 $(ROOT_DIR)spec/ddoc.html#behavior_flags,
Behavior Flags) for details.
120 changes: 120 additions & 0 deletions compiler/src/dmd/doc.d
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,122 @@ void emitVisibility(ref OutBuffer buf, Visibility vis)
buf.writeByte(' ');
}

/****************************************************
* Emit the behavior-flag badges (`@nogc`, `nothrow`, `pure`, and the
* manual-only `no-alloc`) directly under a declaration's title.
*
* A flag is emitted automatically when the matching attribute is present on any
* of the documented declarations. Flag macros written manually anywhere in the
* comment body are moved here too. Duplicates are collapsed into one badge.
* Params:
* buf = buffer holding the description, which the flags are inserted into
* start = the index in `buf` where the description begins
* a = the declarations sharing this documentation comment
*/
void emitBehaviorFlags(ref OutBuffer buf, size_t start, Dsymbols* a)
{
static immutable string[4] names = ["NOALLOC", "NOGC", "NOTHROW", "PURE"];
bool[4] present;

bool matchAt(size_t pos, string name)
{
if (pos + 3 + name.length > buf.length || buf[pos] != '$' || buf[pos + 1] != '(')
return false;
foreach (k, ch; name)
if (buf[pos + 2 + k] != ch)
return false;
return buf[pos + 2 + name.length] == ')';
}

// Pull any manually-written flag macros out of the body.
for (size_t i = start; i < buf.length;)
{
bool removed = false;
foreach (fi, name; names)
if (matchAt(i, name))
{
present[fi] = true;
buf.remove(i, name.length + 3);
removed = true;
break;
}
if (!removed)
++i;
}

// Add flags implied by the declarations' attributes (NOALLOC has none).
foreach (sym; *a)
{
if (TypeFunction tf = isTypeFunction(sym))
{
if (tf.isNogc)
present[1] = true;
if (tf.isNothrow)
present[2] = true;
if (tf.purity != PURE.impure)
present[3] = true;
}
}

bool any = false;
foreach (p; present)
any |= p;
if (!any)
return;

OutBuffer flags;
flags.writestring("$(DDOC_FLAGS ");
foreach (fi, name; names)
if (present[fi])
{
flags.writestring("$(");
flags.writestring(name);
flags.writeByte(')');
}
flags.writeByte(')');
buf.insert(start, flags[]);
}

unittest
{
// No flags: buffer is left untouched.
Dsymbols a;
OutBuffer buf;
buf.writestring("no flags here");
emitBehaviorFlags(buf, 0, &a);
assert(buf[] == "no flags here");
}

unittest
{
// Manual flag macros are hoisted to the front and duplicates collapsed.
Dsymbols a;
OutBuffer buf;
buf.writestring("text $(NOGC) more $(PURE) and $(NOGC) end");
emitBehaviorFlags(buf, 0, &a);
assert(buf[] == "$(DDOC_FLAGS $(NOGC)$(PURE))text more and end");
}

unittest
{
// Flags are always emitted in canonical order regardless of input order.
Dsymbols a;
OutBuffer buf;
buf.writestring("$(PURE)$(NOTHROW)$(NOALLOC)");
emitBehaviorFlags(buf, 0, &a);
assert(buf[] == "$(DDOC_FLAGS $(NOALLOC)$(NOTHROW)$(PURE))");
}

unittest
{
// Text before `start` is not scanned or modified.
Dsymbols a;
OutBuffer buf;
buf.writestring("$(NOGC)|$(PURE)");
emitBehaviorFlags(buf, 8, &a);
assert(buf[] == "$(NOGC)|$(DDOC_FLAGS $(PURE))");
}

void emitComment(Dsymbol s, ref OutBuffer buf, Scope* sc)
{
extern (C++) final class EmitComment : Visitor
Expand Down Expand Up @@ -1439,7 +1555,11 @@ void emitComment(Dsymbol s, ref OutBuffer buf, Scope* sc)
// Put the ddoc comment as the document 'description'
buf.writestring(ddoc_decl_dd_s);
{
size_t iDescStart = buf.length;
dc.writeSections(sc, &dc.a, *buf);
// Emit flags from this declaration's own sections, before
// members are written, so nested member flags aren't consumed.
emitBehaviorFlags(*buf, iDescStart, &dc.a);
foreach (sym; dc.a)
if (ScopeDsymbol sds = sym.isScopeDsymbol())
{
Expand Down
39 changes: 39 additions & 0 deletions compiler/src/dmd/res/default_ddoc_theme.ddoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ YELLOW = <span class="color_yellow">$0</span>
BLACK = <span class="color_black">$0</span>
WHITE = <span class="color_white">$0</span>

NOALLOC = <span class="ddoc_flag ddoc_flag_noalloc" title="Does not allocate">No Allocations</span>
NOGC = <span class="ddoc_flag ddoc_flag_nogc" title="Does not use the garbage collector">No GC</span>
NOTHROW = <span class="ddoc_flag ddoc_flag_nothrow" title="Does not throw exceptions">No Exceptions Thrown</span>
PURE = <span class="ddoc_flag ddoc_flag_pure" title="Has no observable side effects">Is Pure</span>
DDOC_FLAGS = <div class="ddoc_flags"><span class="ddoc_flags_label">Current Behaviors:</span>$0</div>$(LF)

D_CODE =
<section class="code_listing">
<div class="code_sample">
Expand Down Expand Up @@ -164,6 +170,39 @@ DDOC =
.color_black { color: black; }
.color_white { color: white; }

.ddoc_flags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 0px 5px 5px 5px;
}

.ddoc_flags_label {
align-self: center;
font-weight: bold;
font-size: 11px;
margin-right: 2px;
}

.ddoc_flag {
display: inline-block;
margin: 2px 4px 2px 0;
padding: 2px 8px;
border-radius: 4px;
font-family: Menlo, monospace;
font-size: 11px;
font-weight: bold;
line-height: 1.6;
color: #ffffff;
white-space: nowrap;
vertical-align: middle;
}

.ddoc_flag_noalloc { background-color: #8e44ad; }
.ddoc_flag_nogc { background-color: #d33682; }
.ddoc_flag_nothrow { background-color: #859901; }
.ddoc_flag_pure { background-color: #268bd2; }

.font_big {
font-size: 1.2em;
}
Expand Down
6 changes: 4 additions & 2 deletions compiler/test/compilable/extra-files/ddoc10.html
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,8 @@ <h4>Declaration</h4>
</section>
</div>
<div class="ddoc_decl">

<div class="ddoc_flags"><span class="ddoc_flags_label">Current Behaviors:</span><span class="ddoc_flag ddoc_flag_nothrow" title="Does not throw exceptions">No Exceptions Thrown</span><span class="ddoc_flag ddoc_flag_pure" title="Has no observable side effects">Is Pure</span></div>


</div>

Expand All @@ -607,7 +608,8 @@ <h4>Declaration</h4>
</section>
</div>
<div class="ddoc_decl">

<div class="ddoc_flags"><span class="ddoc_flags_label">Current Behaviors:</span><span class="ddoc_flag ddoc_flag_nothrow" title="Does not throw exceptions">No Exceptions Thrown</span><span class="ddoc_flag ddoc_flag_pure" title="Has no observable side effects">Is Pure</span></div>


</div>

Expand Down
Loading
Loading