[clang] C++26 Annotation (PR #166287)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 20 01:23:57 PST 2025
https://github.com/zebullax updated https://github.com/llvm/llvm-project/pull/166287
>From e2f13c0d8c6e5c6bed1fe445d16eed609e5f03e2 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Sat, 1 Nov 2025 11:11:25 +0900
Subject: [PATCH 01/19] Add annotation to attribute tablegen
Recognize annotation starting token in attr parsing
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/include/clang/Basic/Attr.td | 45 +++++++++++++++++++
.../clang/Basic/DiagnosticParseKinds.td | 5 +++
clang/lib/Parse/ParseDeclCXX.cpp | 11 +++++
clang/utils/TableGen/ClangAttrEmitter.cpp | 8 ++--
4 files changed, 66 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 749f531ec9ab1..06b9b1ff3e295 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -744,6 +744,9 @@ class Attr {
// Note: Any additional data members will leak and should be constructed
// externally on the ASTContext.
code AdditionalMembers = [{}];
+ // Any additional text that should be included verbatim after instantiating
+ // an attribute on a template.
+ code PostInstantiationStmts =[{}];
// Any documentation that should be associated with the attribute. Since an
// attribute may be documented under multiple categories, more than one
// Documentation entry may be listed.
@@ -930,6 +933,48 @@ def AlwaysInline : DeclOrStmtAttr {
let Documentation = [AlwaysInlineDocs];
}
+def CXX26Annotation : InheritableParamAttr {
+ let Spellings = [];
+ let Args = [ExprArgument<"Arg">];
+ let AdditionalMembers = [{
+private:
+ APValue Value;
+ SourceLocation EqLoc;
+
+public:
+ static CXX26AnnotationAttr *Create(ASTContext &Ctx, \
+ const AttributeCommonInfo &CommonInfo) {
+ return CXX26AnnotationAttr::Create(Ctx, nullptr, CommonInfo);
+ }
+ static CXX26AnnotationAttr *CreateImplicit( \
+ ASTContext &Ctx, \
+ const AttributeCommonInfo &CommonInfo) {
+ return CXX26AnnotationAttr::CreateImplicit(Ctx, nullptr, CommonInfo);
+ }
+
+ APValue getValue() const { return Value; }
+ void setValue(APValue V) { Value = V; }
+
+ SourceLocation getEqLoc() const { return EqLoc; }
+ void setEqLoc(SourceLocation Loc) { EqLoc = Loc; }
+ }];
+
+ let PostInstantiationStmts = [{
+ Expr::EvalResult V;
+ if (!Result->getArg()->isValueDependent() &&
+ !Result->getArg()->EvaluateAsRValue(V, C, true))
+ llvm_unreachable("failed to evaluate annotation expression");
+
+ Result->setValue(V.Val);
+ Result->setEqLoc(A->getEqLoc());
+ }];
+
+ let HasCustomParsing = 1;
+ let TemplateDependent = 1;
+ let MeaningfulToClassTemplateDefinition = 1;
+ let Documentation = [InternalOnly];
+}
+
def Artificial : InheritableAttr {
let Spellings = [GCC<"artificial">];
let Subjects = SubjectList<[InlineFunction]>;
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index e5e071f43fa75..c7e59ad21b3bb 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1848,6 +1848,11 @@ def err_placeholder_expected_auto_or_decltype_auto : Error<
"expected 'auto' or 'decltype(auto)' after concept name">;
}
+let CategoryName = "Reflection Issue" in {
+def err_annotation_with_using : Error<
+ "annotations are not permitted following an attribute-using-prefix">;
+}
+
def warn_max_tokens : Warning<
"the number of preprocessor source tokens (%0) exceeds this token limit (%1)">,
InGroup<MaxTokens>, DefaultIgnore;
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index b96968d4592f5..4ca0568b37b12 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -17,6 +17,7 @@
#include "clang/Basic/Attributes.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/DiagnosticParse.h"
+#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Lex/LiteralSupport.h"
@@ -4700,6 +4701,16 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
SourceLocation ScopeLoc, AttrLoc;
IdentifierInfo *ScopeName = nullptr, *AttrName = nullptr;
+ // '=' token marks the beginning of an annotation
+ if (getLangOpts().CPlusPlus26 && Tok.is(tok::equal)) {
+ if (CommonScopeName) {
+ Diag(Tok.getLocation(), diag::err_annotation_with_using);
+ SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
+ continue;
+ }
+ continue;
+ }
+
AttrName = TryParseCXX11AttributeIdentifier(
AttrLoc, SemaCodeCompletion::AttributeCompletion::Attribute,
CommonScopeName);
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 183952af590e1..7bc4719289441 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -4091,13 +4091,15 @@ EmitClangAttrTemplateInstantiateHelper(ArrayRef<const Record *> Attrs,
for (auto const &ai : Args)
ai->writeTemplateInstantiation(OS);
- OS << " return new (C) " << R.getName() << "Attr(C, *A";
+ OS << " auto* Result = new (C) " << R.getName() << "Attr(C, *A";
for (auto const &ai : Args) {
OS << ", ";
ai->writeTemplateInstantiationArgs(OS);
}
- OS << ");\n"
- << " }\n";
+ OS << ");\n";
+ OS << R.getValueAsString("PostInstantiationStmts");
+ OS << "return Result;\n";
+ OS << " }\n";
}
OS << " } // end switch\n"
<< " llvm_unreachable(\"Unknown attribute!\");\n"
>From 76337f8ae60947346fe4fe2f2569f5affe8f0722 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Sun, 2 Nov 2025 10:52:44 +0900
Subject: [PATCH 02/19] Build logic around parsing of annotation inside list
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
.../clang/Basic/DiagnosticParseKinds.td | 4 ++
clang/include/clang/Parse/Parser.h | 4 ++
clang/lib/Parse/ParseDeclCXX.cpp | 40 ++++++++++++++-----
3 files changed, 37 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index c7e59ad21b3bb..e87b605632d19 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -795,6 +795,10 @@ def err_cxx11_attribute_forbids_ellipsis : Error<
def warn_cxx14_compat_using_attribute_ns : Warning<
"default scope specifier for attributes is incompatible with C++ standards "
"before C++17">, InGroup<CXXPre17Compat>, DefaultIgnore;
+def warn_cxx26_compat_annotation : Warning<
+ "annotation is a C++26 extension">, InGroup<CXXPre26Compat>, DefaultIgnore;
+def err_mixed_attributes_and_annotations : Error<
+ "attribute specifier cannot contain both attributes and annotations">;
def ext_using_attribute_ns : ExtWarn<
"default scope specifier for attributes is a C++17 extension">,
InGroup<CXX17>;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index dad8efd0f017f..dd1e774316ca0 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3046,6 +3046,10 @@ class Parser : public CodeCompletionHandler {
SourceLocation ScopeLoc,
CachedTokens &OpenMPTokens);
+ /// TODO doc
+ void ParseAnnotationSpecifier(ParsedAttributes &Attrs,
+ SourceLocation *EndLoc);
+
/// Parse the argument to C++23's [[assume()]] attribute. Returns true on
/// error.
bool
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 4ca0568b37b12..4a82a365bb0b8 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4443,6 +4443,12 @@ static bool IsBuiltInOrStandardCXX11Attribute(IdentifierInfo *AttrName,
}
}
+void Parser::ParseAnnotationSpecifier(ParsedAttributes &Attrs,
+ SourceLocation *EndLoc)
+{
+ // TODO
+}
+
bool Parser::ParseCXXAssumeAttributeArg(
ParsedAttributes &Attrs, IdentifierInfo *AttrName,
SourceLocation AttrNameLoc, IdentifierInfo *ScopeName,
@@ -4682,16 +4688,21 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
Diag(Tok.getLocation(), diag::err_expected) << tok::colon;
}
- bool AttrParsed = false;
+ bool hasAttribute = false;
+ bool hasAnnotation = false;
while (!Tok.isOneOf(tok::r_square, tok::semi, tok::eof)) {
- if (AttrParsed) {
- // If we parsed an attribute, a comma is required before parsing any
- // additional attributes.
+ // If we parsed an attribute/annotation, a comma is required before parsing
+ // any additional ones.
+ if (hasAttribute || hasAnnotation) {
if (ExpectAndConsume(tok::comma)) {
SkipUntil(tok::r_square, StopAtSemi | StopBeforeMatch);
continue;
}
- AttrParsed = false;
+ if (hasAttribute && hasAnnotation) {
+ Diag(Tok.getLocation(), diag::err_mixed_attributes_and_annotations);
+ SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
+ continue;
+ }
}
// Eat all remaining superfluous commas before parsing the next attribute.
@@ -4702,12 +4713,19 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
IdentifierInfo *ScopeName = nullptr, *AttrName = nullptr;
// '=' token marks the beginning of an annotation
- if (getLangOpts().CPlusPlus26 && Tok.is(tok::equal)) {
+ if (Tok.is(tok::equal)) {
+ if (!getLangOpts().CPlusPlus26) {
+ Diag(Tok.getLocation(), diag::warn_cxx26_compat_annotation);
+ SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
+ continue;
+ }
if (CommonScopeName) {
Diag(Tok.getLocation(), diag::err_annotation_with_using);
SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
continue;
}
+ ParseAnnotationSpecifier(Attrs, EndLoc);
+ hasAnnotation = true;
continue;
}
@@ -4744,11 +4762,11 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
}
// Parse attribute arguments
- if (Tok.is(tok::l_paren))
- AttrParsed = ParseCXX11AttributeArgs(AttrName, AttrLoc, Attrs, EndLoc,
- ScopeName, ScopeLoc, OpenMPTokens);
+ hasAttribute = Tok.is(tok::l_paren)
+ && ParseCXX11AttributeArgs(AttrName, AttrLoc, Attrs, EndLoc,
+ ScopeName, ScopeLoc, OpenMPTokens);
- if (!AttrParsed) {
+ if (!hasAttribute) {
Attrs.addNew(AttrName,
SourceRange(ScopeLoc.isValid() && CommonScopeLoc.isInvalid()
? ScopeLoc
@@ -4758,7 +4776,7 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
nullptr, 0,
getLangOpts().CPlusPlus ? ParsedAttr::Form::CXX11()
: ParsedAttr::Form::C23());
- AttrParsed = true;
+ hasAttribute = true;
}
if (TryConsumeToken(tok::ellipsis))
>From cc405d31f4a9c0eb3d931b7d92729c98b7170889 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 4 Nov 2025 09:19:07 +0900
Subject: [PATCH 03/19] Add sema handler for annotation
Add parsing for annotation
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
.../include/clang/Basic/AttributeCommonInfo.h | 8 ++-
clang/lib/Parse/ParseDeclCXX.cpp | 28 +++++++++-
clang/lib/Sema/SemaDeclAttr.cpp | 56 +++++++++++++++++++
3 files changed, 88 insertions(+), 4 deletions(-)
diff --git a/clang/include/clang/Basic/AttributeCommonInfo.h b/clang/include/clang/Basic/AttributeCommonInfo.h
index 77b5eb8a1a7cc..c404d840555fd 100644
--- a/clang/include/clang/Basic/AttributeCommonInfo.h
+++ b/clang/include/clang/Basic/AttributeCommonInfo.h
@@ -61,7 +61,10 @@ class AttributeCommonInfo {
/// The attibute has no source code manifestation and is only created
/// implicitly.
- AS_Implicit
+ AS_Implicit,
+
+ /// The attribute is a C++26 annotation.
+ AS_Annotation,
};
enum Kind {
@@ -133,6 +136,7 @@ class AttributeCommonInfo {
static Form ContextSensitiveKeyword() { return AS_ContextSensitiveKeyword; }
static Form HLSLAnnotation() { return AS_HLSLAnnotation; }
static Form Implicit() { return AS_Implicit; }
+ static Form Annotation() { return AS_Annotation; }
private:
constexpr Form(Syntax SyntaxUsed)
@@ -156,7 +160,7 @@ class AttributeCommonInfo {
SpellingIndex(FormUsed.getSpellingIndex()),
IsAlignas(FormUsed.isAlignas()),
IsRegularKeywordAttribute(FormUsed.isRegularKeywordAttribute()) {
- assert(SyntaxUsed >= AS_GNU && SyntaxUsed <= AS_Implicit &&
+ assert(SyntaxUsed >= AS_GNU && SyntaxUsed <= AS_Annotation &&
"Invalid syntax!");
}
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 4a82a365bb0b8..df33948790fd3 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4446,7 +4446,23 @@ static bool IsBuiltInOrStandardCXX11Attribute(IdentifierInfo *AttrName,
void Parser::ParseAnnotationSpecifier(ParsedAttributes &Attrs,
SourceLocation *EndLoc)
{
- // TODO
+ assert(Tok.is(tok::equal) && "not an annotation");
+ SourceLocation EqLoc = ConsumeToken();
+
+ ExprResult AnnotExpr = ParseConstantExpression();
+ if (AnnotExpr.isInvalid() || AnnotExpr.get()->containsErrors())
+ return;
+
+ IdentifierTable &IT = Actions.PP.getIdentifierTable();
+ IdentifierInfo &Placeholder = IT.get("__annotation_placeholder");
+
+ ArgsVector ArgExprs;
+ ArgExprs.push_back(AnnotExpr.get());
+ Attrs.addNew(&Placeholder, EqLoc, {}, ArgExprs.data(), 1,
+ ParsedAttr::Form::Annotation());
+
+ if (EndLoc)
+ *EndLoc = AnnotExpr.get()->getEndLoc();
}
bool Parser::ParseCXXAssumeAttributeArg(
@@ -4712,7 +4728,10 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
SourceLocation ScopeLoc, AttrLoc;
IdentifierInfo *ScopeName = nullptr, *AttrName = nullptr;
- // '=' token marks the beginning of an annotation
+ // A '=' token marks the beginning of an annotation
+ // - We must not be in C++ < 26
+ // - We must not have seen 'using X::'
+ // - We must not mix with an attribute
if (Tok.is(tok::equal)) {
if (!getLangOpts().CPlusPlus26) {
Diag(Tok.getLocation(), diag::warn_cxx26_compat_annotation);
@@ -4724,6 +4743,11 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
continue;
}
+ if (hasAttribute) {
+ Diag(Tok.getLocation(), diag::err_mixed_attributes_and_annotations);
+ SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
+ continue;
+ }
ParseAnnotationSpecifier(Attrs, EndLoc);
hasAnnotation = true;
continue;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index a9e7b44ac9d73..725a84fd85c3c 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6409,6 +6409,59 @@ static void handleRequiresCapabilityAttr(Sema &S, Decl *D,
D->addAttr(RCA);
}
+static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ Expr *CE = AL.getArgAsExpr(0);
+ if (CE->isLValue()) {
+ if (CE->getType()->isRecordType()) {
+ InitializedEntity Entity =
+ InitializedEntity::InitializeTemporary(
+ CE->getType().getUnqualifiedType());
+ InitializationKind Kind =
+ InitializationKind::CreateCopy(CE->getExprLoc(), SourceLocation());
+ InitializationSequence Seq(S, Entity, Kind, CE);
+
+ ExprResult CopyResult = Seq.Perform(S, Entity, Kind, CE);
+ if (CopyResult.isInvalid())
+ return;
+
+ CE = CopyResult.get();
+ } else {
+ ExprResult RVExprResult = S.DefaultLvalueConversion(AL.getArgAsExpr(0));
+ assert(!RVExprResult.isInvalid() && RVExprResult.get());
+
+ CE = RVExprResult.get();
+ }
+ }
+
+ Expr::EvalResult Result;
+
+ SmallVector<PartialDiagnosticAt, 4> Notes;
+ Result.Diag = &Notes;
+
+ if (!CE->isValueDependent()) {
+ ConstantExprKind CEKind = (CE->getType()->isClassType() ?
+ ConstantExprKind::ClassTemplateArgument :
+ ConstantExprKind::NonClassTemplateArgument);
+
+ if (!CE->EvaluateAsConstantExpr(Result, S.Context, CEKind)) {
+ S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
+ << "C++26 annotation" << 4 << CE->getSourceRange();
+ for (auto P : Notes)
+ S.Diag(P.first, P.second);
+
+ return;
+ } else if (!CE->getType()->isStructuralType()) {
+ S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
+ << "C++26 annotation" << 5 << CE->getSourceRange();
+ return;
+ }
+ }
+ auto *Annot = CXX26AnnotationAttr::Create(S.Context, CE, AL);
+ Annot->setValue(Result.Val);
+ Annot->setEqLoc(AL.getLoc());
+ D->addAttr(Annot);
+}
+
static void handleDeprecatedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
if (const auto *NSD = dyn_cast<NamespaceDecl>(D)) {
if (NSD->isAnonymousNamespace()) {
@@ -7179,6 +7232,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Constructor:
handleConstructorAttr(S, D, AL);
break;
+ case ParsedAttr::AT_CXX26Annotation:
+ handleCxx26AnnotationAttr(S, D, AL);
+ break;
case ParsedAttr::AT_Deprecated:
handleDeprecatedAttr(S, D, AL);
break;
>From 0a8e12564aae79285802f6d6598038c1f74de41c Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 4 Nov 2025 09:43:08 +0900
Subject: [PATCH 04/19] Fix the copy around of common info for attribute
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/lib/Sema/ParsedAttr.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index 2b5ad33ad7b29..888a5d42a0ec6 100644
--- a/clang/lib/Sema/ParsedAttr.cpp
+++ b/clang/lib/Sema/ParsedAttr.cpp
@@ -107,6 +107,13 @@ const ParsedAttrInfo &ParsedAttrInfo::get(const AttributeCommonInfo &A) {
if ((size_t)A.getParsedKind() < std::size(AttrInfoMap))
return *AttrInfoMap[A.getParsedKind()];
+ // If this is an annotation then return an appropriate ParsedAttrInfo.
+ static const ParsedAttrInfo AnnotationAttrInfo(
+ AttributeCommonInfo::AT_CXX26Annotation, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, {},
+ nullptr);
+ if (A.getSyntax() == AttributeCommonInfo::AS_Annotation)
+ return AnnotationAttrInfo;
+
// If this is an ignored attribute then return an appropriate ParsedAttrInfo.
static const ParsedAttrInfo IgnoredParsedAttrInfo(
AttributeCommonInfo::IgnoredAttribute);
>From c579bd993143b0c281fd1e7b4e7e3cb028eb0f4a Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 4 Nov 2025 11:21:59 +0900
Subject: [PATCH 05/19] Fill the doc for ParseAnnotationSpecifier
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/include/clang/Parse/Parser.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index dd1e774316ca0..52b909ffcae91 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3046,7 +3046,7 @@ class Parser : public CodeCompletionHandler {
SourceLocation ScopeLoc,
CachedTokens &OpenMPTokens);
- /// TODO doc
+ /// Parse an annotation as specified from C++26
void ParseAnnotationSpecifier(ParsedAttributes &Attrs,
SourceLocation *EndLoc);
>From 7673caba149e586a84f0c8e371680a38ccc7cce8 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 4 Nov 2025 11:45:06 +0900
Subject: [PATCH 06/19] Apply formatter
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/lib/Parse/ParseDeclCXX.cpp | 9 ++++-----
clang/lib/Sema/SemaDeclAttr.cpp | 12 ++++++------
2 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index df33948790fd3..04eea962a0208 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4444,8 +4444,7 @@ static bool IsBuiltInOrStandardCXX11Attribute(IdentifierInfo *AttrName,
}
void Parser::ParseAnnotationSpecifier(ParsedAttributes &Attrs,
- SourceLocation *EndLoc)
-{
+ SourceLocation *EndLoc) {
assert(Tok.is(tok::equal) && "not an annotation");
SourceLocation EqLoc = ConsumeToken();
@@ -4786,9 +4785,9 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
}
// Parse attribute arguments
- hasAttribute = Tok.is(tok::l_paren)
- && ParseCXX11AttributeArgs(AttrName, AttrLoc, Attrs, EndLoc,
- ScopeName, ScopeLoc, OpenMPTokens);
+ hasAttribute = Tok.is(tok::l_paren) &&
+ ParseCXX11AttributeArgs(AttrName, AttrLoc, Attrs, EndLoc,
+ ScopeName, ScopeLoc, OpenMPTokens);
if (!hasAttribute) {
Attrs.addNew(AttrName,
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 725a84fd85c3c..f72328facddfc 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6413,9 +6413,8 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
Expr *CE = AL.getArgAsExpr(0);
if (CE->isLValue()) {
if (CE->getType()->isRecordType()) {
- InitializedEntity Entity =
- InitializedEntity::InitializeTemporary(
- CE->getType().getUnqualifiedType());
+ InitializedEntity Entity = InitializedEntity::InitializeTemporary(
+ CE->getType().getUnqualifiedType());
InitializationKind Kind =
InitializationKind::CreateCopy(CE->getExprLoc(), SourceLocation());
InitializationSequence Seq(S, Entity, Kind, CE);
@@ -6439,9 +6438,10 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
Result.Diag = &Notes;
if (!CE->isValueDependent()) {
- ConstantExprKind CEKind = (CE->getType()->isClassType() ?
- ConstantExprKind::ClassTemplateArgument :
- ConstantExprKind::NonClassTemplateArgument);
+ ConstantExprKind CEKind =
+ (CE->getType()->isClassType()
+ ? ConstantExprKind::ClassTemplateArgument
+ : ConstantExprKind::NonClassTemplateArgument);
if (!CE->EvaluateAsConstantExpr(Result, S.Context, CEKind)) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
>From c28d0666990ffdc06286fa2515714fcb81e6cca8 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 4 Nov 2025 14:39:00 +0900
Subject: [PATCH 07/19] Address review
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
Undo dumb edit
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/lib/Parse/ParseDeclCXX.cpp | 18 +++++++++---------
clang/lib/Sema/SemaDeclAttr.cpp | 3 ++-
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 04eea962a0208..920b7b9429c4c 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4703,17 +4703,17 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
Diag(Tok.getLocation(), diag::err_expected) << tok::colon;
}
- bool hasAttribute = false;
- bool hasAnnotation = false;
+ bool HasAttribute = false;
+ bool HasAnnotation = false;
while (!Tok.isOneOf(tok::r_square, tok::semi, tok::eof)) {
// If we parsed an attribute/annotation, a comma is required before parsing
// any additional ones.
- if (hasAttribute || hasAnnotation) {
+ if (HasAttribute || HasAnnotation) {
if (ExpectAndConsume(tok::comma)) {
SkipUntil(tok::r_square, StopAtSemi | StopBeforeMatch);
continue;
}
- if (hasAttribute && hasAnnotation) {
+ if (HasAttribute && HasAnnotation) {
Diag(Tok.getLocation(), diag::err_mixed_attributes_and_annotations);
SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
continue;
@@ -4742,13 +4742,13 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
continue;
}
- if (hasAttribute) {
+ if (HasAttribute) {
Diag(Tok.getLocation(), diag::err_mixed_attributes_and_annotations);
SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
continue;
}
ParseAnnotationSpecifier(Attrs, EndLoc);
- hasAnnotation = true;
+ HasAnnotation = true;
continue;
}
@@ -4785,11 +4785,11 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
}
// Parse attribute arguments
- hasAttribute = Tok.is(tok::l_paren) &&
+ HasAttribute = Tok.is(tok::l_paren) &&
ParseCXX11AttributeArgs(AttrName, AttrLoc, Attrs, EndLoc,
ScopeName, ScopeLoc, OpenMPTokens);
- if (!hasAttribute) {
+ if (!HasAttribute) {
Attrs.addNew(AttrName,
SourceRange(ScopeLoc.isValid() && CommonScopeLoc.isInvalid()
? ScopeLoc
@@ -4799,7 +4799,7 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
nullptr, 0,
getLangOpts().CPlusPlus ? ParsedAttr::Form::CXX11()
: ParsedAttr::Form::C23());
- hasAttribute = true;
+ HasAttribute = true;
}
if (TryConsumeToken(tok::ellipsis))
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index f72328facddfc..2e293b15b97d0 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6450,7 +6450,8 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
S.Diag(P.first, P.second);
return;
- } else if (!CE->getType()->isStructuralType()) {
+ }
+ if (!CE->getType()->isStructuralType()) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
<< "C++26 annotation" << 5 << CE->getSourceRange();
return;
>From 4898a6387781065a1556091757cff9dfa76b1e03 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 4 Nov 2025 19:19:51 +0900
Subject: [PATCH 08/19] Verify we are not emitting hasAttribute for Annotation
spelling
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/utils/TableGen/ClangAttrEmitter.cpp | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 7bc4719289441..17e53ac944201 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -3860,6 +3860,11 @@ void EmitClangAttrHasAttrImpl(const RecordKeeper &Records, raw_ostream &OS) {
Pragma.emplace_back(R, SI);
else if (Variety == "HLSLAnnotation")
HLSLAnnotation.emplace_back(R, SI);
+ else if (Variety == "AS_Annotation") {
+ // We should not be code gening anything with a C++26
+ // annotation syntax.
+ // TODO do we want to warn here ?
+ }
}
}
@@ -3904,6 +3909,10 @@ void EmitClangAttrHasAttrImpl(const RecordKeeper &Records, raw_ostream &OS) {
OS << " llvm_unreachable (\"hasAttribute not supported for "
"AS_Implicit\");\n";
OS << " return 0;\n";
+ OS << "case AttributeCommonInfo::Syntax::AS_Annotation:\n";
+ OS << " llvm_unreachable (\"hasAttribute not supported for "
+ "AS_Annotation\");\n";
+ OS << " return 0;\n";
OS << "}\n";
}
>From 41511c0dc7ebf0877ea4b60d5d0ccca9c3fe1798 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 11 Nov 2025 12:00:33 +0900
Subject: [PATCH 09/19] Fix checks to allow annotation on declaration
Add smoke test
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
.../include/clang/Basic/AttributeCommonInfo.h | 2 ++
clang/include/clang/Sema/DeclSpec.h | 3 +-
clang/lib/Sema/ParsedAttr.cpp | 2 +-
clang/test/SemaCXX/cxx26-annotation.cpp | 35 +++++++++++++++++++
4 files changed, 40 insertions(+), 2 deletions(-)
create mode 100644 clang/test/SemaCXX/cxx26-annotation.cpp
diff --git a/clang/include/clang/Basic/AttributeCommonInfo.h b/clang/include/clang/Basic/AttributeCommonInfo.h
index c404d840555fd..0baee12071e3c 100644
--- a/clang/include/clang/Basic/AttributeCommonInfo.h
+++ b/clang/include/clang/Basic/AttributeCommonInfo.h
@@ -230,6 +230,8 @@ class AttributeCommonInfo {
bool isCXX11Attribute() const { return SyntaxUsed == AS_CXX11 || IsAlignas; }
+ bool isCXX26Annotation() const { return SyntaxUsed == AS_Annotation; }
+
bool isC23Attribute() const { return SyntaxUsed == AS_C23; }
bool isAlignas() const {
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 43a48c92fc305..a5d1f155605e4 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -2008,7 +2008,8 @@ class Declarator {
assert(llvm::all_of(DeclarationAttrs,
[](const ParsedAttr &AL) {
return (AL.isStandardAttributeSyntax() ||
- AL.isRegularKeywordAttribute());
+ AL.isRegularKeywordAttribute() ||
+ AL.isCXX26Annotation());
}) &&
"DeclarationAttrs may only contain [[]] and keyword attributes");
}
diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index 888a5d42a0ec6..8f648d90e5742 100644
--- a/clang/lib/Sema/ParsedAttr.cpp
+++ b/clang/lib/Sema/ParsedAttr.cpp
@@ -222,7 +222,7 @@ bool ParsedAttr::slidesFromDeclToDeclSpecLegacyBehavior() const {
// atributes.
return false;
- assert(isStandardAttributeSyntax() || isAlignas());
+ assert(isStandardAttributeSyntax() || isAlignas() || isCXX26Annotation());
// We have historically allowed some type attributes with standard attribute
// syntax to slide to the decl-specifier-seq, so we have to keep supporting
diff --git a/clang/test/SemaCXX/cxx26-annotation.cpp b/clang/test/SemaCXX/cxx26-annotation.cpp
new file mode 100644
index 0000000000000..333d420114666
--- /dev/null
+++ b/clang/test/SemaCXX/cxx26-annotation.cpp
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -std=c++26 -x c++ %s -verify
+
+struct F {
+ bool V;
+};
+
+// Type
+struct [[=1]] f1 {};
+struct [[=1, =F{true}]] f2 {};
+struct [[=1]] [[=2]] f3 {};
+
+// Declaration
+const [[=1]] F ff{};
+
+// Redeclaration
+[[=2, =3, =2]] void g();
+void g [[=4, =2]] ();
+
+// Error case
+struct [[nodiscard, =1]] f4 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
+struct [[=1, nodiscard, ]] f5 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
+
+struct G {
+ [[using CC: =1]] [[=2]] int f; // expected-error {{annotations are not permitted following an attribute-using-prefix}}
+};
+
+template<class T>
+ [[=T::type()]] void h(T t); // expected-error {{type 'char' cannot be used prior to '::' because it has no members}}
+
+void h(int);
+
+void hh() {
+ h(0);
+ h('0'); // expected-note {{in instantiation of function template specialization 'h<char>' requested here}}
+}
>From dd45005c1ac6251d1f6ab8ad8c5d6ab9a6f7f4b4 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Fri, 14 Nov 2025 17:08:28 +0900
Subject: [PATCH 10/19] Fix logic on mixed attribute and annotation
Improve documentation
Emit error if annotation syntax found in tablegen
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/include/clang/Basic/Attr.td | 14 ++------
.../clang/Basic/DiagnosticParseKinds.td | 4 +--
.../clang/Basic/DiagnosticSemaKinds.td | 3 +-
clang/lib/Parse/ParseDeclCXX.cpp | 17 +++++-----
clang/lib/Sema/ParsedAttr.cpp | 16 ++++++++--
clang/lib/Sema/SemaDeclAttr.cpp | 2 ++
clang/test/SemaCXX/cxx26-annotation.cpp | 32 +++++++++----------
clang/utils/TableGen/ClangAttrEmitter.cpp | 4 ++-
8 files changed, 51 insertions(+), 41 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index fe838bbeec5a6..7003a75e3e82e 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -748,8 +748,9 @@ class Attr {
// Note: Any additional data members will leak and should be constructed
// externally on the ASTContext.
code AdditionalMembers = [{}];
- // Any additional text that should be included verbatim after instantiating
- // an attribute on a template.
+ // Any additional text that should be included verbatim after instantiating
+ // an attribute on a template. This can be used if additional bookkeeping is
+ // required for the data members
code PostInstantiationStmts =[{}];
// Any documentation that should be associated with the attribute. Since an
// attribute may be documented under multiple categories, more than one
@@ -946,15 +947,6 @@ private:
SourceLocation EqLoc;
public:
- static CXX26AnnotationAttr *Create(ASTContext &Ctx, \
- const AttributeCommonInfo &CommonInfo) {
- return CXX26AnnotationAttr::Create(Ctx, nullptr, CommonInfo);
- }
- static CXX26AnnotationAttr *CreateImplicit( \
- ASTContext &Ctx, \
- const AttributeCommonInfo &CommonInfo) {
- return CXX26AnnotationAttr::CreateImplicit(Ctx, nullptr, CommonInfo);
- }
APValue getValue() const { return Value; }
void setValue(APValue V) { Value = V; }
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index c9b9b00eddac6..adbd6c55883ea 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -798,8 +798,8 @@ def err_cxx11_attribute_forbids_ellipsis : Error<
def warn_cxx14_compat_using_attribute_ns : Warning<
"default scope specifier for attributes is incompatible with C++ standards "
"before C++17">, InGroup<CXXPre17Compat>, DefaultIgnore;
-def warn_cxx26_compat_annotation : Warning<
- "annotation is a C++26 extension">, InGroup<CXXPre26Compat>, DefaultIgnore;
+def err_cxx26_compat_annotation : ExtWarn<
+ "annotations are a C++26 extension">, InGroup<CXXPre26Compat>, DefaultError;
def err_mixed_attributes_and_annotations : Error<
"attribute specifier cannot contain both attributes and annotations">;
def ext_using_attribute_ns : ExtWarn<
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3e864475f22a1..423dbfdb5a557 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3424,7 +3424,8 @@ def err_attribute_argument_n_type : Error<
"constant|a string|an identifier|a constant expression|a builtin function}2">;
def err_attribute_argument_type : Error<
"%0 attribute requires %select{int or bool|an integer "
- "constant|a string|an identifier}1">;
+ "constant|a string|an identifier|an expression usable as a template argument|"
+ "a value of structural type}1">;
def err_attribute_argument_out_of_range : Error<
"%0 attribute requires integer constant between %1 and %2 inclusive">;
def err_init_priority_object_attr : Error<
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 920b7b9429c4c..2ef393dc228bf 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4707,20 +4707,16 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
bool HasAnnotation = false;
while (!Tok.isOneOf(tok::r_square, tok::semi, tok::eof)) {
// If we parsed an attribute/annotation, a comma is required before parsing
- // any additional ones.
+ // any additional ones.
if (HasAttribute || HasAnnotation) {
if (ExpectAndConsume(tok::comma)) {
SkipUntil(tok::r_square, StopAtSemi | StopBeforeMatch);
continue;
}
- if (HasAttribute && HasAnnotation) {
- Diag(Tok.getLocation(), diag::err_mixed_attributes_and_annotations);
- SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
- continue;
- }
}
- // Eat all remaining superfluous commas before parsing the next attribute.
+ // Eat all remaining superfluous commas before parsing the next attribute
+ // or annotation
while (TryConsumeToken(tok::comma))
;
@@ -4733,7 +4729,7 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
// - We must not mix with an attribute
if (Tok.is(tok::equal)) {
if (!getLangOpts().CPlusPlus26) {
- Diag(Tok.getLocation(), diag::warn_cxx26_compat_annotation);
+ Diag(Tok.getLocation(), diag::err_cxx26_compat_annotation);
SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
continue;
}
@@ -4790,6 +4786,11 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
ScopeName, ScopeLoc, OpenMPTokens);
if (!HasAttribute) {
+ if (HasAnnotation) {
+ Diag(Tok.getLocation(), diag::err_mixed_attributes_and_annotations);
+ SkipUntil(tok::r_square, tok::colon, StopBeforeMatch);
+ continue;
+ }
Attrs.addNew(AttrName,
SourceRange(ScopeLoc.isValid() && CommonScopeLoc.isInvalid()
? ScopeLoc
diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index 8f648d90e5742..4a9586de3d4ff 100644
--- a/clang/lib/Sema/ParsedAttr.cpp
+++ b/clang/lib/Sema/ParsedAttr.cpp
@@ -109,8 +109,20 @@ const ParsedAttrInfo &ParsedAttrInfo::get(const AttributeCommonInfo &A) {
// If this is an annotation then return an appropriate ParsedAttrInfo.
static const ParsedAttrInfo AnnotationAttrInfo(
- AttributeCommonInfo::AT_CXX26Annotation, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, {},
- nullptr);
+ AttributeCommonInfo::AT_CXX26Annotation, // AttrKind
+ 1, // NumArgs
+ 0, // OptArgs
+ 1, // NumArgMembers
+ 1, // HasCustomParsing
+ 0, // AcceptsExprPack
+ 0, // IsTargetSpecific
+ 1, // IsType
+ 1, // IsStmt
+ 0, // IsKnownToGCC,
+ 0, // IsSupportedByPragmaAttribute
+ {}, // Spellings
+ nullptr // ArgNames
+ );
if (A.getSyntax() == AttributeCommonInfo::AS_Annotation)
return AnnotationAttrInfo;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 2e293b15b97d0..6b18ca6c3d957 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6443,6 +6443,7 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
? ConstantExprKind::ClassTemplateArgument
: ConstantExprKind::NonClassTemplateArgument);
+ // Argument to annotation must be usable as template argument
if (!CE->EvaluateAsConstantExpr(Result, S.Context, CEKind)) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
<< "C++26 annotation" << 4 << CE->getSourceRange();
@@ -6451,6 +6452,7 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
return;
}
+ // Argument to annotation must evaluate to structural type
if (!CE->getType()->isStructuralType()) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
<< "C++26 annotation" << 5 << CE->getSourceRange();
diff --git a/clang/test/SemaCXX/cxx26-annotation.cpp b/clang/test/SemaCXX/cxx26-annotation.cpp
index 333d420114666..13d420e5814f4 100644
--- a/clang/test/SemaCXX/cxx26-annotation.cpp
+++ b/clang/test/SemaCXX/cxx26-annotation.cpp
@@ -1,35 +1,35 @@
// RUN: %clang_cc1 -std=c++26 -x c++ %s -verify
-struct F {
- bool V;
-};
-
+// Nominal cases
// Type
struct [[=1]] f1 {};
struct [[=1, =F{true}]] f2 {};
struct [[=1]] [[=2]] f3 {};
-
// Declaration
-const [[=1]] F ff{};
-
+struct F {
+ bool V;
+};
+const [[=1]] F f4{};
// Redeclaration
-[[=2, =3, =2]] void g();
-void g [[=4, =2]] ();
+[[=2, =3, =2]] void f5();
+void f5 [[=4, =2]] ();
// Error case
-struct [[nodiscard, =1]] f4 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
-struct [[=1, nodiscard, ]] f5 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
-
+// Mixing annotation and attributes, with or without trailing characters
+struct [[nodiscard, =1]] f6 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
+struct [[nodiscard, =1,]] f7 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
+struct [[=1, nodiscard, ]] f8 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
+struct [[=1, nodiscard ]] f9 {}; // expected-error {{attribute specifier cannot contain both attributes and annotations}}
+// Mixing attribute using and annotation
struct G {
[[using CC: =1]] [[=2]] int f; // expected-error {{annotations are not permitted following an attribute-using-prefix}}
};
-
+// Substituting into an annotation is not in the immediate context
template<class T>
[[=T::type()]] void h(T t); // expected-error {{type 'char' cannot be used prior to '::' because it has no members}}
-
+ // expected-note@#inst {{in instantiation of function template specialization 'h<char>' requested here}}
void h(int);
-
void hh() {
h(0);
- h('0'); // expected-note {{in instantiation of function template specialization 'h<char>' requested here}}
+ h('0'); // #inst
}
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 17e53ac944201..cf648ad7848b8 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -3863,7 +3863,9 @@ void EmitClangAttrHasAttrImpl(const RecordKeeper &Records, raw_ostream &OS) {
else if (Variety == "AS_Annotation") {
// We should not be code gening anything with a C++26
// annotation syntax.
- // TODO do we want to warn here ?
+ PrintError(R->getLoc(),
+ "Invalid syntax 'Annotation' used on the node '" +
+ R->getName() + "'");
}
}
}
>From 098155533457ac1c0247e3f1a33ef659cf717cb1 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Fri, 14 Nov 2025 17:20:37 +0900
Subject: [PATCH 11/19] Apply formatter
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/lib/Sema/ParsedAttr.cpp | 26 +++++++++++------------
clang/utils/TableGen/ClangAttrEmitter.cpp | 4 ++--
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index 4a9586de3d4ff..ddfb43601d1db 100644
--- a/clang/lib/Sema/ParsedAttr.cpp
+++ b/clang/lib/Sema/ParsedAttr.cpp
@@ -109,19 +109,19 @@ const ParsedAttrInfo &ParsedAttrInfo::get(const AttributeCommonInfo &A) {
// If this is an annotation then return an appropriate ParsedAttrInfo.
static const ParsedAttrInfo AnnotationAttrInfo(
- AttributeCommonInfo::AT_CXX26Annotation, // AttrKind
- 1, // NumArgs
- 0, // OptArgs
- 1, // NumArgMembers
- 1, // HasCustomParsing
- 0, // AcceptsExprPack
- 0, // IsTargetSpecific
- 1, // IsType
- 1, // IsStmt
- 0, // IsKnownToGCC,
- 0, // IsSupportedByPragmaAttribute
- {}, // Spellings
- nullptr // ArgNames
+ AttributeCommonInfo::AT_CXX26Annotation, // AttrKind
+ 1, // NumArgs
+ 0, // OptArgs
+ 1, // NumArgMembers
+ 1, // HasCustomParsing
+ 0, // AcceptsExprPack
+ 0, // IsTargetSpecific
+ 1, // IsType
+ 1, // IsStmt
+ 0, // IsKnownToGCC,
+ 0, // IsSupportedByPragmaAttribute
+ {}, // Spellings
+ nullptr // ArgNames
);
if (A.getSyntax() == AttributeCommonInfo::AS_Annotation)
return AnnotationAttrInfo;
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index cf648ad7848b8..7c21bc48593a2 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -3864,8 +3864,8 @@ void EmitClangAttrHasAttrImpl(const RecordKeeper &Records, raw_ostream &OS) {
// We should not be code gening anything with a C++26
// annotation syntax.
PrintError(R->getLoc(),
- "Invalid syntax 'Annotation' used on the node '" +
- R->getName() + "'");
+ "Invalid syntax 'Annotation' used on the node '" +
+ R->getName() + "'");
}
}
}
>From 0eaf4dc67fd830c019923f70c4efe2023de534ce Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Fri, 14 Nov 2025 17:55:16 +0900
Subject: [PATCH 12/19] Fix UT
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/test/SemaCXX/cxx26-annotation.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/test/SemaCXX/cxx26-annotation.cpp b/clang/test/SemaCXX/cxx26-annotation.cpp
index 13d420e5814f4..c310f9f69893f 100644
--- a/clang/test/SemaCXX/cxx26-annotation.cpp
+++ b/clang/test/SemaCXX/cxx26-annotation.cpp
@@ -1,14 +1,14 @@
// RUN: %clang_cc1 -std=c++26 -x c++ %s -verify
+struct F {
+ bool V;
+};
// Nominal cases
// Type
struct [[=1]] f1 {};
struct [[=1, =F{true}]] f2 {};
struct [[=1]] [[=2]] f3 {};
// Declaration
-struct F {
- bool V;
-};
const [[=1]] F f4{};
// Redeclaration
[[=2, =3, =2]] void f5();
>From 8dd0e8bbafbc6f2680f22ad5642f05890279dd23 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Fri, 14 Nov 2025 18:21:19 +0900
Subject: [PATCH 13/19] Close comments with .
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/include/clang/Basic/Attr.td | 2 +-
clang/include/clang/Parse/Parser.h | 2 +-
clang/lib/Parse/ParseDeclCXX.cpp | 12 ++++++------
clang/lib/Sema/ParsedAttr.cpp | 2 +-
clang/lib/Sema/SemaDeclAttr.cpp | 4 ++--
5 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 7003a75e3e82e..1771d7fe4d5a5 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -750,7 +750,7 @@ class Attr {
code AdditionalMembers = [{}];
// Any additional text that should be included verbatim after instantiating
// an attribute on a template. This can be used if additional bookkeeping is
- // required for the data members
+ // required for the data members.
code PostInstantiationStmts =[{}];
// Any documentation that should be associated with the attribute. Since an
// attribute may be documented under multiple categories, more than one
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 52b909ffcae91..9ca8b69265878 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3046,7 +3046,7 @@ class Parser : public CodeCompletionHandler {
SourceLocation ScopeLoc,
CachedTokens &OpenMPTokens);
- /// Parse an annotation as specified from C++26
+ /// Parse an annotation as specified from C++26.
void ParseAnnotationSpecifier(ParsedAttributes &Attrs,
SourceLocation *EndLoc);
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 2ef393dc228bf..0368b6d63a576 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4716,17 +4716,17 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
}
// Eat all remaining superfluous commas before parsing the next attribute
- // or annotation
+ // or annotation.
while (TryConsumeToken(tok::comma))
;
SourceLocation ScopeLoc, AttrLoc;
IdentifierInfo *ScopeName = nullptr, *AttrName = nullptr;
- // A '=' token marks the beginning of an annotation
- // - We must not be in C++ < 26
- // - We must not have seen 'using X::'
- // - We must not mix with an attribute
+ // A '=' token marks the beginning of an annotation with the restriction
+ // - must not be in C++ < 26,
+ // - must not have seen 'using X::',
+ // - must not mix with an attribute.
if (Tok.is(tok::equal)) {
if (!getLangOpts().CPlusPlus26) {
Diag(Tok.getLocation(), diag::err_cxx26_compat_annotation);
@@ -4780,7 +4780,7 @@ void Parser::ParseCXX11AttributeSpecifierInternal(ParsedAttributes &Attrs,
}
}
- // Parse attribute arguments
+ // Parse attribute arguments.
HasAttribute = Tok.is(tok::l_paren) &&
ParseCXX11AttributeArgs(AttrName, AttrLoc, Attrs, EndLoc,
ScopeName, ScopeLoc, OpenMPTokens);
diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index ddfb43601d1db..409862631e7fa 100644
--- a/clang/lib/Sema/ParsedAttr.cpp
+++ b/clang/lib/Sema/ParsedAttr.cpp
@@ -118,7 +118,7 @@ const ParsedAttrInfo &ParsedAttrInfo::get(const AttributeCommonInfo &A) {
0, // IsTargetSpecific
1, // IsType
1, // IsStmt
- 0, // IsKnownToGCC,
+ 0, // IsKnownToGCC
0, // IsSupportedByPragmaAttribute
{}, // Spellings
nullptr // ArgNames
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 6b18ca6c3d957..55e24e52e4f15 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6443,7 +6443,7 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
? ConstantExprKind::ClassTemplateArgument
: ConstantExprKind::NonClassTemplateArgument);
- // Argument to annotation must be usable as template argument
+ // Argument to annotation must be usable as template argument.
if (!CE->EvaluateAsConstantExpr(Result, S.Context, CEKind)) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
<< "C++26 annotation" << 4 << CE->getSourceRange();
@@ -6452,7 +6452,7 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
return;
}
- // Argument to annotation must evaluate to structural type
+ // Argument to annotation must evaluate to structural type.
if (!CE->getType()->isStructuralType()) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
<< "C++26 annotation" << 5 << CE->getSourceRange();
>From 8559a6eba58ce64104d5a62f37f69ab44939e55f Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 18 Nov 2025 08:34:45 +0900
Subject: [PATCH 14/19] Add test covering deleted copy on record type for
annotation
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/lib/Sema/SemaDeclAttr.cpp | 5 +++--
clang/test/SemaCXX/cxx26-annotation.cpp | 20 ++++++++++++++++++--
2 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 55e24e52e4f15..c49008a89d505 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6446,7 +6446,7 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// Argument to annotation must be usable as template argument.
if (!CE->EvaluateAsConstantExpr(Result, S.Context, CEKind)) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
- << "C++26 annotation" << 4 << CE->getSourceRange();
+ << "C++26 annotation" << /*template arg=*/4 << CE->getSourceRange();
for (auto P : Notes)
S.Diag(P.first, P.second);
@@ -6455,7 +6455,8 @@ static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// Argument to annotation must evaluate to structural type.
if (!CE->getType()->isStructuralType()) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
- << "C++26 annotation" << 5 << CE->getSourceRange();
+ << "C++26 annotation" << /*value or structural type*/ 5
+ << CE->getSourceRange();
return;
}
}
diff --git a/clang/test/SemaCXX/cxx26-annotation.cpp b/clang/test/SemaCXX/cxx26-annotation.cpp
index c310f9f69893f..cd9f46ec69f48 100644
--- a/clang/test/SemaCXX/cxx26-annotation.cpp
+++ b/clang/test/SemaCXX/cxx26-annotation.cpp
@@ -10,6 +10,7 @@ struct [[=1, =F{true}]] f2 {};
struct [[=1]] [[=2]] f3 {};
// Declaration
const [[=1]] F f4{};
+void foo([[=F{false}]]int i) {}
// Redeclaration
[[=2, =3, =2]] void f5();
void f5 [[=4, =2]] ();
@@ -27,9 +28,24 @@ struct G {
// Substituting into an annotation is not in the immediate context
template<class T>
[[=T::type()]] void h(T t); // expected-error {{type 'char' cannot be used prior to '::' because it has no members}}
- // expected-note@#inst {{in instantiation of function template specialization 'h<char>' requested here}}
+ // expected-note@#inst-H {{in instantiation of function template specialization 'h<char>' requested here}}
+struct T {
+ static constexpr int type() { return 0; }
+};
+
void h(int);
void hh() {
h(0);
- h('0'); // #inst
+ h('0'); // #inst-H
+ h(T{});
}
+
+// Handle copying lvalue
+struct U {
+ bool V;
+ constexpr U(bool v) : V(v) {}
+ U(const U&) = delete; // #del-U
+};
+constexpr U u(true);
+struct [[ =u ]] h2{}; // expected-error {{call to deleted constructor of 'U'}}
+ // expected-note@#del-U {{'U' has been explicitly marked deleted here}}
>From 3319fc6cca7e7b68487c76fa0c49ba8b8a5cb602 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Tue, 18 Nov 2025 12:44:33 +0900
Subject: [PATCH 15/19] Add more test on structural type
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/test/SemaCXX/cxx26-annotation.cpp | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/clang/test/SemaCXX/cxx26-annotation.cpp b/clang/test/SemaCXX/cxx26-annotation.cpp
index cd9f46ec69f48..df01beded26ad 100644
--- a/clang/test/SemaCXX/cxx26-annotation.cpp
+++ b/clang/test/SemaCXX/cxx26-annotation.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++26 -x c++ %s -verify
+// RUN: %clang_cc1 -std=c++26 -fexperimental-new-constant-interpreter -x c++ %s -verify
struct F {
bool V;
@@ -10,7 +10,7 @@ struct [[=1, =F{true}]] f2 {};
struct [[=1]] [[=2]] f3 {};
// Declaration
const [[=1]] F f4{};
-void foo([[=F{false}]]int i) {}
+void foo([[=F{false}]]int i) {} // function parameters
// Redeclaration
[[=2, =3, =2]] void f5();
void f5 [[=4, =2]] ();
@@ -49,3 +49,11 @@ struct U {
constexpr U u(true);
struct [[ =u ]] h2{}; // expected-error {{call to deleted constructor of 'U'}}
// expected-note@#del-U {{'U' has been explicitly marked deleted here}}
+
+// Non structural
+struct [[="notstructural"]] h3{}; // expected-error {{C++26 annotation attribute requires an expression usable as a template argument}} \
+ expected-note {{reference to string literal is not allowed in a template argument}}
+
+// Pointer into string literal
+struct [[=&"foo"[0]]] h4{}; // expected-error {{C++26 annotation attribute requires an expression usable as a template argument}} \
+ expected-note {{pointer to subobject of string literal is not allowed in a template argument}}
>From 91095572881f9f3f646ef19c555415602dd173bd Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Wed, 19 Nov 2025 08:47:18 +0900
Subject: [PATCH 16/19] Remove placeholder attr name for annotation
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
.../include/clang/Basic/AttributeCommonInfo.h | 1 +
clang/include/clang/Sema/ParsedAttr.h | 41 +++++++++++++++++++
clang/lib/Parse/ParseDeclCXX.cpp | 6 +--
clang/lib/Sema/ParsedAttr.cpp | 8 +++-
4 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Basic/AttributeCommonInfo.h b/clang/include/clang/Basic/AttributeCommonInfo.h
index 0baee12071e3c..4825da7d7f1b1 100644
--- a/clang/include/clang/Basic/AttributeCommonInfo.h
+++ b/clang/include/clang/Basic/AttributeCommonInfo.h
@@ -193,6 +193,7 @@ class AttributeCommonInfo {
return Form(getSyntax(), SpellingIndex, IsAlignas,
IsRegularKeywordAttribute);
}
+
const IdentifierInfo *getAttrName() const { return AttrName; }
void setAttrName(const IdentifierInfo *AttrNameII) { AttrName = AttrNameII; }
SourceLocation getLoc() const { return AttrRange.getBegin(); }
diff --git a/clang/include/clang/Sema/ParsedAttr.h b/clang/include/clang/Sema/ParsedAttr.h
index 5387f9fad6cd2..92659e1dafd9f 100644
--- a/clang/include/clang/Sema/ParsedAttr.h
+++ b/clang/include/clang/Sema/ParsedAttr.h
@@ -216,6 +216,20 @@ class ParsedAttr final
memcpy(getArgsBuffer(), args, numArgs * sizeof(ArgsUnion));
}
+ /// Constructor for an annotation with expression argument
+ ParsedAttr(SourceRange attrRange, AttributeScopeInfo scope, ArgsUnion *args,
+ unsigned numArgs, Form formUsed, SourceLocation ellipsisLoc)
+ : AttributeCommonInfo(attrRange, ParsedAttr::Kind::AT_CXX26Annotation,
+ ParsedAttr::Form::Annotation()),
+ EllipsisLoc(ellipsisLoc), NumArgs(numArgs), Invalid(false),
+ UsedAsTypeAttr(false), IsAvailability(false),
+ IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false),
+ HasProcessingCache(false), IsPragmaClangAttribute(false),
+ Info(ParsedAttrInfo::get(*this)) {
+ assert(numArgs == 1);
+ memcpy(getArgsBuffer(), args, sizeof(ArgsUnion));
+ }
+
/// Constructor for availability attributes.
ParsedAttr(IdentifierInfo *attrName, SourceRange attrRange,
AttributeScopeInfo scope, IdentifierLoc *Parm,
@@ -504,6 +518,7 @@ class ParsedAttr final
/// error. Returns false if a diagnostic is produced.
bool checkAtMostNumArgs(class Sema &S, unsigned Num) const;
+ bool isAnnotationAttr() const;
bool isTargetSpecificAttr() const;
bool isTypeAttr() const;
bool isStmtAttr() const;
@@ -732,6 +747,22 @@ class AttributePool {
/// them at the end of this \c AttributePool.
void takeFrom(ParsedAttributesView &List, AttributePool &Pool);
+ // Create a Cxx26 annotation
+ ParsedAttr *create(SourceRange attrRange, AttributeScopeInfo scope,
+ ArgsUnion *args, unsigned numArgs, ParsedAttr::Form form,
+ SourceLocation ellipsisLoc = SourceLocation()) {
+ // Only annotations are allowed to pass through without an identifier
+ assert(form.getSyntax() == ParsedAttr::Syntax::AS_Annotation);
+
+ void *memory = allocate(
+ ParsedAttr::totalSizeToAlloc<ArgsUnion, detail::AvailabilityData,
+ detail::TypeTagForDatatypeData, ParsedType,
+ detail::PropertyData>(numArgs, 0, 0, 0,
+ 0));
+ return add(new (memory) ParsedAttr(attrRange, scope, args, numArgs, form,
+ ellipsisLoc));
+ }
+
ParsedAttr *create(IdentifierInfo *attrName, SourceRange attrRange,
AttributeScopeInfo scope, ArgsUnion *args,
unsigned numArgs, ParsedAttr::Form form,
@@ -974,6 +1005,16 @@ class ParsedAttributes : public ParsedAttributesView {
Range = SourceRange();
}
+ /// Add annotation with expression argument
+ ParsedAttr *addNew(SourceRange attrRange, AttributeScopeInfo scope,
+ ArgsUnion *args, unsigned numArgs, ParsedAttr::Form form,
+ SourceLocation ellipsisLoc = SourceLocation()) {
+ ParsedAttr *attr =
+ pool.create(attrRange, scope, args, numArgs, form, ellipsisLoc);
+ addAtEnd(attr);
+ return attr;
+ }
+
/// Add attribute with expression arguments.
ParsedAttr *addNew(IdentifierInfo *attrName, SourceRange attrRange,
AttributeScopeInfo scope, ArgsUnion *args,
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 0368b6d63a576..3e922eeb6273f 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -4452,13 +4452,9 @@ void Parser::ParseAnnotationSpecifier(ParsedAttributes &Attrs,
if (AnnotExpr.isInvalid() || AnnotExpr.get()->containsErrors())
return;
- IdentifierTable &IT = Actions.PP.getIdentifierTable();
- IdentifierInfo &Placeholder = IT.get("__annotation_placeholder");
-
ArgsVector ArgExprs;
ArgExprs.push_back(AnnotExpr.get());
- Attrs.addNew(&Placeholder, EqLoc, {}, ArgExprs.data(), 1,
- ParsedAttr::Form::Annotation());
+ Attrs.addNew(EqLoc, {}, ArgExprs.data(), 1, ParsedAttr::Form::Annotation());
if (EndLoc)
*EndLoc = AnnotExpr.get()->getEndLoc();
diff --git a/clang/lib/Sema/ParsedAttr.cpp b/clang/lib/Sema/ParsedAttr.cpp
index 409862631e7fa..2dd432b8943ba 100644
--- a/clang/lib/Sema/ParsedAttr.cpp
+++ b/clang/lib/Sema/ParsedAttr.cpp
@@ -203,6 +203,10 @@ bool ParsedAttr::isTargetSpecificAttr() const {
return getInfo().IsTargetSpecific;
}
+bool ParsedAttr::isAnnotationAttr() const {
+ return getParsedKind() == AT_CXX26Annotation;
+}
+
bool ParsedAttr::isTypeAttr() const { return getInfo().IsType; }
bool ParsedAttr::isStmtAttr() const { return getInfo().IsStmt; }
@@ -213,8 +217,8 @@ bool ParsedAttr::existsInTarget(const TargetInfo &Target) const {
// If the attribute has a target-specific spelling, check that it exists.
// Only call this if the attr is not ignored/unknown. For most targets, this
// function just returns true.
- bool HasSpelling = K != IgnoredAttribute && K != UnknownAttribute &&
- K != NoSemaHandlerAttribute;
+ bool HasSpelling = !isAnnotationAttr() && K != IgnoredAttribute &&
+ K != UnknownAttribute && K != NoSemaHandlerAttribute;
bool TargetSpecificSpellingExists =
!HasSpelling ||
getInfo().spellingExistsInTarget(Target, getAttributeSpellingListIndex());
>From 9ddf0a8b4a6190fa8ae14e6c4e58a13c9cb02e87 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Thu, 20 Nov 2025 18:02:52 +0900
Subject: [PATCH 17/19] Leverage InstantiateAttrs to re-evaluate APValue
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/include/clang/Basic/Attr.td | 14 -----------
clang/lib/Sema/SemaDeclAttr.cpp | 3 +++
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 25 +++++++++++++++++++
clang/utils/TableGen/ClangAttrEmitter.cpp | 8 +++---
4 files changed, 31 insertions(+), 19 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 1771d7fe4d5a5..f6f6624ea2d74 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -748,10 +748,6 @@ class Attr {
// Note: Any additional data members will leak and should be constructed
// externally on the ASTContext.
code AdditionalMembers = [{}];
- // Any additional text that should be included verbatim after instantiating
- // an attribute on a template. This can be used if additional bookkeeping is
- // required for the data members.
- code PostInstantiationStmts =[{}];
// Any documentation that should be associated with the attribute. Since an
// attribute may be documented under multiple categories, more than one
// Documentation entry may be listed.
@@ -955,16 +951,6 @@ public:
void setEqLoc(SourceLocation Loc) { EqLoc = Loc; }
}];
- let PostInstantiationStmts = [{
- Expr::EvalResult V;
- if (!Result->getArg()->isValueDependent() &&
- !Result->getArg()->EvaluateAsRValue(V, C, true))
- llvm_unreachable("failed to evaluate annotation expression");
-
- Result->setValue(V.Val);
- Result->setEqLoc(A->getEqLoc());
- }];
-
let HasCustomParsing = 1;
let TemplateDependent = 1;
let MeaningfulToClassTemplateDefinition = 1;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c49008a89d505..792be8f5e01f9 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6411,6 +6411,9 @@ static void handleRequiresCapabilityAttr(Sema &S, Decl *D,
static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
Expr *CE = AL.getArgAsExpr(0);
+ // Let E be the expression std::meta::reflect_constant(CE). E
+ // shall be a constant expression; the result of E is the underlying constant
+ // of the annotation.
if (CE->isLValue()) {
if (CE->getType()->isRecordType()) {
InitializedEntity Entity = InitializedEntity::InitializeTemporary(
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 4d58f00168298..3de4e8fbb2677 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -149,6 +149,26 @@ static void instantiateDependentAlignedAttr(
}
}
+static void instantiateCxx26AnnotationAttr(
+ Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
+ const CXX26AnnotationAttr *CXX26AnnotationAttr, Decl *New) {
+ Expr *CE = CXX26AnnotationAttr->getArg();
+ ExprResult Result = S.SubstExpr(CE, TemplateArgs);
+ if (Result.isInvalid())
+ return;
+
+ Expr *concreteExpr = Result.get();
+ Expr::EvalResult V;
+ if (!concreteExpr->EvaluateAsRValue(V, S.getASTContext(), true))
+ llvm_unreachable("failed to evaluate annotation expression");
+
+ auto *Annot =
+ CXX26AnnotationAttr::Create(S.Context, CE, *CXX26AnnotationAttr);
+ Annot->setEqLoc(CXX26AnnotationAttr->getEqLoc());
+ Annot->setValue(V.Val);
+ New->addAttr(Annot);
+}
+
static void instantiateDependentAssumeAlignedAttr(
Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
const AssumeAlignedAttr *Aligned, Decl *New) {
@@ -856,6 +876,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
continue;
}
+ if (const auto *Cxx26Annotation = dyn_cast<CXX26AnnotationAttr>(TmplAttr)) {
+ instantiateCxx26AnnotationAttr(*this, TemplateArgs, Cxx26Annotation, New);
+ continue;
+ }
+
if (const auto *AssumeAligned = dyn_cast<AssumeAlignedAttr>(TmplAttr)) {
instantiateDependentAssumeAlignedAttr(*this, TemplateArgs, AssumeAligned, New);
continue;
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 7c21bc48593a2..2ce70b51a7e5e 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -4102,15 +4102,13 @@ EmitClangAttrTemplateInstantiateHelper(ArrayRef<const Record *> Attrs,
for (auto const &ai : Args)
ai->writeTemplateInstantiation(OS);
- OS << " auto* Result = new (C) " << R.getName() << "Attr(C, *A";
+ OS << " return new (C) " << R.getName() << "Attr(C, *A";
for (auto const &ai : Args) {
OS << ", ";
ai->writeTemplateInstantiationArgs(OS);
}
- OS << ");\n";
- OS << R.getValueAsString("PostInstantiationStmts");
- OS << "return Result;\n";
- OS << " }\n";
+ OS << ");\n"
+ << " }\n";
}
OS << " } // end switch\n"
<< " llvm_unreachable(\"Unknown attribute!\");\n"
>From a14918f495f02690a2ac577f05cdde8ac377aab0 Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Thu, 20 Nov 2025 18:15:37 +0900
Subject: [PATCH 18/19] Add draft of release notes
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/docs/ReleaseNotes.rst | 2 ++
1 file changed, 2 insertions(+)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c43f236aab319..6df6e1d46d3ac 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -341,6 +341,8 @@ Attribute Changes in Clang
- New format attributes ``gnu_printf``, ``gnu_scanf``, ``gnu_strftime`` and ``gnu_strfmon`` are added
as aliases for ``printf``, ``scanf``, ``strftime`` and ``strfmon``. (#GH16219)
+- Annotations from C++26 are supported (still experimental), and gated under the `-freflection` flag.
+
Improvements to Clang's diagnostics
-----------------------------------
- Diagnostics messages now refer to ``structured binding`` instead of ``decomposition``,
>From a440d3feb742bc127a3cc125b9f6a7050307332f Mon Sep 17 00:00:00 2001
From: acassagnes <acassagnes at bloomberg.net>
Date: Thu, 20 Nov 2025 18:23:33 +0900
Subject: [PATCH 19/19] Return const ref AP Value from annotation attr
Signed-off-by: acassagnes <acassagnes at bloomberg.net>
---
clang/include/clang/Basic/Attr.td | 2 +-
clang/test/SemaCXX/cxx26-annotation.cpp | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index f6f6624ea2d74..d1847754f6329 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -944,7 +944,7 @@ private:
public:
- APValue getValue() const { return Value; }
+ const APValue & getValue() const { return Value; }
void setValue(APValue V) { Value = V; }
SourceLocation getEqLoc() const { return EqLoc; }
diff --git a/clang/test/SemaCXX/cxx26-annotation.cpp b/clang/test/SemaCXX/cxx26-annotation.cpp
index df01beded26ad..e76d02ff89f6e 100644
--- a/clang/test/SemaCXX/cxx26-annotation.cpp
+++ b/clang/test/SemaCXX/cxx26-annotation.cpp
@@ -10,7 +10,8 @@ struct [[=1, =F{true}]] f2 {};
struct [[=1]] [[=2]] f3 {};
// Declaration
const [[=1]] F f4{};
-void foo([[=F{false}]]int i) {} // function parameters
+void f41([[=F{false}]]int i) {} // function parameters
+template<class T> [[=3]] void f42(T t); // non dep on template decl
// Redeclaration
[[=2, =3, =2]] void f5();
void f5 [[=4, =2]] ();
More information about the cfe-commits
mailing list