Skip to content

Commit

Permalink
doxygen: fix keywords leaking into return type
Browse files Browse the repository at this point in the history
- fixes #225
- fixes #226

Also, some drive-by fixes:
- added support for C++20's `consteval` keyword
- fixed a typo in `CONTRIBUTING.rst`
  • Loading branch information
marzer committed Apr 30, 2022
1 parent 6f5c7d5 commit 5b0691b
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 29 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ On Ubuntu you need these:
.. code:: sh
sudo apt install texlive-base texlive-latex-extra texlive-fonts-extra python3-matplotlib graphviz
pip3 install pelican Pyphen Pillow
pip3 install pelican Pyphen pillow
Once you have all the dependencies, simply go to the ``site/`` subdirectory and
start development server there. The live-reloading website will appear on
Expand Down
82 changes: 56 additions & 26 deletions documentation/doxygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -1992,39 +1992,69 @@ def parse_func(state: State, element: ET.Element):
func.brief = parse_desc(state, element.find('briefdescription'))
func.description, templates, params, func.return_value, func.return_values, func.exceptions, search_keywords, func.deprecated, func.since = parse_func_desc(state, element)

# Friend functions have friend as type. That's just awful. COME ON.
if func.type.startswith('friend '):
func.type = func.type[7:]

def is_identifier(a): return a == '_' or a.isalnum()

# Extract function signature to prefix, suffix and various flags. Important
# things affecting caller such as static or const (and rvalue overloads)
# are put into signature prefix/suffix, other things to various is_*
# properties.
if func.type == 'constexpr': # Constructors
func.type = ''
func.is_constexpr = True
elif func.type.startswith('constexpr'):
func.type = func.type[10:]
func.is_constexpr = True
# For some effing reason, when a constexpr function has decltype(auto)
# return type, Doxygen swaps the order of those two, causing the constexpr
# to be last. See the cpp_function_attributes test for a verification.
elif func.type.endswith('constexpr'):
func.type = func.type[:-10]
func.is_constexpr = True
else:
func.is_constexpr = False
# When 1.8.18 encounters `constexpr static`, it keeps the static there. For
# `static constexpr` it doesn't. In both cases the static="yes" is put
# there correctly. WHY DOXYGEN, WHY?!
if func.type.startswith('static'):
func.type = func.type[7:]
#
# First the prefix keywords - Doxygen has a habit of leaking attributes and
# other specifiers into the function's return type, and not necessarily
# in any consistent order (including swapping it with the actual type!)
func.is_constexpr = False
func.is_consteval = False
func.is_explicit = False
func.is_virtual = False
func.is_static = False
keywords_behaving_badly = (
'constexpr',
'consteval',
'explicit',
'static',
'friend',
'inline',
'virtual'
)
matched_bad_keyword = True
while matched_bad_keyword:
matched_bad_keyword = False
for kw in keywords_behaving_badly:
if func.type == kw: # constructors
matched_bad_keyword = True
func.type = ''
elif func.type.startswith(kw+' '):
matched_bad_keyword = True
func.type = func.type[len(kw):].strip()
elif func.type.endswith(' '+kw):
matched_bad_keyword = True
func.type = func.type[:len(kw)].strip()
else:
continue
if kw == 'constexpr':
func.is_constexpr = True
elif kw == 'consteval':
func.is_consteval = True
elif kw == 'explicit':
func.is_explicit = True
elif kw == 'virtual':
func.is_virtual = True
elif kw == 'static':
func.is_static = True
# Merge any leaked attributes with their corresponding XML attributes to
# account for the situation where Doxygen has only half got it right
# (which, honestly, is most of the time)
func.is_explicit = func.is_explicit or element.attrib['explicit'] == 'yes'
func.is_virtual = func.is_virtual or element.attrib['virt'] != 'non-virtual'
func.is_static = func.is_static or element.attrib['static'] == 'yes'
if 'constexpr' in element.attrib:
func.is_constexpr = func.is_constexpr or element.attrib['constexpr'] == 'yes'
if 'consteval' in element.attrib:
func.is_consteval = func.is_consteval or element.attrib['consteval'] == 'yes'
if func.is_consteval:
func.is_constexpr = False # consteval trumps constexpr
func.prefix = ''
func.is_explicit = element.attrib['explicit'] == 'yes'
func.is_virtual = element.attrib['virt'] != 'non-virtual'
if element.attrib['static'] == 'yes':
if func.is_static:
func.prefix += 'static '
# Extract additional C++11 stuff from the signature. Order matters, going
# from the keywords that can be rightmost to the leftmost.
Expand Down
2 changes: 1 addition & 1 deletion documentation/templates/doxygen/details-func.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ <h3>
</div>
{% endif %}
{% set j = joiner(',\n ') %}
<span class="m-doc-wrap-bumper">{{ func.prefix }}{{ func.type }} {{ prefix }}</span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#{{ func.id }}" class="m-doc-self">{{ func.name }}</a>(</span><span class="m-doc-wrap">{% for param in func.params %}{{ j() }}{{ param.type_name }}{% if param.default %} = {{ param.default }}{% endif %}{% endfor %}){{ func.suffix }}{% if func.is_explicit %} <span class="m-label m-info">explicit</span> {% endif %}{% if func.is_final %} <span class="m-label m-warning">final</span>{% elif func.is_override %} <span class="m-label m-warning">override</span>{% elif func.is_pure_virtual %} <span class="m-label m-warning">pure virtual</span>{% elif func.is_virtual %} <span class="m-label m-warning">virtual</span>{% endif %}{% if func.is_protected %} <span class="m-label m-warning">protected{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_private %} <span class="m-label m-danger">private{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_signal %} <span class="m-label m-success">signal</span>{% elif func.is_slot %} <span class="m-label m-success">public slot</span>{% endif %}{% if func.is_defaulted %} <span class="m-label m-info">defaulted</span>{% endif %}{% if func.is_deleted %} <span class="m-label m-danger">deleted</span>{% endif %}{% if func.is_constexpr %} <span class="m-label m-primary">constexpr</span>{% endif %}{% if func.is_conditional_noexcept %} <span class="m-label m-success">noexcept(…)</span>{% elif func.is_noexcept %} <span class="m-label m-success">noexcept</span>{% endif %}{% if func.since %} {{ func.since }}{% endif %}</span></span>
<span class="m-doc-wrap-bumper">{{ func.prefix }}{{ func.type }} {{ prefix }}</span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#{{ func.id }}" class="m-doc-self">{{ func.name }}</a>(</span><span class="m-doc-wrap">{% for param in func.params %}{{ j() }}{{ param.type_name }}{% if param.default %} = {{ param.default }}{% endif %}{% endfor %}){{ func.suffix }}{% if func.is_explicit %} <span class="m-label m-info">explicit</span> {% endif %}{% if func.is_final %} <span class="m-label m-warning">final</span>{% elif func.is_override %} <span class="m-label m-warning">override</span>{% elif func.is_pure_virtual %} <span class="m-label m-warning">pure virtual</span>{% elif func.is_virtual %} <span class="m-label m-warning">virtual</span>{% endif %}{% if func.is_protected %} <span class="m-label m-warning">protected{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_private %} <span class="m-label m-danger">private{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_signal %} <span class="m-label m-success">signal</span>{% elif func.is_slot %} <span class="m-label m-success">public slot</span>{% endif %}{% if func.is_defaulted %} <span class="m-label m-info">defaulted</span>{% endif %}{% if func.is_deleted %} <span class="m-label m-danger">deleted</span>{% endif %}{% if func.is_consteval %} <span class="m-label m-primary">consteval</span>{% endif %}{% if func.is_constexpr %} <span class="m-label m-primary">constexpr</span>{% endif %}{% if func.is_conditional_noexcept %} <span class="m-label m-success">noexcept(…)</span>{% elif func.is_noexcept %} <span class="m-label m-success">noexcept</span>{% endif %}{% if func.since %} {{ func.since }}{% endif %}</span></span>
{% if func.include and compound.templates == None and func.templates == None %}
<div class="m-doc-include m-code m-inverted m-text-right"><span class="cp">#include</span> <a class="cpf" href="{{ func.include[1] }}">{{ func.include[0] }}</a></div>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion documentation/templates/doxygen/entry-func.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<div class="m-doc-template">template&lt;{% for t in func.templates %}{{ j() }}{{ t.type }}{% if t.name %} {{ t.name }}{% endif %}{% if t.default %} = {{ t.default }}{% endif %}{% endfor %}&gt;</div>
{% endif %}
{% set j = joiner(',\n ') %}
<span class="m-doc-wrap-bumper">{{ func.prefix }}{% if func.type == 'void' %}void {% elif func.type %}auto {% endif %}<a href="{% if func.base_url != compound.url %}{{ func.base_url }}{% endif %}#{{ func.id }}" class="m-doc{% if not func.has_details and func.base_url == compound.url %}-self{% endif %}">{{ func.name }}</a>(</span><span class="m-doc-wrap">{% for param in func.params %}{{ j() }}{{ param.type_name }}{% if param.default %} = {{ param.default }}{% endif %}{% endfor %}){{ func.suffix }}{% if func.type and func.type != 'void' %} -&gt; {{ func.type }}{% endif %}{% if func.deprecated %} <span class="m-label m-danger">{{ func.deprecated }}</span>{% endif %}{% if not func.type or mark_nonpublic %}{% if func.is_protected %} <span class="m-label m-flat m-warning">protected{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_private %} <span class="m-label m-flat m-danger">private{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_signal %} <span class="m-label m-flat m-success">signal</span>{% elif func.is_slot %} <span class="m-label m-flat m-success">public slot</span>{% endif %}{% endif %}{% if func.is_defaulted %} <span class="m-label m-flat m-info">defaulted</span>{% endif %}{% if func.is_deleted %} <span class="m-label m-flat m-danger">deleted</span>{% endif %}{% if func.is_explicit %} <span class="m-label m-flat m-info">explicit</span> {% endif %}{% if func.is_final %} <span class="m-label m-flat m-warning">final</span>{% elif func.is_override %} <span class="m-label m-flat m-warning">override</span>{% elif func.is_pure_virtual %} <span class="m-label m-flat m-warning">pure virtual</span>{% elif func.is_virtual %} <span class="m-label m-flat m-warning">virtual</span>{% endif %}{% if func.is_constexpr %} <span class="m-label m-flat m-primary">constexpr</span>{% endif %}{% if func.is_conditional_noexcept %} <span class="m-label m-flat m-success">noexcept(…)</span>{% elif func.is_noexcept %} <span class="m-label m-flat m-success">noexcept</span>{% endif %}{% if func.since %} {{ func.since }}{% endif %}</span>
<span class="m-doc-wrap-bumper">{{ func.prefix }}{% if func.type == 'void' %}void {% elif func.type %}auto {% endif %}<a href="{% if func.base_url != compound.url %}{{ func.base_url }}{% endif %}#{{ func.id }}" class="m-doc{% if not func.has_details and func.base_url == compound.url %}-self{% endif %}">{{ func.name }}</a>(</span><span class="m-doc-wrap">{% for param in func.params %}{{ j() }}{{ param.type_name }}{% if param.default %} = {{ param.default }}{% endif %}{% endfor %}){{ func.suffix }}{% if func.type and func.type != 'void' %} -&gt; {{ func.type }}{% endif %}{% if func.deprecated %} <span class="m-label m-danger">{{ func.deprecated }}</span>{% endif %}{% if not func.type or mark_nonpublic %}{% if func.is_protected %} <span class="m-label m-flat m-warning">protected{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_private %} <span class="m-label m-flat m-danger">private{% if func.is_slot %} slot{% endif %}</span>{% elif func.is_signal %} <span class="m-label m-flat m-success">signal</span>{% elif func.is_slot %} <span class="m-label m-flat m-success">public slot</span>{% endif %}{% endif %}{% if func.is_defaulted %} <span class="m-label m-flat m-info">defaulted</span>{% endif %}{% if func.is_deleted %} <span class="m-label m-flat m-danger">deleted</span>{% endif %}{% if func.is_explicit %} <span class="m-label m-flat m-info">explicit</span> {% endif %}{% if func.is_final %} <span class="m-label m-flat m-warning">final</span>{% elif func.is_override %} <span class="m-label m-flat m-warning">override</span>{% elif func.is_pure_virtual %} <span class="m-label m-flat m-warning">pure virtual</span>{% elif func.is_virtual %} <span class="m-label m-flat m-warning">virtual</span>{% endif %}{% if func.is_consteval %} <span class="m-label m-flat m-primary">consteval</span>{% endif %}{% if func.is_constexpr %} <span class="m-label m-flat m-primary">constexpr</span>{% endif %}{% if func.is_conditional_noexcept %} <span class="m-label m-flat m-success">noexcept(…)</span>{% elif func.is_noexcept %} <span class="m-label m-flat m-success">noexcept</span>{% endif %}{% if func.since %} {{ func.since }}{% endif %}</span>
</dt>
<dd>{{ func.brief }}</dd>
6 changes: 6 additions & 0 deletions documentation/test_doxygen/cpp_friends/File.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class Class {
/** @brief A friend function */
friend void friendFunction(int a, void* b);

/** @brief A 'hidden friend' operator */
friend bool operator==(const Class&, const Class&) noexcept;

/** @brief A constexpr 'hidden friend' operator */
friend constexpr bool operator!=(const Class&, const Class&) noexcept;

/** @{ @name Group with friend functions */

/** @brief A friend grouped function */
Expand Down
10 changes: 10 additions & 0 deletions documentation/test_doxygen/cpp_friends/classClass.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ <h2><a href="#friends">Friends</a></h2>
void* b)</span>
</dt>
<dd>A friend function.</dd>
<dt id="a065fe9c7fbe768fdb374fd1970a83576">
<span class="m-doc-wrap-bumper">auto <a href="#a065fe9c7fbe768fdb374fd1970a83576" class="m-doc-self">operator==</a>(</span><span class="m-doc-wrap">const <a href="classClass.html" class="m-doc">Class</a>&amp;,
const <a href="classClass.html" class="m-doc">Class</a>&amp;) -&gt; bool <span class="m-label m-flat m-success">noexcept</span></span>
</dt>
<dd>A &#x27;hidden friend&#x27; operator.</dd>
<dt id="ae506b77d3dfd4c00550bb3c4e2394e22">
<span class="m-doc-wrap-bumper">auto <a href="#ae506b77d3dfd4c00550bb3c4e2394e22" class="m-doc-self">operator!=</a>(</span><span class="m-doc-wrap">const <a href="classClass.html" class="m-doc">Class</a>&amp;,
const <a href="classClass.html" class="m-doc">Class</a>&amp;) -&gt; bool <span class="m-label m-flat m-primary">constexpr</span> <span class="m-label m-flat m-success">noexcept</span></span>
</dt>
<dd>A constexpr &#x27;hidden friend&#x27; operator.</dd>
</dl>
</section>
</div>
Expand Down
14 changes: 14 additions & 0 deletions documentation/test_doxygen/cpp_function_attributes/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,27 @@ struct Foo {
*/
constexpr static int constexprStaticFunction();

/**
* @brief Consteval before static
*
* Same as above, but for C++20's consteval.
*/
consteval static int constevalStaticFunction();

/**
* @brief Constexpr after static
*
* In this case, `static` is not in the return type. FFS.
*/
static constexpr int staticConstexprFunction();

/**
* @brief Consteval after static
*
* Same as above, but for C++20's consteval.
*/
static consteval int staticConstevalFunction();

/**
* @brief Combined default and noexcept
*
Expand Down
22 changes: 22 additions & 0 deletions documentation/test_doxygen/cpp_function_attributes/structFoo.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,18 @@ <h2><a href="#pub-static-methods">Public static functions</a></h2>
<span class="m-doc-wrap-bumper">static auto <a href="#a77f46786436a39eb3b53343580f41b89" class="m-doc">constexprStaticFunction</a>(</span><span class="m-doc-wrap">) -&gt; int <span class="m-label m-flat m-primary">constexpr</span></span>
</dt>
<dd>Constexpr before static.</dd>
<dt>
<span class="m-doc-wrap-bumper">static auto <a href="#a37fb9b85cc5a56110f12460a822112a9" class="m-doc">constevalStaticFunction</a>(</span><span class="m-doc-wrap">) -&gt; int <span class="m-label m-flat m-primary">consteval</span></span>
</dt>
<dd>Consteval before static.</dd>
<dt>
<span class="m-doc-wrap-bumper">static auto <a href="#a178c5f6f19fa2ffb1fc7a346e2e877d9" class="m-doc">staticConstexprFunction</a>(</span><span class="m-doc-wrap">) -&gt; int <span class="m-label m-flat m-primary">constexpr</span></span>
</dt>
<dd>Constexpr after static.</dd>
<dt>
<span class="m-doc-wrap-bumper">static auto <a href="#a26b977ac73c54993504324aae7a08e08" class="m-doc">staticConstevalFunction</a>(</span><span class="m-doc-wrap">) -&gt; int <span class="m-label m-flat m-primary">consteval</span></span>
</dt>
<dd>Consteval after static.</dd>
</dl>
</section>
<section id="typeless-methods">
Expand Down Expand Up @@ -91,13 +99,27 @@ <h3>
</h3>
<p>Constexpr before static.</p>
<p>1.8.18 puts both <code>constexpr</code> and <code>static</code> into the return type so I have to remove them. WHY! HOW IS THAT USEFUL IN ANY WAY?!</p>
</div></section>
<section class="m-doc-details" id="a37fb9b85cc5a56110f12460a822112a9"><div>
<h3>
<span class="m-doc-wrap-bumper">static int Foo::<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#a37fb9b85cc5a56110f12460a822112a9" class="m-doc-self">constevalStaticFunction</a>(</span><span class="m-doc-wrap">) <span class="m-label m-primary">consteval</span></span></span>
</h3>
<p>Consteval before static.</p>
<p>Same as above, but for C++20&#x27;s consteval.</p>
</div></section>
<section class="m-doc-details" id="a178c5f6f19fa2ffb1fc7a346e2e877d9"><div>
<h3>
<span class="m-doc-wrap-bumper">static int Foo::<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#a178c5f6f19fa2ffb1fc7a346e2e877d9" class="m-doc-self">staticConstexprFunction</a>(</span><span class="m-doc-wrap">) <span class="m-label m-primary">constexpr</span></span></span>
</h3>
<p>Constexpr after static.</p>
<p>In this case, <code>static</code> is not in the return type. FFS.</p>
</div></section>
<section class="m-doc-details" id="a26b977ac73c54993504324aae7a08e08"><div>
<h3>
<span class="m-doc-wrap-bumper">static int Foo::<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#a26b977ac73c54993504324aae7a08e08" class="m-doc-self">staticConstevalFunction</a>(</span><span class="m-doc-wrap">) <span class="m-label m-primary">consteval</span></span></span>
</h3>
<p>Consteval after static.</p>
<p>Same as above, but for C++20&#x27;s consteval.</p>
</div></section>
<section class="m-doc-details" id="ad5953d17211071264b501747c67e6fdc"><div>
<h3>
Expand Down

0 comments on commit 5b0691b

Please sign in to comment.