mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-01 14:53:54 -06:00
qapi: Tighten checking of unions
Previous commits demonstrated that the generator had several flaws with less-than-perfect unions: - a simple union that listed the same branch twice (or two variant names that map to the same C enumerator, including the implicit MAX sentinel) ended up generating invalid C code - an anonymous union that listed two branches with the same qtype ended up generating invalid C code - the generator crashed on anonymous union attempts to use an array type - the generator was silently ignoring a base type for anonymous unions - the generator allowed unknown types or nested anonymous unions as a branch in an anonymous union Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
parent
a8d4a2e4d7
commit
44bd1276a7
48 changed files with 113 additions and 106 deletions
|
@ -181,17 +181,8 @@ const int %(name)s_qtypes[QTYPE_MAX] = {
|
||||||
name=name)
|
name=name)
|
||||||
|
|
||||||
for key in members:
|
for key in members:
|
||||||
qapi_type = members[key]
|
qtype = find_anonymous_member_qtype(members[key])
|
||||||
if builtin_types.has_key(qapi_type):
|
assert qtype, "Invalid anonymous union member"
|
||||||
qtype = builtin_types[qapi_type]
|
|
||||||
elif find_struct(qapi_type):
|
|
||||||
qtype = "QTYPE_QDICT"
|
|
||||||
elif find_union(qapi_type):
|
|
||||||
qtype = "QTYPE_QDICT"
|
|
||||||
elif find_enum(qapi_type):
|
|
||||||
qtype = "QTYPE_QSTRING"
|
|
||||||
else:
|
|
||||||
assert False, "Invalid anonymous union member"
|
|
||||||
|
|
||||||
ret += mcgen('''
|
ret += mcgen('''
|
||||||
[ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s,
|
[ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s,
|
||||||
|
|
|
@ -224,6 +224,23 @@ def find_base_fields(base):
|
||||||
return None
|
return None
|
||||||
return base_struct_define['data']
|
return base_struct_define['data']
|
||||||
|
|
||||||
|
# Return the qtype of an anonymous union branch, or None on error.
|
||||||
|
def find_anonymous_member_qtype(qapi_type):
|
||||||
|
if builtin_types.has_key(qapi_type):
|
||||||
|
return builtin_types[qapi_type]
|
||||||
|
elif find_struct(qapi_type):
|
||||||
|
return "QTYPE_QDICT"
|
||||||
|
elif find_enum(qapi_type):
|
||||||
|
return "QTYPE_QSTRING"
|
||||||
|
else:
|
||||||
|
union = find_union(qapi_type)
|
||||||
|
if union:
|
||||||
|
discriminator = union.get('discriminator')
|
||||||
|
if discriminator == {}:
|
||||||
|
return None
|
||||||
|
return "QTYPE_QDICT"
|
||||||
|
return None
|
||||||
|
|
||||||
# Return the discriminator enum define if discriminator is specified as an
|
# Return the discriminator enum define if discriminator is specified as an
|
||||||
# enum type, otherwise return None.
|
# enum type, otherwise return None.
|
||||||
def discriminator_find_enum_define(expr):
|
def discriminator_find_enum_define(expr):
|
||||||
|
@ -258,6 +275,8 @@ def check_union(expr, expr_info):
|
||||||
base = expr.get('base')
|
base = expr.get('base')
|
||||||
discriminator = expr.get('discriminator')
|
discriminator = expr.get('discriminator')
|
||||||
members = expr['data']
|
members = expr['data']
|
||||||
|
values = { 'MAX': '(automatic)' }
|
||||||
|
types_seen = {}
|
||||||
|
|
||||||
# If the object has a member 'base', its value must name a complex type,
|
# If the object has a member 'base', its value must name a complex type,
|
||||||
# and there must be a discriminator.
|
# and there must be a discriminator.
|
||||||
|
@ -266,26 +285,35 @@ def check_union(expr, expr_info):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPIExprError(expr_info,
|
||||||
"Union '%s' requires a discriminator to go "
|
"Union '%s' requires a discriminator to go "
|
||||||
"along with base" %name)
|
"along with base" %name)
|
||||||
|
|
||||||
|
# If the union object has no member 'discriminator', it's a
|
||||||
|
# simple union. If 'discriminator' is {}, it is an anonymous union.
|
||||||
|
if discriminator is None or discriminator == {}:
|
||||||
|
enum_define = None
|
||||||
|
if base is not None:
|
||||||
|
raise QAPIExprError(expr_info,
|
||||||
|
"Union '%s' must not have a base"
|
||||||
|
% name)
|
||||||
|
|
||||||
|
# Else, it's a flat union.
|
||||||
|
else:
|
||||||
|
# The object must have a string member 'base'.
|
||||||
|
if not isinstance(base, str):
|
||||||
|
raise QAPIExprError(expr_info,
|
||||||
|
"Flat union '%s' must have a string base field"
|
||||||
|
% name)
|
||||||
base_fields = find_base_fields(base)
|
base_fields = find_base_fields(base)
|
||||||
if not base_fields:
|
if not base_fields:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPIExprError(expr_info,
|
||||||
"Base '%s' is not a valid type"
|
"Base '%s' is not a valid type"
|
||||||
% base)
|
% base)
|
||||||
|
|
||||||
# If the union object has no member 'discriminator', it's a
|
|
||||||
# simple union. If 'discriminator' is {}, it is an anonymous union.
|
|
||||||
if not discriminator or discriminator == {}:
|
|
||||||
enum_define = None
|
|
||||||
|
|
||||||
# Else, it's a flat union.
|
|
||||||
else:
|
|
||||||
# The object must have a member 'base'.
|
|
||||||
if not base:
|
|
||||||
raise QAPIExprError(expr_info,
|
|
||||||
"Flat union '%s' must have a base field"
|
|
||||||
% name)
|
|
||||||
# The value of member 'discriminator' must name a member of the
|
# The value of member 'discriminator' must name a member of the
|
||||||
# base type.
|
# base type.
|
||||||
|
if not isinstance(discriminator, str):
|
||||||
|
raise QAPIExprError(expr_info,
|
||||||
|
"Flat union '%s' discriminator must be a string"
|
||||||
|
% name)
|
||||||
discriminator_type = base_fields.get(discriminator)
|
discriminator_type = base_fields.get(discriminator)
|
||||||
if not discriminator_type:
|
if not discriminator_type:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPIExprError(expr_info,
|
||||||
|
@ -301,15 +329,42 @@ def check_union(expr, expr_info):
|
||||||
|
|
||||||
# Check every branch
|
# Check every branch
|
||||||
for (key, value) in members.items():
|
for (key, value) in members.items():
|
||||||
# If this named member's value names an enum type, then all members
|
# If the discriminator names an enum type, then all members
|
||||||
# of 'data' must also be members of the enum type.
|
# of 'data' must also be members of the enum type.
|
||||||
if enum_define and not key in enum_define['enum_values']:
|
if enum_define:
|
||||||
|
if not key in enum_define['enum_values']:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPIExprError(expr_info,
|
||||||
"Discriminator value '%s' is not found in "
|
"Discriminator value '%s' is not found in "
|
||||||
"enum '%s'" %
|
"enum '%s'" %
|
||||||
(key, enum_define["enum_name"]))
|
(key, enum_define["enum_name"]))
|
||||||
# Todo: add checking for values. Key is checked as above, value can be
|
|
||||||
# also checked here, but we need more functions to handle array case.
|
# Otherwise, check for conflicts in the generated enum
|
||||||
|
else:
|
||||||
|
c_key = _generate_enum_string(key)
|
||||||
|
if c_key in values:
|
||||||
|
raise QAPIExprError(expr_info,
|
||||||
|
"Union '%s' member '%s' clashes with '%s'"
|
||||||
|
% (name, key, values[c_key]))
|
||||||
|
values[c_key] = key
|
||||||
|
|
||||||
|
# Ensure anonymous unions have no type conflicts.
|
||||||
|
if discriminator == {}:
|
||||||
|
if isinstance(value, list):
|
||||||
|
raise QAPIExprError(expr_info,
|
||||||
|
"Anonymous union '%s' member '%s' must "
|
||||||
|
"not be array type" % (name, key))
|
||||||
|
qtype = find_anonymous_member_qtype(value)
|
||||||
|
if not qtype:
|
||||||
|
raise QAPIExprError(expr_info,
|
||||||
|
"Anonymous union '%s' member '%s' has "
|
||||||
|
"invalid type '%s'" % (name, key, value))
|
||||||
|
if qtype in types_seen:
|
||||||
|
raise QAPIExprError(expr_info,
|
||||||
|
"Anonymous union '%s' member '%s' can't "
|
||||||
|
"be distinguished from member '%s'"
|
||||||
|
% (name, key, types_seen[qtype]))
|
||||||
|
types_seen[qtype] = key
|
||||||
|
|
||||||
|
|
||||||
def check_enum(expr, expr_info):
|
def check_enum(expr, expr_info):
|
||||||
name = expr['enum']
|
name = expr['enum']
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/alternate-array.json:5: Anonymous union 'MyUnion' member 'two' must not be array type
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should not allow array branches in anonymous unions
|
# we do not allow array branches in anonymous unions
|
||||||
# TODO: should we support this?
|
# TODO: should we support this?
|
||||||
{ 'type': 'One',
|
{ 'type': 'One',
|
||||||
'data': { 'name': 'str' } }
|
'data': { 'name': 'str' } }
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
|
|
||||||
OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', ['int'])]))])]
|
|
||||||
[{'enum_name': 'MyUnionKind', 'enum_values': None}]
|
|
||||||
[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))])]
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/alternate-base.json:4: Union 'MyUnion' must not have a base
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should reject anonymous union with base type
|
# we reject anonymous union with base type
|
||||||
{ 'type': 'Base',
|
{ 'type': 'Base',
|
||||||
'data': { 'string': 'str' } }
|
'data': { 'string': 'str' } }
|
||||||
{ 'union': 'MyUnion',
|
{ 'union': 'MyUnion',
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]),
|
|
||||||
OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', OrderedDict()), ('data', OrderedDict([('number', 'int')]))])]
|
|
||||||
[{'enum_name': 'MyUnionKind', 'enum_values': None}]
|
|
||||||
[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])]
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/alternate-clash.json:2: Union 'Union1' member 'ONE' clashes with 'one'
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should detect C enum collisions in an anonymous union
|
# we detect C enum collisions in an anonymous union
|
||||||
{ 'union': 'Union1',
|
{ 'union': 'Union1',
|
||||||
'discriminator': {},
|
'discriminator': {},
|
||||||
'data': { 'one': 'str', 'ONE': 'int' } }
|
'data': { 'one': 'str', 'ONE': 'int' } }
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('ONE', 'int')]))])]
|
|
||||||
[{'enum_name': 'Union1Kind', 'enum_values': None}]
|
|
||||||
[]
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/alternate-conflict-dict.json:6: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one'
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should reject anonymous unions with multiple object branches
|
# we reject anonymous unions with multiple object branches
|
||||||
{ 'type': 'One',
|
{ 'type': 'One',
|
||||||
'data': { 'name': 'str' } }
|
'data': { 'name': 'str' } }
|
||||||
{ 'type': 'Two',
|
{ 'type': 'Two',
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))]),
|
|
||||||
OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', 'Two')]))])]
|
|
||||||
[{'enum_name': 'MyUnionKind', 'enum_values': None}]
|
|
||||||
[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))])]
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/alternate-conflict-string.json:4: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one'
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should reject anonymous unions with multiple string-like branches
|
# we reject anonymous unions with multiple string-like branches
|
||||||
{ 'enum': 'Enum',
|
{ 'enum': 'Enum',
|
||||||
'data': [ 'hello', 'world' ] }
|
'data': [ 'hello', 'world' ] }
|
||||||
{ 'union': 'MyUnion',
|
{ 'union': 'MyUnion',
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]),
|
|
||||||
OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('two', 'Enum')]))])]
|
|
||||||
[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']},
|
|
||||||
{'enum_name': 'MyUnionKind', 'enum_values': None}]
|
|
||||||
[]
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/alternate-nested.json:5: Anonymous union 'Union2' member 'nested' has invalid type 'Union1'
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should reject a nested anonymous union branch
|
# we reject a nested anonymous union branch
|
||||||
{ 'union': 'Union1',
|
{ 'union': 'Union1',
|
||||||
'discriminator': {},
|
'discriminator': {},
|
||||||
'data': { 'name': 'str', 'value': 'int' } }
|
'data': { 'name': 'str', 'value': 'int' } }
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('name', 'str'), ('value', 'int')]))]),
|
|
||||||
OrderedDict([('union', 'Union2'), ('discriminator', OrderedDict()), ('data', OrderedDict([('nested', 'Union1')]))])]
|
|
||||||
[{'enum_name': 'Union1Kind', 'enum_values': None},
|
|
||||||
{'enum_name': 'Union2Kind', 'enum_values': None}]
|
|
||||||
[]
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/alternate-unknown.json:2: Anonymous union 'Union' member 'unknown' has invalid type 'MissingType'
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should reject an anonymous union with unknown type in branch
|
# we reject an anonymous union with unknown type in branch
|
||||||
{ 'union': 'Union',
|
{ 'union': 'Union',
|
||||||
'discriminator': {},
|
'discriminator': {},
|
||||||
'data': { 'unknown': 'MissingType' } }
|
'data': { 'unknown': 'MissingType' } }
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[OrderedDict([('union', 'Union'), ('discriminator', OrderedDict()), ('data', OrderedDict([('unknown', 'MissingType')]))])]
|
|
||||||
[{'enum_name': 'UnionKind', 'enum_values': None}]
|
|
||||||
[]
|
|
|
@ -1 +1 @@
|
||||||
tests/qapi-schema/flat-union-bad-base.json:9: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type
|
tests/qapi-schema/flat-union-bad-base.json:9: Flat union 'TestUnion' must have a string base field
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: poor message: we require the base to be an existing complex type
|
# we require the base to be an existing complex type
|
||||||
# TODO: should we allow an anonymous inline base type?
|
# TODO: should we allow an anonymous inline base type?
|
||||||
{ 'enum': 'TestEnum',
|
{ 'enum': 'TestEnum',
|
||||||
'data': [ 'value1', 'value2' ] }
|
'data': [ 'value1', 'value2' ] }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/flat-union-bad-discriminator.json:10: Flat union 'TestUnion' discriminator must be a string
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should require the discriminator to be a string naming a base-type member
|
# we require the discriminator to be a string naming a base-type member
|
||||||
{ 'enum': 'TestEnum',
|
{ 'enum': 'TestEnum',
|
||||||
'data': [ 'value1', 'value2' ] }
|
'data': [ 'value1', 'value2' ] }
|
||||||
{ 'type': 'TestBase',
|
{ 'type': 'TestBase',
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
|
|
||||||
OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
|
|
||||||
OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', []), ('data', OrderedDict([('kind1', 'TestTypeA'), ('kind2', 'TestTypeB')]))])]
|
|
||||||
[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']},
|
|
||||||
{'enum_name': 'TestUnionKind', 'enum_values': None}]
|
|
||||||
[OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
|
|
|
@ -1 +1 @@
|
||||||
tests/qapi-schema/flat-union-inline.json:7: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type
|
tests/qapi-schema/flat-union-inline.json:7: Flat union 'TestUnion' must have a string base field
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: poor message: we require branches to be a complex type name
|
# we require branches to be a complex type name
|
||||||
# TODO: should we allow anonymous inline types?
|
# TODO: should we allow anonymous inline types?
|
||||||
{ 'enum': 'TestEnum',
|
{ 'enum': 'TestEnum',
|
||||||
'data': [ 'value1', 'value2' ] }
|
'data': [ 'value1', 'value2' ] }
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a base field
|
tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a string base field
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: flat unions should require a base
|
# flat unions require a base
|
||||||
# TODO: simple unions should be able to use an enum discriminator
|
# TODO: simple unions should be able to use an enum discriminator
|
||||||
{ 'type': 'TestTypeA',
|
{ 'type': 'TestTypeA',
|
||||||
'data': { 'string': 'str' } }
|
'data': { 'string': 'str' } }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/union-bad-branch.json:6: Union 'MyUnion' member 'ONE' clashes with 'one'
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# FIXME: we should reject normal unions where branches would collide in C
|
# we reject normal unions where branches would collide in C
|
||||||
{ 'type': 'One',
|
{ 'type': 'One',
|
||||||
'data': { 'string': 'str' } }
|
'data': { 'string': 'str' } }
|
||||||
{ 'type': 'Two',
|
{ 'type': 'Two',
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))]),
|
|
||||||
OrderedDict([('union', 'MyUnion'), ('data', OrderedDict([('one', 'One'), ('ONE', 'Two')]))])]
|
|
||||||
[{'enum_name': 'MyUnionKind', 'enum_values': None}]
|
|
||||||
[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]),
|
|
||||||
OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))])]
|
|
|
@ -0,0 +1 @@
|
||||||
|
tests/qapi-schema/union-max.json:2: Union 'Union' member 'max' clashes with '(automatic)'
|
|
@ -1 +1 @@
|
||||||
0
|
1
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# FIXME: we should reject 'max' branch in a union, for collision with C enum
|
# we reject 'max' branch in a union, for collision with C enum
|
||||||
{ 'union': 'Union',
|
{ 'union': 'Union',
|
||||||
'data': { 'max': 'int' } }
|
'data': { 'max': 'int' } }
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[OrderedDict([('union', 'Union'), ('data', OrderedDict([('max', 'int')]))])]
|
|
||||||
[{'enum_name': 'UnionKind', 'enum_values': None}]
|
|
||||||
[]
|
|
Loading…
Add table
Add a link
Reference in a new issue