breathe-doc / breathe

ReStructuredText and Sphinx bridge to Doxygen
https://breathe-doc.org
Other
737 stars 199 forks source link

4.17.0 regression "WARNING: Duplicate declaration" #518

Open rouault opened 4 years ago

rouault commented 4 years ago

I've spotted a 4.16.0 -> 4.17.0 regression regarding a confusion when a enum class enumerator and a static variable share the same name

Minimum reproducer:

breathe_projects = { "doxygen_api": "xml/" }


* given index.rst with

.. doxygennamespace:: common :project: doxygen_api :members:



Run:
`printf "GENERATE_XML=YES\nGENERATE_HTML=NO\nINPUT=test.hpp\n"  | doxygen -` and then `sphinx-build -b html . ./html`

With 4.17.0, this emits ```index.rst:1: WARNING: Duplicate declaration, const Foo NONE```
vermeeren commented 4 years ago

@jakobandersen At a glance i would think this is a problem at the Sphinx side, can you share thoughts?

rouault commented 4 years ago

I've tried a pure Sphinx workflow with index.rst being just

.. cpp:class:: MyClass

    Bla

    .. cpp:enum-class:: MyEnum

        Bla

        .. cpp:enumerator:: NONE

            Bla

    .. cpp:var:: const MyClass NONE

        Bla

The above doesn't throw a warning. But if I replace .. cpp:enum-class:: MyEnum by .. cpp:enum:: MyEnum, I get a WARNING: Duplicate declaration, const MyClass NONE warning, which is probably expected since the correspond C++ code wouldn't compile. So I believe this might be a Breathe issue, in that it likely emits a .. cpp:enum:: directive for a enum class. From what I can see in the Doxygen output when comparing a enum vs enum class, the later has a strong="yes" attribute in its <memberdef kind="enum" ....> element whereas the former has strong="no". So I guess Breathe should be able to use that to emit the proper Sphinx directive

jakobandersen commented 4 years ago

I believe this is a Doxygen (and thereby Breathe) issue related to scoped vs. unscoped enumerations. When I run Doxygen (v1.8.13) I get the following XML for the enum:

      <memberdef kind="enum" id="classcommon_1_1Foo_1a2889a8a66c3032f6179d172ae80dcd21" prot="public" static="no">
        <name>Type</name>
        <enumvalue id="classcommon_1_1Foo_1a2889a8a66c3032f6179d172ae80dcd21ab50339a10e1de285ac99d4c3990b8693" prot="public">
          <name>NONE</name>
          <briefdescription>
          </briefdescription>
          <detaileddescription>
<para>Enum value </para>          </detaileddescription>
        </enumvalue>
        <briefdescription>
        </briefdescription>
        <detaileddescription>
<para>Enum Type </para>        </detaileddescription>
        <inbodydescription>
        </inbodydescription>
        <location file="test.hpp" line="7" column="1" bodyfile="test.hpp" bodystart="6" bodyend="10"/>
      </memberdef>

Maybe I missed it somewhere, or my Doxygen is too old, but basically the handling need to be done at https://github.com/michaeljones/breathe/blob/master/breathe/renderer/sphinxrenderer.py#L1453 where handle_declaration needs to get obj_type='enum-struct' or obj_type='enum-class' instead of the default obj_type=None if the enum is scoped.

As another note, for helping debugging this type of issue: In the beginning of sphinxrenderer.py there are 3 new variables for printing debug info (they should probably be available to set in a project configuration, but I didn't immediately find out how to do that when I added them). If you set debug_trace_directives = True you get info about the directives that are actually seen by the Sphinx domains, including nesting. In this case:

Running directive: .. cpp:type:: common
  Running directive: .. cpp:class::  Foo
    Running directive: .. cpp:enum:: Type
      Running directive: .. cpp:enumerator:: NONE
    Running directive: .. cpp:var::  const Foo NONE

so from that we can also see that it is not a direct clash of declarations, but very likely because Type::None is added to the parent scope due to the enum being unscoped. In Sphinx there is a similar debug variable to print the complete symbols trees (debug_show_tree), so for the fun of it, it gives:

::
  common: common    (index)
    Foo: Foo    (index)
      Type: Type    (index)
        NONE: NONE  (index)
      NONE: NONE    (index)
      NONE: !!duplicate!! const Foo NONE    (index)
rouault commented 4 years ago

Yes Doxygen version must matter. With 1.8.17, I get

      <memberdef kind="enum" id="classcommon_1_1Foo_1a2889a8a66c3032f6179d172ae80dcd21" prot="public" static="no" strong="yes">
        <type></type>
        <name>Type</name>
        <enumvalue id="classcommon_1_1Foo_1a2889a8a66c3032f6179d172ae80dcd21ab50339a10e1de285ac99d4c3990b8693" prot="public">
          <name>NONE</name>
          <briefdescription>
          </briefdescription>
          <detaileddescription>
<para>Enum value </para>
          </detaileddescription>
        </enumvalue>
        <briefdescription>
        </briefdescription>
        <detaileddescription>
<para>Enum Type </para>
        </detaileddescription>
        <inbodydescription>
        </inbodydescription>
        <location file="test.hpp" line="7" column="1" bodyfile="test.hpp" bodystart="6" bodyend="10"/>
      </memberdef>