From: John Snow <jsnow@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Ani Sinha" <anisinha@redhat.com>,
"Michael Roth" <michael.roth@amd.com>,
"Igor Mammedov" <imammedo@redhat.com>,
"John Snow" <jsnow@redhat.com>,
"Peter Maydell" <peter.maydell@linaro.org>,
"Eric Blake" <eblake@redhat.com>,
"Philippe Mathieu-Daudé" <philmd@mailo.com>,
"Mauro Carvalho Chehab" <mchehab+huawei@kernel.org>,
"Michael S. Tsirkin" <mst@redhat.com>,
"Markus Armbruster" <armbru@redhat.com>,
"Marc-André Lureau" <marcandre.lureau@redhat.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Pierrick Bouvier" <pierrick.bouvier@oss.qualcomm.com>,
"Richard Henderson" <richard.henderson@linaro.org>,
"Gerd Hoffmann" <kraxel@redhat.com>,
"Philippe Mathieu-Daudé" <philmd@linaro.org>,
linux-edac@vger.kernel.org, "Cleber Rosa" <crosa@redhat.com>
Subject: [PATCH v3 09/16] qapi/docs: adjust stub member insertion algorithm
Date: Tue, 2 Jun 2026 23:21:54 -0400 [thread overview]
Message-ID: <20260603032201.993015-10-jsnow@redhat.com> (raw)
In-Reply-To: <20260603032201.993015-1-jsnow@redhat.com>
A forthcoming patch removes the implicit PLAIN section that always
starts a QAPIDoc section list. Further future changes begin converting
"PLAIN" sections to "INTRO" sections. To accommodate this, the
insertion algorithm that places stub and dummy members must be
adjusted to cope with not only the finished state, but temporary
intermediate states while the series is merged.
This algorithm can handle zero-or-more PLAIN *or* INTRO sections at
the beginning of a QAPIDoc object, in contrast to the previous
algorithm which assumed and relied upon there being always one PLAIN
section at the beginning of every QAPIDoc section list.
In other words: (PLAIN | INTRO)* <EverythingElse>
This does not impact what the parser itself will actually produce. As
of this patch, the parser will still always generate QAPIDoc section
lists that start with precisely one PLAIN section (whether or not it
is empty), followed by the remaining sections. Those remaining
sections may or may not include additional PLAIN sections, but never
two such sections contiguously as the parser will always treat that
layout as one PLAIN section consisting of multiple paragraph(s).
In other other words: This insertion algorithm is more lenient than
the parser, but this is on purpose for flexibility mid-stream as we
convert QAPI to using explicit introductory sections. The allowed
order of sections will eventually become strictly enforced in the
parser, which will in turn allow dramatic simplifications to the
insertion algorithm. This only exists as transitory code until we are
able to enforce that order.
Fear not: the intermediate rest output before and after this patch
are byte identical, so failing all else, we at least know it doesn't
make anything worse.
Lastly, because we have three places in the code that need to insert
stub/dummy sections, we take the opportunity to consolidate this code
to handle all three cases with one function. This winds up
necessitating the qapidoc.py generator actually modify the section
list to insert a "dummy" member that acts as a placeholder for "The
members of ..." text. While it looks like a code smell to modify the
caller's argument, it is ultimately safe because the QAPI Schema
object is re-parsed and re-constructed in memory for each individual
process that needs to operate on it. In other words, the Sphinx
document generator already does have "its own copy" of the section
lists, so it is "safe" to modify here without regards to other
consumers of the QAPIDoc objects. It only *looks* like it smells
bad. Ultimately, this code will also be removed once the inliner is
merged, so it is only a temporary aesthetic issue regardless.
That's my story and I'm sticking to it.
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/sphinx/qapidoc.py | 42 ++++++++++++++++--------------
scripts/qapi/parser.py | 58 +++++++++++++++++++++++++++++-------------
2 files changed, 64 insertions(+), 36 deletions(-)
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 1f7c15b7075..16ad15fe94f 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -349,30 +349,38 @@ def _get_target(
)
def visit_sections(self, ent: QAPISchemaDefinition) -> None:
+ # Generate a placeholder right after the member section(s) which
+ # may be used to generate documentation for "The members of..."
+ # pointers in the rendered document.
+ #
+ # This is a temporary hack until the inliner is merged. Note
+ # that although we modify the caller's section list, the
+ # Sphinx document generator has its own copy of the parsed
+ # schema in memory, so this action does not interfere with
+ # other users of the QAPISchema or QAPIDoc objects outside of
+ # the document generator. Fishy, but not harmful.
+ if ent.doc:
+ ent.doc.append_member_stub(
+ QAPIDoc.ArgSection(
+ ent.doc.info, QAPIDoc.Kind.MEMBER, "q_dummy"
+ )
+ )
+
sections = ent.doc.all_sections if ent.doc else []
- # Determine the index location at which we should generate
- # documentation for "The members of ..." pointers. This should
- # go at the end of the members section(s) if any. Note that
- # index 0 is assumed to be a plain intro section, even if it is
- # empty; and that a members section if present will always
- # immediately follow the opening PLAIN section.
- gen_index = 1
- if len(sections) > 1:
- while sections[gen_index].kind == QAPIDoc.Kind.MEMBER:
- gen_index += 1
- if gen_index >= len(sections):
- break
-
# Add sections in source order:
- for i, section in enumerate(sections):
+ for section in sections:
section.text = self.reformat_arobase(section.text)
if section.kind.name in ("PLAIN", "INTRO"):
self.visit_paragraph(section)
elif section.kind == QAPIDoc.Kind.MEMBER:
assert isinstance(section, QAPIDoc.ArgSection)
- self.visit_member(section)
+ if section.name == "q_dummy":
+ # Generate "The members of ..." entries if necessary
+ self._insert_member_pointer(ent)
+ else:
+ self.visit_member(section)
elif section.kind == QAPIDoc.Kind.FEATURE:
assert isinstance(section, QAPIDoc.ArgSection)
self.visit_feature(section)
@@ -386,10 +394,6 @@ def visit_sections(self, ent: QAPISchemaDefinition) -> None:
else:
assert False
- # Generate "The members of ..." entries if necessary:
- if i == gen_index - 1:
- self._insert_member_pointer(ent)
-
self.ensure_blank_line()
# Transmogrification core methods
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 261f8ba9f8b..406edf6aa9d 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -832,6 +832,42 @@ def _insert_near_kind(
return True
return False
+ def _insert_after_intro(
+ self,
+ section: 'QAPIDoc.Section',
+ ) -> None:
+ """
+ Insert a section immediately after the intro section.
+
+ While we convert PLAIN sections to INTRO sections, all
+ contiguous INTRO/PLAIN sections at the start of a QAPIDoc
+ section list are treated as "the intro".
+
+ Once INTRO conversion is complete, this helper will no longer be
+ needed and ``_insert_near_kind(QAPIDoc.Kind.INTRO, ...)`` will
+ be sufficient.
+ """
+ index = 0
+ for index, ref_section in enumerate(self.all_sections):
+ if ref_section.kind.name in ("PLAIN", "INTRO"):
+ continue
+ break
+ else:
+ index += 1
+
+ self.all_sections.insert(index, section)
+
+ def append_member_stub(self, stub: 'QAPIDoc.Section') -> None:
+
+ """
+ Append a stub section after any Member sections.
+ """
+ if self._insert_near_kind(QAPIDoc.Kind.MEMBER, stub, True):
+ return
+
+ # No MEMBER sections present. Insert after INTRO/PLAIN sections.
+ self._insert_after_intro(stub)
+
def connect_member(self, member: 'QAPISchemaMember') -> None:
if member.name not in self._args:
assert member.info
@@ -841,20 +877,10 @@ def connect_member(self, member: 'QAPISchemaMember') -> None:
% (member.role, member.name))
# Insert stub documentation section for missing member docs.
# TODO: drop when undocumented members are outlawed
-
- section = QAPIDoc.ArgSection(
+ stub_section = QAPIDoc.ArgSection(
self.info, QAPIDoc.Kind.MEMBER, member.name)
- self._args[member.name] = section
-
- # Determine where to insert stub doc - it should go at the
- # end of the members section(s), if any. Note that index 0
- # is assumed to be an untagged intro section, even if it is
- # empty.
- index = 1
- if len(self.all_sections) > 1:
- while self.all_sections[index].kind == QAPIDoc.Kind.MEMBER:
- index += 1
- self.all_sections.insert(index, section)
+ self._args[member.name] = stub_section
+ self.append_member_stub(stub_section)
self._args[member.name].connect(member)
@@ -882,10 +908,8 @@ def ensure_returns(self, info: QAPISourceInfo) -> None:
)):
return
- # Otherwise, it should go right after the intro. The intro
- # is always the first section and is always present (even
- # when empty), so we can insert directly at index=1 blindly.
- self.all_sections.insert(1, stub)
+ # Otherwise, it should go right after the intro.
+ self._insert_after_intro(stub)
def check_expr(self, expr: QAPIExpression) -> None:
if 'command' in expr:
--
2.54.0
next prev parent reply other threads:[~2026-06-03 3:22 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-03 3:21 [PATCH v3 00/16] qapi: add formal "intro" section John Snow
2026-06-03 3:21 ` [PATCH v3 01/16] python: temporarily restrict max mypy version John Snow
2026-06-03 3:21 ` [PATCH v3 02/16] tests/qapi: generate output in source order John Snow
2026-06-03 3:21 ` [PATCH v3 03/16] qapi/docs: remove unused QAPIDoc subsection members John Snow
2026-06-03 3:21 ` [PATCH v3 04/16] qapi/docs: add has_features property John Snow
2026-06-03 3:21 ` [PATCH v3 05/16] qapi/docs: make remaining subsection members "private" John Snow
2026-06-03 3:21 ` [PATCH v3 06/16] qapi/docs: fix comment phrasing John Snow
2026-06-03 3:21 ` [PATCH v3 07/16] qapi/docs: add "Intro" section John Snow
2026-06-03 3:21 ` [PATCH v3 08/16] qapi/parser: move _insert_near_kind() method John Snow
2026-06-03 3:21 ` John Snow [this message]
2026-06-03 11:27 ` [PATCH v3 09/16] qapi/docs: adjust stub member insertion algorithm Markus Armbruster
2026-06-03 3:21 ` [PATCH v3 10/16] qapi/docs: remove implicit Plain section John Snow
2026-06-03 3:21 ` [PATCH v3 11/16] qapi/docs: add rendering for INTRO sections John Snow
2026-06-03 3:21 ` [PATCH v3 12/16] qapi/docs: add "Intro" section parsing John Snow
2026-06-03 3:21 ` [PATCH v3 13/16] qapi: convert intro sections for accelerator.json John Snow
2026-06-03 3:21 ` [PATCH v3 14/16] qapi: convert intro sections for acpi-hest.json John Snow
2026-06-03 3:22 ` [PATCH v3 15/16] qapi: convert intro sections for acpi.json John Snow
2026-06-03 3:22 ` [PATCH v3 16/16] qapi: convert intro sections for audio.json John Snow
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260603032201.993015-10-jsnow@redhat.com \
--to=jsnow@redhat.com \
--cc=anisinha@redhat.com \
--cc=armbru@redhat.com \
--cc=crosa@redhat.com \
--cc=eblake@redhat.com \
--cc=imammedo@redhat.com \
--cc=kraxel@redhat.com \
--cc=linux-edac@vger.kernel.org \
--cc=marcandre.lureau@redhat.com \
--cc=mchehab+huawei@kernel.org \
--cc=michael.roth@amd.com \
--cc=mst@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@linaro.org \
--cc=philmd@mailo.com \
--cc=pierrick.bouvier@oss.qualcomm.com \
--cc=qemu-devel@nongnu.org \
--cc=richard.henderson@linaro.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox