[cfe-dev] [RFC] Preliminary patch to support MSVC __declspec(property)

John McCall rjmccall at apple.com
Mon Dec 17 10:34:40 PST 2012


On Dec 17, 2012, at 1:06 AM, endlessroad1991 at gmail.com wrote:
> On Sat, Dec 15, 2012 at 2:50 AM, John McCall <rjmccall at apple.com> wrote:
> On Dec 13, 2012, at 10:01 PM, endlessroad1991 at gmail.com wrote:
>> On Thu, Dec 13, 2012 at 4:23 PM, John McCall <rjmccall at apple.com> wrote:
>> On Dec 12, 2012, at 11:45 PM, endlessroad1991 at gmail.com wrote:
>>> Thanks, that's a very detailed and thorough comment.
>>>  
>>> MSDN page for __declspec(property): http://msdn.microsoft.com/en-us/library/yhfk0thd.aspx
>>> we can declare "array-like" property:
>>>         __declspec(property(get=GetX, put=PutX)) int x[][];
>>> and access it with indices:
>>>         var = obj->x[expr1][expr2];
>>> it will be translated into:
>>>         var = obj->GetX(expr1, expr2);
>>>  
>>> And that's what "ParamCount" in "PropertyAttr" is for.
>>> We don't know how many params until we parse to "int x[][]", so we have to modify the Attr when we get here.
>> 
>> That documentation says you can declare this as "int x[]" and it permits an arbitrary amount of subscripts, but apparently you can *also* add multiple []s.  It would be good to understand the requirements and limitations of this language feature as implemented in MSVC.  In particular:
>> 
>> 1.  Can you directly template these declarations? (template <class T> _declspec(property) T foo;)
>> No 
>> 1a.  Are any of the following answers different if T is dependent, and if so, are those likely to be bugs or actually intended?
>> 1b.  What if you have template <class T> struct A { _declspec(property) T foo; }; and then instantiate the template at T=int[]?
>> Yes, this works fine. 
> 
> Does it add an expected index argument?
>   template <class T> struct A {
>     int *getFoo() { .. }
>     int getFoo(int index) { ... }
>     _declspec(property(get=getFoo)) T foo;
>   };
> 
>   int test(A<int[]> &a) {
>     return a.foo[0];
>   }
> 
> Does this call getFoo() or getFoo(int)?
> Doesn't compile. Return type of getters must be exactly the same as property type. 

Does it not compile at template-parse time, at instantiation time, or at time of first use?  In other words, does it not compile if you just have the template definition but don't use it?  What about if you have the template definition, and you instantiate it at some type, but you don't use the property declaration?

> 
>> 2.  What's the relationship between []s and the number of indices?
>> 2a.  Given _declspec(property) int x[], can you in fact use multiple indices with it, like foo.x[2][3]?
>> No 
> 
> Okay, that actually explicitly contradicts MS's own documentation that you linked above.  Make sure you test this with an unscriptable element type — like int, not int*.
> If getter with more/less parameters is defined, you can. Otherwise, you can't.

Interesting.

> 
>> 2b.  If so, what happens if the "element type" is itself subscriptable?
>> 2b.i. _declspec(property) std::vector<int> x[] and foo.x[0][1]?
>> No 
> 
> What do you mean by "no" here?  Are you saying this program was rejected?  What's the error?
> Doesn't compile. Error says "GetX() doesn't accept 2 arguments", just like std::vector<int> is not subscriptable. 
> 
>> 2b.ii. _declspec(property) int *x[] and foo.x[0][1]?
>> Yes, this works fine. 
>> 2c.  Do parentheses change these answers at all?
>> 2c.i. _declspec(property) int* x[] and (foo.x[0])[1]?
>> Yes, this works fine.
> 
> What about _declspec(property) int x[][] and (foo.x[0])[1]?
> Compiles and runs as expected. 

Okay.

> 
>> 2c.ii. For that matter, given _declspec(property) int x[], is (foo.x)[0] legal at all?
>> Yes, this works fine.
> 
> Okay, good.
> 
>> 2d.  Given _declspec(property) int x[][], can you use fewer subscripts than that?  Can you still use more?
>> Answer for both questions is NO.
> 
> Good.
> 
>> 3.  Are there restrictions on the element type?
>> 3a.  Can it be a reference?
>> Yes 
> 
> Okay.  If I have _declspec(property(get=getFoo)) int &foo, can I do
>   x.foo = 7
> with the semantics of x.getFoo() = 7?
> No. Seems that it's not that smart. All assignments will be translated into setters.

Well, that does make it simple.
 
> 
>> 3b.  Can it be a bounded array type?  (_declspec(property) int x[][10])
>> No. Seems that whatever you put in the brackets will be ignored, and treated as a param to getter/setter. 
> 
> Okay.
> 
>> 3c.  Can it be an unbounded array type if you use, e.g., a typedef?  (typedef int intarr[]; _declspec(property) intarr x;, or intarr x[] for that matter.)  Does this change the behavior of subscripting?
>> Surprisingly, in this situation, the [] in intarr is still interpreted as a param to getter/setter. 
> 
> Alright.  My question above about template arguments is designed to figure out whether this is a hack for non-dependent type sugar, or whether it works through anything that extends the type this way.
> 
>> 4.  What exactly does the element type do there?  Is it just for type-checking the property against the accessors?
>> 4a.  I'd been assuming that the accessors were selected by overload resolution at access time using the name given/inferred in the property attribute.  Is this not true?  Are they selected/filtered by initial type-checking somehow?
>> It is true. You can define "T GetV(int, int)" and "T GetV(double, double)". And getter will choose the best match like normal overload functions.
> 
> Okay.
> 
>> 4b.  Is access control checked on the accessors from the declaration point of the property or from the use point?
>> 4c.  Can the accessors be inherited from base classes?  Are normal ambiguity controls enforced?  Can you scope-qualify the accessor names, e.g. get=Base1::getElt?
>> Inherit: yes. Ambiguity: yes. Scope-quailify: No, seems that there can only be one identifier after get= or put=. 
>> 4d.  Are the index types in the accessors limited in some way, or can they be e.g. arbitrary class types?
>> They can be arbitary types. 
>> 
>> I'm sure we can find more questions. :)
>> 
>>> Considering this situation, is it still possible to do it in your suggested way?
>> 
>> Sure, that's not really a problem.  One very important thing — something I seem to have unconscionably forgotten put in my initial review — is that this is way, way more than just a property on a FieldDecl;  this really needs to be some new kind of Decl, probably named MSPropertyDecl.  That node would then be a reasonable place to stash things like the expected (minimum?) number of indices and any other important information from type-checking the declaration, e.g. the possibly-filtered lookup results.  You'll probably need to recognize the presence of the attribute in the parser and enter into a completely different Sema entrypoint, kindof like how the 'friend' keyword does.
>> 
>> John.
>> 
>>  
>> Based on answers of these questions, let's guess how VC implements property.
>> I think the design principle for them is, reuse existing compiler code as much as possible, and make the solution as simple as possible.
>> Here are my opinions:
>> 1. Any [], [10], or [] that comes from typedef, is treated as a param to getter/setter. Hence, they lose their origin semantics meaning.
> 
> Right, you need to walk through type sugar.  If template instantiation can't add to the number of arguments, then you'll need to do this at template parse time and then remember it in the MSPropertyDecl.
> 
>> 2. For accessors, they will be translated into getter/setter.
>>     i. property can be private, as long as getter/setter is public, they work well.
> 
> Oh, now that's interesting.  Okay, you'll have to suppress access control on a member lookup that resolves to one of these.
> 
>>     ii. getter/setter will be chosen like any normal overloaded functions.
>> 3. property are treated like normal members. So they can be inherited, and have ambiguity problem when a class has multiple base classes.
>> The only problem here, is when the element type is subscriptable itself.
>>     i. When it's pointer type, subscripting the property works as expected.
>>     ii. When it's class type which has overwritten operator[] (no matter vector or self-defined class),  subscripting the property doesn't compile.
>> I can't guess why this happens.
> 
> If int x[] can, in fact, take more than one subscript (as documented), then I would guess that the rule is that all possible subscripts are consumed by the property *unless* the element type is "obviously subscriptable", which is probably defined as "a pointer type" and doesn't recognize classes with operator[]s.  Interesting corner cases here would be
>   int *&x[]; // does it realize that this result type is still "obviously subscriptable"?
>   int *x[]; // if I do a.x[0][1][2], does it steal both [0] and [1] for the subscript, or does it steal just [0] and then try to subscript int* twice (which will fail, of course)?
> Here's two good samples, I think:
>  
> #include <iostream>
> class C
> {
> public:
>  __declspec(property(get=GetA, put=SetA)) int **A[];
>  int** GetA(int i) { std::cout << "Get\n"; return &v; }
>  void SetA(int i, int **value) { std::cout << "Set\n";}
>  int *v;
> };
> int main()
> {
>  C c;
>  c.A[123][0][0] = 2; // GetA()
>  // c.A[123][0] = 2; // doesn't compile! "Cannot convert int to int*"

Well, that's true, you can't.  But if the RHS were (int*) 2, this would presumably compile (calling GetA()).

>  c.A[1] = 0; // SetA()
> }
>  
>  
> #include <iostream>
> class C
> {
> public:
>  __declspec(property(get=GetA, put=SetA)) int *A[];
>  int* GetA(int i) { std::cout << "Get 1\n"; return v; }
>  int* GetA(int i, int j) { std::cout << "Get 2\n"; return v; }
>  void SetA(int i, int *value) { std::cout << "Set 1\n"; }
>  void SetA(int i, int j, int *value) { std::cout << "Set 2\n"; }
>  int *v;
> };
> int main()
> {
>  C c;
>  c.A[123][0] = 2; // Get 1
>  c.A[1] = 0; // Set 1
> }

Very nice test case.  Okay, one more:

> class C
> {
> public:
>  __declspec(property(get=GetA, put=SetA)) int *A[];
>  int* GetA(int i) { std::cout << "Get 1\n"; return v; }
>  int* GetA(std::string i, std::string j) { std::cout << "Get 2\n"; return v; }
>  void SetA(int i, int *value) { std::cout << "Set 1\n"; }
>  void SetA(std::string i, std::string j, int *value) { std::cout << "Set 2\n"; }
>  int *v;
> };
> int main()
> {
>  C c;
>  c.A["a"]["b"][4] = 2;
>  c.A["a"]["b"] = 0;
> }

Do these lines compile?

If not, and the error's something like "cannot subscript an int* with a const char(&)[2]",
then I think the rule is that you look at the getters and setters (at class parse time),
find the minimum number of arguments, and then consume that number of
subscripts.  (It'd be good to figure out what happens if the getters and setters are
mismatched in argument counts.)

If so, then the rule may have to be "apply overload resolution with successively
more and more subscripts, stopping as soon as something succeeds".

John.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20121217/82a09de2/attachment.html>


More information about the cfe-dev mailing list