diff --git a/scripts/qapi.py b/scripts/qapi.py index 6c4d554165..b7a25e4759 100644 --- a/scripts/qapi.py +++ b/scripts/qapi.py @@ -812,11 +812,26 @@ def check_alternate(expr, info): if not qtype: raise QAPISemError(info, "Alternate '%s' member '%s' cannot use " "type '%s'" % (name, key, value)) - if qtype in types_seen: + conflicting = set([qtype]) + if qtype == 'QTYPE_QSTRING': + enum_expr = enum_types.get(value) + if enum_expr: + for v in enum_expr['data']: + if v in ['on', 'off']: + conflicting.add('QTYPE_QBOOL') + if re.match(r'[-+0-9.]', v): # lazy, could be tightened + conflicting.add('QTYPE_QINT') + conflicting.add('QTYPE_QFLOAT') + else: + conflicting.add('QTYPE_QINT') + conflicting.add('QTYPE_QFLOAT') + conflicting.add('QTYPE_QBOOL') + if conflicting & set(types_seen): raise QAPISemError(info, "Alternate '%s' member '%s' can't " "be distinguished from member '%s'" % (name, key, types_seen[qtype])) - types_seen[qtype] = key + for qt in conflicting: + types_seen[qt] = key def check_enum(expr, info): diff --git a/tests/Makefile.include b/tests/Makefile.include index 75893838e5..f42f3dfa72 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -342,6 +342,8 @@ qapi-schema += alternate-array.json qapi-schema += alternate-base.json qapi-schema += alternate-clash.json qapi-schema += alternate-conflict-dict.json +qapi-schema += alternate-conflict-enum-bool.json +qapi-schema += alternate-conflict-enum-int.json qapi-schema += alternate-conflict-string.json qapi-schema += alternate-empty.json qapi-schema += alternate-nested.json diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json index d566cca816..3d7881279c 100644 --- a/tests/qapi-schema/alternate-conflict-dict.json +++ b/tests/qapi-schema/alternate-conflict-dict.json @@ -1,4 +1,4 @@ -# we reject alternates with multiple object branches +# alternate branches of object type conflict with each other { 'struct': 'One', 'data': { 'name': 'str' } } { 'struct': 'Two', diff --git a/tests/qapi-schema/alternate-conflict-enum-bool.err b/tests/qapi-schema/alternate-conflict-enum-bool.err new file mode 100644 index 0000000000..0dfc00242d --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-enum-bool.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-conflict-enum-bool.json:4: Alternate 'Alt' member 'two' can't be distinguished from member 'one' diff --git a/tests/qapi-schema/alternate-conflict-enum-bool.exit b/tests/qapi-schema/alternate-conflict-enum-bool.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-enum-bool.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/alternate-conflict-enum-bool.json b/tests/qapi-schema/alternate-conflict-enum-bool.json new file mode 100644 index 0000000000..bff25c3147 --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-enum-bool.json @@ -0,0 +1,6 @@ +# alternate branch of 'enum' type that conflicts with bool +{ 'enum': 'Enum', + 'data': [ 'aus', 'off' ] } +{ 'alternate': 'Alt', + 'data': { 'one': 'Enum', + 'two': 'bool' } } diff --git a/tests/qapi-schema/alternate-conflict-enum-bool.out b/tests/qapi-schema/alternate-conflict-enum-bool.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-conflict-enum-int.err b/tests/qapi-schema/alternate-conflict-enum-int.err new file mode 100644 index 0000000000..2cc8e7b9aa --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-enum-int.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-conflict-enum-int.json:4: Alternate 'Alt' member 'two' can't be distinguished from member 'one' diff --git a/tests/qapi-schema/alternate-conflict-enum-int.exit b/tests/qapi-schema/alternate-conflict-enum-int.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-enum-int.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/alternate-conflict-enum-int.json b/tests/qapi-schema/alternate-conflict-enum-int.json new file mode 100644 index 0000000000..beb428c10b --- /dev/null +++ b/tests/qapi-schema/alternate-conflict-enum-int.json @@ -0,0 +1,6 @@ +# alternate branches of 'enum' type that conflicts with numbers +{ 'enum': 'Enum', + 'data': [ '1', '2', '3' ] } +{ 'alternate': 'Alt', + 'data': { 'one': 'Enum', + 'two': 'int' } } diff --git a/tests/qapi-schema/alternate-conflict-enum-int.out b/tests/qapi-schema/alternate-conflict-enum-int.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err index fc523b0879..fe2f188295 100644 --- a/tests/qapi-schema/alternate-conflict-string.err +++ b/tests/qapi-schema/alternate-conflict-string.err @@ -1 +1 @@ -tests/qapi-schema/alternate-conflict-string.json:4: Alternate 'Alt' member 'two' can't be distinguished from member 'one' +tests/qapi-schema/alternate-conflict-string.json:2: Alternate 'Alt' member 'two' can't be distinguished from member 'one' diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json index 72f04a820a..85adbd4adc 100644 --- a/tests/qapi-schema/alternate-conflict-string.json +++ b/tests/qapi-schema/alternate-conflict-string.json @@ -1,6 +1,4 @@ -# we reject alternates with multiple string-like branches -{ 'enum': 'Enum', - 'data': [ 'hello', 'world' ] } +# alternate branches of 'str' type conflict with all scalar types { 'alternate': 'Alt', 'data': { 'one': 'str', - 'two': 'Enum' } } + 'two': 'int' } } diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 303f2b23cd..17649c6398 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -101,12 +101,14 @@ # for testing use of 'number' within alternates { 'alternate': 'AltEnumBool', 'data': { 'e': 'EnumOne', 'b': 'bool' } } { 'alternate': 'AltEnumNum', 'data': { 'e': 'EnumOne', 'n': 'number' } } -{ 'alternate': 'AltNumStr', 'data': { 'n': 'number', 's': 'str' } } { 'alternate': 'AltNumEnum', 'data': { 'n': 'number', 'e': 'EnumOne' } } { 'alternate': 'AltEnumInt', 'data': { 'e': 'EnumOne', 'i': 'int' } } { 'alternate': 'AltIntNum', 'data': { 'i': 'int', 'n': 'number' } } { 'alternate': 'AltNumInt', 'data': { 'n': 'number', 'i': 'int' } } +# for testing use of 'str' within alternates +{ 'alternate': 'AltStrObj', 'data': { 's': 'str', 'o': 'TestStruct' } } + # for testing native lists { 'union': 'UserDefNativeListUnion', 'data': { 'integer': ['int'], diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 3081091818..9f68610dc2 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -22,10 +22,10 @@ alternate AltNumInt tag type case n: number case i: int -alternate AltNumStr +alternate AltStrObj tag type - case n: number case s: str + case o: TestStruct event EVENT_A None boxed=False event EVENT_B None diff --git a/tests/test-keyval.c b/tests/test-keyval.c index c556b1b117..c3be00524c 100644 --- a/tests/test-keyval.c +++ b/tests/test-keyval.c @@ -614,22 +614,26 @@ static void test_keyval_visit_alternate(void) Error *err = NULL; Visitor *v; QDict *qdict; - AltNumStr *ans; + AltStrObj *aso; AltNumInt *ani; + AltEnumBool *aeb; /* * Can't do scalar alternate variants other than string. You get * the string variant if there is one, else an error. + * TODO make it work for unambiguous cases like AltEnumBool below */ - qdict = keyval_parse("a=1,b=2", NULL, &error_abort); + qdict = keyval_parse("a=1,b=2,c=on", NULL, &error_abort); v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); QDECREF(qdict); visit_start_struct(v, NULL, NULL, 0, &error_abort); - visit_type_AltNumStr(v, "a", &ans, &error_abort); - g_assert_cmpint(ans->type, ==, QTYPE_QSTRING); - g_assert_cmpstr(ans->u.s, ==, "1"); - qapi_free_AltNumStr(ans); - visit_type_AltNumInt(v, "a", &ani, &err); + visit_type_AltStrObj(v, "a", &aso, &error_abort); + g_assert_cmpint(aso->type, ==, QTYPE_QSTRING); + g_assert_cmpstr(aso->u.s, ==, "1"); + qapi_free_AltStrObj(aso); + visit_type_AltNumInt(v, "b", &ani, &err); + error_free_or_abort(&err); + visit_type_AltEnumBool(v, "c", &aeb, &err); error_free_or_abort(&err); visit_end_struct(v, NULL); visit_free(v); diff --git a/util/keyval.c b/util/keyval.c index 93d5db6b59..7dbda62305 100644 --- a/util/keyval.c +++ b/util/keyval.c @@ -65,11 +65,11 @@ * denote numbers, true, false or null. The special QObject input * visitor returned by qobject_input_visitor_new_keyval() mostly hides * this by automatically converting strings to the type the visitor - * expects. Breaks down for alternate types and type 'any', where the - * visitor's expectation isn't clear. Code visiting such types needs - * to do the conversion itself, but only when using this keyval - * visitor. Awkward. Alternate types without a string member don't - * work at all. + * expects. Breaks down for type 'any', where the visitor's + * expectation isn't clear. Code visiting 'any' needs to do the + * conversion itself, but only when using this keyval visitor. + * Awkward. Note that we carefully restrict alternate types to avoid + * similar ambiguity. * * Additional syntax for use with an implied key: *