This is an archive of the discontinued LLVM Phabricator instance.

Drop qualifiers from return types in C (DR423)
ClosedPublic

Authored by aaron.ballman on May 18 2022, 12:34 PM.

Details

Summary

WG14 DR423 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2148.htm#dr_423), resolved during the C11 time frame, changed the way qualifiers are handled on function return types and in cast expressions after it was noted that these types are now directly observable via generic selection expressions. In C, the function declarator is adjusted to ignore all qualifiers (including _Atomic qualifiers).

Clang already handles the cast expression case correctly (by performing the lvalue conversion, which drops the qualifiers as well), but with these changes it will now also handle function declarations appropriately.

Fixes #39595

Diff Detail

Event Timeline

aaron.ballman created this revision.May 18 2022, 12:34 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 18 2022, 12:34 PM
aaron.ballman requested review of this revision.May 18 2022, 12:34 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 18 2022, 12:34 PM
erichkeane accepted this revision.May 18 2022, 12:39 PM
erichkeane added inline comments.
clang/test/Sema/warn-missing-prototypes.c
61

Hrmph, this is unfortunate...

This revision is now accepted and ready to land.May 18 2022, 12:39 PM
aaron.ballman marked an inline comment as done.

Adding some Apple reviewers because this touches ObjC behavior (I would be very surprised if ObjC wanted different rules here than C though).

clang/test/Sema/warn-missing-prototypes.c
61

It is, but it didn't seem like a sufficiently common situation to warrant trying to fix it.

We definitely don't want ObjC to differ here; thanks for the CC.

aaron.ballman marked an inline comment as done.

Fixes failed AST matching unit test and speculatively fixes a CodeGen test, all found via precommit CI testing.

We definitely don't want ObjC to differ here; thanks for the CC.

Excellent, thank you for the confirmation!

This revision was landed with ongoing or failed builds.May 19 2022, 10:07 AM
This revision was automatically updated to reflect the committed changes.

@aaron.ballman Hey I just saw this change and had questions about it. For others looking I think the resolution to DR423 is in https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1863.pdf, I found https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2148.htm#dr_423 hard to parse.

  1. It looks like we're completely dropping qualifiers on the return types of function in C. If that's the case, what's even the point of having qualifiers on return types of functions?
  2. Reading DR423 I see the problem but I don't understand why the desire to make _Generic work better in the presence of qualifiers means that qualifiers on function return types have to be dropped in all contexts. Couldn't this just be a thing that is done inside _Generic and no where else?

Sorry if these are silly questions and if I've misunderstood something, I saw n1863 say "functions return unqualified types" and I was very surprised.

@aaron.ballman Hey I just saw this change and had questions about it. For others looking I think the resolution to DR423 is in https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1863.pdf, I found https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2148.htm#dr_423 hard to parse.

  1. It looks like we're completely dropping qualifiers on the return types of function in C. If that's the case, what's even the point of having qualifiers on return types of functions?

As best I can tell, there is no point to having a qualified return type for a function in C because it is effectively unobservable, which is likely why qualifiers are removed from the function return type per spec. C2x 6.7.3p5 says "The properties associated with qualified types are meaningful only for expressions that are lvalues." and a function's returned value is never an lvalue in C. I say "effectively" unobservable because there can still be language extensions that make the return type more observable.

  1. Reading DR423 I see the problem but I don't understand why the desire to make _Generic work better in the presence of qualifiers means that qualifiers on function return types have to be dropped in all contexts. Couldn't this just be a thing that is done inside _Generic and no where else?

The reason qualifiers are to be dropped is because of C2x 6.7.6.3p4, which was what was updated by DR423 to say: "If, in the declaration "T D1", D1 has the form D ( parameter-type-list_opt ) attribute-specifier-sequence_opt and the type specified for ident in the declaration "T D" is "derived-declarator-type-list T", then the type specified for ident is "derived-declarator-type-list function returning the unqualified version of T". Note how the type specified for the function is returning the unqualified version of T. This will impact more than just _Generic; for example __builtin_types_compatible_p() is also affected: https://godbolt.org/z/1bhcvhv3M

However, I reverted the changes in this patch in c745f2ce6c03bc6d1e59cac69cc15923d4400191 as I don't think they're correct. I've got some open questions on the WG14 reflectors regarding this function type rewriting exercise, but I think my dropping of the _Atomic qualifier is likely wrong in this patch and the fact that we're losing source fidelity in the AST is definitely an issue. The subject of https://github.com/llvm/llvm-project/issues/39595 was the behavior of the _Atomic specifier in a _Generic selection; when I realized we don't fully implement DR423, I mistakenly connected the issue with the DR. But now that I no longer think the _Atomic qualifier should be dropped as I was doing, these changes really don't address the issue in _Generic.

Sorry if these are silly questions and if I've misunderstood something, I saw n1863 say "functions return unqualified types" and I was very surprised.

These are not at all silly questions, so thank you for asking them!

@aaron.ballman Hey I just saw this change and had questions about it. For others looking I think the resolution to DR423 is in https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1863.pdf, I found https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2148.htm#dr_423 hard to parse.

  1. It looks like we're completely dropping qualifiers on the return types of function in C. If that's the case, what's even the point of having qualifiers on return types of functions?

I think that's the point: qualifiers don't mean anything on return types, so they shouldn't be represented in the type. If the user writes a qualifier directly on the return type, like const int twice(int x);, we should warn about that; otherwise they should be silently dropped.

  1. Reading DR423 I see the problem but I don't understand why the desire to make _Generic work better in the presence of qualifiers means that qualifiers on function return types have to be dropped in all contexts. Couldn't this just be a thing that is done inside _Generic and no where else?

Sorry if these are silly questions and if I've misunderstood something, I saw n1863 say "functions return unqualified types" and I was very surprised.

Just to be clear, you understand that this is only about top-level qualifiers on the return type, right? const void *foo(); is still meaningful, it's just that const void * const foo(); isn't.

However, I reverted the changes in this patch in c745f2ce6c03bc6d1e59cac69cc15923d4400191 as I don't think they're correct. I've got some open questions on the WG14 reflectors regarding this function type rewriting exercise, but I think my dropping of the _Atomic qualifier is likely wrong in this patch and the fact that we're losing source fidelity in the AST is definitely an issue. The subject of https://github.com/llvm/llvm-project/issues/39595 was the behavior of the _Atomic specifier in a _Generic selection; when I realized we don't fully implement DR423, I mistakenly connected the issue with the DR. But now that I no longer think the _Atomic qualifier should be dropped as I was doing, these changes really don't address the issue in _Generic.

The source fidelity issue seems like it could be solved with AdjustedType. What's the issue with _Generic?

However, I reverted the changes in this patch in c745f2ce6c03bc6d1e59cac69cc15923d4400191 as I don't think they're correct. I've got some open questions on the WG14 reflectors regarding this function type rewriting exercise, but I think my dropping of the _Atomic qualifier is likely wrong in this patch and the fact that we're losing source fidelity in the AST is definitely an issue. The subject of https://github.com/llvm/llvm-project/issues/39595 was the behavior of the _Atomic specifier in a _Generic selection; when I realized we don't fully implement DR423, I mistakenly connected the issue with the DR. But now that I no longer think the _Atomic qualifier should be dropped as I was doing, these changes really don't address the issue in _Generic.

The source fidelity issue seems like it could be solved with AdjustedType. What's the issue with _Generic?

We get this wrong:

_Atomic int f(void);

int main(void) {
  _Generic(f(), int: 0); // Error, no matching association
}

The controlling expression has to undergo lvalue conversion, which drops the _Atomic qualifier per C2x 6.3.2.1p2, so we should match the only association rather than error. We do perform the lvalue conversion: https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/SemaExpr.cpp#L1650 but for whatever reason, we're not matching its type to the association type.

Ah, yeah, that seems unrelated (and surprising).

You think we're *not* supposed to drop _Atomic in a return type per C2x? That seems surprising; I thought it was generally treated as a qualifier and would expect that we'd be supposed to drop it.

Ah, yeah, that seems unrelated (and surprising).

You think we're *not* supposed to drop _Atomic in a return type per C2x? That seems surprising; I thought it was generally treated as a qualifier and would expect that we'd be supposed to drop it.

My first inclination was that it gets dropped as well because it's a qualifier and surely "unqualified" means drop all qualifiers... but the standard makes it far less clear (alas).

6.2.5p29 (types):
"Further, there is the _Atomic qualifier. The presence of the _Atomic qualifier designates an atomic
type. The size, representation, and alignment of an atomic type need not be the same as those of
the corresponding unqualified type. Therefore, this document explicitly uses the phrase "atomic,
qualified, or unqualified type" whenever the atomic version of a type is permitted along with the
other qualified versions of a type. The phrase "qualified or unqualified type", without specific
mention of atomic, does not include the atomic types."

