https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/150282
>From 0bb04ba187b4d4a343f2f6f949615a0d98fd046d Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Wed, 23 Jul 2025 14:00:45 -0400 Subject: [PATCH 1/2] [C23] Handle type compatibility for enumerations better An enumeration is compatible with its underlying type, which means that code like the following should be accepted: struct A { int h; }; void func() { extern struct A x; enum E : int { e }; struct A { enum E h; }; extern struct A x; } because the structures are declared in different scopes, the two declarations of 'x' are both compatible. Note, the structural equivalence checker does not take scope into account, but that is something the C standard requires. This means we are accepting code we should be rejecting per the standard, like: void func() { struct A { int h; }; extern struct A x; enum E : int { e }; struct A { enum E h; }; extern struct A x; } Because the structures are declared in the same scope, the type compatibility rule require the structures to use the same types, not merely compatible ones. Fixes #149965 --- clang/lib/AST/ASTStructuralEquivalence.cpp | 24 ++++++++++++++++++- clang/test/C/C23/n3037.c | 27 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index 0f2762d5c0f14..f113b32d6eb30 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -870,7 +870,29 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, else if (T1->getTypeClass() == Type::FunctionNoProto && T2->getTypeClass() == Type::FunctionProto) TC = Type::FunctionNoProto; - else + else if (Context.LangOpts.C23 && !Context.StrictTypeSpelling && + (T1->getTypeClass() == Type::Enum || + T2->getTypeClass() == Type::Enum)) { + // In C23, if not being strict about token equivalence, we need to handle + // the case where one type is an enumeration and the other type is an + // integral type. + // + // C23 6.7.3.3p16: The enumerated type is compatible with the underlying + // type of the enumeration. + // + // Treat the enumeration as its underlying type and use the builtin type + // class comparison. + if (T1->getTypeClass() == Type::Enum) { + T1 = T1->getAs<EnumType>()->getDecl()->getIntegerType(); + if (!T2->isBuiltinType() || T1.isNull()) // Sanity check + return false; + } else if (T2->getTypeClass() == Type::Enum) { + T2 = T2->getAs<EnumType>()->getDecl()->getIntegerType(); + if (!T1->isBuiltinType() || T2.isNull()) // Sanity check + return false; + } + TC = Type::Builtin; + } else return false; } diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c index ce6f4c4ea7acf..54e8ab43ec271 100644 --- a/clang/test/C/C23/n3037.c +++ b/clang/test/C/C23/n3037.c @@ -401,3 +401,30 @@ _Static_assert(0 == _Generic(inner_anon_tagged.untagged, struct { int i; } : 1, // unions and structures are both RecordDecl objects, whereas EnumDecl is not). enum { E_Untagged1 } nontag_enum; // both-note {{previous definition is here}} _Static_assert(0 == _Generic(nontag_enum, enum { E_Untagged1 } : 1, default : 0)); // both-error {{redefinition of enumerator 'E_Untagged1'}} + +// Test that enumerations are compatible with their underlying type, but still +// diagnose when "same type" is required rather than merely "compatible type". +struct GH149965_1 { int h; }; +struct GH149965_2 { int h; }; +void gh149965(void) { + extern struct GH149965_1 x1; // c17-note {{previous declaration is here}} + extern struct GH149965_2 x2; // c17-note {{previous declaration is here}} + + enum E1 : int { e1 }; // Fixed underlying type + enum E2 { e2 }; // Unfixed underlying type, defaults to int in Clang (unsigned in GCC) + + // Both the structure and the variable declarations are fine because only a + // compatible type is required, not the same type, because the structures are + // declared in different scopes. + struct GH149965_1 { enum E1 h; }; + struct GH149965_2 { enum E2 h; }; + + extern struct GH149965_1 x1; // c17-error {{redeclaration of 'x1'}} + extern struct GH149965_2 x2; // c17-error {{redeclaration of 'x2'}} + + // However, in the same scope, the same type is required, not just compatible + // types. + // FIXME: this should be an error in both C17 and C23 mode. + struct GH149965_3 { int h; }; // c17-note {{previous definition is here}} + struct GH149965_3 { enum E1 h; }; // c17-error {{redefinition of 'GH149965_3'}} +} >From 405ca1d4c313f8fb6001ddb2e6ddfdeaee983c7d Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Wed, 23 Jul 2025 15:04:14 -0400 Subject: [PATCH 2/2] Hopefully fix the precommit CI issues --- clang/test/C/C23/n3037.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c index 54e8ab43ec271..0f5744de4f822 100644 --- a/clang/test/C/C23/n3037.c +++ b/clang/test/C/C23/n3037.c @@ -404,15 +404,17 @@ _Static_assert(0 == _Generic(nontag_enum, enum { E_Untagged1 } : 1, default : 0) // Test that enumerations are compatible with their underlying type, but still // diagnose when "same type" is required rather than merely "compatible type". +enum E1 : int { e1 }; // Fixed underlying type +enum E2 { e2 }; // Unfixed underlying type, defaults to int or unsigned int + struct GH149965_1 { int h; }; -struct GH149965_2 { int h; }; +// This typeof trick is used to get the underlying type of the enumeration in a +// platform agnostic way. +struct GH149965_2 { __typeof__(+(enum E2){}) h; }; void gh149965(void) { extern struct GH149965_1 x1; // c17-note {{previous declaration is here}} extern struct GH149965_2 x2; // c17-note {{previous declaration is here}} - enum E1 : int { e1 }; // Fixed underlying type - enum E2 { e2 }; // Unfixed underlying type, defaults to int in Clang (unsigned in GCC) - // Both the structure and the variable declarations are fine because only a // compatible type is required, not the same type, because the structures are // declared in different scopes. _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits