jyknight wrote:

I now see there's two different parts of the problem to worry about:

First case: A redeclaration in a _different_ scope. This always defines a new 
distinct type. This was valid before C23, and is still valid regardless of 
whether the type is compatible. (In contrast with function decls, which must be 
globally compatible across the entire TU in all scopes). This is the case I was 
thinking about earlier. This part really feels pretty-much straight-forward.

E.g. The following example defines two different `struct Foo` types. The second 
does not inherit any semantics from the former, and the code is valid before 
and after C23.
```
struct Foo { [[deprecated("hello")]] int x; };
int test2() {
  struct Foo { int x; };
  struct Foo f = {0};
  return f.x; // no deprecation
}
```

Newly in C23, the inner `struct Foo` is compatible with the outer `Foo`.  As 
such, you can now implicitly cast between pointers to the two types, use them 
to read/write the same memory, etc. The expected semantics seems to me to be 
very clear here: we should determine whether it's a compatible type according 
to the standard's rules, and we shouldn't inherit any attributes from the other 
scope.

For determining compatibility of nonstandard attributes, we can do similarly to 
how we handle functions: explicitly check a handful of properties, such as 
calling convention, and assume compatibility if not otherwise specified. For 
aggregates, we can also validate that the field offsets are the same, which 
will handle most of the questionable cases.

But then there's the other case...

Where you have a redefinition of a struct in the _same scope_. This was 
formerly invalid, and is now valid only when compatible. In this case, both 
definitions define "the same type", so it seems reasonable to expect some sort 
of decl merging semantics. But the standard doesn't appear to say anything at 
all about what to do.

However, we do have some prior art -- we're not inventing the whole idea of 
decl-merging from scratch right now. So maybe we can follow that prior art? For 
example, top-level attributes on a struct's definition can be inherited from a 
declaration in the same scope. It seems plausible that the same would be true 
for redefinition, and then maybe we'd also extend that same 
declaration-attribute-merging to fields within the struct.

Except, while investigating, I discovered that the pre-C23 behavior is 
inconsistent between Clang and GCC, potentially resulting in ABI differences. 
E.g.
```
struct __attribute__((packed)) Foo;
struct Foo {
    char a;
    int b;
};

_Static_assert(sizeof(struct Foo) == 8); // with GCC, because packed on the 
decl was ignored
_Static_assert(sizeof(struct Foo) == 5); // with Clang, because packed on decl 
was inherited by the definition
```

Both GCC and Clang do inherit deprecation from the decl. E.g.:
```
struct [[deprecated("hello")]] Foo;
struct Foo* test1; // Both GCC and Clang say: deprecated: hello

struct Foo { int x; };
struct Foo* test2; // Both GCC and Clang say: deprecated: hello
```

Given that, I'd definitely expect that under the new C23 semantics, 
_redefining_ the struct should obviously also preserve the deprecated 
attribute. Except...apparently it doesn't in GCC's implementation. Gah. Adding 
on to the previous test,
```
struct Foo { int x; };
struct Foo* test3; // GCC: no deprecation warning.
```

So...yeah...

https://github.com/llvm/llvm-project/pull/132939
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to