Isn't that a gem? :-)

6.3.2.1p2 (lvalue conversion):
"... If the lvalue has qualified type, the value has the unqualified
version of the type of the lvalue; additionally, if the lvalue has
atomic type, the value has the non-atomic version of the type of the
lvalue. ..."

Which also gives a hint that _Atomic is special in terms of the phrase "unqualified".

Ah, true, the standard doesn't describe it as a normal qualifier. From the DR, it sounds like the committee certainly expected that this reasoning would also apply to _Atomic, even if that's not quite what they've written, but that the DR submitter seems to not want that behavior. Have you been able to track down the solicited paper mentioned in that DR? In the absence of further indication, I think dropping _Atomic like anything else is the right thing to do; like the other qualifiers, it isn't meaningful for r-values.

Ah, true, the standard doesn't describe it as a normal qualifier. From the DR, it sounds like the committee certainly expected that this reasoning would also apply to _Atomic, even if that's not quite what they've written, but that the DR submitter seems to not want that behavior. Have you been able to track down the solicited paper mentioned in that DR? In the absence of further indication, I think dropping _Atomic like anything else is the right thing to do; like the other qualifiers, it isn't meaningful for r-values.

I think it's less clear to me that the committee expected to drop _Atomic because the standard basically makes _Atomic introduce a new type rather than a qualified use like const; it's more akin to _Complex in that regard. e.g., it's not clear to me that these are redeclarations:

int func(void);
_Atomic(int) func(void);

When I survey non-Clang C compilers which support _Atomic, it's evenly split between thinking these are valid redeclarations or not (GCC and ICC say they're not valid redeclarations, ICC and TCC say they are) but when I use const int and int as the return types, only ICC claims they're incompatible types.

Further, this seems like a gratuitous incompatibility with C++ where the qualifiers are *not* dropped, given that function call expressions explicitly drop the qualifiers anyway. (So you're right, they are effectively useless on the return type, but why should C and C++ differ in what's a valid redeclaration?)

rjmccall added a comment.EditedJun 3 2022, 10:22 AM

Are you now arguing that C was wrong to drop qualifiers in return types and so we shouldn't implement that rule, or...? Because your argument is much broader than just _Atomic.

I don't understand the analogy you're making to _Complex at all. Values of type _Complex T are fundamentally different from values of type T, because inherently they have to store two of them. _Imaginary (which Clang does not implement) is more or less just a semantic recast of T, but that recast is still critically important to the nature of the type, and in no way does it act like a qualifier. Meanwhile, _Atomic is only meaningful at the top level on l-values, exactly like a qualifier, and you can assign non-atomic r-values into an atomic l-value and read them back out again, just like a qualifier.

Now, the standard has chosen not to talk about _Atomic as a qualifier, I assume because there's a fair number of rules in the standard that assume that qualifiers can be freely added to pointers and don't change layout and so on, none of which apply to _Atomic. But those rules also don't apply to a large number of other extended qualifiers, like address spaces and the ARC ownership qualifiers and __ptrauth. The committee should probably just come to terms with the fact that it's the relatively easy-come-easy-go nature of the CVR qualifiers which is the special case. I've thought for awhile that Clang should really be representing _Atomic as a qualifier instead of having to treat AtomicType as a special case in a million places.

I see absolutely no reason why anybody should ever want to declare a function as _Atomic(int) func(void);, and if the C and C++ committees decide to break people who aren't just doing it but are doing it inconsistently, they're doing the world a service.

@aaron.ballman

Sorry if these are silly questions and if I've misunderstood something, I saw n1863 say "functions return unqualified types" and I was very surprised.

These are not at all silly questions, so thank you for asking them!

