[Development] [Feature] Q_INFO: Annotations for classes, methods, properties and enums

Olivier Goffart olivier at woboq.com
Thu Sep 5 09:09:33 CEST 2013


Hi,

On Wednesday 04 September 2013 22:32:34 Stefan Merettig wrote:
> > I think adding general information to method or properties are a good
> > idea.
> > The current tag system is indeed very limited.
> > But before trying to get something to general, we need to focus on use
> > cases.
> 
> Alright, I present to you: libAuth, a authentication framework which
> lets the dev write simple or complex permission rules right in the code. As
> modern business logic demands that code is flexible, there's not a big issue
> with hard-coding certain key values.
> 
>    // We need to allows access to employees and the daemons.
>    Q_INFO("libAuth.allowedGroups" ARGS "admin", "moderators", "staff",
> "daemons")
>    Q_INFO("libAuth.db.connection" ARGS "BillingPermissions")
>    class BillingService : public QObject {
> 
>      // Customer support may not use this!
>      Q_INFO("libAuth.disallowedGroups" ARGS "support")
>      public void depositFunds (int credits);
> 
>      // Maintenance windows must be respected. Only admins.
>      Q_INFO("libAuth.allowedTimeRange" ARGS "20:00", "6:00")
>      Q_INFO("libAuth.allowedGroups" ARGS "admin")
>      public void startMaintenance ();
> 
>    };

Thank for your example.  It really helps to have real world use case to be 
able to design a feature that suits the needs.

For example, i don't see the need for two arguments here:
Q_CLASSINFO("libAuth.allowedGroups", "admin,moderators,staff")

Q_INFO("libAuth.allowedTimeRange", "20:00-6:00")

Rationale: coma separated lists are easy to parse (QString::split)
so you'd do:   
QStringList allowed = QString(mobj->info("libAuth.allowedGroup")).split(',');

TimeRange tr = TimeRange::fromString(method.info("libAuth.allowedTimeRange"));




> Another use-case would be to do contract-based programming or what the right
> buzzword for that was. (For people who don't know what that is: You define
> allowed values somewhere where a compiler can error if you don't comply to
> these.) We may not be able to get the compiler to throw, but we could nicely
> combine this into a API which gets exposed to a scripting environment:
> 
>    Q_INFO("Contract.times" ARGS "0", "99")
>    Q_INFO("Contact.itemId.checkSlot" ARGS "checkIfItemIdIsValid")
>    bool addItemToCart (int itemId, int times);
> 
> This is already a pretty complex use-case. In this example, I say that
> the arguments, which are matched by their name, must follow certain
> "rules". That is, the "times" argument must be between 0 and 99. As the
> check if itemId is pretty complex, we tell the system to instead invoke a
> slot which tests it for us.

This is less like a real example, but again, you can just use a coma separated 
list. or some specific appropriate expression.

Q_INFO("Contract", "0 < times < 99 && checkIfItemIdIsValid(itemid)")

> Now try this with a simple key-value store. If we'd not allow multiple
> values, then this would simply not possible without really ugly hacks.
> Period.
> If we would allow multiple values, but only a argument per item .. well,
> that'd create a mess for no good. It at least wouldn't improve readability.
[...]
> So, if a user wants to accept a variable amount of arguments, you want him
> to use "argX", while the internal code counts upwards, and the user of this
> feature has to keep track of the number scheme too? That's two levels of
> unneded complexity with a big mess-up potentional. With the proposed API the
> user can't simply write
>    Q_INFO("foo" = "bar")
>    Q_INFO("foo" = "baz")
> as that would interfere with the concept of a plain key-value store.

Q_INFO("foo" = "bar,baz")

> Of course you can abuse them to do something "better", but meta programming
> is already strange enough without weird string hacks all over the place. A
> clean solution which is easily extend-able in the future may encourage
> others to use it instead of using those hacks, or not using the current
> system at all because these hacks are so ugly and weird.
> 
> What I would be fine with is allowing a alternative syntax (So allowing
> both):
>    Q_INFO("<Name>" [ARGS <Value>, ...])
>    Q_INFO("<Name>", "<Value>")

Remember also that variadic macro requires C++11 and we don't make it 
mandatory yet.
So your macro need to have a fixed number of coma.

> The QMetaInfo class *could* even have a convenience method like
> firstArgument(). I'm totally fine with all of these fixes, but the currently
> available facts don't show me why my approach is or may be harmful.

One of the API principles is:
"Make common tasks easy. Rare tasks should be possible but not the focus. 
Solve the specific problem; don't make the solution overly general when this is 
not needed."

Most case i see will just be key/value.
For the cases where the value is a list,  you can just use QString::split, 
that's easy. For all the other case, you will have to have a custom parser 
anyway.

> > BTW,  All of that can be done currently by abusing Q_CLASSINFO
> 
> I also could write a const char* as static class member. That's not too
> far from abusing Q_CLASSINFO for something like this.

That's right. and that's why I believe Q_INFO would be a good thing for 
methods.   But it still has to be designed properly


> > Notice that we could also re-use the "tag" field of the data array for some
> > purpose (it would mean the old tag or the info count depending on one
> > of the bit or of the revision)
> 
> Breaks compatibility. If no one cares, well we could of course re-use that
> field as "pointer" to a data table in the meta-data structure. But that's
> really ugly. And the last thing I want is storing something like
> "Foo=Bar Baz=House" as string-data. That'd be a nightmare to parse. We
> did that in Qt4 for the method signature, we now store every part
> individually. We should not do the same mistake again. 

The way I see it:  (look for the comments with ** )

static const uint qt_meta_data_FOO[] = {
  // content:
       8,       // revision   ** now we upgraded to  8 **
       0,       // classname
       0,    0, // classinfo
       2,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       MetaObjectFlags::HasInfo,  // flags  ** alternative, a flag here **
       0,       // signalCount
       
 
 // slots: name, argc, parameters, tag / **or info_count** , flags
     1,    2,   24,   1, 0x0a | MethodFlags::HasInfo, //** we can use the flag
     4,    1,   32,   0x80000000 | 1, 0x0a,  //** or a bit in the tag location

   // slots: parameters
    QMetaType::Void, QMetaType::QString,    3,  // Slot 1
    QMetaType::Void, 0x80000000 | 5,    2, 
    42, 42  //  ** Infos comme directly after the parameters  **
     //(slot 2 starts here) ...
      

> The only solution
> would be storing JSON instead - If that's a solution I don't know.

JSON could be an option too since moc already has a json parser and binary 
json exporter.


-- 
Olivier

Woboq - Qt services and support - http://woboq.com - http://code.woboq.org





More information about the Development mailing list