On 11/13/22 17:56, Alejandro Colomar wrote:>>> On 11/13/22 17:28, Alejandro Colomar wrote: >>>> SYNOPSIS: >>>> >>>> unary-operator:  . identifier >>>> >>>> >>>> DESCRIPTION: >>>> >>>> -  It is not an lvalue. >>>> >>>>     -  This means sizeof() and _Lengthof() cannot be applied to them. >>> >>> Sorry, the above is a thinko. >>> >>> I wanted to say that, like sizeof() and _Lengthof(), you can't assign to it. >>> >>>>     -  This prevents ambiguity with a designator in an initializer-list >>>> within a nested braced-initializer. >>>> >>>> -  The type of a .identifier is always an incomplete type. >> >> Or rather, more easily prohibit explicitly using typeof(), sizeof(), and >> _Lengthof() to it. > > Hmm, this is not enough.  Pointer arithmetics are interesting, and for that, you > need to implicitly know the sizeof(*.p). > > How about allowing only integral types or pointers to integral types? I've been thinking about keeping the number of passes as low as possible, while allowing most useful expressions: Maybe forcing some ordering can help: - The type of a .initializer is complete after the opening parenthesis of the function-declarator (if it refers to a parameter) or after the opening brace of a braced-initializer, if it refers to a struct/union member, except when the type is a variably-modified type, which will be complete after the closing parenthesis or brace respectively. I'm not sure I got the wording precisely, or if I covered all cases (like types that cannot be completed for other reasons, even after the closing ')' or '}'. > >> >>>> >>>>     -  This prevents circular dependencies involving sizeof() or _Lengthof(). >>>> >>>> -  Shadowing rules apply. >>>> >>>>     -  This prevents ambiguity. >>>> >>>> >>>> EXAMPLES: >>>> >>>> >>>> -  Valid examples (libc): >>>> >>>>         int >>>>         strncmp(const char s1[.n], >>>>                 const char s2[.n], >>>>                 size_t n); >>>> >>>>         int >>>>         cacheflush(void addr[.nbytes], >>>>                    int nbytes, >>>>                    int cache); >>>> >>>>         long >>>>         mbind(void addr[.len], >>>>               unsigned long len, >>>>               int mode, >>>>               const unsigned long nodemask[(.maxnode + ULONG_WIDTH ‐ 1) >>>>                                            / ULONG_WIDTH], >>>>               unsigned long maxnode, unsigned int flags); >>>> >>>>         void * >>>>         bsearch(const void key[.size], >>>>                 const void base[.size * .nmemb], >>>>                 size_t nmemb, >>>>                 size_t size, >>>>                 int (*compar)(const void [.size], const void [.size])); >>>> >>>> -  Valid examples (my own): >>>> >>>>         void >>>>         ustr2str(char dst[restrict .len + 1], >>>>                  const char src[restrict .len], >>>>                  size_t len); >>>> >>>>         char * >>>>         stpecpy(char dst[.end - .dst + 1], >>>>                 char *restrict src, >>>>                 char end[1]); >>>> >>>> -  Valid examples (from this thread): >>>> >>>>     - >>>>         struct s { int a; }; >>>>         void f(int a, int b[((struct s) { .a = 1 }).a]); >>>> >>>>         Explanation: >>>>         -  Because of shadowing rules, .a=1 refers to the struct member. >>>>            -  Also, if .a referred to the parameter, it would be an rvalue, >>>> so it wouldn't be valid to assign to it. >>>>         -  (...).a refers to the struct member too, since otherwise an >>>> rvalue is not expected there. >>>> >>>>     - >>>>         void foo(struct bar { int x; char c[.x] } a, int x); >>>> >>>>         Explanation: >>>>         -  Because of shadowing rules, [.x] refers to the struct member. >>>> >>>>     - >>>>         struct bar { int y; }; >>>>         void foo(char p[((struct bar){ .y = .x }).y], int x); >>>> >>>>         Explanation: >>>>         -  .x unambiguously refers to the parameter. >>>> >>>> -  Undefined behavior: >>>> >>>>     - >>>>         struct bar { int y; }; >>>>         void foo(char p[((struct bar){ .y = .y }).y], int y); >>>> >>>>         Explanation: >>>>         -  Because of shadowing rules, =.y refers to the struct member. >>>>         -  .y=.y means initialize the member with itself (uninitialized use). >>>>         -  (...).y refers to the struct member, since otherwise an rvalue is >>>> not expected there. >>>> >>>> -  Constraint violations: >>>> >>>>     - >>>>         void foo(char (*a)[sizeof *.b], char (*b)[sizeof *.a]); >>>> >>>>         Explanation: >>>>         -  sizeof(*.b): Cannot get size of incomplete type. >>>>         -  sizeof(*.a): Cannot get size of incomplete type. >>>> >>>>     - >>>>         void f(size_t s, int a[sizeof(1) = 1]); >>>> >>>>         Explanation: >>>>         -  Cannot assign to rvalue. >>>> >>>>     - >>>>         void f(size_t s, int a[.s = 1]); >>>> >>>>         Explanation: >>>>         -  Cannot assign to rvalue. >>>> >>>>     - >>>>         void f(size_t s, int a[sizeof(.s)]); This should actually be valid. >>>> >>>>         Explanation: >>>>         -  sizeof(.s): Cannot get size of incomplete type. >>>> >>>> >>>> Does this idea make sense to you? >>>> >>>> >>>> Cheers, >>>> Alex >>> >> > --