Thanks for taking the time to answer my questions. Your answers make sense to me apart from the derived-declarator-type-list. I'm not sure what that is, the copy of the standard I found doesn't seem seem to define what it is. I see other people have complained about this in the past (https://stackoverflow.com/questions/13779273/in-the-standard-what-is-derived-declarator-type) so I'll guess I'll stare at that for a while until it makes sense.

Are you now arguing that C was wrong to drop qualifiers in return types and so we shouldn't implement that rule, or...? Because your argument is much broader than just _Atomic.

That's my current thinking, but I'm still considering the ramifications. But it seems to me that dropping qualifications on lvalue to rvalue conversion, which happens for function returns anyways, is sufficient to have resolved DR423.

I'm quite peeved that:

int func(void);
const int func(void);

is valid C but invalid C++; that seems gratuitously incompatible in light of the lvalue to rvalue behavior.

I don't understand the analogy you're making to _Complex at all. Values of type _Complex T are fundamentally different from values of type T, because inherently they have to store two of them. _Imaginary (which Clang does not implement) is more or less just a semantic recast of T, but that recast is still critically important to the nature of the type, and in no way does it act like a qualifier. Meanwhile, _Atomic is only meaningful at the top level on l-values, exactly like a qualifier, and you can assign non-atomic r-values into an atomic l-value and read them back out again, just like a qualifier.

I disagree with the assessment that _Atomic behaves exactly like a qualifier. It *should* (IMHO), but it doesn't. C allows the size and alignment of an atomic type be *different* from its unqualified version, to allow for embedded locks and such. Because the size and alignment can be different, to my mind, _Atomic int and int are different types in the same way as _Complex float and float -- the object representations can (must, in the case of _Complex) be different, so they're different types. The same is not true for const, volatile, or restrict qualifiers -- those are required to have the same size and alignment as the unqualified type.

Now, the standard has chosen not to talk about _Atomic as a qualifier, I assume because there's a fair number of rules in the standard that assume that qualifiers can be freely added to pointers and don't change layout and so on, none of which apply to _Atomic. But those rules also don't apply to a large number of other extended qualifiers, like address spaces and the ARC ownership qualifiers and __ptrauth. The committee should probably just come to terms with the fact that it's the relatively easy-come-easy-go nature of the CVR qualifiers which is the special case. I've thought for awhile that Clang should really be representing _Atomic as a qualifier instead of having to treat AtomicType as a special case in a million places.

I'm not certain if we can get away with that. IIRC, Microsoft uses embedded locks on Windows in some circumstances. However, cl doesn't support _Atomic and so maybe we can get away with it on the assumption we have no other targets where the size/alignment would be different for an atomic type vs a non-atomic type?

I see absolutely no reason why anybody should ever want to declare a function as _Atomic(int) func(void);, and if the C and C++ committees decide to break people who aren't just doing it but are doing it inconsistently, they're doing the world a service.

I tend to agree -- top-level qualification on a return type is deeply strange in C. However, it's something observable in C++ due to template metaprogramming and type traits, so I don't see C++ changing the type system to disallow it as C as done. Given that C++ is unlikely to move the needle, it seems more plausible to move it in C where top-level qualification of a return type is really hard to observe except through redeclaration errors.

@rjmccall

Sorry if these are silly questions and if I've misunderstood something, I saw n1863 say "functions return unqualified types" and I was very surprised.

Just to be clear, you understand that this is only about top-level qualifiers on the return type, right? const void *foo(); is still meaningful, it's just that const void * const foo(); isn't.

Oh that wasn't obvious to me at all. I had assumed "unqualified types" means remove qualifiers from everywhere in the type, not just the top level. Presumably something was meant to imply that?

@aaron.ballman

Sorry if these are silly questions and if I've misunderstood something, I saw n1863 say "functions return unqualified types" and I was very surprised.

These are not at all silly questions, so thank you for asking them!

Thanks for taking the time to answer my questions. Your answers make sense to me apart from the derived-declarator-type-list. I'm not sure what that is, the copy of the standard I found doesn't seem seem to define what it is. I see other people have complained about this in the past (https://stackoverflow.com/questions/13779273/in-the-standard-what-is-derived-declarator-type) so I'll guess I'll stare at that for a while until it makes sense.

:-D It took me a while to find the answer when I was looking at that yesterday, but it's defined in C2x 6.2.5p26 (without the -list part, that's added because there could be multiple declarators for the same T, as in const int f(void), g(int);).

rsmith added a subscriber: rsmith.Jun 3 2022, 12:22 PM

Now, the standard has chosen not to talk about _Atomic as a qualifier, I assume because there's a fair number of rules in the standard that assume that qualifiers can be freely added to pointers and don't change layout and so on, none of which apply to _Atomic. But those rules also don't apply to a large number of other extended qualifiers, like address spaces and the ARC ownership qualifiers and __ptrauth. The committee should probably just come to terms with the fact that it's the relatively easy-come-easy-go nature of the CVR qualifiers which is the special case. I've thought for awhile that Clang should really be representing _Atomic as a qualifier instead of having to treat AtomicType as a special case in a million places.

I'm not certain if we can get away with that. IIRC, Microsoft uses embedded locks on Windows in some circumstances. However, cl doesn't support _Atomic and so maybe we can get away with it on the assumption we have no other targets where the size/alignment would be different for an atomic type vs a non-atomic type?

That assumption does not hold. Given struct A { char c[3]; };, struct A has size 3 and align 1, but _Atomic struct A has size 4 and align 4 across many (perhaps all?) of our targets. (This is an ABI divergence between GCC and Clang, which as far as I know the psABI owners have so far not succeeded in resolving.)

I saw some talk in WG14 of separating _Atomic(T) and _Atomic T so that _Atomic T would be a qualifier that doesn't affect size or alignment, only what code is generated to access the value, and _Atomic(T) would be a distinct type with potentially a different representation in order to support lock-free atomic access. Did that go anywhere? (I'd assume not, it seems to be at least a decade too late for such an idea.)

