qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH 5/6] qapi: Add support for aliases


From: Markus Armbruster
Subject: Re: [PATCH 5/6] qapi: Add support for aliases
Date: Wed, 10 Feb 2021 14:47:55 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux)

Kevin Wolf <kwolf@redhat.com> writes:

> Am 10.02.2021 um 10:17 hat Markus Armbruster geschrieben:
>> Kevin Wolf <kwolf@redhat.com> writes:
>> 
>> > Introduce alias definitions for object types (structs and unions). This
>> > allows using the same QAPI type and visitor for many syntax variations
>> > that exist in the external representation, like between QMP and the
>> > command line. It also provides a new tool for evolving the schema while
>> > maintaining backwards compatibility during a deprecation period.
>> >
>> > Signed-off-by: Kevin Wolf <kwolf@redhat.com>
>> > ---
>> >  docs/devel/qapi-code-gen.txt           | 37 +++++++++++++++++++++++---
>> >  docs/sphinx/qapidoc.py                 |  2 +-
>> >  scripts/qapi/expr.py                   | 34 +++++++++++++++++++++--
>> >  scripts/qapi/schema.py                 | 27 +++++++++++++++----
>> >  scripts/qapi/types.py                  |  4 ++-
>> >  scripts/qapi/visit.py                  | 33 ++++++++++++++++++++---
>> >  tests/qapi-schema/test-qapi.py         |  7 ++++-
>> >  tests/qapi-schema/double-type.err      |  2 +-
>> >  tests/qapi-schema/unknown-expr-key.err |  2 +-
>> >  9 files changed, 130 insertions(+), 18 deletions(-)
>> >
>> > diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
>> > index 6906a06ad2..6da14d5275 100644
>> > --- a/docs/devel/qapi-code-gen.txt
>> > +++ b/docs/devel/qapi-code-gen.txt
>> > @@ -231,7 +231,8 @@ Syntax:
>> >                 'data': MEMBERS,
>> >                 '*base': STRING,
>> >                 '*if': COND,
>> > -               '*features': FEATURES }
>> > +               '*features': FEATURES,
>> > +               '*aliases': ALIASES }
>> >      MEMBERS = { MEMBER, ... }
>> >      MEMBER = STRING : TYPE-REF
>> >             | STRING : { 'type': TYPE-REF,
>> 
>> Missing: a forward reference, like we have for 'if' and 'features'.
>> Here's the obvious one:
>> 
>>    The optional 'if' member specifies a conditional.  See "Configuring
>>    the schema" below for more on this.
>> 
>>    The optional 'features' member specifies features.  See "Features"
>>    below for more on this.
>> 
>>   +The optional 'aliases' member specifies aliases.  See "Aliases" below
>>   +for more on this.
>> 
>> > @@ -286,13 +287,15 @@ Syntax:
>> >      UNION = { 'union': STRING,
>> >                'data': BRANCHES,
>> >                '*if': COND,
>> > -              '*features': FEATURES }
>> > +              '*features': FEATURES,
>> > +              '*aliases': ALIASES }
>> >            | { 'union': STRING,
>> >                'data': BRANCHES,
>> >                'base': ( MEMBERS | STRING ),
>> >                'discriminator': STRING,
>> >                '*if': COND,
>> > -              '*features': FEATURES }
>> > +              '*features': FEATURES,
>> > +              '*aliases': ALIASES }
>> >      BRANCHES = { BRANCH, ... }
>> >      BRANCH = STRING : TYPE-REF
>> >             | STRING : { 'type': TYPE-REF, '*if': COND }
>> 
>> Likewise.
>> 
>> > @@ -837,6 +840,34 @@ shows a conditional entity only when the condition is 
>> > satisfied in
>> >  this particular build.
>> >  
>> >  
>> > +=== Aliases ===
>> > +
>> > +Object types, including structs and unions, can contain alias
>> > +definitions.
>> > +
>> > +Aliases define alternative member names that may be used in the
>> > +external representation to provide a value for a member in the same
>> > +object or in a nested object.
>> 
>> "or one if its sub-objects"?  Not sure which is better.
>
> "nested object" feels a little clearer to me, but not that it's a big
> difference. If you feel "sub-object" is better, I can use that.
>
>> > +
>> > +Syntax:
>> > +    ALIAS = { '*alias': STRING,
>> > +              'source': [ STRING, ... ] }
>> 
>> You used non-terminal ALIASES above.  Please define it here.
>> 
>> I have doubts about the name 'alias'.  The alias is the complete thing,
>> and 'alias' is just one property of the complete thing.  I think 'name'
>> would be better.  Further evidence: below, you write "If 'alias' is
>> present" and "If 'alias' is not present".  I think both read better with
>> 'name' instead of 'alias'.
>
> Works for me.
>
>> > +
>> > +'source' is a list of member names representing the path to an object
>> > +member, starting from the type where the alias definition is
>> > +specified.
>> 
>> May 'source' be empty?  More on that below.
>
> No. Empty 'source' isn't the path to any object member, so it doesn't
> meet the requirement. If you prefer, we can explicitly specify a
> "non-empty list".

I think it's best to be tediously explicit here.

>> "where the definition is specified" feels a bit awkward, and "path"
>> slightly hand-wavy.  Let me try induction:
>> 
>>    'source' is a list of member names.  The first name is resolved in
>>    the same object.  Each subsequent member is resolved in the object
>>    named by the preceding member.
>> 
>> Differently awkward, I guess.
>
> Now you've left out what the purpose of it is. I think I'll combine your
> version with my first part ("'source' is a list of member names
> representing the path to an object member").
>
>> >              It may refer to another alias name.  It is allowed to use
>> > +a path that doesn't necessarily match an existing member in every
>> > +variant or even at all; in this case, the alias remains unused.
>> 
>> Aha!  Knowing this would've saved me some trouble in reviewing code.
>> 
>> I wrote on PATCH 1:
>> 
>>     I think updating the big comment in visitor.h for aliases would help.
>>     Let's postpone it until I've seen the rest of the series.
>> 
>> We can cover unused aliases right there.  Whether they also need to go
>> into contracts we'll see.
>
> Ok. I assume updating that big comment is still postponed because you
> saw the series, but didn't actually review all of it yet?

Writing documentation before I understand the code is probably not a
good use of my time, and my reviewer's time, too.  Getting there.

If you want to try, go right ahead.

>> What if only a *prefix* of 'source' matches?  E.g.
>> 
>>     'source': ['eins', 'zwei', 'drei']
>> 
>> and we have an object-valued member 'eins' (match), which has a member
>> 'zwei' (match), which is *not* an object.  Is that an error?  Is it
>> caught?
>
> This feels like a realistic case to me when 'eins' is a union type where
> some variants contain an object 'zwei' with a member 'drei' and others
> have 'zwei' as a non-object member.
>
> In this case, we want the alias not to match in the non-object 'zwei'
> case, but we do want it to match in another variant. So it is
> intentionally not an error.
>
> The QAPI generator could try to prove that there is at least one variant
> where the alias would actually be applied, but just leaving it unused
> when it doesn't match anywhere seemed good enough to me.

I see.

A typo can get your alias silently ignored.  A bit of a trap.  Testing
should catch this, of course.

Consider adding a comment in the QAPI generator along the lines "could
check this, but not sure it's worth our while", and a short note in
qapi-code-gen.txt to warn users about the trap.

>> > +
>> > +If 'alias' is present, then the single member referred to by 'source'
>> > +is made accessible with the name given in 'alias' in the type where
>> > +the alias definition is specified.
>> 
>> 'source' may not be empty here.  Correct?
>> 
>> If yes, please spell it out.
>
> Yes. Does spelling it out more explicitly in the description of 'source'
> suffice?

I think so, yes.

>> Double-checking I got it...  Say we have
>> 
>>     'alias': 'foo',
>>     'source': ['bar', 'baz']
>> 
>> where 'bar' is an object with a member 'baz'.
>> 
>> Then input "foo": FOOVAL gets interpreted like "bar": {"baz": FOOVAL}.
>> 
>> If input also contains "bar", we merge.  Duplicates are an error.
>> 
>> This is the case even when 'baz' is an object.  If you want to alias
>> member 'foo' of 'baz', you have to say
>> 
>>     'alias': 'foo',
>>     'source': ['bar', 'baz', 'foo']
>> 
>> Correct?
>
> Correct.
>
>> > +
>> > +If 'alias' is not present, then all members in the object referred to
>> > +by 'source' are made accessible in the type where the alias definition
>> > +is specified with the same name as they have in 'source'.
>> 
>> 'source' may not be empty here, either.  Correct?
>> 
>> If yes, please spell it out, and make sure the code catches it.
>
> Yes, as above. It's checked in check_aliases():
>
>         if not a['source']:
>             raise QAPISemError(info, "'source' must not be empty")
>
>> What if it resolve to a non-object?  Is that an error?  Is it caught?
>
> Same as above, it just doesn't match.
>
>> Continuing the double-checking...  Say we have
>> 
>>     # alias missing
>>     'source': ['gnu']
>> 
>> where 'gnu' is an object with a member 'gnat'.
>> 
>> Input "gnat": GNATVAL gets interpreted like "gnu": {"gnat": GNATVAL}.
>> 
>> Correct?
>
> Yes.
>
>> The document could use examples.  Feel free to steal mine.
>> 
>> I think we should talk about 'alias' first, and only then about
>> 'source'.  It matches their order in the schema, and also matches how I
>> think about aliases, namely "this name actually means that".  Here,
>> "this name" is 'alias', and "that" is 'source'.
>> 
>> > +
>> > +
>> 
>> Don't get deceived by my comments; this is a pretty good start.
>> 
>> I wish I had studied this part before PATCH 1.
>> 
>> >  === Documentation comments ===
>> >  
>> >  A multi-line comment that starts and ends with a '##' line is a
>> 
>> I intend to look at the remainder shortly.
>
> Ok. I'll prepare for a context switch to actually be able to address
> your comments on the other patches and to figure out what I had already
> addressed in my branch during your last review attempt.

I intend to look at the remainder of PATCH 5 this afternoon.

> I thought I had done a better than average job on documenting the code
> (at least compare to my other patches), but doesn't seem so...

Writing excellent documentation for code you just wrote is *hard*!  I
think yours was pretty good, actually.




reply via email to

[Prev in Thread] Current Thread [Next in Thread]