Skip to content

Conversation

@zebullax
Copy link
Contributor

@zebullax zebullax commented Nov 4, 2025

⚠️ WIP to onboard C++26 annotation into Clang.
The ongoing work to support reflection done for ex. in #164692 is early yet, so the current PR does not add metafunctions that relate to annotations, since the eval mechanism isnt yet known.

The design is taken from the clang P2996 experimental compiler

  • annotation leverage attributes inner working
  • there is additional bookkeeping to be done on attribute being instantiated, for the AP value wrapped by this attribute to be updated, so tablegen for attribute is now allowing for some post instantiation code to run.

WIP

  • Mooooaar UT
  • Support unnamed parsed attributes
  • Address comment on post template instantiation re-eval of AP

TODO

  • Unwrap all other comments into todos

🙇 Credits shall go to @katzdm, blame to @zebullax

Recognize annotation starting token in attr parsing

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Add parsing for annotation

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@frederick-vs-ja frederick-vs-ja added clang:frontend Language frontend issues, e.g. anything involving "Sema" c++26 reflection Issues related to C++26 reflection labels Nov 4, 2025
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@github-actions
Copy link

github-actions bot commented Nov 4, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@zebullax
Copy link
Contributor Author

zebullax commented Nov 4, 2025

@katzdm FYI 🙇

Signed-off-by: acassagnes <acassagnes@bloomberg.net>

Undo dumb edit

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@Sirraide
Copy link
Member

Sirraide commented Nov 5, 2025

CC @erichkeane

@zebullax
Copy link
Contributor Author

AST representation for

struct [[=1, =2]] Foo4 {};

is