That assumption does not hold. Given struct A { char c[3]; };, struct A has size 3 and align 1, but _Atomic struct A has size 4 and align 4 across many (perhaps all?) of our targets. (This is an ABI divergence between GCC and Clang, which as far as I know the psABI owners have so far not succeeded in resolving.)

And while GCC indeed never changes the size (that is: it never introduces padding), it, too, increases the alignment when the size was already appropriately-aligned.

That is, given struct A { char c[4]; };, struct A has size 4 and align 1, but _Atomic struct A has size 4 and align 4, under both GCC and Clang.

rjmccall added a comment.EditedJun 3 2022, 9:10 PM

I disagree with the assessment that _Atomic behaves exactly like a qualifier. It *should* (IMHO), but it doesn't. C allows the size and alignment of an atomic type be *different* from its unqualified version, to allow for embedded locks and such. Because the size and alignment can be different, to my mind, _Atomic int and int are different types in the same way as _Complex float and float -- the object representations can (must, in the case of _Complex) be different, so they're different types. The same is not true for const, volatile, or restrict qualifiers -- those are required to have the same size and alignment as the unqualified type.

I think our apparent disagreement here is rooted in something quite simple: you seem to be assuming that a qualified type by definition cannot have a different size and alignment from the unqualified type, and I think that's a property that's *guaranteed* for the CVR qualifiers and merely *happens to be true* for our current set of extended qualifiers, and only really because we don't represent _Atomic as a qualifier.

Many of our extended qualifiers completely change the ABI of the type they qualify, and they can change the basic semantic properties of types they're nested within. I don't see why we'd draw a line around type layout for what counts as a qualifier and what doesn't. For example, ObjC ARC happens to implement __weak references without using extra inline storage, but it would certainly be a reasonable ABI choice to make them larger than just a pointer — Swift does with its native weak references, for example. That ABI choice wouldn't make __weak suddenly not a qualifier. Type constructors like _Atomic, _Complex, const, and * are qualifiers or not based on the role and behavior of the constructed type in the language, not its ABI properties.

I saw some talk in WG14 of separating _Atomic(T) and _Atomic T so that _Atomic T would be a qualifier that doesn't affect size or alignment, only what code is generated to access the value, and _Atomic(T) would be a distinct type with potentially a different representation in order to support lock-free atomic access. Did that go anywhere? (I'd assume not, it seems to be at least a decade too late for such an idea.)

That seems like a terrible idea.

Now, the standard has chosen not to talk about _Atomic as a qualifier, I assume because there's a fair number of rules in the standard that assume that qualifiers can be freely added to pointers and don't change layout and so on, none of which apply to _Atomic. But those rules also don't apply to a large number of other extended qualifiers, like address spaces and the ARC ownership qualifiers and __ptrauth. The committee should probably just come to terms with the fact that it's the relatively easy-come-easy-go nature of the CVR qualifiers which is the special case. I've thought for awhile that Clang should really be representing _Atomic as a qualifier instead of having to treat AtomicType as a special case in a million places.

I'm not certain if we can get away with that. IIRC, Microsoft uses embedded locks on Windows in some circumstances. However, cl doesn't support _Atomic and so maybe we can get away with it on the assumption we have no other targets where the size/alignment would be different for an atomic type vs a non-atomic type?

That assumption does not hold. Given struct A { char c[3]; };, struct A has size 3 and align 1, but _Atomic struct A has size 4 and align 4 across many (perhaps all?) of our targets. (This is an ABI divergence between GCC and Clang, which as far as I know the psABI owners have so far not succeeded in resolving.)

Good point!

I saw some talk in WG14 of separating _Atomic(T) and _Atomic T so that _Atomic T would be a qualifier that doesn't affect size or alignment, only what code is generated to access the value, and _Atomic(T) would be a distinct type with potentially a different representation in order to support lock-free atomic access. Did that go anywhere? (I'd assume not, it seems to be at least a decade too late for such an idea.)

So far that's just been talk (and I'm not convinced it'll be possible to change the semantics there without silently breaking code).

I disagree with the assessment that _Atomic behaves exactly like a qualifier. It *should* (IMHO), but it doesn't. C allows the size and alignment of an atomic type be *different* from its unqualified version, to allow for embedded locks and such. Because the size and alignment can be different, to my mind, _Atomic int and int are different types in the same way as _Complex float and float -- the object representations can (must, in the case of _Complex) be different, so they're different types. The same is not true for const, volatile, or restrict qualifiers -- those are required to have the same size and alignment as the unqualified type.

I think our apparent disagreement here is rooted in something quite simple: you seem to be assuming that a qualified type by definition cannot have a different size and alignment from the unqualified type, and I think that's a property that's *guaranteed* for the CVR qualifiers and merely *happens to be true* for our current set of extended qualifiers, and only really because we don't represent _Atomic as a qualifier.

I think you're right about the root of our disagreement. Any qualifiers other than CVR, _Atomic, and address spaces are fully implementation-defined and can do whatever they want, thus aren't really relevant to how standard qualifiers behave. Address spaces are covered by TR 18037 (which states they're extending the list of CVR qualifiers in C, thus I'd expect would follow the same rules as CVR despite predating DR423). We treat all standards-based qualifiers as a Qualifier in Clang, except for _Atomic, which I believe we treat differently because it behaves as a distinct type. I think we *want* that behavior -- these are not valid redeclarations: void func(int a); void func(_Atomic int a); while these are valid (and have utility): void func(int a); void func(const int a);.

Many of our extended qualifiers completely change the ABI of the type they qualify, and they can change the basic semantic properties of types they're nested within. I don't see why we'd draw a line around type layout for what counts as a qualifier and what doesn't. For example, ObjC ARC happens to implement __weak references without using extra inline storage, but it would certainly be a reasonable ABI choice to make them larger than just a pointer — Swift does with its native weak references, for example. That ABI choice wouldn't make __weak suddenly not a qualifier. Type constructors like _Atomic, _Complex, const, and * are qualifiers or not based on the role and behavior of the constructed type in the language, not its ABI properties.

For our implementation-defined qualifiers, I think the design space is wide open and we can do what we want. However, I think _Atomic specifically is more closely related to a new type than a qualified type for the other standards-based qualifiers. The C standard goes out of its way to segregate atomic types from other types but doesn't do anything similar for any other qualifier.

All that said, I think you can see why I'm hoping to get an answer from WG14 as to what to do. Reasonable folks are disagreeing on what the standard requires here.

All that said, I think you can see why I'm hoping to get an answer from WG14 as to what to do. Reasonable folks are disagreeing on what the standard requires here.

The discussion on the WG14 reflector seems to be settling down to a consensus position that the _Atomic qualifier is only syntactically a qualifier and its use designates an entirely new type. When the standard says "unqualified type", the _Atomic is not considered a qualification. So we should *not* be stripping the _Atomic as I was doing in this patch. (SC22WG14.22200 has most of the details spelled out nicely, if you have access to the reflectors.)

I had asked other questions in related areas that also got answers.

const void func(volatile void); -- the return type is adjusted to void; the parameter type is UB (by lack of specification) and we can do what we want here. We currently diagnose the parameter as being invalid: https://godbolt.org/z/9c8bTrerY. Our behavior with the parameter is consistent with GCC and EDG.

const int main(void) -- this is valid and equivalent to int main(void), so it should be accepted; we currently reject: https://godbolt.org/z/v43h596ev

const int func(void); int func(void) { } -- this is DR423. It is valid, the composite type is int(void); we give a conflicting types error: https://godbolt.org/z/Yb841r7Ex

All that said, I think you can see why I'm hoping to get an answer from WG14 as to what to do. Reasonable folks are disagreeing on what the standard requires here.

The discussion on the WG14 reflector seems to be settling down to a consensus position that the _Atomic qualifier is only syntactically a qualifier and its use designates an entirely new type. When the standard says "unqualified type", the _Atomic is not considered a qualification. So we should *not* be stripping the _Atomic as I was doing in this patch. (SC22WG14.22200 has most of the details spelled out nicely, if you have access to the reflectors.)

What a strange position. *All* qualifiers produce an "entirely new type". But okay, if the committee wants to pretend that _Atomic is meaningful in return values, I guess we all have to live with that.

I had asked other questions in related areas that also got answers.

const void func(volatile void); -- the return type is adjusted to void; the parameter type is UB (by lack of specification) and we can do what we want here. We currently diagnose the parameter as being invalid: https://godbolt.org/z/9c8bTrerY. Our behavior with the parameter is consistent with GCC and EDG.

const int main(void) -- this is valid and equivalent to int main(void), so it should be accepted; we currently reject: https://godbolt.org/z/v43h596ev

const int func(void); int func(void) { } -- this is DR423. It is valid, the composite type is int(void); we give a conflicting types error: https://godbolt.org/z/Yb841r7Ex

Is that only because of the type compositing rules, or are they saying that the type of the first is const int (void), which is different from int (void)?

All that said, I think you can see why I'm hoping to get an answer from WG14 as to what to do. Reasonable folks are disagreeing on what the standard requires here.

The discussion on the WG14 reflector seems to be settling down to a consensus position that the _Atomic qualifier is only syntactically a qualifier and its use designates an entirely new type. When the standard says "unqualified type", the _Atomic is not considered a qualification. So we should *not* be stripping the _Atomic as I was doing in this patch. (SC22WG14.22200 has most of the details spelled out nicely, if you have access to the reflectors.)

What a strange position. *All* qualifiers produce an "entirely new type". But okay, if the committee wants to pretend that _Atomic is meaningful in return values, I guess we all have to live with that.

lol, right? :-D

I had asked other questions in related areas that also got answers.

const void func(volatile void); -- the return type is adjusted to void; the parameter type is UB (by lack of specification) and we can do what we want here. We currently diagnose the parameter as being invalid: https://godbolt.org/z/9c8bTrerY. Our behavior with the parameter is consistent with GCC and EDG.

const int main(void) -- this is valid and equivalent to int main(void), so it should be accepted; we currently reject: https://godbolt.org/z/v43h596ev

const int func(void); int func(void) { } -- this is DR423. It is valid, the composite type is int(void); we give a conflicting types error: https://godbolt.org/z/Yb841r7Ex

Is that only because of the type compositing rules, or are they saying that the type of the first is const int (void), which is different from int (void)?

Quoting from the reflector question I asked,

> Question 4
> Also along the same thinking, what is the composite type of int
> f(int); and const int f(const int);? Is it before or after adjustment?
> If before adjustment, we don't seem to say anything in 6.2.7p3 about
> the parameter type but p1 then suggests the types are not compatible
> in the first place; if after adjustment, the composite type would be
> int(int). There's significant compiler divergence here: Clang, GCC,
> ppci, cc65, and TCC consider after adjusting both parameters and
> return types, while EDG, Tendra, cproc, SDCC, and MSVC consider after
> adjusting parameter types but not return types.

According to the normative text, the qualifiers on the return type never form
part of the type of the function at all.  I expect implementations doing otherwise
simply failed to implement the resolution to DR#423.

For parameter types, I think it's generally understood that qualifiers don't affect the
function type (so the types are *the same type*, for the purposes of handling the
rules on duplicate typedef declarations, not just *compatible types*), but all the
wording actually says is that the qualifiers are disregarded "In the determination of
type compatibility and of a composite type", leaving open the possibility that they
form part of the function type but don't affect type compatibility or composite types.
In any case the composite type of the two types you list must be int (int) because of
the inclusion of "composite type" in that wording, even if you could form a case for
int (const int) and int (int) being different (compatible) types.

I take this as meaning we *could* make int func(void); and const int func(void); have different types, but that seems like a pretty strange thing to do and we should probably just make them have the same types (so then there aren't special rules for compatibility and compositing for them).

Yeah, that makes sense. Thanks for following that up.