John Snow <[email protected]> writes:
> On Tue, Sep 29, 2026, 8:36 AM Markus Armbruster <[email protected]> wrote:
>
>> John Snow <[email protected]> writes:
>>
>> > Enforce the relative ordering of "Intro", "Members", "Features",
>> > "Errors" and "Returns" sections.
>> >
>> > Signed-off-by: John Snow <[email protected]>
>> > ---
>> > docs/devel/qapi-code-gen.rst | 19 +++++++++++------
>> > scripts/qapi/parser.py | 41 ++++++++++++++++++++++++++++++------
>> > 2 files changed, 48 insertions(+), 12 deletions(-)
>> >
>> > diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst
>> > index 3ac1c2dae18..29e9c5a3da6 100644
>> > --- a/docs/devel/qapi-code-gen.rst
>> > +++ b/docs/devel/qapi-code-gen.rst
>> > @@ -985,12 +985,19 @@ When documentation is required (see pragma_
>> 'doc-required'), every
>> > definition must have documentation.
>> >
>> > Definition documentation starts with a description naming the
>> > -definition with an optional indented overview (the "intro"),
>> > -:ref:`descriptions <doc-descrip>` of each argument (for commands and
>> > -events), member (for structs and unions), branch (for alternates), or
>> > -value (for enums), :ref:`feature descriptions <doc-feat>` of each
>> > -feature (if any), and finally optional :ref:`tagged sections
>> > -<doc-tagged>`.
>> > +definition with an optional indented overview (the "intro").
>> > +
>> > +The introduction is followed by :ref:`descriptions <doc-descrip>` of
>> > +each argument (for commands and events), member (for structs and
>> > +unions), branch (for alternates), or value (for enums).
>> > +
>> > +:ref:`Tagged sections <doc-tagged>` follow in order: Returns (for
>> > +commands), Errors (for commands), and lastly :ref:`feature
>> > +descriptions <doc-feat>` if applicable.
>>
>> *Some* of the tagged sections do.
>>
>> Does this document still need the concept "tagged section"?
>>
>
> I doubt it does, beyond an explanation of common syntax. Happy to hear
> suggestions...
I'll keep it in mind when I review PATCH 02.
> We could also stand to change some of the method names like "ensure
> untagged section" could become "ensure details", and the like.
Fair!
> tagged vs untagged has become increasingly non-meaningful.
True.
>> > +
>> > +Definition documentation concludes with rST-formatted detail
>> > +paragraphs (including examples, notes, &c), and finally an optional
>> > +"Since" tagged section at the very end.
>> >
>> > .. _doc-descrip:
>> >
>> > diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
>> > index fe284a3f866..94a60a23482 100644
>> > --- a/scripts/qapi/parser.py
>> > +++ b/scripts/qapi/parser.py
>> > @@ -545,6 +545,28 @@ def get_doc(self) -> 'QAPIDoc':
>> > doc.all_sections.append(QAPIDoc.Section(info,
>> QAPIDoc.Kind.INTRO))
>> > line = self.get_doc_indented(doc)
>> > no_more_args = False
>> > + last_ordered_section = QAPIDoc.Kind.INTRO
>> > +
>> > + def _tag_check(this: Union['QAPIDoc.Kind', str]) -> None:
>> > + nonlocal last_ordered_section
>> > + if isinstance(this, str):
>> > + this = QAPIDoc.Kind.from_string(this)
>>
>> Simpler, I think: require a .Kind argument, and do the conversion from
>> string in the one caller that needs it.
>>
>
> You're right.
>
>
>> > +
>> > + if this in (QAPIDoc.Kind.TODO, QAPIDoc.Kind.SINCE,
>> > + QAPIDoc.Kind.DETAILS):
>> > + return
>> > +
>> > + if this.value < last_ordered_section.value:
>> > + raise QAPIParseError(
>> > + self,
>> > + f"'{this}' section cannot appear after "
>> > + f"'{last_ordered_section}' section, please
>> re-order "
>> > + "the sections and adjust phrasing as necessary
>> to "
>> > + "ensure consistent documentation flow between
>> the "
>> > + "source code and the rendered HTML manual"
>> > + )
>>
>> That's a loooong error message. It actually consists of the error
>> proper and advice on how to correct it. Please use
>>
>> "<error>\n<advice>"
>>
>> like we do in expr.py's check_keys(), parser.py's
>> ._validate_doc_line_format(), and schema.py's ._def_definition(). Yes,
>> the embedded newline is a bit of a hack.
>>
>
> ACK.
>
>
>> > +
>> > + last_ordered_section = this
>> >
>> > while line is not None:
>> > # Blank lines
>> > @@ -558,6 +580,7 @@ def get_doc(self) -> 'QAPIDoc':
>> > if doc.has_features:
>> > raise QAPIParseError(
>> > self, "duplicated 'Features:' line")
>> > + _tag_check(QAPIDoc.Kind.FEATURE)
>> > self.accept(False)
>> > line = self.get_doc_line()
>> > while line == '':
>> > @@ -581,6 +604,7 @@ def get_doc(self) -> 'QAPIDoc':
>> > self,
>> > "description of '@%s:' follows a section"
>> > % match.group(1))
>> > + _tag_check(QAPIDoc.Kind.MEMBER)
>> > while (line is not None
>> > and (match :=
>> self._match_at_name_colon(line))):
>> > doc.new_argument(self.info, match.group(1))
>> > @@ -621,6 +645,7 @@ def get_doc(self) -> 'QAPIDoc':
>> > )
>> > raise QAPIParseError(self, emsg)
>> >
>> > + _tag_check(match.group(1))
>>
>> This is the caller that needs it.
>>
>
> Yep.
>
>
>> > doc.new_tagged_section(
>> > self.info,
>> > QAPIDoc.Kind.from_string(match.group(1))
>> > @@ -632,6 +657,7 @@ def get_doc(self) -> 'QAPIDoc':
>> > no_more_args = True
>> > else:
>> > # plain paragraph(s)
>> > + _tag_check(QAPIDoc.Kind.DETAILS)
>> > doc.ensure_untagged_section(self.info)
>> > doc.append_line(line)
>> > line = self.get_doc_paragraph(doc)
>> > @@ -674,14 +700,17 @@ class QAPIDoc:
>> > """
>> >
>> > class Kind(enum.Enum):
>> > - DETAILS = 0
>> > + # The order here is the order in which sections must appear in
>> > + # source code; with the exception of 'TODO' which may appear
>> > + # anywhere but is treated as a comment.
>> > + INTRO = 0
>> > MEMBER = 1
>> > - FEATURE = 2
>> > - RETURNS = 3
>> > - ERRORS = 4
>> > - SINCE = 5
>> > + RETURNS = 2
>> > + ERRORS = 3
>> > + FEATURE = 4
>> > + DETAILS = 5
>> > TODO = 6
>> > - INTRO = 7
>> > + SINCE = 7
>> >
>> > @staticmethod
>> > def from_string(kind: str) -> 'QAPIDoc.Kind':
>>
>> Missing: negative test for the new error.
>>
>
> Yep. If you are overall pleased with this change enough (pending nits),
> I'll add the tests now.
I am!