[clang] 58b7032 - [Clang] Properly parse enumeration declarations with fixed underlying type in generic associations (#210353)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 2 08:49:25 PDT 2026
Author: Abdulmalek Almkainzi
Date: 2026-10-02T15:49:16Z
New Revision: 58b70325a0548e6447dd6eeff9ea1e6f0426f6d2
URL: https://github.com/llvm/llvm-project/commit/58b70325a0548e6447dd6eeff9ea1e6f0426f6d2
DIFF: https://github.com/llvm/llvm-project/commit/58b70325a0548e6447dd6eeff9ea1e6f0426f6d2.diff
LOG: [Clang] Properly parse enumeration declarations with fixed underlying type in generic associations (#210353)
added a new `ParsingGenericAssociationType` flag that is managed by
`GenericAssociationTypeRAIIObject`. If that flag is true, 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.
to check if the next tokens are a type, I added a new function
`isNextCXXTypeId`, which works the same as `isCXXTypeId`, but starts the
check one token ahead.
Fixed #207357
---------
Co-authored-by: Aaron Ballman <aaron at aaronballman.com>
Added:
clang/test/Parser/c23-enum-generic-assoc.c
Modified:
clang/include/clang/Parse/Parser.h
clang/include/clang/Parse/RAIIObjectsForParser.h
clang/lib/Parse/ParseDecl.cpp
clang/lib/Parse/ParseDeclCXX.cpp
clang/lib/Parse/ParseExpr.cpp
clang/lib/Parse/ParseExprCXX.cpp
clang/lib/Parse/ParseTentative.cpp
clang/lib/Parse/Parser.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 6ecd6c71506158..d29d3f0ac84a15 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -289,6 +289,7 @@ class Parser : public CodeCompletionHandler {
public:
friend class ColonProtectionRAIIObject;
+ friend class GenericAssociationTypeRAIIObject;
friend class PoisonSEHIdentifiersRAIIObject;
friend class ParenBraceBracketBalancer;
friend class BalancedDelimiterTracker;
@@ -4513,6 +4514,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.
diff --git a/clang/include/clang/Parse/RAIIObjectsForParser.h b/clang/include/clang/Parse/RAIIObjectsForParser.h
index 3adcbfe9d70168..7b2c9c6c049a45 100644
--- a/clang/include/clang/Parse/RAIIObjectsForParser.h
+++ b/clang/include/clang/Parse/RAIIObjectsForParser.h
@@ -290,6 +290,26 @@ namespace clang {
}
};
+ class GenericAssociationTypeRAIIObject {
+ Parser &P;
+ bool OldVal;
+
+ public:
+ GenericAssociationTypeRAIIObject(Parser &P, bool Value = true)
+ : P(P), OldVal(P.ParsingGenericAssociationType) {
+ P.ParsingGenericAssociationType = Value;
+ }
+
+ GenericAssociationTypeRAIIObject(const GenericAssociationTypeRAIIObject &) =
+ delete;
+ GenericAssociationTypeRAIIObject &
+ operator=(const GenericAssociationTypeRAIIObject &) = delete;
+
+ 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 fbecae7b80fb97..6ab03e8a7fc4a1 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5216,8 +5216,15 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
bool CanBeBitfield =
getCurScope()->isClassScope() && ScopedEnumKWLoc.isInvalid() && Name;
+ auto IsCXXTypeAhead = [this]() {
+ RevertingTentativeParsingAction PA(*this);
+ ConsumeToken();
+ return Parser::isCXXTypeId(TentativeCXXTypeIdContext::Unambiguous);
+ };
+
// Parse the fixed underlying type.
- if (Tok.is(tok::colon)) {
+ if (Tok.is(tok::colon) &&
+ (!ParsingGenericAssociationType || IsCXXTypeAhead())) {
// 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 1eeb0d21b68d7b..8e0613623b9828 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1543,7 +1543,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 05cb680faa2158..10f70dc64d8a21 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -2667,6 +2667,10 @@ 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,
+ /*Value=*/false);
+
BalancedDelimiterTracker T(*this, tok::l_paren);
if (T.consumeOpen())
return ExprError();
@@ -3107,7 +3111,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/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 16d74904f6d1dc..c3ec0f03abd629 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -415,7 +415,8 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
// If we get foo:bar, this is almost certainly a typo for foo::bar. Recover
// and emit a fixit hint for it.
- if (Next.is(tok::colon) && !ColonIsSacred) {
+ if (Next.is(tok::colon) && !ColonIsSacred &&
+ !ParsingGenericAssociationType) {
if (Actions.IsInvalidUnlessNestedName(getCurScope(), SS, IdInfo,
EnteringContext) &&
// If the token after the colon isn't an identifier, it's still an
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index bd0c4753dd67d3..c66a92e7359da3 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -1566,8 +1566,20 @@ 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 (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;
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index c86ed6b2ea3f98..e35666b5d6420a 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -74,8 +74,8 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool skipFunctionBodies)
PreferredType(&actions.getASTContext(), pp.isCodeCompletionEnabled()),
Actions(actions), Diags(PP.getDiagnostics()), StackHandler(Diags),
GreaterThanIsOperator(true), ColonIsSacred(false),
- InMessageExpression(false), ParsingInObjCContainer(false),
- TemplateParameterDepth(0) {
+ ParsingGenericAssociationType(false), InMessageExpression(false),
+ ParsingInObjCContainer(false), TemplateParameterDepth(0) {
SkipFunctionBodies = pp.isCodeCompletionEnabled() || skipFunctionBodies;
Tok.startToken();
Tok.setKind(tok::eof);
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 00000000000000..ce621255e81ec3
--- /dev/null
+++ b/clang/test/Parser/c23-enum-generic-assoc.c
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c23 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s
+
+typedef long l;
+enum E : long { X };
+
+
+#ifdef __cplusplus
+
+static_assert(_Generic(0L, enum A : l { B } : 1, int: 0), ""); // expected-error {{'A' cannot be defined in a type specifier}}
+
+enum X { Y };
+void f()
+{
+ static_assert(_Generic(0L, enum X : int {Y}, long: 1), "'int {Y}' parses as an expression");
+}
+
+#else
+// expected-no-diagnostics
+
+static_assert(_Generic(0L, enum E : long { X } : 1, int: 0), "");
+static_assert(_Generic(0L, enum E : 1, int: 0), "");
+static_assert(_Generic(0L, enum A : l { B } : 1, int: 0), "");
+static_assert(_Generic(0L, struct { enum A : l { B } a : 1; } : 0, long: 1), "");
+static_assert(_Generic(0, struct S { enum F : int {A} : 1; } : 0, int: 1));
+static_assert(_Generic(0, struct S { enum E : 1; } : 0, int: 1));
+
+#endif
More information about the cfe-commits
mailing list