[clang] [Clang] Properly parse enumeration declarations with fixed underlying type in generic associations (PR #210353)
Abdulmalek Almkainzi via cfe-commits
cfe-commits at lists.llvm.org
Sat Aug 29 17:20:59 PDT 2026
https://github.com/aalmkainzi updated https://github.com/llvm/llvm-project/pull/210353
>From d86b41d1bba99f109c20af17956cd1d8acca6f8f Mon Sep 17 00:00:00 2001
From: Abdulmalek Almkainzi <aalmkainzi at gmail.com>
Date: Fri, 17 Jul 2026 00:33:31 +0300
Subject: [PATCH 1/5] Fixed #207357
added a new ParsingGenericAssociationType flag that is managed by GenericAssociationTypeRAIIObject. Using that, when encountering a : after enum name, try to parse next token as a type, if successful, then that's the underlying type, if failed, then that is the end of the enum declaration, and the colon will be parsed as the generic association.
---
clang/include/clang/Parse/Parser.h | 13 +++++++++++++
clang/include/clang/Parse/RAIIObjectsForParser.h | 15 +++++++++++++++
clang/lib/Parse/ParseDecl.cpp | 4 +++-
clang/lib/Parse/ParseDeclCXX.cpp | 3 ++-
clang/lib/Parse/ParseExpr.cpp | 3 ++-
clang/lib/Parse/ParseTentative.cpp | 8 ++++++++
clang/test/Parser/c23-enum-generic-assoc.c | 10 ++++++++++
7 files changed, 53 insertions(+), 3 deletions(-)
create mode 100644 clang/test/Parser/c23-enum-generic-assoc.c
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index ae91153e34e3a..ced7607c71a69 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -279,6 +279,7 @@ class Parser : public CodeCompletionHandler {
public:
friend class ColonProtectionRAIIObject;
+ friend class GenericAssociationTypeRAIIObject;
friend class PoisonSEHIdentifiersRAIIObject;
friend class ParenBraceBracketBalancer;
friend class BalancedDelimiterTracker;
@@ -4497,6 +4498,11 @@ class Parser : public CodeCompletionHandler {
/// ColonProtectionRAIIObject RAII object.
bool ColonIsSacred;
+ // ParsingGenericAssociationType - Currently parsing the typename in
+ // _Generic association. This is to consume the colon if what comes after it
+ // is a type.
+ bool ParsingGenericAssociationType;
+
/// ParseCXXAmbiguousParenExpression - We have parsed the left paren of a
/// parenthesized ambiguous type-id. This uses tentative parsing to
/// disambiguate based on the context past the parens.
@@ -8684,6 +8690,13 @@ class Parser : public CodeCompletionHandler {
return isCXXTypeId(Context, isAmbiguous);
}
+ bool isNextCXXTypeId(TentativeCXXTypeIdContext Context, bool &isAmbiguous);
+
+ bool isNextCXXTypeId(TentativeCXXTypeIdContext Context) {
+ bool isAmbiguous;
+ return isNextCXXTypeId(Context, isAmbiguous);
+ }
+
/// TPResult - Used as the result value for functions whose purpose is to
/// disambiguate C++ constructs by "tentatively parsing" them.
enum class TPResult { True, False, Ambiguous, Error };
diff --git a/clang/include/clang/Parse/RAIIObjectsForParser.h b/clang/include/clang/Parse/RAIIObjectsForParser.h
index 3adcbfe9d7016..747dbe90b1bf6 100644
--- a/clang/include/clang/Parse/RAIIObjectsForParser.h
+++ b/clang/include/clang/Parse/RAIIObjectsForParser.h
@@ -290,6 +290,21 @@ namespace clang {
}
};
+ class GenericAssociationTypeRAIIObject {
+ Parser &P;
+ bool OldVal;
+
+ public:
+ GenericAssociationTypeRAIIObject(Parser &p, bool Value = true)
+ : P(p), OldVal(P.ParsingGenericAssociationType) {
+ P.ParsingGenericAssociationType = Value;
+ }
+
+ void restore() { P.ParsingGenericAssociationType = OldVal; }
+
+ ~GenericAssociationTypeRAIIObject() { restore(); }
+ };
+
/// Activates OpenMP parsing mode to preseve OpenMP specific annotation
/// tokens.
class ParsingOpenMPDirectiveRAII {
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 11ba2b81baacc..5908c0459d4d6 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5174,7 +5174,9 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
getCurScope()->isClassScope() && ScopedEnumKWLoc.isInvalid() && Name;
// Parse the fixed underlying type.
- if (Tok.is(tok::colon)) {
+ if (Tok.is(tok::colon) &&
+ (!ParsingGenericAssociationType ||
+ isNextCXXTypeId(TentativeCXXTypeIdContext::Unambiguous))) {
// This might be an enum-base or part of some unrelated enclosing context.
//
// 'enum E : base' is permitted in two circumstances:
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index d701cbaa43bdd..261b87571f55b 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1561,7 +1561,8 @@ bool Parser::isValidAfterTypeSpecifier(bool CouldBeBitfield) {
return true;
case tok::colon:
return CouldBeBitfield || // enum E { ... } : 2;
- ColonIsSacred; // _Generic(..., enum E : 2);
+ ColonIsSacred ||
+ ParsingGenericAssociationType; // _Generic(..., enum E : 2);
// Microsoft compatibility
case tok::kw___cdecl: // struct foo {...} __cdecl x;
case tok::kw___fastcall: // struct foo {...} __fastcall x;
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 8f212fab4cdbf..b370ccde25d8e 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -3106,7 +3106,8 @@ ExprResult Parser::ParseGenericSelectionExpression() {
DefaultLoc = ConsumeToken();
Ty = nullptr;
} else {
- ColonProtectionRAIIObject X(*this);
+ GenericAssociationTypeRAIIObject X(*this);
+
TypeResult TR = ParseTypeName(nullptr, DeclaratorContext::Association);
if (TR.isInvalid()) {
SkipUntil(tok::r_paren, StopAtSemi);
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index 07d45925e892e..7aeb0c39fbf37 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -616,6 +616,14 @@ bool Parser::isCXXTypeId(TentativeCXXTypeIdContext Context, bool &isAmbiguous) {
return TPR == TPResult::True;
}
+bool Parser::isNextCXXTypeId(TentativeCXXTypeIdContext Context,
+ bool &isAmbiguous) {
+ RevertingTentativeParsingAction PA(*this);
+ ConsumeToken();
+ bool ret = isCXXTypeId(Context, isAmbiguous);
+ return ret;
+}
+
CXX11AttributeKind
Parser::isCXX11AttributeSpecifier(bool Disambiguate,
bool OuterMightBeMessageSend) {
diff --git a/clang/test/Parser/c23-enum-generic-assoc.c b/clang/test/Parser/c23-enum-generic-assoc.c
new file mode 100644
index 0000000000000..b0ea715bacc36
--- /dev/null
+++ b/clang/test/Parser/c23-enum-generic-assoc.c
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c23 %s
+// expected-no-diagnostics
+
+typedef long l;
+
+_Static_assert(_Generic(0L, enum E : long { A } : 0, int: 1) == 0, "");
+
+_Static_assert(_Generic(0L, enum E : 0, int: 1) == 0, "");
+
+_Static_assert(_Generic(0L, enum A : l { B } : 0, int: 1) == 0, "");
\ No newline at end of file
>From 0170afdb664994f9d125c25de937bb2a77d8face Mon Sep 17 00:00:00 2001
From: Abdulmalek Almkainzi <aalmkainzi at gmail.com>
Date: Sat, 25 Jul 2026 01:17:14 +0300
Subject: [PATCH 2/5] fixed `enum A : T {B} : 0` in C++ mode, where it used to
parse T {B} as an expression, rather than the underlying type + enum body
---
clang/lib/Parse/ParseExpr.cpp | 2 ++
clang/lib/Parse/ParseTentative.cpp | 16 ++++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index b370ccde25d8e..2277a0011ee68 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -2665,6 +2665,8 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
ParsedType &CastTy, SourceLocation &RParenLoc) {
assert(Tok.is(tok::l_paren) && "Not a paren expr!");
ColonProtectionRAIIObject ColonProtection(*this, false);
+ GenericAssociationTypeRAIIObject NotParsingGenericAssociationType(*this, false);
+
BalancedDelimiterTracker T(*this, tok::l_paren);
if (T.consumeOpen())
return ExprError();
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index 7aeb0c39fbf37..2c49581ff0fdb 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -1538,7 +1538,23 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
// enum E : int { 4 }; // bit-field
// };
if (getLangOpts().CPlusPlus11 && NextToken().is(tok::l_brace))
+ {
+ if (ParsingGenericAssociationType)
+ {
+ RevertingTentativeParsingAction PA(*this);
+ ConsumeAnyToken(); // skip keyword
+ ConsumeBrace(); // skip l_brace
+ if (SkipUntil(tok::r_brace, StopBeforeMatch))
+ {
+ ConsumeBrace(); // skip r_brace
+ if (Tok.is(tok::colon))
+ {
+ return TPResult::True;
+ }
+ }
+ }
return BracedCastResult;
+ }
if (isStartOfObjCClassMessageMissingOpenBracket())
return TPResult::False;
>From 1414fc3475380e9de3484830d35f67250e506672 Mon Sep 17 00:00:00 2001
From: Abdulmalek Almkainzi <aalmkainzi at gmail.com>
Date: Tue, 28 Jul 2026 00:39:59 +0300
Subject: [PATCH 3/5] formatting
---
clang/lib/Parse/ParseExpr.cpp | 3 ++-
clang/lib/Parse/ParseTentative.cpp | 14 +++++---------
2 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 2277a0011ee68..2c2898aa99b24 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -2665,7 +2665,8 @@ Parser::ParseParenExpression(ParenParseOption &ExprType, bool StopIfCastExpr,
ParsedType &CastTy, SourceLocation &RParenLoc) {
assert(Tok.is(tok::l_paren) && "Not a paren expr!");
ColonProtectionRAIIObject ColonProtection(*this, false);
- GenericAssociationTypeRAIIObject NotParsingGenericAssociationType(*this, false);
+ GenericAssociationTypeRAIIObject NotParsingGenericAssociationType(*this,
+ false);
BalancedDelimiterTracker T(*this, tok::l_paren);
if (T.consumeOpen())
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index 2c49581ff0fdb..e9bbea576725e 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -1537,18 +1537,14 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
// enum E : int { a = 4 }; // enum
// enum E : int { 4 }; // bit-field
// };
- if (getLangOpts().CPlusPlus11 && NextToken().is(tok::l_brace))
- {
- if (ParsingGenericAssociationType)
- {
+ if (getLangOpts().CPlusPlus11 && NextToken().is(tok::l_brace)) {
+ if (ParsingGenericAssociationType) {
RevertingTentativeParsingAction PA(*this);
ConsumeAnyToken(); // skip keyword
- ConsumeBrace(); // skip l_brace
- if (SkipUntil(tok::r_brace, StopBeforeMatch))
- {
+ ConsumeBrace(); // skip l_brace
+ if (SkipUntil(tok::r_brace, StopBeforeMatch)) {
ConsumeBrace(); // skip r_brace
- if (Tok.is(tok::colon))
- {
+ if (Tok.is(tok::colon)) {
return TPResult::True;
}
}
>From 088ecfa109fe72a7ea4869c73f144e49294563c9 Mon Sep 17 00:00:00 2001
From: Abdulmalek Almkainzi <aalmkainzi at gmail.com>
Date: Tue, 28 Jul 2026 01:10:00 +0300
Subject: [PATCH 4/5] applied suggested changes
---
clang/include/clang/Parse/Parser.h | 2 +-
clang/include/clang/Parse/RAIIObjectsForParser.h | 4 ++--
clang/lib/Parse/ParseTentative.cpp | 5 ++---
clang/test/Parser/c23-enum-generic-assoc.c | 2 +-
4 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index ced7607c71a69..4dda0f9d5e5ff 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -8690,7 +8690,7 @@ class Parser : public CodeCompletionHandler {
return isCXXTypeId(Context, isAmbiguous);
}
- bool isNextCXXTypeId(TentativeCXXTypeIdContext Context, bool &isAmbiguous);
+ bool isNextCXXTypeId(TentativeCXXTypeIdContext Context, bool &IsAmbiguous);
bool isNextCXXTypeId(TentativeCXXTypeIdContext Context) {
bool isAmbiguous;
diff --git a/clang/include/clang/Parse/RAIIObjectsForParser.h b/clang/include/clang/Parse/RAIIObjectsForParser.h
index 747dbe90b1bf6..7293897981b27 100644
--- a/clang/include/clang/Parse/RAIIObjectsForParser.h
+++ b/clang/include/clang/Parse/RAIIObjectsForParser.h
@@ -295,8 +295,8 @@ namespace clang {
bool OldVal;
public:
- GenericAssociationTypeRAIIObject(Parser &p, bool Value = true)
- : P(p), OldVal(P.ParsingGenericAssociationType) {
+ GenericAssociationTypeRAIIObject(Parser &P, bool Value = true)
+ : P(P), OldVal(P.ParsingGenericAssociationType) {
P.ParsingGenericAssociationType = Value;
}
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index e9bbea576725e..8204f2033e171 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -617,11 +617,10 @@ bool Parser::isCXXTypeId(TentativeCXXTypeIdContext Context, bool &isAmbiguous) {
}
bool Parser::isNextCXXTypeId(TentativeCXXTypeIdContext Context,
- bool &isAmbiguous) {
+ bool &IsAmbiguous) {
RevertingTentativeParsingAction PA(*this);
ConsumeToken();
- bool ret = isCXXTypeId(Context, isAmbiguous);
- return ret;
+ return isCXXTypeId(Context, IsAmbiguous);
}
CXX11AttributeKind
diff --git a/clang/test/Parser/c23-enum-generic-assoc.c b/clang/test/Parser/c23-enum-generic-assoc.c
index b0ea715bacc36..e522fcbe0ee3f 100644
--- a/clang/test/Parser/c23-enum-generic-assoc.c
+++ b/clang/test/Parser/c23-enum-generic-assoc.c
@@ -7,4 +7,4 @@ _Static_assert(_Generic(0L, enum E : long { A } : 0, int: 1) == 0, "");
_Static_assert(_Generic(0L, enum E : 0, int: 1) == 0, "");
-_Static_assert(_Generic(0L, enum A : l { B } : 0, int: 1) == 0, "");
\ No newline at end of file
+_Static_assert(_Generic(0L, enum A : l { B } : 0, int: 1) == 0, "");
>From ff738c92b773c185194742eaf96ba2d2b4f23a3b Mon Sep 17 00:00:00 2001
From: Abdulmalek Almkainzi <aalmkainzi at gmail.com>
Date: Tue, 28 Jul 2026 01:18:23 +0300
Subject: [PATCH 5/5] applied suggested changes
---
clang/include/clang/Parse/Parser.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 4dda0f9d5e5ff..d4ed848ed75ae 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -8693,8 +8693,8 @@ class Parser : public CodeCompletionHandler {
bool isNextCXXTypeId(TentativeCXXTypeIdContext Context, bool &IsAmbiguous);
bool isNextCXXTypeId(TentativeCXXTypeIdContext Context) {
- bool isAmbiguous;
- return isNextCXXTypeId(Context, isAmbiguous);
+ bool IsAmbiguous;
+ return isNextCXXTypeId(Context, IsAmbiguous);
}
/// TPResult - Used as the result value for functions whose purpose is to
More information about the cfe-commits
mailing list