From: Marc-André Lureau <[email protected]> Modify check_if() to build an IfPredicate tree (the schema documentation is updated in a following patch).
Signed-off-by: Marc-André Lureau <[email protected]> --- tests/unit/test-qmp-cmds.c | 1 + scripts/qapi/expr.py | 62 ++++++++++++++----- scripts/qapi/schema.py | 13 +--- tests/qapi-schema/bad-if.err | 3 +- tests/qapi-schema/doc-good.out | 12 ++-- tests/qapi-schema/enum-if-invalid.err | 3 +- tests/qapi-schema/features-if-invalid.err | 2 +- tests/qapi-schema/qapi-schema-test.json | 20 +++--- tests/qapi-schema/qapi-schema-test.out | 59 ++++++++++-------- .../qapi-schema/struct-member-if-invalid.err | 2 +- 10 files changed, 106 insertions(+), 71 deletions(-) diff --git a/tests/unit/test-qmp-cmds.c b/tests/unit/test-qmp-cmds.c index 1b0b7d99df..83efa39720 100644 --- a/tests/unit/test-qmp-cmds.c +++ b/tests/unit/test-qmp-cmds.c @@ -51,6 +51,7 @@ FeatureStruct1 *qmp_test_features0(bool has_fs0, FeatureStruct0 *fs0, bool has_cfs1, CondFeatureStruct1 *cfs1, bool has_cfs2, CondFeatureStruct2 *cfs2, bool has_cfs3, CondFeatureStruct3 *cfs3, + bool has_cfs4, CondFeatureStruct4 *cfs4, Error **errp) { return g_new0(FeatureStruct1, 1); diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py index 496f7e0333..0a97a6f020 100644 --- a/scripts/qapi/expr.py +++ b/scripts/qapi/expr.py @@ -42,7 +42,14 @@ cast, ) -from .common import c_name +from .common import ( + IfAll, + IfAny, + IfNot, + IfOption, + IfPredicate, + c_name, +) from .error import QAPISemError from .parser import QAPIDoc from .source import QAPISourceInfo @@ -261,6 +268,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None: """ Normalize and validate the ``if`` member of an object. + The ``if`` field may be either a ``str``, a ``List[str]`` or a dict. + A ``str`` element or a ``List[str]`` will be normalized to + ``IfAll([str])``. + The ``if`` member may be either a ``str`` or a ``List[str]``. A ``str`` value will be normalized to ``List[str]``. @@ -281,25 +292,44 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None: if ifcond is None: return - if isinstance(ifcond, list): - if not ifcond: - raise QAPISemError( - info, "'if' condition [] of %s is useless" % source) - else: - # Normalize to a list - ifcond = expr['if'] = [ifcond] - - for elt in ifcond: - if not isinstance(elt, str): + def normalize(cond: Union[str, List[str], object]) -> IfPredicate: + if isinstance(cond, str): + if not cond.strip(): + raise QAPISemError( + info, + "'if' condition '%s' of %s makes no sense" + % (cond, source)) + return IfOption(cond) + if isinstance(cond, list): + cond = {"all": cond} + if not isinstance(cond, dict): raise QAPISemError( info, - "'if' condition of %s must be a string or a list of strings" - % source) - if not elt.strip(): + "'if' condition of %s must be a string, " + "a list of strings or a dict" % source) + if len(cond) != 1: raise QAPISemError( info, - "'if' condition '%s' of %s makes no sense" - % (elt, source)) + "'if' condition dict of %s must have one key: " + "'all', 'any' or 'not'" % source) + check_keys(cond, info, "'if' condition", [], + ["all", "any", "not"]) + oper, operands = next(iter(cond.items())) + if oper == "not": + return IfNot(normalize(operands)) + if not operands: + raise QAPISemError( + info, "'if' condition [] of %s is useless" % source) + if not isinstance(operands, list): + raise QAPISemError( + info, "'%s' condition of %s must be a list" % (oper, source)) + operands = [normalize(o) for o in operands] + return IfAll(operands) if oper == "all" else IfAny(operands) + + ifcond = expr.get('if') + if ifcond is None: + return + expr['if'] = normalize(ifcond) def normalize_members(members: object) -> None: diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 366a53ab64..61664a4c5e 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -19,22 +19,15 @@ import re from typing import Optional -from .common import ( - POINTER_SUFFIX, - IfAll, - IfOption, - c_name, - mcgen, -) +from .common import POINTER_SUFFIX, c_name, mcgen from .error import QAPISemError, QAPISourceError from .expr import check_exprs from .parser import QAPISchemaParser class QAPISchemaIfCond: - def __init__(self, ifcond=None): - pred_list = [IfOption(opt) for opt in ifcond or []] - self.pred = IfAll(pred_list) + def __init__(self, pred=None): + self.pred = pred def gen_doc(self): if self.pred: diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err index f83dee65da..454fbae387 100644 --- a/tests/qapi-schema/bad-if.err +++ b/tests/qapi-schema/bad-if.err @@ -1,2 +1,3 @@ bad-if.json: In struct 'TestIfStruct': -bad-if.json:2: 'if' condition of struct must be a string or a list of strings +bad-if.json:2: 'if' condition has unknown key 'value' +Valid keys are 'all', 'any', 'not'. diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index 6bf996f539..ca7e53f3b5 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -12,15 +12,15 @@ enum QType module doc-good.json enum Enum member one - if IfAll(['defined(IFONE)']) + if 'defined(IFONE)' member two - if IfAll(['defined(IFCOND)']) + if 'defined(IFCOND)' feature enum-feat object Base member base1: Enum optional=False object Variant1 member var1: str optional=False - if IfAll(['defined(IFSTR)']) + if 'defined(IFSTR)' feature member-feat feature variant1-feat object Variant2 @@ -29,7 +29,7 @@ object Object tag base1 case one: Variant1 case two: Variant2 - if IfAll(['IFTWO']) + if 'IFTWO' feature union-feat1 object q_obj_Variant1-wrapper member data: Variant1 optional=False @@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper enum SugaredUnionKind member one member two - if IfAll(['IFTWO']) + if 'IFTWO' object SugaredUnion member type: SugaredUnionKind optional=False tag type case one: q_obj_Variant1-wrapper case two: q_obj_Variant2-wrapper - if IfAll(['IFTWO']) + if 'IFTWO' feature union-feat2 alternate Alternate tag type diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err index 0556dc967b..3bb84075a9 100644 --- a/tests/qapi-schema/enum-if-invalid.err +++ b/tests/qapi-schema/enum-if-invalid.err @@ -1,2 +1,3 @@ enum-if-invalid.json: In enum 'TestIfEnum': -enum-if-invalid.json:2: 'if' condition of 'data' member 'bar' must be a string or a list of strings +enum-if-invalid.json:2: 'if' condition has unknown key 'val' +Valid keys are 'all', 'any', 'not'. diff --git a/tests/qapi-schema/features-if-invalid.err b/tests/qapi-schema/features-if-invalid.err index f63b89535e..724a810086 100644 --- a/tests/qapi-schema/features-if-invalid.err +++ b/tests/qapi-schema/features-if-invalid.err @@ -1,2 +1,2 @@ features-if-invalid.json: In struct 'Stru': -features-if-invalid.json:2: 'if' condition of 'features' member 'f' must be a string or a list of strings +features-if-invalid.json:2: 'if' condition of 'features' member 'f' must be a string, a list of strings or a dict diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 84b9d41f15..2d5e480b44 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -231,8 +231,8 @@ { 'union': 'TestIfUnion', 'data': { 'foo': 'TestStruct', - 'bar': { 'type': 'str', 'if': 'defined(TEST_IF_UNION_BAR)'} }, - 'if': 'defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)' } + 'union-bar': { 'type': 'str', 'if': 'defined(TEST_IF_UNION_BAR)'} }, + 'if': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] } { 'command': 'test-if-union-cmd', 'data': { 'union-cmd-arg': 'TestIfUnion' }, @@ -241,11 +241,10 @@ { 'alternate': 'TestIfAlternate', 'data': { 'foo': 'int', 'bar': { 'type': 'TestStruct', 'if': 'defined(TEST_IF_ALT_BAR)'} }, - 'if': 'defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)' } + 'if': {'all': ['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'] } } -{ 'command': 'test-if-alternate-cmd', - 'data': { 'alt-cmd-arg': 'TestIfAlternate' }, - 'if': 'defined(TEST_IF_ALT)' } +{ 'command': 'test-if-alternate-cmd', 'data': { 'alt-cmd-arg': 'TestIfAlternate' }, + 'if': {'all': ['defined(TEST_IF_ALT)', {'not': 'defined(TEST_IF_NOT_ALT)'}] } } { 'command': 'test-if-cmd', 'data': { @@ -259,7 +258,7 @@ { 'event': 'TEST_IF_EVENT', 'data': { 'foo': 'TestIfStruct', 'bar': { 'type': ['TestIfEnum'], 'if': 'defined(TEST_IF_EVT_BAR)' } }, - 'if': 'defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)' } + 'if': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] } # test 'features' @@ -290,6 +289,10 @@ 'data': { 'foo': 'int' }, 'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'] } ] } +{ 'struct': 'CondFeatureStruct4', + 'data': { 'foo': 'int' }, + 'features': [ { 'name': 'feature1', 'if': {'any': ['defined(TEST_IF_COND_1)', + 'defined(TEST_IF_COND_2)'] } } ] } { 'enum': 'FeatureEnum1', 'data': [ 'eins', 'zwei', 'drei' ], @@ -313,7 +316,8 @@ '*fs4': 'FeatureStruct4', '*cfs1': 'CondFeatureStruct1', '*cfs2': 'CondFeatureStruct2', - '*cfs3': 'CondFeatureStruct3' }, + '*cfs3': 'CondFeatureStruct3', + '*cfs4': 'CondFeatureStruct4' }, 'returns': 'FeatureStruct1', 'features': [] } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index c2d303aa18..f859bf648d 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -298,49 +298,49 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio object TestIfStruct member foo: int optional=False member bar: int optional=False - if IfAll(['defined(TEST_IF_STRUCT_BAR)']) - if IfAll(['defined(TEST_IF_STRUCT)']) + if 'defined(TEST_IF_STRUCT_BAR)' + if 'defined(TEST_IF_STRUCT)' enum TestIfEnum member foo member bar - if IfAll(['defined(TEST_IF_ENUM_BAR)']) - if IfAll(['defined(TEST_IF_ENUM)']) + if 'defined(TEST_IF_ENUM_BAR)' + if 'defined(TEST_IF_ENUM)' object q_obj_TestStruct-wrapper member data: TestStruct optional=False enum TestIfUnionKind member foo - member bar - if IfAll(['defined(TEST_IF_UNION_BAR)']) - if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']) + member union-bar + if 'defined(TEST_IF_UNION_BAR)' + if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)']) object TestIfUnion member type: TestIfUnionKind optional=False tag type case foo: q_obj_TestStruct-wrapper - case bar: q_obj_str-wrapper - if IfAll(['defined(TEST_IF_UNION_BAR)']) - if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']) + case union-bar: q_obj_str-wrapper + if 'defined(TEST_IF_UNION_BAR)' + if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)']) object q_obj_test-if-union-cmd-arg member union-cmd-arg: TestIfUnion optional=False - if IfAll(['defined(TEST_IF_UNION)']) + if 'defined(TEST_IF_UNION)' command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None gen=True success_response=True boxed=False oob=False preconfig=False - if IfAll(['defined(TEST_IF_UNION)']) + if 'defined(TEST_IF_UNION)' alternate TestIfAlternate tag type case foo: int case bar: TestStruct - if IfAll(['defined(TEST_IF_ALT_BAR)']) - if IfAll(['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)']) + if 'defined(TEST_IF_ALT_BAR)' + if IfAll(['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)']) object q_obj_test-if-alternate-cmd-arg member alt-cmd-arg: TestIfAlternate optional=False - if IfAll(['defined(TEST_IF_ALT)']) + if IfAll(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')]) command test-if-alternate-cmd q_obj_test-if-alternate-cmd-arg -> None gen=True success_response=True boxed=False oob=False preconfig=False - if IfAll(['defined(TEST_IF_ALT)']) + if IfAll(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')]) object q_obj_test-if-cmd-arg member foo: TestIfStruct optional=False member bar: TestIfEnum optional=False - if IfAll(['defined(TEST_IF_CMD_BAR)']) + if 'defined(TEST_IF_CMD_BAR)' if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']) command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree gen=True success_response=True boxed=False oob=False preconfig=False @@ -348,15 +348,15 @@ command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree command test-cmd-return-def-three None -> UserDefThree gen=True success_response=True boxed=False oob=False preconfig=False array TestIfEnumList TestIfEnum - if IfAll(['defined(TEST_IF_ENUM)']) + if 'defined(TEST_IF_ENUM)' object q_obj_TEST_IF_EVENT-arg member foo: TestIfStruct optional=False member bar: TestIfEnumList optional=False - if IfAll(['defined(TEST_IF_EVT_BAR)']) - if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']) + if 'defined(TEST_IF_EVT_BAR)' + if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)']) event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg boxed=False - if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']) + if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)']) object FeatureStruct0 member foo: int optional=False object FeatureStruct1 @@ -379,17 +379,21 @@ object FeatureStruct4 object CondFeatureStruct1 member foo: int optional=False feature feature1 - if IfAll(['defined(TEST_IF_FEATURE_1)']) + if 'defined(TEST_IF_FEATURE_1)' object CondFeatureStruct2 member foo: int optional=False feature feature1 - if IfAll(['defined(TEST_IF_FEATURE_1)']) + if 'defined(TEST_IF_FEATURE_1)' feature feature2 - if IfAll(['defined(TEST_IF_FEATURE_2)']) + if 'defined(TEST_IF_FEATURE_2)' object CondFeatureStruct3 member foo: int optional=False feature feature1 if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']) +object CondFeatureStruct4 + member foo: int optional=False + feature feature1 + if IfAny(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']) enum FeatureEnum1 member eins member zwei @@ -417,6 +421,7 @@ object q_obj_test-features0-arg member cfs1: CondFeatureStruct1 optional=True member cfs2: CondFeatureStruct2 optional=True member cfs3: CondFeatureStruct3 optional=True + member cfs4: CondFeatureStruct4 optional=True command test-features0 q_obj_test-features0-arg -> FeatureStruct1 gen=True success_response=True boxed=False oob=False preconfig=False command test-command-features1 None -> None @@ -429,13 +434,13 @@ command test-command-features3 None -> None command test-command-cond-features1 None -> None gen=True success_response=True boxed=False oob=False preconfig=False feature feature1 - if IfAll(['defined(TEST_IF_FEATURE_1)']) + if 'defined(TEST_IF_FEATURE_1)' command test-command-cond-features2 None -> None gen=True success_response=True boxed=False oob=False preconfig=False feature feature1 - if IfAll(['defined(TEST_IF_FEATURE_1)']) + if 'defined(TEST_IF_FEATURE_1)' feature feature2 - if IfAll(['defined(TEST_IF_FEATURE_2)']) + if 'defined(TEST_IF_FEATURE_2)' command test-command-cond-features3 None -> None gen=True success_response=True boxed=False oob=False preconfig=False feature feature1 diff --git a/tests/qapi-schema/struct-member-if-invalid.err b/tests/qapi-schema/struct-member-if-invalid.err index 42e7fdae3c..c18157c1f9 100644 --- a/tests/qapi-schema/struct-member-if-invalid.err +++ b/tests/qapi-schema/struct-member-if-invalid.err @@ -1,2 +1,2 @@ struct-member-if-invalid.json: In struct 'Stru': -struct-member-if-invalid.json:2: 'if' condition of 'data' member 'member' must be a string or a list of strings +struct-member-if-invalid.json:2: 'if' condition of 'data' member 'member' must be a string, a list of strings or a dict -- 2.29.0
