Hmm, there's more union-specific code to move out of the base. Revised patch:
diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 658c288f8f..4a2e62d919 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -328,7 +328,8 @@ def visit_object_type(self, name, info, ifcond, features, + self._nodes_for_sections(doc) + self._nodes_for_if_section(ifcond)) - def visit_alternate_type(self, name, info, ifcond, features, variants): + def visit_alternate_type(self, name, info, ifcond, features, + alternatives): doc = self._cur_doc self._add_doc('Alternate', self._nodes_for_members(doc, 'Members') diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index c38df61a6d..e5cea8004e 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -26,6 +26,7 @@ from .gen import QAPISchemaMonolithicCVisitor from .schema import ( QAPISchema, + QAPISchemaAlternatives, QAPISchemaArrayType, QAPISchemaBuiltinType, QAPISchemaEntity, @@ -343,12 +344,12 @@ def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo], def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo], ifcond: QAPISchemaIfCond, features: List[QAPISchemaFeature], - variants: QAPISchemaVariants) -> None: + alternatives: QAPISchemaAlternatives) -> None: self._gen_tree( name, 'alternate', {'members': [Annotated({'type': self._use_type(m.type)}, m.ifcond) - for m in variants.variants]}, + for m in alternatives.variants]}, ifcond, features ) diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 0d9a70ab4c..f64e337ba2 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -187,7 +187,8 @@ def visit_object_type_flat(self, name, info, ifcond, features, members, variants): pass - def visit_alternate_type(self, name, info, ifcond, features, variants): + def visit_alternate_type(self, name, info, ifcond, features, + alternatives): pass def visit_command(self, name, info, ifcond, features, @@ -563,8 +564,7 @@ class QAPISchemaAlternateType(QAPISchemaType): def __init__(self, name, info, doc, ifcond, features, variants): super().__init__(name, info, doc, ifcond, features) - assert isinstance(variants, QAPISchemaVariants) - assert variants.tag_member + assert isinstance(variants, QAPISchemaAlternatives) variants.set_defined_in(name) variants.tag_member.set_defined_in(self.name) self.variants = variants @@ -625,19 +625,12 @@ def visit(self, visitor): self.name, self.info, self.ifcond, self.features, self.variants) -class QAPISchemaVariants: - def __init__(self, tag_name, info, tag_member, variants): - # Unions pass tag_name but not tag_member. - # Alternates pass tag_member but not tag_name. - # After check(), tag_member is always set. - assert bool(tag_member) != bool(tag_name) - assert (isinstance(tag_name, str) or - isinstance(tag_member, QAPISchemaObjectTypeMember)) +class QAPISchemaVariantsBase: + def __init__(self, info, variants): for v in variants: assert isinstance(v, QAPISchemaVariant) - self._tag_name = tag_name self.info = info - self.tag_member = tag_member + self.tag_member = None self.variants = variants def set_defined_in(self, name): @@ -645,66 +638,8 @@ def set_defined_in(self, name): v.set_defined_in(name) def check(self, schema, seen): - if self._tag_name: # union - self.tag_member = seen.get(c_name(self._tag_name)) - base = "'base'" - # Pointing to the base type when not implicit would be - # nice, but we don't know it here - if not self.tag_member or self._tag_name != self.tag_member.name: - raise QAPISemError( - self.info, - "discriminator '%s' is not a member of %s" - % (self._tag_name, base)) - # Here we do: - base_type = schema.resolve_type(self.tag_member.defined_in) - if not base_type.is_implicit(): - base = "base type '%s'" % self.tag_member.defined_in - if not isinstance(self.tag_member.type, QAPISchemaEnumType): - raise QAPISemError( - self.info, - "discriminator member '%s' of %s must be of enum type" - % (self._tag_name, base)) - if self.tag_member.optional: - raise QAPISemError( - self.info, - "discriminator member '%s' of %s must not be optional" - % (self._tag_name, base)) - if self.tag_member.ifcond.is_present(): - raise QAPISemError( - self.info, - "discriminator member '%s' of %s must not be conditional" - % (self._tag_name, base)) - else: # alternate - assert isinstance(self.tag_member.type, QAPISchemaEnumType) - assert not self.tag_member.optional - assert not self.tag_member.ifcond.is_present() - if self._tag_name: # union - # branches that are not explicitly covered get an empty type - cases = {v.name for v in self.variants} - for m in self.tag_member.type.members: - if m.name not in cases: - v = QAPISchemaVariant(m.name, self.info, - 'q_empty', m.ifcond) - v.set_defined_in(self.tag_member.defined_in) - self.variants.append(v) - if not self.variants: - raise QAPISemError(self.info, "union has no branches") for v in self.variants: v.check(schema) - # Union names must match enum values; alternate names are - # checked separately. Use 'seen' to tell the two apart. - if seen: - if v.name not in self.tag_member.type.member_names(): - raise QAPISemError( - self.info, - "branch '%s' is not a value of %s" - % (v.name, self.tag_member.type.describe())) - if not isinstance(v.type, QAPISchemaObjectType): - raise QAPISemError( - self.info, - "%s cannot use %s" - % (v.describe(self.info), v.type.describe())) - v.type.check(schema) def check_clash(self, info, seen): for v in self.variants: @@ -713,6 +648,79 @@ def check_clash(self, info, seen): v.type.check_clash(info, dict(seen)) +class QAPISchemaVariants(QAPISchemaVariantsBase): + def __init__(self, info, variants, tag_name): + assert isinstance(tag_name, str) + super().__init__(info, variants) + self._tag_name = tag_name + + def check(self, schema, seen): + self.tag_member = seen.get(c_name(self._tag_name)) + base = "'base'" + # Pointing to the base type when not implicit would be + # nice, but we don't know it here + if not self.tag_member or self._tag_name != self.tag_member.name: + raise QAPISemError( + self.info, + "discriminator '%s' is not a member of %s" + % (self._tag_name, base)) + # Here we do: + base_type = schema.resolve_type(self.tag_member.defined_in) + if not base_type.is_implicit(): + base = "base type '%s'" % self.tag_member.defined_in + if not isinstance(self.tag_member.type, QAPISchemaEnumType): + raise QAPISemError( + self.info, + "discriminator member '%s' of %s must be of enum type" + % (self._tag_name, base)) + if self.tag_member.optional: + raise QAPISemError( + self.info, + "discriminator member '%s' of %s must not be optional" + % (self._tag_name, base)) + if self.tag_member.ifcond.is_present(): + raise QAPISemError( + self.info, + "discriminator member '%s' of %s must not be conditional" + % (self._tag_name, base)) + # branches that are not explicitly covered get an empty type + cases = {v.name for v in self.variants} + for m in self.tag_member.type.members: + if m.name not in cases: + v = QAPISchemaVariant(m.name, self.info, + 'q_empty', m.ifcond) + v.set_defined_in(self.tag_member.defined_in) + self.variants.append(v) + if not self.variants: + raise QAPISemError(self.info, "union has no branches") + super().check(schema, seen) + for v in self.variants: + if v.name not in self.tag_member.type.member_names(): + raise QAPISemError( + self.info, + "branch '%s' is not a value of %s" + % (v.name, self.tag_member.type.describe())) + if not isinstance(v.type, QAPISchemaObjectType): + raise QAPISemError( + self.info, + "%s cannot use %s" + % (v.describe(self.info), v.type.describe())) + v.type.check(schema) + + +class QAPISchemaAlternatives(QAPISchemaVariantsBase): + def __init__(self, info, variants, tag_member): + assert isinstance(tag_member, QAPISchemaObjectTypeMember) + super().__init__(info, variants) + self.tag_member = tag_member + + def check(self, schema, seen): + super().check(schema, seen) + assert isinstance(self.tag_member.type, QAPISchemaEnumType) + assert not self.tag_member.optional + assert not self.tag_member.ifcond.is_present() + + class QAPISchemaMember: """ Represents object members, enum members and features """ role = 'member' @@ -1184,7 +1192,7 @@ def _def_union_type(self, expr: QAPIExpression): QAPISchemaObjectType(name, info, expr.doc, ifcond, features, base, members, QAPISchemaVariants( - tag_name, info, None, variants))) + info, variants, tag_name))) def _def_alternate_type(self, expr: QAPIExpression): name = expr['alternate'] @@ -1202,7 +1210,7 @@ def _def_alternate_type(self, expr: QAPIExpression): self._def_definition( QAPISchemaAlternateType( name, info, expr.doc, ifcond, features, - QAPISchemaVariants(None, info, tag_member, variants))) + QAPISchemaAlternatives(info, variants, tag_member))) def _def_command(self, expr: QAPIExpression): name = expr['command'] diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index c39d054d2c..05da30b855 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -23,6 +23,7 @@ ) from .schema import ( QAPISchema, + QAPISchemaAlternatives, QAPISchemaEnumMember, QAPISchemaFeature, QAPISchemaIfCond, @@ -369,11 +370,11 @@ def visit_alternate_type(self, info: Optional[QAPISourceInfo], ifcond: QAPISchemaIfCond, features: List[QAPISchemaFeature], - variants: QAPISchemaVariants) -> None: + alternatives: QAPISchemaAlternatives) -> None: with ifcontext(ifcond, self._genh): self._genh.preamble_add(gen_fwd_object_or_array(name)) self._genh.add(gen_object(name, ifcond, None, - [variants.tag_member], variants)) + [alternatives.tag_member], alternatives)) with ifcontext(ifcond, self._genh, self._genc): self._gen_type_cleanup(name) diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index c56ea4d724..725bfcef50 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -28,6 +28,7 @@ ) from .schema import ( QAPISchema, + QAPISchemaAlternatives, QAPISchemaEnumMember, QAPISchemaEnumType, QAPISchemaFeature, @@ -222,7 +223,8 @@ def gen_visit_enum(name: str) -> str: c_name=c_name(name)) -def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str: +def gen_visit_alternate(name: str, + alternatives: QAPISchemaAlternatives) -> str: ret = mcgen(''' bool visit_type_%(c_name)s(Visitor *v, const char *name, @@ -244,7 +246,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str: ''', c_name=c_name(name)) - for var in variants.variants: + for var in alternatives.variants: ret += var.ifcond.gen_if() ret += mcgen(''' case %(case)s: @@ -414,10 +416,10 @@ def visit_alternate_type(self, info: Optional[QAPISourceInfo], ifcond: QAPISchemaIfCond, features: List[QAPISchemaFeature], - variants: QAPISchemaVariants) -> None: + alternatives: QAPISchemaAlternatives) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name)) - self._genc.add(gen_visit_alternate(name, variants)) + self._genc.add(gen_visit_alternate(name, alternatives)) def gen_visit(schema: QAPISchema, diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 14f7b62a44..b66ceb81b8 100755 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -61,9 +61,10 @@ def visit_object_type(self, name, info, ifcond, features, self._print_if(ifcond) self._print_features(features) - def visit_alternate_type(self, name, info, ifcond, features, variants): + def visit_alternate_type(self, name, info, ifcond, features, + alternatives): print('alternate %s' % name) - self._print_variants(variants) + self._print_variants(alternatives) self._print_if(ifcond) self._print_features(features)