[clang] [Clang][C23] Fix tag compatibility for incomplete enumerations (PR #225662)
Akash Manna via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 27 04:26:15 PDT 2026
https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/225662
>From e9421e5626c17bb257b0fa55e9dd64ad56b3ac7e Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Wed, 23 Sep 2026 15:07:22 +0530
Subject: [PATCH] [Clang][C23] Fix tag compatibility for incomplete
enumerations
The C23 tag compatibility check treated an enumeration without a
definition as equivalent to anything, so an incomplete `enum E` declared
in a parameter list was merged with a later file scope `enum E`. The
definition then carried a function type whose parameter is the
incomplete enumeration, and CodeGen asserted on its null underlying
type.
Only complete enumerations are compatible in C23 (6.2.7p1). Without a
definition an enumeration is complete only if it has a fixed underlying
type, so two such declarations are compatible only when those types
match.
Fixes #173477
---
clang/docs/ReleaseNotes.md | 5 +++
clang/lib/AST/ASTStructuralEquivalence.cpp | 19 +++++++---
clang/test/C/C23/n3037.c | 44 ++++++++++++++++++++++
3 files changed, 63 insertions(+), 5 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f4a34a37aff52..b11453d5e9bdf 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -231,6 +231,11 @@ features cannot lower the translation-unit ABI level;
#### C23 Feature Support
+- An incomplete enumeration is no longer treated as compatible with a complete
+ enumeration declared with the same tag in a different scope, as C23 6.2.7p1
+ only applies to complete types. This fixes a crash when generating code for a
+ function redeclared with such enumeration types as parameters. (#GH173477)
+
### Objective-C Language Changes
### Non-comprehensive list of changes in this release
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 029e33ae0db76..3aca7b7b1382c 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2154,13 +2154,22 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
}
// Compare the definitions of these two enums. If either or both are
- // incomplete (i.e. forward declared), we assume that they are equivalent.
+ // incomplete (i.e. forward declared), we assume that they are equivalent,
+ // except in C23 mode, where only complete enumerations are compatible. An
+ // enumeration without a definition is complete only if its underlying type
+ // is fixed, and then only that type needs to be compared.
// In C23, the order of the enumerations does not matter, only the names and
// values do.
- D1 = D1->getDefinition();
- D2 = D2->getDefinition();
- if (!D1 || !D2)
- return true;
+ EnumDecl *Def1 = D1->getDefinition(), *Def2 = D2->getDefinition();
+ if (!Def1 || !Def2) {
+ if (!Context.LangOpts.C23)
+ return true;
+ return !Def1 && !Def2 && D1->isComplete() && D2->isComplete() &&
+ IsStructurallyEquivalent(Context, D1->getIntegerType(),
+ D2->getIntegerType());
+ }
+ D1 = Def1;
+ D2 = Def2;
if (Context.LangOpts.C23 &&
!CheckStructurallyEquivalentAttributes(Context, D1, D2))
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 8dd46ef69b414..b3d21acdb842b 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -948,3 +948,47 @@ struct GH199417_3 {
// it would be better to print the original type.
} a; // c23-note {{field 'a' has type 'int' here}}
};
+
+// An incomplete enumeration declared in a parameter list is not compatible with
+// a file scope enumeration of the same name; only complete tag types can be.
+void GH173477_1(enum GH173477_E1 e); // both-warning {{ISO C forbids forward references to 'enum' types}} \
+ both-warning {{declaration of 'enum GH173477_E1' will not be visible outside of this function}} \
+ both-note {{previous declaration is here}}
+enum GH173477_E1 { GH173477_A };
+void GH173477_1(enum GH173477_E1 e) {} // both-error {{conflicting types for 'GH173477_1'}}
+
+void GH173477_2(enum GH173477_E2 *e); // both-warning {{ISO C forbids forward references to 'enum' types}} \
+ both-warning {{declaration of 'enum GH173477_E2' will not be visible outside of this function}} \
+ both-note {{previous declaration is here}}
+enum GH173477_E2 { GH173477_B };
+void GH173477_2(enum GH173477_E2 *e) {} // both-error {{conflicting types for 'GH173477_2'}}
+
+// A complete one is compatible in C23.
+void GH173477_3(enum GH173477_E3 { GH173477_C } e); // c17-warning {{declaration of 'enum GH173477_E3' will not be visible outside of this function}} \
+ c17-note {{previous declaration is here}}
+enum GH173477_E3 { GH173477_C };
+void GH173477_3(enum GH173477_E3 e) {} // c17-error {{conflicting types for 'GH173477_3'}}
+
+// An enumeration with a fixed underlying type is complete without a definition,
+// so it is compatible with one that is declared the same way, but not with a
+// definition or with a different underlying type.
+enum GH173477_E4 : int;
+void GH173477_4(enum GH173477_E4 e); // c17-note {{previous declaration is here}}
+void GH173477_4_test(void) {
+ enum GH173477_E4 : int;
+ void GH173477_4(enum GH173477_E4 e); // c17-error {{conflicting types for 'GH173477_4'}}
+}
+
+enum GH173477_E5 : int;
+void GH173477_5(enum GH173477_E5 e); // both-note {{previous declaration is here}}
+void GH173477_5_test(void) {
+ enum GH173477_E5 : int { GH173477_D };
+ void GH173477_5(enum GH173477_E5 e); // both-error {{conflicting types for 'GH173477_5'}}
+}
+
+enum GH173477_E6 : int;
+void GH173477_6(enum GH173477_E6 e); // both-note {{previous declaration is here}}
+void GH173477_6_test(void) {
+ enum GH173477_E6 : short;
+ void GH173477_6(enum GH173477_E6 e); // both-error {{conflicting types for 'GH173477_6'}}
+}
More information about the cfe-commits
mailing list