Skip to content

Commit 315e2e2

Browse files
authored
[C23] Handle type compatibility for enumerations better (llvm#150282)
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 llvm#149965
1 parent 3ede2de commit 315e2e2

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

clang/lib/AST/ASTStructuralEquivalence.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,29 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
870870
else if (T1->getTypeClass() == Type::FunctionNoProto &&
871871
T2->getTypeClass() == Type::FunctionProto)
872872
TC = Type::FunctionNoProto;
873-
else
873+
else if (Context.LangOpts.C23 && !Context.StrictTypeSpelling &&
874+
(T1->getTypeClass() == Type::Enum ||
875+
T2->getTypeClass() == Type::Enum)) {
876+
// In C23, if not being strict about token equivalence, we need to handle
877+
// the case where one type is an enumeration and the other type is an
878+
// integral type.
879+
//
880+
// C23 6.7.3.3p16: The enumerated type is compatible with the underlying
881+
// type of the enumeration.
882+
//
883+
// Treat the enumeration as its underlying type and use the builtin type
884+
// class comparison.
885+
if (T1->getTypeClass() == Type::Enum) {
886+
T1 = T1->getAs<EnumType>()->getDecl()->getIntegerType();
887+
if (!T2->isBuiltinType() || T1.isNull()) // Sanity check
888+
return false;
889+
} else if (T2->getTypeClass() == Type::Enum) {
890+
T2 = T2->getAs<EnumType>()->getDecl()->getIntegerType();
891+
if (!T1->isBuiltinType() || T2.isNull()) // Sanity check
892+
return false;
893+
}
894+
TC = Type::Builtin;
895+
} else
874896
return false;
875897
}
876898

clang/test/C/C23/n3037.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,39 @@ _Static_assert(0 == _Generic(inner_anon_tagged.untagged, struct { int i; } : 1,
401401
// unions and structures are both RecordDecl objects, whereas EnumDecl is not).
402402
enum { E_Untagged1 } nontag_enum; // both-note {{previous definition is here}}
403403
_Static_assert(0 == _Generic(nontag_enum, enum { E_Untagged1 } : 1, default : 0)); // both-error {{redefinition of enumerator 'E_Untagged1'}}
404+
405+
// Test that enumerations are compatible with their underlying type, but still
406+
// diagnose when "same type" is required rather than merely "compatible type".
407+
enum E1 : int { e1 }; // Fixed underlying type
408+
enum E2 { e2 }; // Unfixed underlying type, defaults to int or unsigned int
409+
410+
struct GH149965_1 { int h; };
411+
// This typeof trick is used to get the underlying type of the enumeration in a
412+
// platform agnostic way.
413+
struct GH149965_2 { __typeof__(+(enum E2){}) h; };
414+
void gh149965(void) {
415+
extern struct GH149965_1 x1; // c17-note {{previous declaration is here}}
416+
extern struct GH149965_2 x2; // c17-note {{previous declaration is here}}
417+
418+
// Both the structure and the variable declarations are fine because only a
419+
// compatible type is required, not the same type, because the structures are
420+
// declared in different scopes.
421+
struct GH149965_1 { enum E1 h; };
422+
struct GH149965_2 { enum E2 h; };
423+
424+
extern struct GH149965_1 x1; // c17-error {{redeclaration of 'x1'}}
425+
extern struct GH149965_2 x2; // c17-error {{redeclaration of 'x2'}}
426+
427+
// However, in the same scope, the same type is required, not just compatible
428+
// types.
429+
// FIXME: this should be an error in both C17 and C23 mode.
430+
struct GH149965_3 { int h; }; // c17-note {{previous definition is here}}
431+
struct GH149965_3 { enum E1 h; }; // c17-error {{redefinition of 'GH149965_3'}}
432+
433+
// For Clang, the composite type after declaration merging is the enumeration
434+
// type rather than an integer type.
435+
enum E1 *eptr;
436+
[[maybe_unused]] __typeof__(x1.h) *ptr = eptr;
437+
enum E2 *eptr2;
438+
[[maybe_unused]] __typeof__(x2.h) *ptr2 = eptr2;
439+
}

0 commit comments

Comments
 (0)