* [PATCH resend 0/3] scripts/qapi: non-Rust-specific preparatory patches
@ 2026-07-24 13:00 Paolo Bonzini
2026-07-24 13:00 ` [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional Paolo Bonzini
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Paolo Bonzini @ 2026-07-24 13:00 UTC (permalink / raw)
To: qemu-devel; +Cc: armbru
This series include a few small QAPI patches extracted out of the Rust
series.
The first two tighten the language in ways that are necessary for Rust (the
structs would not be representable) but probably a good idea in general.
The last one moves C-specific knowledge out of functions with a generic
name.
Paolo
Paolo Bonzini (3):
scripts/qapi: enum with conditional first item must be optional
scripts/qapi: reject empty enums
scripts/qapi: pull c_name and lstrip from camel_to_upper to caller
scripts/qapi/common.py | 4 ++--
scripts/qapi/schema.py | 12 ++++++++++++
tests/qapi-schema/doc-good.json | 16 ++++++++--------
tests/qapi-schema/doc-good.out | 12 ++++++------
tests/qapi-schema/doc-good.txt | 8 ++++----
tests/qapi-schema/enum-empty.err | 2 ++
tests/qapi-schema/enum-empty.json | 2 ++
tests/qapi-schema/enum-empty.out | 0
tests/qapi-schema/enum-if-first-required.err | 2 ++
tests/qapi-schema/enum-if-first-required.json | 6 ++++++
tests/qapi-schema/enum-if-first-required.out | 0
tests/qapi-schema/meson.build | 2 ++
tests/qapi-schema/qapi-schema-test.json | 3 ---
tests/qapi-schema/qapi-schema-test.out | 1 -
tests/qapi-schema/union-empty-if.err | 2 ++
tests/qapi-schema/union-empty-if.json | 6 ++++++
tests/qapi-schema/union-empty-if.out | 0
tests/qapi-schema/union-empty.err | 4 ++--
18 files changed, 56 insertions(+), 26 deletions(-)
create mode 100644 tests/qapi-schema/enum-empty.err
create mode 100644 tests/qapi-schema/enum-empty.json
create mode 100644 tests/qapi-schema/enum-empty.out
create mode 100644 tests/qapi-schema/enum-if-first-required.err
create mode 100644 tests/qapi-schema/enum-if-first-required.json
create mode 100644 tests/qapi-schema/enum-if-first-required.out
create mode 100644 tests/qapi-schema/union-empty-if.err
create mode 100644 tests/qapi-schema/union-empty-if.json
create mode 100644 tests/qapi-schema/union-empty-if.out
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-07-24 13:00 [PATCH resend 0/3] scripts/qapi: non-Rust-specific preparatory patches Paolo Bonzini @ 2026-07-24 13:00 ` Paolo Bonzini 2026-07-25 6:15 ` Markus Armbruster 2026-07-24 13:00 ` [PATCH 2/3] scripts/qapi: reject empty enums Paolo Bonzini 2026-07-24 13:00 ` [PATCH 3/3] scripts/qapi: pull c_name and lstrip from camel_to_upper to caller Paolo Bonzini 2 siblings, 1 reply; 13+ messages in thread From: Paolo Bonzini @ 2026-07-24 13:00 UTC (permalink / raw) To: qemu-devel; +Cc: armbru Prevent an all-zero struct from having different meanings with different configurations or builds of QEMU. This needs some changes to doc-good.json, which used unwittingly such an enum. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- scripts/qapi/schema.py | 8 ++++++++ tests/qapi-schema/doc-good.json | 16 ++++++++-------- tests/qapi-schema/doc-good.out | 12 ++++++------ tests/qapi-schema/doc-good.txt | 8 ++++---- tests/qapi-schema/enum-if-first-required.err | 2 ++ tests/qapi-schema/enum-if-first-required.json | 6 ++++++ tests/qapi-schema/enum-if-first-required.out | 0 tests/qapi-schema/meson.build | 1 + 8 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 tests/qapi-schema/enum-if-first-required.err create mode 100644 tests/qapi-schema/enum-if-first-required.json create mode 100644 tests/qapi-schema/enum-if-first-required.out diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 8d88b40de2e..71296b15de2 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -963,6 +963,14 @@ def check(self, schema: QAPISchema) -> None: assert self.defined_in self.type = schema.resolve_type(self._type_name, self.info, self.describe) + if (not self.optional + and isinstance(self.type, QAPISchemaEnumType) + and self.type.members[0].ifcond.is_present()): + raise QAPISemError( + self.info, + "enum type '%s' of %s has a conditional first value" + " and must be optional" + % (self.type.name, self.describe(self.info))) seen: Dict[str, QAPISchemaMember] = {} for f in self.features: f.check_clash(self.info, seen) diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json index fac13425b72..76521ffe9e6 100644 --- a/tests/qapi-schema/doc-good.json +++ b/tests/qapi-schema/doc-good.json @@ -73,12 +73,12 @@ # @enum-feat: Also _one_ {and only} # @enum-member-feat: a member feature # -# @two is undocumented +# @zero is undocumented ## { 'enum': 'Enum', - 'data': [ { 'name': 'one', 'if': 'IFONE', - 'features': [ 'enum-member-feat' ] }, - 'two' ], + 'data': [ 'zero', + { 'name': 'one', 'if': 'IFONE', + 'features': [ 'enum-member-feat' ] } ], 'features': [ 'enum-feat' ], 'if': 'IFCOND' } @@ -112,10 +112,10 @@ 'if': 'IFSTR' } } } ## -# @Variant2: +# @Variant0: # ## -{ 'struct': 'Variant2', 'data': {} } +{ 'struct': 'Variant0', 'data': {} } ## # @Object: @@ -128,8 +128,8 @@ 'base': 'Base', 'discriminator': 'base1', 'data': { 'one': 'Variant1', - 'two': { 'type': 'Variant2', - 'if': { 'any': ['IFONE', 'IFTWO'] } } } } + 'zero': { 'type': 'Variant0', + 'if': { 'any': ['IFONE', 'IFTWO'] } } } } ## # @Alternate: diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index 371dd25ffc7..2782f6b57d9 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -10,10 +10,10 @@ enum QType member qbool module doc-good.json enum Enum + member zero member one if IFONE feature enum-member-feat - member two if IFCOND feature enum-feat object Base @@ -24,12 +24,12 @@ object Variant1 if IFSTR feature member-feat feature variant1-feat -object Variant2 +object Variant0 object Object base Base tag base1 case one: Variant1 - case two: Variant2 + case zero: Variant0 if {'any': ['IFONE', 'IFTWO']} feature union-feat1 alternate Alternate @@ -110,14 +110,14 @@ doc symbol=Enum Member=one The _one_ {and only}, description on the same line - Member=two + Member=zero Feature=enum-feat Also _one_ {and only} Feature=enum-member-feat a member feature Plain -@two is undocumented +@zero is undocumented doc symbol=Base Intro @@ -139,7 +139,7 @@ Another paragraph a feature Feature=member-feat a member feature -doc symbol=Variant2 +doc symbol=Variant0 Intro doc symbol=Object diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt index 74b73681d32..922a61dcf23 100644 --- a/tests/qapi-schema/doc-good.txt +++ b/tests/qapi-schema/doc-good.txt @@ -43,14 +43,14 @@ Enum Enum Values: * **one** -- The _one_ {and only}, description on the same line - * **two** -- Not documented + * **zero** -- Not documented Features: * **enum-feat** -- Also _one_ {and only} * **enum-member-feat** -- a member feature - "two" is undocumented + "zero" is undocumented Object Base *Availability*: "IFALL1 and IFALL2" @@ -75,7 +75,7 @@ Object Variant1 * **member-feat** -- a member feature -Object Variant2 +Object Variant0 Object Object @@ -84,7 +84,7 @@ Object Object * When "base1" is "one": The members of "Variant1". - * When "base1" is "two": The members of "Variant2". + * When "base1" is "zero": The members of "Variant0". Features: * **union-feat1** -- a feature diff --git a/tests/qapi-schema/enum-if-first-required.err b/tests/qapi-schema/enum-if-first-required.err new file mode 100644 index 00000000000..6d8bdcf2507 --- /dev/null +++ b/tests/qapi-schema/enum-if-first-required.err @@ -0,0 +1,2 @@ +enum-if-first-required.json: In struct 'TestStruct': +enum-if-first-required.json:5: enum type 'TestEnum' of member 'field' has a conditional first value and must be optional diff --git a/tests/qapi-schema/enum-if-first-required.json b/tests/qapi-schema/enum-if-first-required.json new file mode 100644 index 00000000000..1769b5fdef9 --- /dev/null +++ b/tests/qapi-schema/enum-if-first-required.json @@ -0,0 +1,6 @@ +# Enum with conditional first value cannot be used in required fields +{ 'enum': 'TestEnum', + 'data': [ { 'name': 'member1', 'if': 'CONFIG_FOO' }, + 'member2' ] } +{ 'struct': 'TestStruct', + 'data': { 'field': 'TestEnum' } } diff --git a/tests/qapi-schema/enum-if-first-required.out b/tests/qapi-schema/enum-if-first-required.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/qapi-schema/meson.build b/tests/qapi-schema/meson.build index debff633ac1..3b0c16a8b67 100644 --- a/tests/qapi-schema/meson.build +++ b/tests/qapi-schema/meson.build @@ -97,6 +97,7 @@ schemas = [ 'enum-bad-prefix.json', 'enum-clash-member.json', 'enum-dict-member-unknown.json', + 'enum-if-first-required.json', 'enum-if-invalid.json', 'enum-int-member.json', 'enum-member-case.json', -- 2.55.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-07-24 13:00 ` [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional Paolo Bonzini @ 2026-07-25 6:15 ` Markus Armbruster 2026-07-25 12:20 ` Paolo Bonzini 0 siblings, 1 reply; 13+ messages in thread From: Markus Armbruster @ 2026-07-25 6:15 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel Paolo Bonzini <pbonzini@redhat.com> writes: > Prevent an all-zero struct from having different meanings with > different configurations or builds of QEMU. This is a bit terse. What *exactly* is broken? Spelling this out matters, because it can expose holes in the argument, if any. Let me try. The numeric encoding of enum values is irrelevant except for zero, because we actually use numeric zero via zero initialization. If the enum's first value is conditional, the meaning of zero depends on build configuration. We don't want such a default at the external interface. It could conceivably lead to purely internal bugs, too. However, I'm not sure your solution fixes this problem completely. Consider type Arg with a mandatory member @mand and an optional enum member @opt, both of enum type ENUM_TYPE. QMP input gets converted to native C like this: if (!visit_type_Arg(v, NULL, &arg, errp)) { return; } For members present in the input, there is no zero initialization problem. @mand is always present, so arg.mand is whatever the user said. If @opt is present, arg.has_opt is true and arg.opt is whatever the user said. If @opt is absent, then arg.has_opt and arg.opt are both zero. Code providing an explicit default then is still fine: if (!arg.has_opt) { arg.opt = ENUM_TYPE_MUMBLE; } But we often use arg.opt without checking arg.has_opt for brevity. This is an implicit default to zero, whatever zero may mean. If the the optional enum's first member is conditional, this default depends on build configuration. Stupidest solution that could possibly work: an enum's first member cannot be conditional. Thoughts? > This needs some changes to doc-good.json, which used unwittingly > such an enum. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > scripts/qapi/schema.py | 8 ++++++++ > tests/qapi-schema/doc-good.json | 16 ++++++++-------- > tests/qapi-schema/doc-good.out | 12 ++++++------ > tests/qapi-schema/doc-good.txt | 8 ++++---- > tests/qapi-schema/enum-if-first-required.err | 2 ++ > tests/qapi-schema/enum-if-first-required.json | 6 ++++++ > tests/qapi-schema/enum-if-first-required.out | 0 > tests/qapi-schema/meson.build | 1 + > 8 files changed, 35 insertions(+), 18 deletions(-) > create mode 100644 tests/qapi-schema/enum-if-first-required.err > create mode 100644 tests/qapi-schema/enum-if-first-required.json > create mode 100644 tests/qapi-schema/enum-if-first-required.out Thanks for the negative test. docs/devel/qapi-code-gen.rst section "Enumeration types" could perhaps use an update. It's less than clear even before the patch: The generated C enumeration constants have values 0, 1, ..., N-1 (in QAPI schema order), where N is the number of values. There is an additional enumeration constant PREFIX__MAX with value N. Do not use string or an integer type when an enumeration type can do the job satisfactorily. The optional 'if' member specifies a conditional. See `Configuring the schema`_ below for more on this. The first paragraph can lead readers to assume we generate something like typedef enum Example { EXAMPLE_FOO = 0, #if defined(COND) EXAMPLE_BAR = 1, #endif EXAMPLE__MAX = 2, } Example; We don't, because we'd have to deal with a "hole" in the value range when COND is not defined. Instead, we do typedef enum Example { EXAMPLE_FOO, #if defined(COND) EXAMPLE_BAR, #endif EXAMPLE__MAX, } Example; ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-07-25 6:15 ` Markus Armbruster @ 2026-07-25 12:20 ` Paolo Bonzini 2026-07-27 9:32 ` Markus Armbruster 0 siblings, 1 reply; 13+ messages in thread From: Paolo Bonzini @ 2026-07-25 12:20 UTC (permalink / raw) To: Markus Armbruster; +Cc: qemu-devel [-- Attachment #1: Type: text/plain, Size: 4511 bytes --] Il sab 25 lug 2026, 08:15 Markus Armbruster <armbru@redhat.com> ha scritto: > Paolo Bonzini <pbonzini@redhat.com> writes: > > > Prevent an all-zero struct from having different meanings with > > different configurations or builds of QEMU. > > This is a bit terse. What *exactly* is broken? Spelling this out > matters, because it can expose holes in the argument, if any. Nothing is broken; it's just something that can have unexpected consequences... The numeric encoding of enum values is irrelevant except for zero, > because we actually use numeric zero via zero initialization. If the > enum's first value is conditional, the meaning of zero depends on build > configuration. We don't want such a default at the external interface. > It could conceivably lead to purely internal bugs, too. > ... like these. However, I'm not sure your solution fixes this problem completely. > > Consider type Arg with a mandatory member @mand and an optional enum > member @opt, both of enum type ENUM_TYPE. > > QMP input gets converted to native C like this: > > if (!visit_type_Arg(v, NULL, &arg, errp)) { > return; > } > > If @opt is absent, then arg.has_opt and arg.opt are both zero. > > Code providing an explicit default then is still fine: > > if (!arg.has_opt) { > arg.opt = ENUM_TYPE_MUMBLE; > } > > But we often use arg.opt without checking arg.has_opt for brevity. This > is an implicit default to zero, whatever zero may mean. > > If the the optional enum's first member is conditional, this default > depends on build configuration. > > Stupidest solution that could possibly work: an enum's first member > cannot be conditional. > That would prevent an enum that is entirely compiled out, which seems like a plausibly desirable feature. Honestly I think this is a C problem, not a QAPI problem. Skipping has_xx for bools that default to false is already borderline; doing it for enums is well into "you shouldn't do it" territory. Whereas "unless you have mandatory pointer fields, g_malloc0(...) returns a valid *and portable* QAPI struct" is in my opinion supporting a desirable idiom. Rust would spell it "Default::default()"; the language can help rejecting it if you have mandatory string fields (it would recursively default boxed structs, unlike C) but it cannot do anything about portability; this patch closes the gap completely for Rust, and does what it can for C. Paolo Thoughts? > > This needs some changes to doc-good.json, which used unwittingly > > such an enum. > > > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > > --- > > scripts/qapi/schema.py | 8 ++++++++ > > tests/qapi-schema/doc-good.json | 16 ++++++++-------- > > tests/qapi-schema/doc-good.out | 12 ++++++------ > > tests/qapi-schema/doc-good.txt | 8 ++++---- > > tests/qapi-schema/enum-if-first-required.err | 2 ++ > > tests/qapi-schema/enum-if-first-required.json | 6 ++++++ > > tests/qapi-schema/enum-if-first-required.out | 0 > > tests/qapi-schema/meson.build | 1 + > > 8 files changed, 35 insertions(+), 18 deletions(-) > > create mode 100644 tests/qapi-schema/enum-if-first-required.err > > create mode 100644 tests/qapi-schema/enum-if-first-required.json > > create mode 100644 tests/qapi-schema/enum-if-first-required.out > > Thanks for the negative test. > > docs/devel/qapi-code-gen.rst section "Enumeration types" could perhaps > use an update. > > It's less than clear even before the patch: > > The generated C enumeration constants have values 0, 1, ..., N-1 (in > QAPI schema order), where N is the number of values. There is an > additional enumeration constant PREFIX__MAX with value N. > > Do not use string or an integer type when an enumeration type can do > the job satisfactorily. > > The optional 'if' member specifies a conditional. See `Configuring the > schema`_ below for more on this. > > The first paragraph can lead readers to assume we generate something > like > > typedef enum Example { > EXAMPLE_FOO = 0, > #if defined(COND) > EXAMPLE_BAR = 1, > #endif > EXAMPLE__MAX = 2, > } Example; > > We don't, because we'd have to deal with a "hole" in the value range > when COND is not defined. > > Instead, we do > > typedef enum Example { > EXAMPLE_FOO, > #if defined(COND) > EXAMPLE_BAR, > #endif > EXAMPLE__MAX, > } Example; > > [-- Attachment #2: Type: text/html, Size: 6787 bytes --] ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-07-25 12:20 ` Paolo Bonzini @ 2026-07-27 9:32 ` Markus Armbruster 2026-08-03 5:14 ` Paolo Bonzini 0 siblings, 1 reply; 13+ messages in thread From: Markus Armbruster @ 2026-07-27 9:32 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel Paolo Bonzini <pbonzini@redhat.com> writes: > Il sab 25 lug 2026, 08:15 Markus Armbruster <armbru@redhat.com> ha scritto: > >> Paolo Bonzini <pbonzini@redhat.com> writes: >> >> > Prevent an all-zero struct from having different meanings with >> > different configurations or builds of QEMU. >> >> This is a bit terse. What *exactly* is broken? Spelling this out >> matters, because it can expose holes in the argument, if any. > > > Nothing is broken; it's just something that can have unexpected > consequences... > >> The numeric encoding of enum values is irrelevant except for zero, >> because we actually use numeric zero via zero initialization. If the >> enum's first value is conditional, the meaning of zero depends on build >> configuration. We don't want such a default at the external interface. >> It could conceivably lead to purely internal bugs, too. >> > > ... like these. I count 168 enum types including tests, 12 have conditional members, and none of them have a first member that is conditional. So this is merely a latent issue. Doesn't mean we shouldn't get rid of it, of course. >> However, I'm not sure your solution fixes this problem completely. >> >> Consider type Arg with a mandatory member @mand and an optional enum >> member @opt, both of enum type ENUM_TYPE. >> >> QMP input gets converted to native C like this: >> >> if (!visit_type_Arg(v, NULL, &arg, errp)) { >> return; >> } >> >> If @opt is absent, then arg.has_opt and arg.opt are both zero. >> >> Code providing an explicit default then is still fine: >> >> if (!arg.has_opt) { >> arg.opt = ENUM_TYPE_MUMBLE; >> } >> >> But we often use arg.opt without checking arg.has_opt for brevity. This >> is an implicit default to zero, whatever zero may mean. >> >> If the the optional enum's first member is conditional, this default >> depends on build configuration. >> >> Stupidest solution that could possibly work: an enum's first member >> cannot be conditional. >> > > That would prevent an enum that is entirely compiled out, which seems like > a plausibly desirable feature. > > Honestly I think this is a C problem, not a QAPI problem. Skipping has_xx > for bools that default to false is already borderline; doing it for enums > is well into "you shouldn't do it" territory. Let's review how we represent optional members in C. * Values of absent members are zero-initialized. * There may be a bool has_member that is false when the member is absent, true when present. * We commonly supply default values like this: member = obj.has_member ? obj.member : default_value; We then use member without checking .has_member again. * Anything that maps to pointers: since null is not a valid value, we use it to represent "absent". There is no obj.has_member then; we use !!obj.member instead. * Except for arrays, which we represent as singly linked list[*], where null means empty: we need has_member. Absent array usually defaults to empty. There's no need to make this explicit like member = obj.has_member ? obj.member : NULL; because obj.member is already null. So we don't for brevity's sake, we just use obj.member. Since obj.member has the correct default value, there is no need to check .has_member, so we don't. In particular, we use code like for (elt = obj.member; elt; elt = elt->next) ... instead of guarding it with if (obj.has_member) ... * Anything that doesn't map to pointers: we need has_member. When the default is zero, which is fairly common, there's again no need to explicitly supply it. We just use obj.member, without cluttering the code with .has_member conditionals. In my opinion, this (non-)usage of .has_member is just *fine* as long as the meaning of zero is well-defined at the QAPI level. For bool and and numbers, the meaning isn't just well-defined, it's also completely obvious. It's well-defined for enums as long as the enum's first member is unconditional. The meaning of zero is admittedly less obvious there. We could certainly change the existing code to only read obj.member where guarded by if (obj.has_member). Inhowfar we'd then succeed at keeping the shorter (and in my opinion more readable) forms from creeping back is less certain. But what's the benefit? Enabling "a plausibly desirable feature" we haven't found a use for is one, but I don't think it can justify the change. Are there other benefits I don't see? For Rust, maybe? > Whereas "unless you have > mandatory pointer fields, g_malloc0(...) returns a valid *and portable* > QAPI struct" is in my opinion supporting a desirable idiom. We do rely on zero-initialization to do the right thing. > Rust would spell it "Default::default()"; the language can help rejecting > it if you have mandatory string fields (it would recursively default boxed > structs, unlike C) but it cannot do anything about portability; this patch > closes the gap completely for Rust, and does what it can for C. I wouldn't call it a portability problem. The actual problem is that build-time configuration can have unwanted effects at least in theory. Since different host platforms can require different configuration, porting can trigger the problem. I.e. it's a special case. But I'm getting close to splitting hairs, and should stop :) I'm of course willing to evolve the schema language to help Rust, or to eliminate ways for us to screw up. You propose to * Forbid empty enums [PATCH 2]. They are allowed simply because I never found a compelling reason to forbid them. Yes, they're useless, but users can figure that out without the QAPI generator insisting. Does forbidding them help Rust? You still allow enums whose members are all conditional. Build configuration could make these empty, but there are conceivable uses. * Require members of enum type to be optional when the enum's first member is conditional [PATCH 1]. Does this help Rust? I believe these rules are mildly bothersome to explain. Which you sidestepped by not documenting them in docs/devel/qapi-code-gen.rst :) PATCH 1's commit message claims it prevents "an all-zero struct from having different meanings with different configurations or builds of QEMU". This is misleading without further qualifications: it doesn't actually prevent it *with the existing coding conventions*. The commit message would be easy enough to fix, of course. To actually prevent it reliably, we'd have to attack the root of the problem, namely the meaning of enum types' zero value. The obvious way to do that is to require enums to have an unconditional first member. This is also simpler to document, I think. Drawback: it removes the ability to define an enum whose members are all conditional, usable with optional object members. Does this matter enough to complicate things? > Paolo > >> Thoughts? >> >> > This needs some changes to doc-good.json, which used unwittingly >> > such an enum. >> > >> > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> [...] [*] A questionable choice, but here we are. ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-07-27 9:32 ` Markus Armbruster @ 2026-08-03 5:14 ` Paolo Bonzini 2026-08-06 7:00 ` Markus Armbruster 0 siblings, 1 reply; 13+ messages in thread From: Paolo Bonzini @ 2026-08-03 5:14 UTC (permalink / raw) To: Markus Armbruster; +Cc: qemu-devel On 7/27/26 11:32, Markus Armbruster wrote: > I count 168 enum types including tests, 12 have conditional members, and > none of them have a first member that is conditional. So this is merely > a latent issue. Doesn't mean we shouldn't get rid of it, of course. Yes, it's latent. Rust issues (see below) obviously are latent too. >>> However, I'm not sure your solution fixes this problem completely. >>> >>> Code providing an explicit default then is still fine: >>> >>> if (!arg.has_opt) { >>> arg.opt = ENUM_TYPE_MUMBLE; >>> } >>> >>> But we often use arg.opt without checking arg.has_opt for brevity. This >>> is an implicit default to zero, whatever zero may mean. If the optional >>> enum's first member is conditional, this default depends on build >>> configuration. >>> >>> Stupidest solution that could possibly work: an enum's first member >>> cannot be conditional. >> >> That would prevent an enum that is entirely compiled out, which seems like >> a plausibly desirable feature. >> >> Honestly I think this is a C problem, not a QAPI problem. Skipping has_xx >> for bools that default to false is already borderline; doing it for enums >> is well into "you shouldn't do it" territory. > > Let's review how we represent optional members in C. > > * Values of absent members are zero-initialized. > > * Anything that doesn't map to pointers: we need has_member. > > When the default is zero, which is fairly common, there's again no > need to explicitly supply it. We just use obj.member, without > cluttering the code with .has_member conditionals. > > In my opinion, this (non-)usage of .has_member is just *fine* as long as > the meaning of zero is well-defined at the QAPI level. [...] > It's well-defined for enums as long as the enum's first member is > unconditional. The meaning of zero is admittedly less obvious there. > We could certainly change the existing code to only read obj.member > where guarded by if (obj.has_member). Inhowfar we'd then succeed at > keeping the shorter (and in my opinion more readable) forms from > creeping back is less certain. I'm certain they would creep back, and I guess it's also fine if the enum has a 'default' or 'none' value. But the language doesn't make a difference, because the programmer just uses "0" even if the first member is conditional. Instead, the Rust backend won't let you write Default::default() if the first item is conditional; it's a compile-time failure. In that sense the C language has a deficiency. > But what's the benefit? Enabling "a plausibly desirable feature" we > haven't found a use for is one, but I don't think it can justify the > change. The plausibly desirable feature if enums with conditional items only. The alternative is to forbid enums that can disappear completely. How desirable is it? Here are the two that come closest: { 'enum': 'Qcow2CompressionType', 'data': [ 'zlib', { 'name': 'zstd', 'if': 'CONFIG_ZSTD' } ] } { 'enum': 'BlockExportType', 'data': [ 'nbd', { 'name': 'vhost-user-blk', 'if': 'CONFIG_VHOST_USER_BLK_SERVER' }, { 'name': 'fuse', 'if': 'CONFIG_FUSE' }, { 'name': 'vduse-blk', 'if': 'CONFIG_VDUSE_BLK_EXPORT' } ] } Other cases have a 'none' or even a 'default' member (DisplayType). Qcow2CompressionType could grow one in case zlib option becomes mandatory. BlockExportType is the most potentially problematic, because it does not make sense to have 'none' there. It would still not be usable - for example the C visit_type_BlockExportOptions_members() would collapse to if (!visit_type_q_obj_BlockExportOptions_base_members(...) { return false; } switch (obj->type) { default: abort(); } i.e. basically "return false" because neither visit_type_BlockExportType nor visit_type_q_obj_BlockExportOptions_base_members can succeed. However, QAPI schema generation would work, meaning that you _could_ write the QMP commands generically, even if in practice they're dead code. Bug or feature? >> Rust would spell it "Default::default()"; the language can help rejecting >> it if you have mandatory string fields (it would recursively default boxed >> structs, unlike C) but it cannot do anything about portability; this patch >> closes the gap completely for Rust, and does what it can for C. > > I wouldn't call it a portability problem. The actual problem is that > build-time configuration can have unwanted effects at least in theory. > Since different host platforms can require different configuration, > porting can trigger the problem. Yeah, portability in a wider sense---such as different behavior of clients when talking to different QEMU binaries. > You propose to > > * Forbid empty enums [PATCH 2]. They are allowed simply because I never > found a compelling reason to forbid them. Yes, they're useless, but > users can figure that out without the QAPI generator insisting. > > Does forbidding them help Rust? Somewhat; I cannot generate Default::default() for them. It would be easy enough to skip generation of that method, but you'd still get a Rust compilation error if you put them in a struct. IMO this is telling us that empty enums should indeed fail to compile even earlier, in the QAPI schema generator. Empty enum types cannot be created, and neither can any structs or commands they contain. There's simply no way to have code that refers to them, unless they're wrapped in something else that lets the code pretend they can exist: for example you can make a field optional and write "None" for it. With conditionally empty enums, this at least makes sense as a concept; on the other hand I just don't see the point in unconditionally empty enums, even if they're optional, so I left them out. > You still allow enums whose members are all conditional. Build > configuration could make these empty, but there are conceivable uses. I allow them because patch 1 forces them to be optional and then, even if *the enum* has no default, "None" is a sensible default for *the field*. In Rust, unlike C, I cannot just access the value of the enum if it's "None"; on top of that, optional_field.unwrap_or_default() fails to compile because the enum has no default. > * Require members of enum type to be optional when the enum's first > member is conditional [PATCH 1]. > > Does this help Rust? Yes, it lets me cop out of writing a Default::default() implementation for them, while letting them have a sensible default ("None", just like in C) if embedded in a struct. Again, the language is IMO telling us that enums with a conditional first member are in general a bad idea, and the solution is to catch them earlier, in the QAPI schema generator. I didn't ban them outright because IMO the fact that in C you can shoot yourself in the foot, and assume a meaning of "zero" for such an enum, *is* a language problem. In particular it is a language problem that it is not able to express the relationship between has_foo and foo. But I can be convinced to do it, if you think we need not care about cases like BlockExportType. Paolo > I believe these rules are mildly bothersome to explain. Which you > sidestepped by not documenting them in docs/devel/qapi-code-gen.rst :) > > To actually prevent it reliably, we'd have to attack the root of the > problem, namely the meaning of enum types' zero value. The obvious way > to do that is to require enums to have an unconditional first member. > This is also simpler to document, I think. > > Drawback: it removes the ability to define an enum whose members are all > conditional, usable with optional object members. Does this matter > enough to complicate things? ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-08-03 5:14 ` Paolo Bonzini @ 2026-08-06 7:00 ` Markus Armbruster 2026-08-07 13:02 ` Paolo Bonzini 0 siblings, 1 reply; 13+ messages in thread From: Markus Armbruster @ 2026-08-06 7:00 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel Paolo Bonzini <pbonzini@redhat.com> writes: > On 7/27/26 11:32, Markus Armbruster wrote: >> I count 168 enum types including tests, 12 have conditional members, and >> none of them have a first member that is conditional. So this is merely >> a latent issue. Doesn't mean we shouldn't get rid of it, of course. > > Yes, it's latent. Rust issues (see below) obviously are latent too. > >>>> However, I'm not sure your solution fixes this problem completely. >>>> >>>> Code providing an explicit default then is still fine: >>>> >>>> if (!arg.has_opt) { >>>> arg.opt = ENUM_TYPE_MUMBLE; >>>> } >>>> >>>> But we often use arg.opt without checking arg.has_opt for brevity. This >>>> is an implicit default to zero, whatever zero may mean. If the optional >>>> enum's first member is conditional, this default depends on build >>>> configuration. >>>> >>>> Stupidest solution that could possibly work: an enum's first member >>>> cannot be conditional. >>> >>> That would prevent an enum that is entirely compiled out, which seems like >>> a plausibly desirable feature. >>> >>> Honestly I think this is a C problem, not a QAPI problem. Skipping has_xx >>> for bools that default to false is already borderline; doing it for enums >>> is well into "you shouldn't do it" territory. >> >> Let's review how we represent optional members in C. >> >> * Values of absent members are zero-initialized. [...] >> * Anything that doesn't map to pointers: we need has_member. >> >> When the default is zero, which is fairly common, there's again no >> need to explicitly supply it. We just use obj.member, without >> cluttering the code with .has_member conditionals. >> >> In my opinion, this (non-)usage of .has_member is just *fine* as long as >> the meaning of zero is well-defined at the QAPI level. [...] >> >> It's well-defined for enums as long as the enum's first member is >> unconditional. The meaning of zero is admittedly less obvious there. >> >> We could certainly change the existing code to only read obj.member >> where guarded by if (obj.has_member). Inhowfar we'd then succeed at >> keeping the shorter (and in my opinion more readable) forms from >> creeping back is less certain. > > I'm certain they would creep back, and I guess it's also fine if the > enum has a 'default' or 'none' value. But the language doesn't make a > difference, because the programmer just uses "0" even if the first > member is conditional. > > Instead, the Rust backend won't let you write Default::default() if > the first item is conditional; it's a compile-time failure. In that > sense the C language has a deficiency. > >> But what's the benefit? Enabling "a plausibly desirable feature" we >> haven't found a use for is one, but I don't think it can justify the >> change. > > The plausibly desirable feature if enums with conditional items > only. The alternative is to forbid enums that can disappear > completely. > > How desirable is it? Here are the two that come closest: > > { 'enum': 'Qcow2CompressionType', > 'data': [ 'zlib', { 'name': 'zstd', 'if': 'CONFIG_ZSTD' } ] } > > { 'enum': 'BlockExportType', > 'data': [ 'nbd', > { 'name': 'vhost-user-blk', > 'if': 'CONFIG_VHOST_USER_BLK_SERVER' }, > { 'name': 'fuse', 'if': 'CONFIG_FUSE' }, > { 'name': 'vduse-blk', 'if': 'CONFIG_VDUSE_BLK_EXPORT' } ] } > > Other cases have a 'none' or even a 'default' member > (DisplayType). Yes. Aside: I prefer "absent defaults to an actual value" over "absent means something else than any value", because I find it simpler. > Qcow2CompressionType could grow one in case zlib option > becomes mandatory. Do you mean "becomes optional?" Note that @zlib can't become optional without a compatibility break, because @compression-type defaults to it in BlockdevCreateOptionsQcow2. Moreover, Qcow2CompressionType needs members, because @compression-type is mandatory in ImageInfoSpecificQCow2. The hard requirement is "need members at build time", i.e. the build configuration must not compile out all members. Without that, query-named-block-nodes cannot be made to work for QCOW2 images: it must have a value to store in the mandatory @compression-type. Storing 0 would be cheating, and also a dangerous violation of invariant "enum variable must contain bits that represent an actual enum value". We may want the stricter "need members in any build configuration", i.e. not all members may be conditional. And once we use that, we can just as well require the first member to be unconditional. > BlockExportType is the most potentially problematic, because it does > not make sense to have 'none' there. BlockExportType also needs members, because BlockExportOptions and BlockExportInfo have a mandatory member @type of BlockExportType. So, these two don't actually come close. Doesn't mean we'll never invent something else that does. > It would still not be usable - > for example the C visit_type_BlockExportOptions_members() would > collapse to > > if (!visit_type_q_obj_BlockExportOptions_base_members(...) { > return false; > } > switch (obj->type) { > default: > abort(); > } > > i.e. basically "return false" because neither > visit_type_BlockExportType nor > visit_type_q_obj_BlockExportOptions_base_members can succeed. > > However, QAPI schema generation would work, meaning that you _could_ > write the QMP commands generically, even if in practice they're dead > code. Bug or feature? Or excursion into the weeds? Joking aside, I wouldn't call it a feature, because "feature" implies "good for something". "Bug" implies "has some undesirable effect". If we volunteer for the mission "don't let the programmer specify useless interfaces", then this is a bug. If we go "you're asking for rope, have some" instead, it's not. Two common excuses for letting the programmer do stupid things: 1. Preventing stupid things would also prevent them from doing clever things. 2. It just isn't worth the bother. Preventing something people do and then regret is worth a lot more bother than preventing something they have not done in years. >>> Rust would spell it "Default::default()"; the language can help rejecting >>> it if you have mandatory string fields (it would recursively default boxed >>> structs, unlike C) but it cannot do anything about portability; this patch >>> closes the gap completely for Rust, and does what it can for C. >> >> I wouldn't call it a portability problem. The actual problem is that >> build-time configuration can have unwanted effects at least in theory. >> Since different host platforms can require different configuration, >> porting can trigger the problem. > > Yeah, portability in a wider sense---such as different behavior of > clients when talking to different QEMU binaries. Exactly. >> You propose to >> >> * Forbid empty enums [PATCH 2]. They are allowed simply because I never >> found a compelling reason to forbid them. Yes, they're useless, but >> users can figure that out without the QAPI generator insisting. >> >> Does forbidding them help Rust? > > Somewhat; I cannot generate Default::default() for them. It would be > easy enough to skip generation of that method, but you'd still get a > Rust compilation error if you put them in a struct. > > IMO this is telling us that empty enums should indeed fail to compile > even earlier, in the QAPI schema generator. > > Empty enum types cannot be created, and neither can any structs or > commands they contain. There's simply no way to have code that refers > to them, unless they're wrapped in something else that lets the code > pretend they can exist: for example you can make a field optional and > write "None" for it. > > With conditionally empty enums, this at least makes sense as a concept; on the other hand I just don't see the point in unconditionally empty enums, even if they're optional, so I left them out. If forbidding empty enums makes Rust generation simpler or easier, that's a compelling reason, isn't it? You should mention the Rust reason(s) in the commit message then. >> You still allow enums whose members are all conditional. Build >> configuration could make these empty, but there are conceivable uses. > > I allow them because patch 1 forces them to be optional and then, even > if *the enum* has no default, "None" is a sensible default for *the > field*. In Rust, unlike C, I cannot just access the value of the enum > if it's "None"; on top of that, optional_field.unwrap_or_default() > fails to compile because the enum has no default. > >> * Require members of enum type to be optional when the enum's first >> member is conditional [PATCH 1]. >> >> Does this help Rust? > > Yes, it lets me cop out of writing a Default::default() implementation > for them, while letting them have a sensible default ("None", just > like in C) if embedded in a struct. > > Again, the language is IMO telling us that enums with a conditional > first member are in general a bad idea, and the solution is to catch > them earlier, in the QAPI schema generator. I might have banned conditional first members from the start if I had thought out things back then. > I didn't ban them outright because IMO the fact that in C you can > shoot yourself in the foot, and assume a meaning of "zero" for such an > enum, *is* a language problem. In particular it is a language problem > that it is not able to express the relationship between has_foo and > foo. > > But I can be convinced to do it, if you think we need not care about > cases like BlockExportType. To me, requiring unconditional first member feels simpler. Perhaps I'm wrong. I've been wrong before. If you'd like to settle the matter instead of relying on my gut feelings, try documenting both solutions in docs/devel/qapi-code-gen.rst. Perhaps we find uses for optional enums with only conditional members later. Doesn't feel too likely. But if we do, we can complicate matters then rather than now. Even with more reasons than just helping Rust, you should still mention the Rust reason(s) in the commit message. > Paolo > >> I believe these rules are mildly bothersome to explain. Which you >> sidestepped by not documenting them in docs/devel/qapi-code-gen.rst :) >> >> To actually prevent it reliably, we'd have to attack the root of the >> problem, namely the meaning of enum types' zero value. The obvious way >> to do that is to require enums to have an unconditional first member. >> This is also simpler to document, I think. >> >> Drawback: it removes the ability to define an enum whose members are all >> conditional, usable with optional object members. Does this matter >> enough to complicate things? ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-08-06 7:00 ` Markus Armbruster @ 2026-08-07 13:02 ` Paolo Bonzini 2026-08-07 13:27 ` Markus Armbruster 0 siblings, 1 reply; 13+ messages in thread From: Paolo Bonzini @ 2026-08-07 13:02 UTC (permalink / raw) To: Markus Armbruster; +Cc: qemu-devel On Thu, Aug 6, 2026 at 9:01 AM Markus Armbruster <armbru@redhat.com> wrote: > > Qcow2CompressionType could grow one in case zlib option > > becomes mandatory. > > Do you mean "becomes optional?" > > Note that @zlib can't become optional without a compatibility break, > because @compression-type defaults to it in BlockdevCreateOptionsQcow2. > > Moreover, Qcow2CompressionType needs members, because @compression-type > is mandatory in ImageInfoSpecificQCow2. > > [...] > > > BlockExportType is the most potentially problematic, because it does > > not make sense to have 'none' there. > > BlockExportType also needs members, because BlockExportOptions and > BlockExportInfo have a mandatory member @type of BlockExportType. That would only mean you can't construct a valid one; but it would be dead code, not an error. > Joking aside, I wouldn't call it a feature, because "feature" implies > "good for something". "Bug" implies "has some undesirable effect". If > we volunteer for the mission "don't let the programmer specify useless > interfaces", then this is a bug. If we go "you're asking for rope, have > some" instead, it's not. There's some advantage in avoiding #ifdefs of this kind: #if defined CONFIG_FOO1 || defined CONFIG_FOO2 || defined CONFIG_FOO3 #define CONFIG_FOO_AVAILABLE #endif and let other stuff compile; sometimes even if you cannot rely on dead code elimination of some sort (which could be *another* language problem). But as you said, this was indeed a bit of an excursion into the weeds. > >> * Forbid empty enums [PATCH 2]. They are allowed simply because I never > >> found a compelling reason to forbid them. Yes, they're useless, but > >> users can figure that out without the QAPI generator insisting. > >> > >> Does forbidding them help Rust? > > > > Somewhat; I cannot generate Default::default() for them. It would be > > easy enough to skip generation of that method, but you'd still get a > > Rust compilation error if you put them in a struct. > > > > IMO this is telling us that empty enums should indeed fail to compile > > even earlier, in the QAPI schema generator. > > > > Empty enum types cannot be created, and neither can any structs or > > commands they contain. There's simply no way to have code that refers > > to them, unless they're wrapped in something else that lets the code > > pretend they can exist: for example you can make a field optional and > > write "None" for it. > > > > With conditionally empty enums, this at least makes sense as a concept; on the other hand I just don't see the point in unconditionally empty enums, even if they're optional, so I left them out. > > If forbidding empty enums makes Rust generation simpler or easier, > that's a compelling reason, isn't it? > > You should mention the Rust reason(s) in the commit message then. Rust can live with it. I'm just taking a clue from the language. > I might have banned conditional first members from the start if I had > thought out things back then. Ok, then let's do it. Paolo ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional 2026-08-07 13:02 ` Paolo Bonzini @ 2026-08-07 13:27 ` Markus Armbruster 0 siblings, 0 replies; 13+ messages in thread From: Markus Armbruster @ 2026-08-07 13:27 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel Paolo Bonzini <pbonzini@redhat.com> writes: > On Thu, Aug 6, 2026 at 9:01 AM Markus Armbruster <armbru@redhat.com> wrote: [...] >> If forbidding empty enums makes Rust generation simpler or easier, >> that's a compelling reason, isn't it? >> >> You should mention the Rust reason(s) in the commit message then. > > Rust can live with it. I'm just taking a clue from the language. No objection. >> I might have banned conditional first members from the start if I had >> thought out things back then. > > Ok, then let's do it. Likewise. ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 2/3] scripts/qapi: reject empty enums 2026-07-24 13:00 [PATCH resend 0/3] scripts/qapi: non-Rust-specific preparatory patches Paolo Bonzini 2026-07-24 13:00 ` [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional Paolo Bonzini @ 2026-07-24 13:00 ` Paolo Bonzini 2026-07-27 8:47 ` Markus Armbruster 2026-07-24 13:00 ` [PATCH 3/3] scripts/qapi: pull c_name and lstrip from camel_to_upper to caller Paolo Bonzini 2 siblings, 1 reply; 13+ messages in thread From: Paolo Bonzini @ 2026-07-24 13:00 UTC (permalink / raw) To: qemu-devel; +Cc: armbru Raise an error if an enum has no members. Such enums cannot be populated with a valid value, and therefore they can only be used as optional members. But QAPI leaves out optional members from the serialization protocol, and therefore the existence of the enum is completely pointless. The same limitation is not extended to an enum whose members are all compiled out; these can still be useful as optional members. However they cannot implicitly block usage of parent structures because, by virtue of having only conditional members, they are "enums with conditional first item" and must therefore be optional. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- scripts/qapi/schema.py | 4 ++++ tests/qapi-schema/enum-empty.err | 2 ++ tests/qapi-schema/enum-empty.json | 2 ++ tests/qapi-schema/enum-empty.out | 0 tests/qapi-schema/meson.build | 1 + tests/qapi-schema/qapi-schema-test.json | 3 --- tests/qapi-schema/qapi-schema-test.out | 1 - tests/qapi-schema/union-empty-if.err | 2 ++ tests/qapi-schema/union-empty-if.json | 6 ++++++ tests/qapi-schema/union-empty-if.out | 0 tests/qapi-schema/union-empty.err | 4 ++-- 11 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 tests/qapi-schema/enum-empty.err create mode 100644 tests/qapi-schema/enum-empty.json create mode 100644 tests/qapi-schema/enum-empty.out create mode 100644 tests/qapi-schema/union-empty-if.err create mode 100644 tests/qapi-schema/union-empty-if.json create mode 100644 tests/qapi-schema/union-empty-if.out diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 71296b15de2..a5a11298817 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -428,6 +428,10 @@ def __init__( def check(self, schema: QAPISchema) -> None: super().check(schema) + if not self.members: + raise QAPISemError( + self.info, + "enum '%s' must have at least one value" % self.name) seen: Dict[str, QAPISchemaMember] = {} for m in self.members: m.check_clash(self.info, seen) diff --git a/tests/qapi-schema/enum-empty.err b/tests/qapi-schema/enum-empty.err new file mode 100644 index 00000000000..6070bf62cbd --- /dev/null +++ b/tests/qapi-schema/enum-empty.err @@ -0,0 +1,2 @@ +enum-empty.json: In enum 'TestEmpty': +enum-empty.json:2: enum 'TestEmpty' must have at least one value diff --git a/tests/qapi-schema/enum-empty.json b/tests/qapi-schema/enum-empty.json new file mode 100644 index 00000000000..3b3dfb2e3d8 --- /dev/null +++ b/tests/qapi-schema/enum-empty.json @@ -0,0 +1,2 @@ +# An enum must have at least one value +{ 'enum': 'TestEmpty', 'data': [] } diff --git a/tests/qapi-schema/enum-empty.out b/tests/qapi-schema/enum-empty.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/qapi-schema/meson.build b/tests/qapi-schema/meson.build index 3b0c16a8b67..1058b7ecfae 100644 --- a/tests/qapi-schema/meson.build +++ b/tests/qapi-schema/meson.build @@ -196,6 +196,7 @@ schemas = [ 'union-clash-member.json', 'union-discriminator-bad-name.json', 'union-empty.json', + 'union-empty-if.json', 'union-inline-invalid-dict.json', 'union-int-branch.json', 'union-invalid-base.json', diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 8ca977c49d2..195f1c4847b 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -23,9 +23,6 @@ 'data': { 'enum1': 'EnumOne', # Intentional forward reference '*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } } -# An empty enum, although unusual, is currently acceptable -{ 'enum': 'MyEnum', 'data': [ ] } - # Likewise for an empty struct, including an empty base { 'struct': 'Empty1', 'data': { } } { 'struct': 'Empty2', 'base': 'Empty1', 'data': { } } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 4617eb4e98a..ddd8bf80d66 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -18,7 +18,6 @@ object NestedEnumsOne member enum2: EnumOne optional=True member enum3: EnumOne optional=False member enum4: EnumOne optional=True -enum MyEnum object Empty1 object Empty2 base Empty1 diff --git a/tests/qapi-schema/union-empty-if.err b/tests/qapi-schema/union-empty-if.err new file mode 100644 index 00000000000..2b8691ab2f5 --- /dev/null +++ b/tests/qapi-schema/union-empty-if.err @@ -0,0 +1,2 @@ +union-empty-if.json: In struct 'Base': +union-empty-if.json:5: enum type 'Empty' of member 'type' has a conditional first value and must be optional diff --git a/tests/qapi-schema/union-empty-if.json b/tests/qapi-schema/union-empty-if.json new file mode 100644 index 00000000000..36625dc4a89 --- /dev/null +++ b/tests/qapi-schema/union-empty-if.json @@ -0,0 +1,6 @@ +# union discriminator enum cannot have only conditional items +{ 'enum': 'Empty', + 'data': [ { 'name' : 'bar', 'if': { 'all': ['FOO'] } } ] } + +{ 'struct': 'Base', 'data': { 'type': 'Empty' } } +{ 'union': 'Union', 'base': 'Base', 'discriminator': 'type', 'data': { } } diff --git a/tests/qapi-schema/union-empty-if.out b/tests/qapi-schema/union-empty-if.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/qapi-schema/union-empty.err b/tests/qapi-schema/union-empty.err index d4284399621..c07dcf32a5a 100644 --- a/tests/qapi-schema/union-empty.err +++ b/tests/qapi-schema/union-empty.err @@ -1,2 +1,2 @@ -union-empty.json: In union 'Union': -union-empty.json:4: union has no branches +union-empty.json: In enum 'Empty': +union-empty.json:2: enum 'Empty' must have at least one value -- 2.55.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 2/3] scripts/qapi: reject empty enums 2026-07-24 13:00 ` [PATCH 2/3] scripts/qapi: reject empty enums Paolo Bonzini @ 2026-07-27 8:47 ` Markus Armbruster 0 siblings, 0 replies; 13+ messages in thread From: Markus Armbruster @ 2026-07-27 8:47 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel Paolo Bonzini <pbonzini@redhat.com> writes: > Raise an error if an enum has no members. Such enums cannot be populated > with a valid value, and therefore they can only be used as optional > members. But QAPI leaves out optional members from the serialization > protocol, and therefore the existence of the enum is completely pointless. > > The same limitation is not extended to an enum whose members are all > compiled out; these can still be useful as optional members. However they > cannot implicitly block usage of parent structures because, by virtue > of having only conditional members, they are "enums with conditional > first item" and must therefore be optional. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> This one definitely needs to update docs/devel/qapi-code-gen.rst, because that one has Nothing prevents an empty enumeration, although it is probably not useful. ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 3/3] scripts/qapi: pull c_name and lstrip from camel_to_upper to caller 2026-07-24 13:00 [PATCH resend 0/3] scripts/qapi: non-Rust-specific preparatory patches Paolo Bonzini 2026-07-24 13:00 ` [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional Paolo Bonzini 2026-07-24 13:00 ` [PATCH 2/3] scripts/qapi: reject empty enums Paolo Bonzini @ 2026-07-24 13:00 ` Paolo Bonzini 2026-08-03 10:19 ` Philippe Mathieu-Daudé 2 siblings, 1 reply; 13+ messages in thread From: Paolo Bonzini @ 2026-07-24 13:00 UTC (permalink / raw) To: qemu-devel; +Cc: armbru Allow using camel_to_upper for other languages too. In particular, the lstrip() is needed to avoid reserved C identifiers, for example: typedef enum __org_qemu_x_Enum { __ORG_QEMU_X_ENUM___ORG_QEMU_X_VALUE, __ORG_QEMU_X_ENUM__MAX, } __org_qemu_x_Enum; Insulate Rust from this, since underscores have a different meaning in Rust (though only for the sake of warnings). Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- scripts/qapi/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index d7c8aa3365c..43a0be5f97f 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -61,7 +61,7 @@ def camel_to_upper(value: str) -> str: ret += ch upc = ch.isupper() - return c_name(ret.upper()).lstrip('_') + return ret.upper() def c_enum_const(type_name: str, @@ -75,7 +75,7 @@ def c_enum_const(type_name: str, :param prefix: Optional, prefix that overrides the type_name. """ if prefix is None: - prefix = camel_to_upper(type_name) + prefix = c_name(camel_to_upper(type_name)).lstrip('_') return prefix + '_' + c_name(const_name, False).upper() -- 2.55.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 3/3] scripts/qapi: pull c_name and lstrip from camel_to_upper to caller 2026-07-24 13:00 ` [PATCH 3/3] scripts/qapi: pull c_name and lstrip from camel_to_upper to caller Paolo Bonzini @ 2026-08-03 10:19 ` Philippe Mathieu-Daudé 0 siblings, 0 replies; 13+ messages in thread From: Philippe Mathieu-Daudé @ 2026-08-03 10:19 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: armbru On 24/7/26 15:00, Paolo Bonzini wrote: > Allow using camel_to_upper for other languages too. > > In particular, the lstrip() is needed to avoid reserved C identifiers, > for example: > > typedef enum __org_qemu_x_Enum { > __ORG_QEMU_X_ENUM___ORG_QEMU_X_VALUE, > __ORG_QEMU_X_ENUM__MAX, > } __org_qemu_x_Enum; > > Insulate Rust from this, since underscores have a different meaning > in Rust (though only for the sake of warnings). > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > scripts/qapi/common.py | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-08-07 13:28 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-24 13:00 [PATCH resend 0/3] scripts/qapi: non-Rust-specific preparatory patches Paolo Bonzini 2026-07-24 13:00 ` [PATCH 1/3] scripts/qapi: enum with conditional first item must be optional Paolo Bonzini 2026-07-25 6:15 ` Markus Armbruster 2026-07-25 12:20 ` Paolo Bonzini 2026-07-27 9:32 ` Markus Armbruster 2026-08-03 5:14 ` Paolo Bonzini 2026-08-06 7:00 ` Markus Armbruster 2026-08-07 13:02 ` Paolo Bonzini 2026-08-07 13:27 ` Markus Armbruster 2026-07-24 13:00 ` [PATCH 2/3] scripts/qapi: reject empty enums Paolo Bonzini 2026-07-27 8:47 ` Markus Armbruster 2026-07-24 13:00 ` [PATCH 3/3] scripts/qapi: pull c_name and lstrip from camel_to_upper to caller Paolo Bonzini 2026-08-03 10:19 ` Philippe Mathieu-Daudé
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.