`-CXXRecordDecl 0x1300266d8 </main.cpp:1:1, col:25> col:19 struct Foo4 definition
  |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
  | |-DefaultConstructor exists trivial constexpr needs_implicit defaulted_is_constexpr
  | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
  | |-MoveConstructor exists simple trivial needs_implicit
  | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
  | |-MoveAssignment exists simple trivial needs_implicit
  | `-Destructor simple irrelevant trivial constexpr needs_implicit
  |-CXX26AnnotationAttr 0x1300267e8 <col:10>
  | `-IntegerLiteral 0x130026690 <col:11> 'int' 1
  |-CXX26AnnotationAttr 0x130026888 <col:14>
  | `-IntegerLiteral 0x1300266b0 <col:15> 'int' 2
  `-CXXRecordDecl 0x130026900 <col:1, col:19> col:19 implicit struct Foo4

Add smoke test

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@zebullax zebullax marked this pull request as ready for review November 11, 2025 04:42
code AdditionalMembers = [{}];
// Any additional text that should be included verbatim after instantiating
// an attribute on a template.
code PostInstantiationStmts =[{}];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems... particularly odd? Can you better explain what is going on here? That comment doesn't help very much. Also, needs a space after the =.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needs attention.

SourceLocation EqLoc;

public:
static CXX26AnnotationAttr *Create(ASTContext &Ctx, \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These creates don't seem right, they should be auto-generated. Or at least help suppress the ones that are already created?

The fact that this is putting so much extra data into an attribute is concerning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those were not strictly needed, cleaned up.

}];

let PostInstantiationStmts = [{
Expr::EvalResult V;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you better explain why this is happening?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there might be a more canonical way to achieve that via Sema::InstantiateAttrs , I'm giving this a try and that will remove this PostInstantiateStmts thingy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave it a try in 9ddf0a8
I assume (?) there are cases that will break and when I find those, we'll either fix it and keep this approach or revert to the more handcrafty PostInstantiationStmts

"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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"annotation is a C++26 extension">, InGroup<CXXPre26Compat>, DefaultIgnore;
"annotations are a C++26 extension">, InGroup<CXXPre26Compat>, DefaultIgnore;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
Expr *CE = AL.getArgAsExpr(0);
if (CE->isLValue()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you include the standardeeze here as to what exactly is happening/the rules of what should happen on the RHS here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still would like this here, other comments not attended to in this func.


CE = CopyResult.get();
} else {
ExprResult RVExprResult = S.DefaultLvalueConversion(AL.getArgAsExpr(0));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason the normal copy-init like above is incorrect here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this and the comment above, a sample like the following would not be properly handled by default lvalue conversion path

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}}

My understanding is that for record type we basically do not do much besides returning the expression itself in DefaultLvalueConversion

// - We must not mix with an attribute
if (Tok.is(tok::equal)) {
if (!getLangOpts().CPlusPlus26) {
Diag(Tok.getLocation(), diag::warn_cxx26_compat_annotation);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should be an error to me. Allowing annotations, even if ignored on earlier code, seems like a mistake.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this an error by default

return;

IdentifierTable &IT = Actions.PP.getIdentifierTable();
IdentifierInfo &Placeholder = IT.get("__annotation_placeholder");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is a little strange unfortunately. Can we try to modify ParsedAttr to accept one without a name? We already have special cases in there for other attribute kinds, and annotations not having one seems sensible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's doable, but I wonder if adding and handling that corner case is worth the wasted identifier ? Unless there is a more technical argument that I am missing

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its more than just the identifier, its that this is too much of a 'hack' here. ParsedAttr/etc are designed to be extended for things like this (See the large number of ctors/addNews anyway).

The 'addNew' being used here is for generic attributes, and annotations are special enough to be worth extra effort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I took a crack at it in 9109557 , is that what you had in mind ?

@cor3ntin
Copy link
Contributor

I think we should have a minimum of reflection (parsing, infra for meta functions) before considering merging that change .
This will let us have fully tested features - at the moment there is nothing we can do except... parsing them.

Note that P3795R0 - approved by the C++ committee last week allow attributes on function parameters

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just making sure we don't merge that that before #164692

@zebullax
Copy link
Contributor Author

Thanks @erichkeane @cor3ntin
I will address those but do give me a tad of time, the next 3 weeks are busy on my end :)

And I am ok with Corentin comment, we can keep this open until we get enough reflection merged, so we can onboard relevant metafunctions here as well.

@erichkeane
Copy link
Collaborator

I think we should have a minimum of reflection (parsing, infra for meta functions) before considering merging that change . This will let us have fully tested features - at the moment there is nothing we can do except... parsing them.

Note that P3795R0 - approved by the C++ committee last week allow attributes on function parameters

Eh, I disagree. Parsing + AST tests (to make sure they do what they're supposed to!) is a beneficial small-bite of this feature that I think is worth merging on its own. Enabling these ONLY under the -freflection flag though would be a good idea.

I think the meta-function querying them is a useful component to make sure these are fully usable, but IMO, there is enough functionality here (modulo a few small things) that we could merge this and simplify our review life from now on.

Improve documentation

Emit error if annotation syntax found in tablegen

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@zebullax
Copy link
Contributor Author

I have addressed the quickest comments and the mishandled case on mixing attribute and annotation.
I will address the remaining questions as I free up some time on my end 🙇

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Copy link
Contributor

@tbaederr tbaederr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to end your comments in a period (I'm just saying this before Aaron does).

@@ -0,0 +1,35 @@
// RUN: %clang_cc1 -std=c++26 -x c++ %s -verify
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also work when passing -fexperimental-new-constant-interpreter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so unless I passed it wrong ?


public:

APValue getValue() const { return Value; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return a const reference here so we don't copy the APValue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
<< "C++26 annotation" << 5 << CE->getSourceRange();
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the order make sense here? The comment says "evaluate to", but the check has nothing to do the the evaluated value, just the ConstantExpr type.

S.Diag(P.first, P.second);

return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by the usage of ConstantExpr here, we evaluate it, so it presumably doesn't have an APValue set yet. But we also don't set the value on it here. Is that just missing or am I misunderstanding something?

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A release note might be a good idea here to show progress on reflection.

I think annotations should ALSO only work under the freflection flag for the short-term, but that isn't in place yet, so we might need to wait on that to merge this.

code AdditionalMembers = [{}];
// Any additional text that should be included verbatim after instantiating
// an attribute on a template.
code PostInstantiationStmts =[{}];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still needs attention.

return;

IdentifierTable &IT = Actions.PP.getIdentifierTable();
IdentifierInfo &Placeholder = IT.get("__annotation_placeholder");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its more than just the identifier, its that this is too much of a 'hack' here. ParsedAttr/etc are designed to be extended for things like this (See the large number of ctors/addNews anyway).

The 'addNew' being used here is for generic attributes, and annotations are special enough to be worth extra effort.


static void handleCxx26AnnotationAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
Expr *CE = AL.getArgAsExpr(0);
if (CE->isLValue()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still would like this here, other comments not attended to in this func.


if (!CE->EvaluateAsConstantExpr(Result, S.Context, CEKind)) {
S.Diag(CE->getBeginLoc(), diag::err_attribute_argument_type)
<< "C++26 annotation" << 4 << CE->getSourceRange();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see it addressed? Usually we'd do something like:

Suggested change
<< "C++26 annotation" << 4 << CE->getSourceRange();
<< "C++26 annotation" << /*template arg=*/4 << CE->getSourceRange();

Though better (and preferred) would be to convert the diagnostic to be a enum_select and use the actual names for these uses.

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@github-actions
Copy link

github-actions bot commented Nov 18, 2025

🐧 Linux x64 Test Results

  • 111348 tests passed
  • 4430 tests skipped

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
@zebullax
Copy link
Contributor Author

@katzdm I suspect you may have tried the approach from 9ddf0a8 instead of having a PostInstantiate statements copied verbatim from attr.td.
If you recall having done so and why that didn't work out, please share 🙇

Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Signed-off-by: acassagnes <acassagnes@bloomberg.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++26 clang:frontend Language frontend issues, e.g. anything involving "Sema" reflection Issues related to C++26 reflection

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants