chiark / gitweb /
doxygen: fix keywords leaking into return type
authorMark Gillard <marzer_@hotmail.com>
Sat, 30 Apr 2022 09:15:13 +0000 (12:15 +0300)
committerVladimír Vondruš <mosra@centrum.cz>
Fri, 13 May 2022 15:40:22 +0000 (17:40 +0200)
- fixes #225
- fixes #226

Also drive-by added support for C++20's `consteval` keyword.

doc/documentation/doxygen.rst
documentation/doxygen.py
documentation/templates/doxygen/details-func.html
documentation/templates/doxygen/entry-func.html
documentation/test_doxygen/cpp_friends/File.h
documentation/test_doxygen/cpp_friends/classClass.html
documentation/test_doxygen/cpp_function_attributes/input.h
documentation/test_doxygen/cpp_function_attributes/structFoo.html

index e935aec00bdd6fafa6bc365abd132a2870164a15..be7f858fd3ce08a5c396ac517d069997d524a599 100644 (file)
@@ -2171,6 +2171,7 @@ Property                            Description
 :py:`func.is_conditional_noexcept`  If the function is conditionally
                                     :cpp:`noexcept`.
 :py:`func.is_constexpr`             If the function is :cpp:`constexpr`
+:py:`func.is_consteval`             If the function is :cpp:`consteval`
 :py:`func.is_defaulted`             If the function is :cpp:`default`\ ed
 :py:`func.is_deleted`               If the function is :cpp:`delete`\ d
 :py:`func.is_signal`                If the function is a Qt signal. Set only
index 5fa5d6e68d36a07b47fcc19eaacd2389718cb566..39c3a7ae42be2563d352a4d2cfc896915e989b34 100755 (executable)
@@ -1992,39 +1992,62 @@ 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!)
+    #
+    # (Note that since 1.8.16 the keyword/type ordering has not been a problem,
+    # but this handling is left as a future-proofing mechanism.)
+    exposed_attribute_keywords = [
+        'constexpr',
+        'consteval',
+        'explicit',
+        'virtual'
+    ]
+    ignored_attribute_keywords = [
+        'static', # Included in func.prefix already
+        'friend',
+        'inline'
+    ]
+    for kw in exposed_attribute_keywords:
+        setattr(func, 'is_' + kw, False)
+    is_static = False
+    matched_bad_keyword = True
+    while matched_bad_keyword:
+        matched_bad_keyword = False
+        for kw in exposed_attribute_keywords + ignored_attribute_keywords:
+            if func.type == kw: # constructors
+                func.type = ''
+            elif func.type.startswith(kw + ' '):
+                func.type = func.type[len(kw):].strip()
+            elif func.type.endswith(' ' + kw):
+                func.type = func.type[:len(kw)].strip()
+            else:
+                continue
+            matched_bad_keyword = True
+            if kw in exposed_attribute_keywords:
+                setattr(func, 'is_' + kw, True)
+            elif kw == 'static':
+                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'
+    is_static = 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'
     func.prefix = ''
-    func.is_explicit = element.attrib['explicit'] == 'yes'
-    func.is_virtual = element.attrib['virt'] != 'non-virtual'
-    if element.attrib['static'] == 'yes':
+    if 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.
index 5044dc95eb3b68564b0ff4c67c1f7993f0974a2c..fba88ccdbcaeca12c686baf29bd3bfd00322608f 100644 (file)
@@ -16,7 +16,7 @@
               </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>{% elif 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 %}
index 0738fe904d51cebaf6d020e07b43edc777879ce4..263cbaf3850c6f7e9fcb8341a4cb4e741efee11e 100644 (file)
@@ -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>{% elif 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>
index 1b84596adb123e1b03121321714b21cacca4c2b9..c62cb46d793c53e0ab5d0bef3dcfd48ed081bf3b 100644 (file)
@@ -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 */
index 6bd6585236dc5866c9d398adf7b4eaefdd407bcb..4bf946b1ac353fecdd84ec6b409bf6303b6548f9 100644 (file)
               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>
index cf4472b11d16652d9f21eb4044605d66a91f917a..056ffa7f1dc22b6b38baaa99560ae9abd59f86d8 100644 (file)
@@ -10,6 +10,13 @@ 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
      *
@@ -17,6 +24,13 @@ struct Foo {
      */
     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
      *
index 8241d92ade3fc884c1ff51133329edc28418cb96..2701acd505cb6c512849b2bb1c7a205b055f4a18 100644 (file)
               <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">
             </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>
             </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>