diff --git a/changelog/dmd.ddoc-behavior-flags.dd b/changelog/dmd.ddoc-behavior-flags.dd new file mode 100644 index 000000000000..7c7cae6d79cd --- /dev/null +++ b/changelog/dmd.ddoc-behavior-flags.dd @@ -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. diff --git a/compiler/src/dmd/doc.d b/compiler/src/dmd/doc.d index 34327ee4419b..8e5259a42e7a 100644 --- a/compiler/src/dmd/doc.d +++ b/compiler/src/dmd/doc.d @@ -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 @@ -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()) { diff --git a/compiler/src/dmd/res/default_ddoc_theme.ddoc b/compiler/src/dmd/res/default_ddoc_theme.ddoc index 9b8349cbcf0f..88a58f19441c 100644 --- a/compiler/src/dmd/res/default_ddoc_theme.ddoc +++ b/compiler/src/dmd/res/default_ddoc_theme.ddoc @@ -65,6 +65,12 @@ YELLOW = $0 BLACK = $0 WHITE = $0 +NOALLOC = No Allocations +NOGC = No GC +NOTHROW = No Exceptions Thrown +PURE = Is Pure +DDOC_FLAGS =
2 @@ -149,7 +150,8 @@
3 @@ -181,7 +183,8 @@
5 @@ -213,7 +216,8 @@
6 @@ -245,7 +249,8 @@
7 @@ -277,7 +282,8 @@
9 @@ -309,7 +315,8 @@
10 @@ -440,7 +447,8 @@
2 @@ -472,7 +480,8 @@
3 @@ -504,7 +513,8 @@
5 @@ -536,7 +546,8 @@
6 @@ -568,7 +579,8 @@
7 @@ -600,7 +612,8 @@
9 @@ -632,7 +645,8 @@
10 @@ -733,7 +747,8 @@
2 @@ -765,7 +780,8 @@
3 @@ -797,7 +813,8 @@
5 @@ -829,7 +846,8 @@
6 @@ -861,7 +879,8 @@
7 @@ -893,7 +912,8 @@
9 @@ -925,7 +945,8 @@
10 diff --git a/compiler/test/compilable/extra-files/ddocYear.html b/compiler/test/compilable/extra-files/ddocYear.html index 4b2239f19716..5ab3aa3f5f40 100644 --- a/compiler/test/compilable/extra-files/ddocYear.html +++ b/compiler/test/compilable/extra-files/ddocYear.html @@ -64,6 +64,39 @@ .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; } diff --git a/spec/ddoc.dd b/spec/ddoc.dd index c14ff3f0e83f..80ca10a27a0d 100644 --- a/spec/ddoc.dd +++ b/spec/ddoc.dd @@ -853,6 +853,49 @@ To prevent unintended emphasis of an identifier, it can be preceded by an underscore ($(UNDERSCORE)). The underscore will be stripped from the output. ) +$(H3 $(LNAME2 behavior_flags, Behavior Flags)) + +$(P +Functions can advertise behavioral guarantees with badge-like $(I behavior flags) +that are displayed under the declaration, similar to the build-status badges +seen in project READMEs. When any are present, the badges are introduced by a +$(D Current Behaviors:) label. Four flags are available: `$(DOLLAR)(NOALLOC)` (rendered +$(I No Allocations)), `$(DOLLAR)(NOGC)` (rendered $(I No GC)), +`$(DOLLAR)(NOTHROW)` (rendered $(I No Exceptions Thrown)) and `$(DOLLAR)(PURE)` +(rendered $(I Is Pure)). Each flag is shown only when it applies, and each is +rendered in a distinct color. +) + +$(P +The `$(DOLLAR)(NOGC)`, `$(DOLLAR)(NOTHROW)` and `$(DOLLAR)(PURE)` flags are +emitted automatically when the corresponding `@nogc`, `nothrow` or `pure` +attribute is present on the documented declaration. The `$(DOLLAR)(NOALLOC)` flag +has no corresponding language attribute and is only shown when written manually. +) + +$(P +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 from an +attribute $(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; +------------------------------------ + +$(P +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 under the function's title after a +$(D Current Behaviors:) label. +) + $(H3 $(LNAME2 character_entities, Character Entities)) $(P @@ -1083,6 +1126,10 @@ $(P $(TROW $(ARGS $(D YELLOW)), $(ARGS argument is set to be yellow)) $(TROW $(ARGS $(D BLACK)), $(ARGS argument is set to be black)) $(TROW $(ARGS $(D WHITE)), $(ARGS argument is set to be white)) + $(TROW $(ARGS $(D NOALLOC)), $(ARGS behavior flag badge for functions that do not allocate)) + $(TROW $(ARGS $(D NOGC)), $(ARGS behavior flag badge for $(D @nogc) functions)) + $(TROW $(ARGS $(D NOTHROW)), $(ARGS behavior flag badge for $(D nothrow) functions)) + $(TROW $(ARGS $(D PURE)), $(ARGS behavior flag badge for $(D pure) functions)) $(TROW $(ARGS $(D D_CODE)), $(ARGS argument is D code)) $(TROW $(ARGS $(D D_INLINECODE)), $(ARGS argument is inline D code)) $(TROW $(ARGS $(D LF)), $(ARGS Insert a line feed (newline))) @@ -1198,6 +1245,7 @@ $(P $(TROW $(ARGS $(D DDOC_KEYWORD)), $(ARGS Highlighting of D keywords.)) $(TROW $(ARGS $(D DDOC_PARAM)), $(ARGS Highlighting of function parameters.)) $(TROW $(ARGS $(D DDOC_BACKQUOTED)), $(ARGS Inserts inline code.)) + $(TROW $(ARGS $(D DDOC_FLAGS)), $(ARGS Container for the behavior flag badges emitted under a declaration.)) $(TROW $(ARGS $(D DDOC_AUTO_PSYMBOL_SUPPRESS)), $(ARGS Highlighting of auto-detected symbol that starts with underscore)) $(TROW $(ARGS $(D DDOC_AUTO_PSYMBOL)), $(ARGS Highlighting of auto-detected symbol)) $(TROW $(ARGS $(D DDOC_AUTO_KEYWORD)), $(ARGS Highlighting of auto-detected keywords))