https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/208877
>From c98e2493a92745e6440e58340e0aee285bccbe5c Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Sat, 11 Jul 2026 05:30:51 +0200 Subject: [PATCH 1/2] [Clang] [C++26] Iterating expansion statements using string injection This patch introduces an API to Sema that takes a string of C++ code and parses it into an expression by calling back into the Parser. To avoid requiring Sema to link against the Parser, this is done via a proxy object similarly to how we implement template instantiation during constant evaluation. The API in question takes a string and a map from strings to tokens; before parsing, the string is lexed (and preprocessed, including macro expansion), and the resulting list of tokens is examined for any identifier tokens which match any of the keys in the map; such tokens are replaced with their corresponding values in the map. This enables us to include tokens in the code string that cannot be represented in code, such as annotation tokens. The API is used in two places in SemaExpand.cpp to generate expressions when we constructing and subsequently expand an iterating expansion statement. This patch also introduces 3 new annotation tokens that are used in the injected code strings: - `tok::annot_expansion_stmt_declare_begin_end`: this token stores a `VarDecl*` that is the `range` variable of an iterating expansion statement and must be followed by two identifier tokens. It instructs the parser to declare the `begin` and `end` variables using `range`. Specifically, this token produces _two_ `DeclStmt`s of the form ```c++ auto <first identifier> = begin-expr; auto <second identifier> = end-expr; ``` where `<first identifier>` and `<second-identifer>` are the identifier tokens that follow the annotation tokens. This is a rather weird construct, but the reason I implemented it this way is that the entire range-based `for` loop code is designed around always creating `begin` and `end` in pairs; even if we refactor it to only create one of them, we'd still have to perform name lookup twice to figure out whether we want member or ADL begin/end. Because this token produces _two_ statements, it is handled in `ParseCompoundStmtBody()`. - `tok::annot_expr`: this stores an `ExprResult`, and when the parser encounters it (in `ParseCastExpression()`), it simply returns that `ExprResult`. (We already have `tok::annot_primary_expr`, but we don't have a primary expression in this case so I didn't want to use that one.) - `tok::annot_value_decl`: this stores a `ValueDecl*`, and when the parser encounters it (also in `ParseCastExpression()`, it creates a `DeclRefExpr` that points to that declaration. The reason we don't just create the DRE and then pass it to the parser via `tok::annot_expr` is that afaik there is an invariant that the same non-`Decl` node must not occur multiple times in the AST of any one function. The range-based `for` code also builds multiple DREs to `begin` and `end` for `begin != end`, `*begin`, etc. Fixes #207778. --- .../clang/Basic/DiagnosticSemaKinds.td | 2 - clang/include/clang/Basic/TokenKinds.def | 6 ++ clang/include/clang/Lex/Lexer.h | 11 ++- clang/include/clang/Lex/Preprocessor.h | 5 ++ clang/include/clang/Lex/PreprocessorLexer.h | 4 ++ clang/include/clang/Parse/Parser.h | 1 + clang/include/clang/Sema/Sema.h | 35 +++++++++- clang/lib/Lex/Lexer.cpp | 29 ++++---- clang/lib/Lex/PPLexerChange.cpp | 8 +++ clang/lib/Lex/Pragma.cpp | 18 ++--- clang/lib/Lex/Preprocessor.cpp | 16 +++++ clang/lib/Parse/ParseExpr.cpp | 9 +++ clang/lib/Parse/ParseStmt.cpp | 49 ++++++++++++++ clang/lib/Parse/Parser.cpp | 57 +++++++++++++++- clang/lib/Sema/SemaExpand.cpp | 67 ++++++++++++++++--- clang/lib/Sema/SemaStmt.cpp | 30 ++++++--- clang/lib/Sema/TreeTransform.h | 6 +- clang/test/AST/ast-dump-expansion-stmt.cpp | 11 +-- clang/test/AST/ast-print-expansion-stmts.cpp | 25 +++---- .../cxx2c-iterating-expansion-stmt.cpp | 3 - clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 51 ++++---------- 21 files changed, 320 insertions(+), 123 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 2cb79b6ba275a..a163ffd4f56da 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -176,8 +176,6 @@ def note_constexpr_assert_failed : Note< "assertion failed during evaluation of constant expression">; def err_expansion_size_expr_not_ice : Error< "expansion statement size is not a constant expression">; -def err_iterating_expansion_stmt_unsupported : Error< - "iterating expansion statements are not yet supported">; // Semantic analysis of constant literals. def ext_predef_outside_function : Warning< diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index f07d8ebb75035..1c936e20ecdcb 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -905,6 +905,12 @@ ANNOTATION(decltype) // annotation for a decltype expression, // e.g., "decltype(foo.bar())" ANNOTATION(pack_indexing_type) // annotation for an indexed pack of type, // e.g., "T...[expr]" +ANNOTATION(value_decl) // a "ValueDecl *" to which we should build an lvalue DeclRefExpr +ANNOTATION(expansion_stmt_declare_begin_end) // a "ValueDecl *" that is the "range" + // variable of an iterating expansion + // statement using which we should build + // declarations for "begin" and "end" +ANNOTATION(expr) // an "ExprResult" // Annotation for #pragma unused(...) // For each argument inside the parentheses the pragma handler will produce diff --git a/clang/include/clang/Lex/Lexer.h b/clang/include/clang/Lex/Lexer.h index b042e5fb088fa..0b4960a966efa 100644 --- a/clang/include/clang/Lex/Lexer.h +++ b/clang/include/clang/Lex/Lexer.h @@ -185,12 +185,11 @@ class Lexer : public PreprocessorLexer { Lexer(const Lexer &) = delete; Lexer &operator=(const Lexer &) = delete; - /// Create_PragmaLexer: Lexer constructor - Create a new lexer object for - /// _Pragma expansion. This has a variety of magic semantics that this method - /// sets up. - static std::unique_ptr<Lexer> Create_PragmaLexer( - SourceLocation SpellingLoc, SourceLocation ExpansionLocStart, - SourceLocation ExpansionLocEnd, unsigned TokLen, Preprocessor &PP); + /// CreateScratchLexer: Lexer constructor - Create a new lexer object that + /// places 'Code' in the scratch buffer and lexes from it. + static std::unique_ptr<Lexer> + CreateScratchLexer(StringRef Code, SourceLocation ExpansionLocStart, + SourceLocation ExpansionLocEnd, Preprocessor &PP); /// getFileLoc - Return the File Location for the file we are lexing out of. /// The physical location encodes the location where the characters come from, diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 413e80e7645f9..089310ce6d3a5 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -1790,6 +1790,11 @@ class Preprocessor { /// Lex the next token for this preprocessor. void Lex(Token &Result); + /// Lex all tokens in 'Code' into 'Tokens' as though we were expanding a macro + /// that expands to 'Code' at location 'Loc'. + bool LexTokensInString(SmallVectorImpl<Token> &Tokens, StringRef Code, + SourceLocation Loc); + /// Lex all tokens for this preprocessor until (and excluding) end of file. void LexTokensUntilEOF(std::vector<Token> *Tokens = nullptr); diff --git a/clang/include/clang/Lex/PreprocessorLexer.h b/clang/include/clang/Lex/PreprocessorLexer.h index d71fe708ab20a..ac3b6085a841f 100644 --- a/clang/include/clang/Lex/PreprocessorLexer.h +++ b/clang/include/clang/Lex/PreprocessorLexer.h @@ -52,6 +52,10 @@ class PreprocessorLexer { /// True after \#include; turns \<xx> or "xxx" into a tok::header_name token. bool ParsingFilename = false; + /// True if the preprocessor should produce an EOF token when this lexer + /// is done. + bool ProduceEOFWhenDone = false; + /// True if in raw mode. /// /// Raw mode disables interpretation of tokens and is a far faster mode to diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 892715a6dac26..a0f2fd3e89d29 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -282,6 +282,7 @@ class Parser : public CodeCompletionHandler { friend class PoisonSEHIdentifiersRAIIObject; friend class ParenBraceBracketBalancer; friend class BalancedDelimiterTracker; + class TokenInjectionHandlerImpl; Parser(Preprocessor &PP, Sema &Actions, bool SkipFunctionBodies); ~Parser() override; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6e6ca1c4aa61a..d82925576bb40 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -860,6 +860,31 @@ enum AttrName { Target, TargetClones, TargetVersion }; void inferNoReturnAttr(Sema &S, Decl *D); +namespace sema { +/// Proxy class to parse a list of tokens and/or code strings. +/// +/// This is really intended to provide access to 'Parser' without making the +/// Sema library depend on the Parser library. We sometimes may want to generate +/// implicit AST nodes, but doing so by calling various ActOnXY() functions in +/// hopefully the right order and with the right arguments is both verbose and +/// fragile; instead, we'd much rather write regular C++ code and just parse it; +/// the APIs defined in this class facilitate this. +class TokenInjectionHandler { +protected: + TokenInjectionHandler() = default; + +public: + virtual ~TokenInjectionHandler() = default; + + /// Parse 'Code' as a single expression. Any identifier token whose name is + /// equal to a key in 'Replacements' will be replaced with the corresponding + /// token in the map before parsing. + virtual ExprResult + ParseAsExpression(StringRef Code, const llvm::StringMap<Token> &Replacements, + SourceLocation InjectionLoc) = 0; +}; +} // namespace sema + #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wattributes" @@ -1268,10 +1293,18 @@ class Sema final : public SemaBase { /// For example, user-defined classes, built-in "id" type, etc. Scope *TUScope; + /// Proxy object that calls into the parser to perform string injection. + std::unique_ptr<sema::TokenInjectionHandler> TokenInjectionHandler = nullptr; + void incrementMSManglingNumber() const { return CurScope->incrementMSManglingNumber(); } + void + setTokenInjectionHandler(std::unique_ptr<sema::TokenInjectionHandler> TIH) { + TokenInjectionHandler = std::move(TIH); + } + /// Try to recover by turning the given expression into a /// call. Returns true if recovery was attempted or an error was /// emitted; this may also leave the ExprResult invalid. @@ -11212,7 +11245,7 @@ class Sema final : public SemaBase { SourceLocation CoawaitLoc, ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps, BuildForRangeKind Kind, bool IsConstexpr, - StmtResult *RebuildResult = nullptr, + bool AccessibleViaNameLookup = false, StmtResult *RebuildResult = nullptr, llvm::function_ref<StmtResult()> RebuildWithDereference = {}, IdentifierInfo *BeginName = nullptr, IdentifierInfo *EndName = nullptr); diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp index 32ad310ca2672..b17fc2f7a094b 100644 --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -235,14 +235,10 @@ void Lexer::resetExtendedTokenMode() { SetCommentRetentionState(PP->getCommentRetentionState()); } -/// Create_PragmaLexer: Lexer constructor - Create a new lexer object for -/// _Pragma expansion. This has a variety of magic semantics that this method -/// sets up. +/// CreateScratchLexer: Lexer constructor - Create a new lexer object that +/// places 'Code' in the scratch buffer and lexes from it. /// -/// On entrance to this routine, TokStartLoc is a macro location which has a -/// spelling loc that indicates the bytes to be lexed for the token and an -/// expansion location that indicates where all lexed tokens should be -/// "expanded from". +/// This is used to implement _Pragma as well as string injection. /// /// TODO: It would really be nice to make _Pragma just be a wrapper around a /// normal lexer that remaps tokens as they fly by. This would require making @@ -250,11 +246,18 @@ void Lexer::resetExtendedTokenMode() { /// interface that could handle this stuff. This would pull GetMappedTokenLoc /// out of the critical path of the lexer! /// -std::unique_ptr<Lexer> Lexer::Create_PragmaLexer( - SourceLocation SpellingLoc, SourceLocation ExpansionLocStart, - SourceLocation ExpansionLocEnd, unsigned TokLen, Preprocessor &PP) { +std::unique_ptr<Lexer> +Lexer::CreateScratchLexer(StringRef Code, SourceLocation ExpansionLocStart, + SourceLocation ExpansionLocEnd, Preprocessor &PP) { SourceManager &SM = PP.getSourceManager(); + // Plop the string into a buffer where we can lex it. + Token TmpTok; + TmpTok.startToken(); + PP.CreateString(Code, TmpTok); + SourceLocation SpellingLoc = TmpTok.getLocation(); + unsigned TokLen = Code.size(); + // Create the lexer as if we were going to lex the file normally. FileID SpellingFID = SM.getFileID(SpellingLoc); llvm::MemoryBufferRef InputFile = SM.getBufferOrFake(SpellingFID); @@ -275,12 +278,6 @@ std::unique_ptr<Lexer> Lexer::Create_PragmaLexer( ExpansionLocStart, ExpansionLocEnd, TokLen); - // Ensure that the lexer thinks it is inside a directive, so that end \n will - // return an EOD token. - L->ParsingPreprocessorDirective = true; - - // This lexer really is for _Pragma. - L->Is_PragmaLexer = true; return L; } diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp index b44bf2cd3c253..74e800f83b2a4 100644 --- a/clang/lib/Lex/PPLexerChange.cpp +++ b/clang/lib/Lex/PPLexerChange.cpp @@ -488,9 +488,17 @@ bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) { SourceMgr.getFileEntryForID(CurPPLexer->getFileID()))) FoundPCHThroughHeader = true; + bool ShouldProduceEOF = CurLexer && CurLexer->ProduceEOFWhenDone; + // We're done with the #included file. RemoveTopOfLexerStack(); + if (ShouldProduceEOF) { + Result.startToken(); + Result.setKind(tok::eof); + return true; + } + // Propagate info about start-of-line/leading white-space/etc. PropagateLineStartLeadingSpaceInfo(Result); diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp index 9b48a45ade668..e9a4582430b25 100644 --- a/clang/lib/Lex/Pragma.cpp +++ b/clang/lib/Lex/Pragma.cpp @@ -279,17 +279,17 @@ void Preprocessor::Handle_Pragma(Token &Tok) { // The _Pragma is lexically sound. Destringize according to C11 6.10.9.1. prepare_PragmaString(StrVal); - // Plop the string (including the newline and trailing null) into a buffer - // where we can lex it. - Token TmpTok; - TmpTok.startToken(); - CreateString(StrVal, TmpTok); - SourceLocation TokLoc = TmpTok.getLocation(); - // Make and enter a lexer object so that we lex and expand the tokens just // like any others. - std::unique_ptr<Lexer> TL = Lexer::Create_PragmaLexer( - TokLoc, PragmaLoc, RParenLoc, StrVal.size(), *this); + std::unique_ptr<Lexer> TL = + Lexer::CreateScratchLexer(StrVal, PragmaLoc, RParenLoc, *this); + + // Ensure that the lexer thinks it is inside a directive, so that end \n will + // return an EOD token. + TL->ParsingPreprocessorDirective = true; + + // This lexer really is for _Pragma. + TL->Is_PragmaLexer = true; EnterSourceFileWithLexer(std::move(TL), nullptr); diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 1625d5918e8f5..9a0311e4879ec 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -573,6 +573,22 @@ Module *Preprocessor::getCurrentModuleImplementation() { return getHeaderSearchInfo().lookupModule(getLangOpts().ModuleName); } +bool Preprocessor::LexTokensInString(SmallVectorImpl<Token> &Tokens, + StringRef Code, SourceLocation Loc) { + std::unique_ptr<Lexer> L = Lexer::CreateScratchLexer(Code, Loc, Loc, *this); + L->ProduceEOFWhenDone = true; // Stop lexing once 'L' is done. + EnterSourceFileWithLexer(std::move(L), nullptr); + for (;;) { + Token Tok; + Lex(Tok); + if (Tok.is(tok::unknown)) + return true; + if (Tok.is(tok::eof)) + return false; + Tokens.push_back(Tok); + } +} + //===----------------------------------------------------------------------===// // Preprocessor Initialization Methods //===----------------------------------------------------------------------===// diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 74209f579776d..36c9e9075543a 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -849,6 +849,7 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand, Res = Actions.ActOnCXXNullPtrLiteral(ConsumeToken()); break; + case tok::annot_expr: case tok::annot_primary_expr: case tok::annot_overload_set: Res = getExprAnnotation(Tok); @@ -876,6 +877,14 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand, NotPrimaryExpression); } + case tok::annot_value_decl: { + auto *VD = static_cast<ValueDecl *>(Tok.getAnnotationValue()); + SourceLocation Loc = Tok.getLocation(); + ConsumeAnnotationToken(); + return Actions.BuildDeclRefExpr(VD, VD->getType().getNonReferenceType(), + VK_LValue, Loc); + } + case tok::kw___super: case tok::kw_decltype: // Annotate the token and tail recurse. diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index df01c91c065a1..5d3a4e268fdcd 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1202,6 +1202,55 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) { continue; } + // Annotation token that tells the parser to build the 'begin' and 'end' + // declarations for an iterating expansion statement; this is used inside + // the lambda that computes the expansion size (see also + // Sema::ComputeExpansionSize()). + // + // The annotation token is followed by 2 identifier tokens, which indicate + // the variable names for the begin and end variable, respectively. + // + // We handle this here because this results in *two* statements, not one, + // and moreover, this annotation token shouldn't appear in any other context + // anyway. + // + // The reason this is a single annotation token is that the code that builds + // 'begin' and 'end' only supports building both at once. + if (Tok.is(tok::annot_expansion_stmt_declare_begin_end)) { + auto *RangeVarDecl = static_cast<VarDecl *>(Tok.getAnnotationValue()); + ConsumeAnnotationToken(); + + assert(Tok.is(tok::identifier)); + IdentifierInfo *BeginName = Tok.getIdentifierInfo(); + ConsumeToken(); + + assert(Tok.is(tok::identifier)); + IdentifierInfo *EndName = Tok.getIdentifierInfo(); + ConsumeToken(); + + SourceLocation Loc = RangeVarDecl->getBeginLoc(); + Sema::ForRangeBeginEndInfo Info = Actions.BuildCXXForRangeBeginEndVars( + getCurScope(), RangeVarDecl, RangeVarDecl->getBeginLoc(), + /*CoawaitLoc=*/{}, + /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, /*Constexpr=*/false, + /*AccessibleViaNameLookup=*/true, + /*RebuildResult=*/nullptr, /*RebuildWithDereference=*/nullptr, + BeginName, EndName); + if (!Info.isValid()) + continue; + + StmtResult BeginStmt = Actions.ActOnDeclStmt( + Actions.ConvertDeclToDeclGroup(Info.BeginVar), Loc, Loc); + StmtResult EndStmt = Actions.ActOnDeclStmt( + Actions.ConvertDeclToDeclGroup(Info.EndVar), Loc, Loc); + if (BeginStmt.isInvalid() || EndStmt.isInvalid()) + continue; + + Stmts.push_back(BeginStmt.get()); + Stmts.push_back(EndStmt.get()); + continue; + } + if (ConsumeNullStmt(Stmts)) continue; diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 5e1fd4df1a3f0..fa7052ab2beed 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -48,6 +48,59 @@ class ActionCommentHandler : public CommentHandler { }; } // end anonymous namespace +class Parser::TokenInjectionHandlerImpl : public sema::TokenInjectionHandler { + Parser &P; + +public: + explicit TokenInjectionHandlerImpl(Parser &P) : P{P} {} + + ExprResult ParseAsExpression(StringRef Code, + const llvm::StringMap<Token> &Replacements, + SourceLocation InjectionLoc) override { + // Collect tokens. + SmallVector<Token> Tokens; + if (P.PP.LexTokensInString(Tokens, Code, InjectionLoc)) + return ExprError(); + + for (Token &Tok : Tokens) { + if (Tok.is(tok::identifier)) { + auto It = Replacements.find(Tok.getIdentifierInfo()->getName()); + if (It != Replacements.end()) + Tok = It->getValue(); + } + } + + // Add an EOF token so we know when to stop. + char EofMarker{}; + Token &EofToken = Tokens.emplace_back(); + EofToken.startToken(); + EofToken.setKind(tok::eof); + EofToken.setEofData(&EofMarker); + + // Start parsing the tokens; we need to save the current token so we + // don't lose it, and consume it since EnterTokenStream() doesn't change + // the current token. + // + // Disable macro expansion since that was already done as part of the call + // to LexTokensInString() above; 'IsReinjected' is for phase 4 tokens, so + // we don't want that either here. + SaveAndRestore SaveCurTok{P.Tok}; + P.PP.EnterTokenStream(Tokens, /*DisableMacroExpansion=*/true, + /*IsReinjected=*/false); + P.ConsumeAnyToken(); + ExprResult Res = P.ParseExpression(); + + // We should have parsed exactly one expression; if we still have tokens + // left, then there was probably an error; don't diagnose this and just + // skip them. + while (P.Tok.isNot(tok::eof)) + P.ConsumeAnyToken(); + + assert(P.Tok.getEofData() == &EofMarker); + return Res; + } +}; + IdentifierInfo *Parser::getSEHExceptKeyword() { // __except is accepted as a (contextual) keyword if (!Ident__except && (getLangOpts().MicrosoftExt || getLangOpts().Borland)) @@ -74,6 +127,8 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool skipFunctionBodies) // destructor. initializePragmaHandlers(); + Actions.setTokenInjectionHandler( + std::make_unique<TokenInjectionHandlerImpl>(*this)); CommentSemaHandler.reset(new ActionCommentHandler(actions)); PP.addCommentHandler(CommentSemaHandler.get()); @@ -482,7 +537,7 @@ Parser::~Parser() { resetPragmaHandlers(); PP.removeCommentHandler(CommentSemaHandler.get()); - + Actions.setTokenInjectionHandler(nullptr); PP.clearCodeCompletionHandler(); DestroyTemplateIds(); diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 9d8893dbdb731..4af60b0a51ed7 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -76,6 +76,26 @@ static auto InitListContainsPack(const InitListExpr *ILE) { [](const Expr *E) { return isa<PackExpansionExpr>(E); }); } +static Token CreateValueDeclAnnotToken(ValueDecl *D, SourceLocation Loc) { + Token Tok; + Tok.startToken(); + Tok.setKind(tok::annot_value_decl); + Tok.setAnnotationValue(D); + Tok.setAnnotationEndLoc(Loc); + Tok.setLocation(Loc); + return Tok; +} + +static Token CreateExprAnnotToken(Expr *E, SourceLocation Loc) { + Token Tok; + Tok.startToken(); + Tok.setKind(tok::annot_expr); + Tok.setAnnotationValue(ExprResult(E).getAsOpaquePointer()); + Tok.setAnnotationEndLoc(Loc); + Tok.setLocation(Loc); + return Tok; +} + static bool HasDependentSize(const DeclContext *CurContext, const CXXExpansionStmtPattern *Pattern) { switch (Pattern->getKind()) { @@ -203,11 +223,17 @@ static IterableExpansionStmtData TryBuildIterableExpansionStmtInitializer( if (BeginStmt.isInvalid()) return Data; - // TODO: Build 'constexpr auto iter = begin + decltype(begin - begin){i};'. - S.Diag(ColonLoc, diag::err_iterating_expansion_stmt_unsupported); - return Data; + // Build 'begin + decltype(begin - begin){i}'. + ExprResult BeginPlusI = S.TokenInjectionHandler->ParseAsExpression( + "__begin + decltype(__begin - __begin){__i}", + { + {"__begin", CreateValueDeclAnnotToken(Info.BeginVar, ColonLoc)}, + {"__i", CreateExprAnnotToken(Index, ColonLoc)}, + }, + ColonLoc); + if (BeginPlusI.isInvalid()) + return Data; -#if 0 // This will be used once we support iterating expansion statements. // Store it in a variable. // See also Sema::BuildCXXForRangeBeginEndVars(). const auto DepthStr = std::to_string(Scope->getDepth() / 2); @@ -233,7 +259,6 @@ static IterableExpansionStmtData TryBuildIterableExpansionStmtInitializer( Data.IterDecl = IterVarStmt.getAs<DeclStmt>(); Data.TheState = IterableExpansionStmtData::IsIterableResult::Iterable; return Data; -#endif } static StmtResult BuildDestructuringDecompositionDecl( @@ -616,11 +641,34 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) { EnterExpressionEvaluationContext ExprEvalCtx( *this, ExpressionEvaluationContext::ConstantEvaluated); - // TODO: Build the lambda and evaluate it. - Diag(Loc, diag::err_iterating_expansion_stmt_unsupported); - return std::nullopt; + // Annotation token that instructs the parser to declare begin/end. + Token DeclareBeginEnd; + DeclareBeginEnd.startToken(); + DeclareBeginEnd.setKind(tok::annot_expansion_stmt_declare_begin_end); + DeclareBeginEnd.setAnnotationValue(Expansion->getRangeVar()); + DeclareBeginEnd.setLocation(Loc); + DeclareBeginEnd.setAnnotationEndLoc(Loc); + + // Build the lambda. + ExprResult Call = TokenInjectionHandler->ParseAsExpression( + R"c++( + [&] consteval { + __PTRDIFF_TYPE__ __result = 0; + // Note: The next line is equivalent to: + // auto __begin = begin-expr; + // auto __end = end-expr; + __expansion_stmt_declare_begin_end __begin __end + for (; __begin != __end; ++__begin) ++__result; + return __result; + }() + )c++", + { + {"__expansion_stmt_declare_begin_end", DeclareBeginEnd}, + }, + Loc); + if (Call.isInvalid() || Call.get()->isTypeDependent()) + return std::nullopt; -#if 0 // This will be used once we support iterating expansion statements. Expr::EvalResult ER; SmallVector<PartialDiagnosticAt, 4> Notes; ER.Diag = &Notes; @@ -635,7 +683,6 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) { // via the built-in '++' on a ptrdiff_t. assert(ER.Val.getInt().isNonNegative()); return ER.Val.getInt().getZExtValue(); -#endif } assert(Expansion->isDestructuring()); diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index e6afcd4404501..06419c810d8ee 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -2353,7 +2353,8 @@ StmtResult Sema::ActOnForEachLValueExpr(Expr *E) { /// Finish building a variable declaration for a for-range statement. /// \return true if an error occurs. static bool FinishForRangeVarDecl(Sema &SemaRef, VarDecl *Decl, Expr *Init, - SourceLocation Loc, int DiagID) { + SourceLocation Loc, + bool AccessibleViaNameLookup, int DiagID) { if (Decl->getType()->isUndeducedType()) { ExprResult Res = Init; if (!Res.isUsable()) { @@ -2392,7 +2393,10 @@ static bool FinishForRangeVarDecl(Sema &SemaRef, VarDecl *Decl, Expr *Init, SemaRef.AddInitializerToDecl(Decl, Init, /*DirectInit=*/false); SemaRef.FinalizeDeclaration(Decl); - SemaRef.CurContext->addHiddenDecl(Decl); + if (AccessibleViaNameLookup) + SemaRef.PushOnScopeChains(Decl, SemaRef.getCurScope()); + else + SemaRef.CurContext->addHiddenDecl(Decl); return false; } @@ -2466,6 +2470,7 @@ StmtResult Sema::BuildCXXForRangeRangeVar(Scope *S, Expr *Range, QualType Type, SourceLocation RangeLoc = Range->getBeginLoc(); VarDecl *RangeVar = BuildForRangeVarDecl(RangeLoc, Type, Name, IsConstexpr); if (FinishForRangeVarDecl(*this, RangeVar, Range, RangeLoc, + /*AccessibleViaNameLookup=*/false, diag::err_for_range_deduction_failure)) return StmtError(); @@ -2554,7 +2559,8 @@ BuildNonArrayForRange(Sema &SemaRef, Expr *BeginRange, Expr *EndRange, QualType RangeType, VarDecl *BeginVar, VarDecl *EndVar, SourceLocation ColonLoc, SourceLocation CoawaitLoc, OverloadCandidateSet *CandidateSet, ExprResult *BeginExpr, - ExprResult *EndExpr, BeginEndFunction *BEF) { + ExprResult *EndExpr, BeginEndFunction *BEF, + bool AccessibleViaNameLookup) { DeclarationNameInfo BeginNameInfo( &SemaRef.PP.getIdentifierTable().get("begin"), ColonLoc); DeclarationNameInfo EndNameInfo(&SemaRef.PP.getIdentifierTable().get("end"), @@ -2587,6 +2593,7 @@ BuildNonArrayForRange(Sema &SemaRef, Expr *BeginRange, Expr *EndRange, return Sema::FRS_DiagnosticIssued; } if (FinishForRangeVarDecl(SemaRef, BeginVar, BeginExpr->get(), ColonLoc, + AccessibleViaNameLookup, diag::err_for_range_iter_deduction_failure)) { NoteForRangeBeginEndFunction(SemaRef, BeginExpr->get(), *BEF); return Sema::FRS_DiagnosticIssued; @@ -2607,6 +2614,7 @@ BuildNonArrayForRange(Sema &SemaRef, Expr *BeginRange, Expr *EndRange, return RangeStatus; } if (FinishForRangeVarDecl(SemaRef, EndVar, EndExpr->get(), ColonLoc, + AccessibleViaNameLookup, diag::err_for_range_iter_deduction_failure)) { NoteForRangeBeginEndFunction(SemaRef, EndExpr->get(), *BEF); return Sema::FRS_DiagnosticIssued; @@ -2734,7 +2742,8 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars( Scope *S, VarDecl *RangeVar, SourceLocation ColonLoc, SourceLocation CoawaitLoc, ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps, - BuildForRangeKind Kind, bool IsConstexpr, StmtResult *RebuildResult, + BuildForRangeKind Kind, bool IsConstexpr, bool AccessibleViaNameLookup, + StmtResult *RebuildResult, llvm::function_ref<StmtResult()> RebuildWithDereference, IdentifierInfo *BeginName, IdentifierInfo *EndName) { QualType RangeVarType = RangeVar->getType(); @@ -2789,6 +2798,7 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars( return {}; } if (FinishForRangeVarDecl(*this, BeginVar, BeginRangeRef.get(), ColonLoc, + AccessibleViaNameLookup, diag::err_for_range_iter_deduction_failure)) { NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin); return {}; @@ -2861,6 +2871,7 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars( if (EndExpr.isInvalid()) return {}; if (FinishForRangeVarDecl(*this, EndVar, EndExpr.get(), ColonLoc, + AccessibleViaNameLookup, diag::err_for_range_iter_deduction_failure)) { NoteForRangeBeginEndFunction(*this, EndExpr.get(), BEF_end); return {}; @@ -2869,10 +2880,10 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars( OverloadCandidateSet CandidateSet(RangeLoc, OverloadCandidateSet::CSK_Normal); BeginEndFunction BEFFailure; - ForRangeStatus RangeStatus = - BuildNonArrayForRange(*this, BeginRangeRef.get(), EndRangeRef.get(), - RangeType, BeginVar, EndVar, ColonLoc, CoawaitLoc, - &CandidateSet, &BeginExpr, &EndExpr, &BEFFailure); + ForRangeStatus RangeStatus = BuildNonArrayForRange( + *this, BeginRangeRef.get(), EndRangeRef.get(), RangeType, BeginVar, + EndVar, ColonLoc, CoawaitLoc, &CandidateSet, &BeginExpr, &EndExpr, + &BEFFailure, AccessibleViaNameLookup); if (Kind == BFRK_Build && RangeStatus == FRS_NoViableFunction && BEFFailure == BEF_begin) { @@ -2980,7 +2991,8 @@ StmtResult Sema::BuildCXXForRangeStmt( ForRangeBeginEndInfo ForRangeInfo = BuildCXXForRangeBeginEndVars( S, RangeVar, ColonLoc, CoawaitLoc, LifetimeExtendTemps, Kind, - /*Constexpr=*/false, &RebuildResult, RebuildWithDereference); + /*Constexpr=*/false, /*AccessibleViaNameLookup=*/false, &RebuildResult, + RebuildWithDereference); if (!RebuildResult.isUnset()) return RebuildResult; diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 1dd7be50c9ff2..0db53215035fb 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -9441,15 +9441,15 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtPattern( getDerived().TransformExpr(S->getExpansionInitializer()); if (ExpansionInitializer.isInvalid()) return StmtError(); + + ExpansionInitializer = + SemaRef.MaybeCreateExprWithCleanups(ExpansionInitializer); } else if (S->isIterating()) { Range = TransformStmtInParentContext(S->getRangeVarStmt()); if (Range.isInvalid()) return StmtError(); } - ExpansionInitializer = - SemaRef.MaybeCreateExprWithCleanups(ExpansionInitializer); - LifetimeExtendTemps = SemaRef.currentEvaluationContext().ForRangeLifetimeExtendTemps; } diff --git a/clang/test/AST/ast-dump-expansion-stmt.cpp b/clang/test/AST/ast-dump-expansion-stmt.cpp index 94f0ee1449f17..eb9acd5e0b652 100644 --- a/clang/test/AST/ast-dump-expansion-stmt.cpp +++ b/clang/test/AST/ast-dump-expansion-stmt.cpp @@ -6,14 +6,12 @@ // RUN: %clang_cc1 -x c++ -std=c++26 -triple x86_64-unknown-unknown -include-pch %t -ast-dump-all /dev/null \ // RUN: | sed -e "s/ <undeserialized declarations>//" -e "s/ imported//" -#if 0 // Disabled until we support iterating expansion statements. template <typename T, __SIZE_TYPE__ size> struct Array { T data[size]{}; constexpr const T* begin() const { return data; } constexpr const T* end() const { return data + size; } }; -#endif // 0 void foo(int); @@ -26,16 +24,13 @@ void test(T t) { foo(x); } -#if 0 // Disabled until we support iterating expansion statements. - // NOTE: Remove 'DISABLED-' when the '#if 0' is removed. - // DISABLED-CHECK: CXXExpansionStmtDecl - // DISABLED-CHECK-NEXT: CXXExpansionStmtPattern {{.*}} iterating - // DISABLED-CHECK: CXXExpansionStmtInstantiation + // CHECK: CXXExpansionStmtDecl + // CHECK-NEXT: CXXExpansionStmtPattern {{.*}} iterating + // CHECK: CXXExpansionStmtInstantiation static constexpr Array<int, 3> a; template for (auto x : a) { foo(x); } -#endif // CHECK: CXXExpansionStmtDecl // CHECK-NEXT: CXXExpansionStmtPattern {{.*}} destructuring diff --git a/clang/test/AST/ast-print-expansion-stmts.cpp b/clang/test/AST/ast-print-expansion-stmts.cpp index 880e17a9d3e2f..014da274af093 100644 --- a/clang/test/AST/ast-print-expansion-stmts.cpp +++ b/clang/test/AST/ast-print-expansion-stmts.cpp @@ -5,14 +5,12 @@ // RUN: %clang_cc1 -std=c++26 -emit-pch -o %t %s // RUN: %clang_cc1 -x c++ -std=c++26 -include-pch %t -ast-print /dev/null | FileCheck %s -#if 0 // Disabled until we support iterating expansion statements. template <typename T, __SIZE_TYPE__ size> struct Array { T data[size]{}; constexpr const T* begin() const { return data; } constexpr const T* end() const { return data + size; } }; -#endif // 0 // CHECK: void foo(int); void foo(int); @@ -29,19 +27,16 @@ void test(T t) { foo(x); } -#if 0 // Disabled until we support iterating expansion statements. // Iterating expansion statement. // - // NOTE: Remove 'DISABLED-' when the '#if 0' is removed. - // DISABLED-CHECK: static constexpr Array<int, 3> a; - // DISABLED-CHECK-NEXT: template for (auto x : (a)) { - // DISABLED-CHECK-NEXT: foo(x); - // DISABLED-CHECK-NEXT: } + // CHECK: static constexpr Array<int, 3> a; + // CHECK-NEXT: template for (auto x : (a)) { + // CHECK-NEXT: foo(x); + // CHECK-NEXT: } static constexpr Array<int, 3> a; template for (auto x : a) { foo(x); } -#endif // 0 // Destructuring expansion statement. // @@ -76,20 +71,16 @@ void test2(T t) { foo(x); } -#if 0 // Disabled until we support iterating expansion statements. // Iterating expansion statement. // - // NOTE: Remove 'DISABLED-' when the '#if 0' is removed. - // DISABLED-CHECK: static constexpr Array<int, 3> a; - // DISABLED-CHECK-NEXT: template for (int x : (a)) { - // DISABLED-CHECK-NEXT: foo(x); - // DISABLED-CHECK-NEXT: } - + // CHECK: static constexpr Array<int, 3> a; + // CHECK-NEXT: template for (int x : (a)) { + // CHECK-NEXT: foo(x); + // CHECK-NEXT: } static constexpr Array<int, 3> a; template for (int x : a) { foo(x); } -#endif // 0 // Destructuring expansion statement. // diff --git a/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp b/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp index 12c8328f41be0..115248a4d58b2 100644 --- a/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp +++ b/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp @@ -1,8 +1,5 @@ // RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s -// Iterating expansion statements are currently not supported. -// XFAIL: * - template <typename T, __SIZE_TYPE__ size> struct Array { T data[size]{}; diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index 84656396361e3..79b64f1e77734 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -118,27 +118,25 @@ struct String { template <__SIZE_TYPE__ n> String(const char (&str)[n]) -> String<n>; -// Note: Remove this test once we do support them. -int iterating_expansion_stmts_unsupported() { +int iterating_expansion_stmts() { static constexpr String s{"abcd"}; int count = 0; - template for (constexpr auto x : s) count++; // expected-error {{iterating expansion statements are not yet supported}} + template for (constexpr auto x : s) count++; return count; } template <typename T> -int iterating_expansion_stmts_unsupported_dependent() { +int iterating_expansion_stmts_dependent() { static constexpr String s{"abcd"}; int count = 0; - template for (auto x : T(s)) count++; // expected-error {{iterating expansion statements are not yet supported}} + template for (auto x : T(s)) count++; return count; } -void iterating_expansion_stmts_unsupported_dependent_instantiate() { - iterating_expansion_stmts_unsupported_dependent<String<5>>(); // expected-note {{in instantiation of}} +void iterating_expansion_stmts_dependent_instantiate() { + iterating_expansion_stmts_dependent<String<5>>(); } -#if 0 // Disabled until we support iterating expansion statements. constexpr int f3() { static constexpr String s{"abcd"}; int count = 0; @@ -200,8 +198,7 @@ struct NotInt { void not_int() { static constexpr NotInt ni; - template for (auto x : ni) g(x); // expected-error {{invalid operands to binary expression}} \ - expected-note {{while attempting to construct 'begin - begin' with iterator type 'iterator'}} + template for (auto x : ni) g(x); // expected-error {{invalid operands to binary expression}} } static constexpr Array<int, 3> integers{1, 2, 3}; @@ -313,8 +310,7 @@ void missing_funcs() { template for (auto x : s2) g(x); // expected-error {{invalid operands to binary expression}} template for (auto x : s3) g(x); // expected-error {{indirection requires pointer operand ('iterator' invalid)}} - template for (auto x : s4) g(x); // expected-error {{invalid operands to binary expression ('iterator' and 'iterator')}} \ - expected-note {{while attempting to construct 'begin - begin' with iterator type 'iterator'}} + template for (auto x : s4) g(x); // expected-error {{invalid operands to binary expression ('iterator' and 'iterator')}} } namespace adl { @@ -380,7 +376,6 @@ constexpr int adl_both_test() { } static_assert(adl_both_test() == 15); -#endif // 0 struct A {}; struct B { int x = 1; }; @@ -613,7 +608,6 @@ constexpr int complex() { static_assert(complex() == 3); -#if 0 // Disabled until we support iterating expansion statements. struct BeginOnly { int x{1}; constexpr const int* begin() const { return nullptr; } @@ -732,7 +726,6 @@ void unpaired_begin_end() { template for (auto x : end_deleted) {} // expected-error {{call to deleted function 'end'}} \ expected-note {{when looking up 'end' function for range expression of type 'const adl10::EndDeleted'}} } -#endif // 0 // Examples taken from [stmt.expand]. namespace stmt_expand_examples { @@ -747,7 +740,6 @@ constexpr int c1[] = {1, 2, 3}; constexpr int c2[] = {4, 3, 2, 1}; static_assert(f(c1, c2) == 5); -#if 0 // Disabled until we support iterating expansion statements. // TODO: This entire example should work without issuing any diagnostics once // we have full support for references to constexpr variables (P2686). consteval int f() { @@ -773,7 +765,6 @@ consteval int f() { return result; } static_assert(f() == 6); // old-interp-error {{static assertion failed due to requirement 'f() == 6'}} old-interp-note {{expression evaluates to '0 == 6'}} -#endif // 0 struct S { int i; @@ -1180,19 +1171,15 @@ void init_list_bad() { // Test that the init statement is evaluated even if the expansion statement // expands to nothing. constexpr int init_stmt_empty_expansion() { -#if 0 // Disabled until we support iterating expansion statements. static constexpr String empty{""}; -#endif // 0 int x = 0; template for (int _ = x += 1; auto i : {}) {} -#if 0 // Disabled until we support iterating expansion statements. template for (int _ = x += 2; auto i : empty) {} -#endif // 0 template for (int _ = x += 3; auto i : Empty()) {} return x; } -static_assert(init_stmt_empty_expansion() == 4); +static_assert(init_stmt_empty_expansion() == 6); void vla(int n) { int a[n]; @@ -1230,7 +1217,6 @@ void lambda_template_call() { lambda_template([]{}); // expected-note {{in instantiation of function template specialization}} } -#if 0 // Disabled until we support iterating expansion statements. // CWG 3131 makes it possible to expand over non-constexpr ranges. namespace cwg3131 { constexpr int f1() { @@ -1242,9 +1228,7 @@ constexpr int f1() { constexpr int f2() { Array<int, 3> a{1, 2, 3}; int j = 0; - template for (auto i : a) j +=i; // new-interp-error {{expansion statement size is not a constant expression}} \ - new-interp-note {{initializer of '__range1' is not a constant expression}} \ - new-interp-note {{declared here}} + template for (auto i : a) j += i; // new-interp-error {{expansion statement size is not a constant expression}} return j; } @@ -1351,7 +1335,6 @@ constexpr int f() { static_assert(f() == 20); } -#endif // 0 namespace cwg3149 { struct NotCopyable { @@ -1453,7 +1436,6 @@ void f2(int q) { } } -#if 0 // Disabled until we support iterating expansion statements. // Check that we apply lifetime extension to iterating expansions statements. // // The new constant interpreter erroring on this is likely https://github.com/llvm/llvm-project/issues/187775. @@ -1473,9 +1455,7 @@ constexpr T g(int& x) noexcept { return T(x); } constexpr int lifetime_extension_iterating() { int x = 5; int sum = 0; - // new-interp-error@+3 {{expansion statement size is not a constant expression}} - // new-interp-note@+2 {{initializer of '__range1' is not a constant expression}} - // new-interp-note@+1 {{declared here}} + // new-interp-error@+1 {{expansion statement size is not a constant expression}} template for (auto e : f(g(x))) { sum += x; } @@ -1486,9 +1466,7 @@ template <typename T> constexpr int lifetime_extension_iterating_dependent() { int x = 5; int sum = 0; - // new-interp-error@+3 {{expansion statement size is not a constant expression}} - // new-interp-note@+2 {{initializer of '__range0' is not a constant expression}} - // new-interp-note@+1 {{declared here}} + // new-interp-error@+1 {{expansion statement size is not a constant expression}} template for (auto e : T(f(g(x)))) { sum += x; } @@ -1499,9 +1477,7 @@ template <typename T> constexpr int lifetime_extension_iterating_instantiation() { int x = 5; int sum = 0; - // new-interp-error@+3 {{expansion statement size is not a constant expression}} - // new-interp-note@+2 {{initializer of '__range2' is not a constant expression}} - // new-interp-note@+1 {{declared here}} + // new-interp-error@+1 {{expansion statement size is not a constant expression}} template for (auto e : f(g(x))) { sum += x; } @@ -1512,7 +1488,6 @@ static_assert(lifetime_extension_iterating() == 47); // new-interp-error {{stati static_assert(lifetime_extension_iterating_dependent<const T&>() == 47); // new-interp-error {{static assertion expression is not an integral constant expression}} new-interp-note {{in instantiation of}} static_assert(lifetime_extension_iterating_instantiation<void>() == 47); // new-interp-error {{static assertion expression is not an integral constant expression}} new-interp-note {{in instantiation of}} } -#endif // 0 // Tests that make sure that certain parts of an expansion statement end up in // the right DeclContext. >From 52ab53899311594facc0fb70c0235e92fafe4b1c Mon Sep 17 00:00:00 2001 From: Sirraide <[email protected]> Date: Sat, 11 Jul 2026 06:03:47 +0200 Subject: [PATCH 2/2] Update cxx_status --- clang/www/cxx_status.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 0675f785bab20..c89f19559ea85 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -435,8 +435,9 @@ <h2 id="cxx26">C++2c implementation status</h2> <td class="partial" align="center"> <details> <summary>Clang 23 (Partial)</summary> - Iterating expansion statements currently cannot be expanded and will - result in a diagnostic, but other types of expansion statements work. + CWG3043 (lifetime extension in enumerating expansion statements) is not yet + implemented, and the feature test macro <code>__cpp_expansion_statements</code> + is not set yet. </details> </td> </tr> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
