https://github.com/ChuanqiXu9 updated https://github.com/llvm/llvm-project/pull/221139
>From 9bf57b9bf05c4cf647008a09e3cb30056413cd1f Mon Sep 17 00:00:00 2001 From: Chuanqi Xu <[email protected]> Date: Fri, 4 Sep 2026 11:19:00 +0800 Subject: [PATCH 1/2] [C++26] [Contracts] Initial parser support --- clang/include/clang/Basic/BuiltinTraits.td | 3 +- .../clang/Basic/DiagnosticParseKinds.td | 4 + clang/include/clang/Basic/LangOptions.def | 2 + clang/include/clang/Basic/TokenKinds.def | 7 + clang/include/clang/Options/Options.td | 8 + clang/include/clang/Parse/Parser.h | 16 ++ clang/include/clang/Sema/Sema.h | 4 + clang/lib/Basic/IdentifierTable.cpp | 6 +- clang/lib/Parse/ParseDecl.cpp | 8 +- clang/lib/Parse/ParseDeclCXX.cpp | 170 ++++++++++++++++-- clang/lib/Parse/ParseExprCXX.cpp | 6 +- clang/lib/Parse/ParseStmt.cpp | 32 ++++ clang/lib/Sema/SemaDeclCXX.cpp | 15 ++ .../Parser/cxx26-contracts-declarators.cpp | 77 ++++++++ clang/test/Parser/cxx26-contracts-keyword.cpp | 15 ++ clang/test/Parser/cxx26-contracts-lambda.cpp | 57 ++++++ clang/test/Parser/cxx26-contracts-language.c | 16 ++ clang/test/Parser/cxx26-contracts-parser.cpp | 59 ++++++ .../test/Parser/cxx26-contracts-recovery.cpp | 52 ++++++ 19 files changed, 532 insertions(+), 25 deletions(-) create mode 100644 clang/test/Parser/cxx26-contracts-declarators.cpp create mode 100644 clang/test/Parser/cxx26-contracts-keyword.cpp create mode 100644 clang/test/Parser/cxx26-contracts-lambda.cpp create mode 100644 clang/test/Parser/cxx26-contracts-language.c create mode 100644 clang/test/Parser/cxx26-contracts-parser.cpp create mode 100644 clang/test/Parser/cxx26-contracts-recovery.cpp diff --git a/clang/include/clang/Basic/BuiltinTraits.td b/clang/include/clang/Basic/BuiltinTraits.td index f80234dd21e14..c424ea42bc7cd 100644 --- a/clang/include/clang/Basic/BuiltinTraits.td +++ b/clang/include/clang/Basic/BuiltinTraits.td @@ -41,8 +41,9 @@ def KEYHLSL : TokenKey<0x8000000>; def KEYFIXEDPOINT : TokenKey<0x10000000>; def KEYDEFERTS : TokenKey<0x20000000>; def KEYNOHLSL : TokenKey<0x40000000>; +def KEYCONTRACTS : TokenKey<0x80000000>; -def KEYMAX : TokenKey<KEYNOHLSL.Value>; +def KEYMAX : TokenKey<KEYCONTRACTS.Value>; def KEYALLCXX : TokenKey<!or(KEYCXX.Value, KEYCXX11.Value, KEYCXX20.Value)>; def KEYALL : TokenKey<!and(!or(KEYMAX.Value, !sub(KEYMAX.Value, 1)), !xor(KEYNOMS18.Value, -1), diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 4ca2ed38363c4..bc46b00e5f4c2 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -428,6 +428,10 @@ def warn_cxx98_compat_trailing_return_type : Warning< InGroup<CXX98Compat>, DefaultIgnore; def err_requires_clause_must_appear_after_trailing_return : Error< "trailing return type must appear before trailing requires clause">; +def err_requires_clause_must_precede_contract_specifiers : Error< + "trailing requires clause must appear before contract specifiers">; +def err_contract_specifier_not_function : Error< + "contract specifiers can only be applied to function declarations">; def err_requires_clause_on_declarator_not_declaring_a_function : Error< "trailing requires clause can only be used when declaring a function">; def err_requires_clause_inside_parens : Error< diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index ad993ce7e5d95..71ddfbe3badda 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -444,6 +444,8 @@ LANGOPT(PaddingOnUnsignedFixedPoint, 1, 0, NotCompatible, LANGOPT(OverflowBehaviorTypes, 1, 0, NotCompatible, "overflow behavior types") +LANGOPT(Contracts, 1, 0, Benign, "C++ contracts support") + ENUM_LANGOPT(RegisterStaticDestructors, RegisterStaticDestructorsKind, 2, RegisterStaticDestructorsKind::All, NotCompatible, "Register C++ static destructors") diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index dc9c7d8109467..70c6cae42d642 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -34,6 +34,9 @@ #ifndef C23_KEYWORD #define C23_KEYWORD(X,Y) KEYWORD(X,KEYC23|(Y)) #endif +#ifndef CONTRACTS_KEYWORD +#define CONTRACTS_KEYWORD(X) KEYWORD(X,KEYCONTRACTS) +#endif #ifndef COROUTINES_KEYWORD #define COROUTINES_KEYWORD(X) CXX20_KEYWORD(X,KEYCOROUTINES) #endif @@ -421,6 +424,9 @@ CXX11_KEYWORD(nullptr , KEYC23) CXX11_KEYWORD(static_assert , KEYMSCOMPAT|KEYC23) CXX11_KEYWORD(thread_local , KEYC23) +// C++26 Contracts keyword. +CONTRACTS_KEYWORD(contract_assert) + // C++20 / coroutines keywords COROUTINES_KEYWORD(co_await) COROUTINES_KEYWORD(co_return) @@ -931,6 +937,7 @@ ANNOTATION(embed) #undef TYPE_TRAIT_1 #undef TYPE_TRAIT #undef MODULES_KEYWORD +#undef CONTRACTS_KEYWORD #undef CXX20_KEYWORD #undef CXX11_KEYWORD #undef KEYWORD diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 3b88dce9c822b..5c06c8ecbf3db 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -1833,6 +1833,14 @@ defm coroutines : BoolFOption<"coroutines", "Enable support for the C++ Coroutines">, NegFlag<SetFalse>>; +defm contracts : BoolFOption<"contracts", + LangOpts<"Contracts">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption, CC1Option], + "Enable support for C++ Contracts">, + NegFlag<SetFalse, [], [ClangOption, CC1Option], + "Disable support for C++ Contracts">>, + ShouldParseIf<cplusplus.KeyPath>; + defm coro_aligned_allocation : BoolFOption<"coro-aligned-allocation", LangOpts<"CoroAlignedAllocation">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption, CC1Option], diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index ae91153e34e3a..13b01f9cc1c1b 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2763,6 +2763,17 @@ class Parser : public CodeCompletionHandler { const Declarator &D, const DeclSpec &DS, std::optional<Sema::CXXThisScopeRAII> &ThisScope); + enum class ContractSpecifierKind { Pre, Post }; + std::optional<ContractSpecifierKind> getContractSpecifierKind(); + void ParseContractSpecifiers(Declarator &D); + /// Parse the function tails. e.g., requires clause and contracts. + /// + /// \param ParametersAlreadyInScope whether the paramers are arealdy in an + /// active function prototype scope. This can be true for lambda as lambda + /// would always build its function prototype scope. + void ParseFunctionDeclaratorTail(Declarator &D, + bool ParametersAlreadyInScope = false); + /// ParseRefQualifier - Parses a member function ref-qualifier. Returns /// true if a ref-qualifier is found. bool ParseRefQualifier(bool &RefQualifierIsLValueRef, @@ -7598,6 +7609,11 @@ class Parser : public CodeCompletionHandler { /// StmtResult ParseBreakStatement(); + /// Parse a C++26 contract-assertion statement. + /// TODO: Currently the AST result of contracts is not support, its predicate + /// will be parsed and the corresponding AST result will be discarded. + StmtResult ParseContractAssertStatement(); + /// ParseReturnStatement /// \verbatim /// jump-statement: diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 9b07b591b8c07..fff66a45fc2a2 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -6501,6 +6501,10 @@ class Sema final : public SemaBase { void SetFunctionBodyKind(Decl *D, SourceLocation Loc, FnBodyKind BodyKind, StringLiteral *DeletedMessage = nullptr); void ActOnStartTrailingRequiresClause(Scope *S, Declarator &D); + /// Create a result variable for postconditions and make it visible in the + /// predicate's scope. + VarDecl *ActOnPostConditionResultName(Scope *S, IdentifierInfo *ResultName, + SourceLocation ResultNameLoc); ExprResult ActOnFinishTrailingRequiresClause(ExprResult ConstraintExpr); ExprResult ActOnRequiresClause(ExprResult ConstraintExpr); diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index aa349fcc384ce..b4a22b3f597b9 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -137,6 +137,8 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts, return LangOpts.ObjC ? KS_Enabled : KS_Unknown; case KEYZVECTOR: return LangOpts.ZVector ? KS_Enabled : KS_Unknown; + case KEYCONTRACTS: + return LangOpts.CPlusPlus && LangOpts.Contracts ? KS_Enabled : KS_Unknown; case KEYCOROUTINES: return LangOpts.Coroutines ? KS_Enabled : KS_Unknown; case KEYMODULES: @@ -199,8 +201,8 @@ KeywordStatus clang::getKeywordStatus(const LangOptions &LangOpts, } static bool IsKeywordInCpp(unsigned Flags) { - return (Flags & (KEYCXX | KEYCXX11 | KEYCXX20 | BOOLSUPPORT | WCHARSUPPORT | - CHAR8SUPPORT)) != 0; + return (Flags & (KEYCXX | KEYCXX11 | KEYCXX20 | KEYCONTRACTS | BOOLSUPPORT | + WCHARSUPPORT | CHAR8SUPPORT)) != 0; } static void MarkIdentifierAsKeywordInCpp(IdentifierTable &Table, diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 1d3789a10d9de..6e3fd2d5a4fc0 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2186,13 +2186,13 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, while (MaybeParseHLSLAnnotations(D)) ; - if (Tok.is(tok::kw_requires)) { + if (Tok.is(tok::kw_requires) || getContractSpecifierKind()) { TemplateParameterDepthRAII CurTemplateDepthTracker(TemplateParameterDepth); // With abbreviated function templates - we need to explicitly add depth to // account for the implicit template parameter list induced by the template. if (!TemplateInfo.TemplateParams && D.getInventedTemplateParameterList()) ++CurTemplateDepthTracker; - ParseTrailingRequiresClauseWithScope(D); + ParseFunctionDeclaratorTail(D); } // Save late-parsed attributes for now; they need to be parsed in the @@ -2441,8 +2441,8 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, // init-declarator: // declarator initializer[opt] // declarator requires-clause - if (Tok.is(tok::kw_requires)) - ParseTrailingRequiresClauseWithScope(D); + if (Tok.is(tok::kw_requires) || getContractSpecifierKind()) + ParseFunctionDeclaratorTail(D); Decl *ThisDecl = ParseDeclarationAfterDeclarator(D, TemplateInfo); D.complete(ThisDecl); if (ThisDecl) diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index ed995398f597d..b60dd203635df 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2563,8 +2563,9 @@ bool Parser::ParseCXXMemberDeclaratorBeforeInitializer( Declarator &DeclaratorInfo, VirtSpecifiers &VS, ExprResult &BitfieldSize, LateParsedAttrList &LateParsedAttrs) { // member-declarator: - // declarator virt-specifier-seq[opt] pure-specifier[opt] - // declarator requires-clause + // declarator virt-specifier-seq[opt] + // function-contract-specifier-seq[opt] pure-specifier[opt] + // declarator requires-clause function-contract-specifier-seq[opt] // declarator brace-or-equal-initializer[opt] // identifier attribute-specifier-seq[opt] ':' constant-expression // brace-or-equal-initializer[opt] @@ -2591,21 +2592,30 @@ bool Parser::ParseCXXMemberDeclaratorBeforeInitializer( BitfieldSize = ParseConstantExpression(); if (BitfieldSize.isInvalid()) SkipUntil(tok::comma, StopAtSemi | StopBeforeMatch); - } else if (Tok.is(tok::kw_requires)) { - TemplateParameterDepthRAII CurTemplateDepthTracker(TemplateParameterDepth); - // With abbreviated function templates - we need to explicitly add depth to - // account for the implicit template parameter list induced by the template. - if (DeclaratorInfo.getTemplateParameterLists().empty() && - DeclaratorInfo.getInventedTemplateParameterList()) - ++CurTemplateDepthTracker; - ParseTrailingRequiresClauseWithScope(DeclaratorInfo); } else { - ParseOptionalCXX11VirtSpecifierSeq( - VS, getCurrentClass().IsInterface, - DeclaratorInfo.getDeclSpec().getFriendSpecLoc()); - if (!VS.isUnset()) - MaybeParseAndDiagnoseDeclSpecAfterCXX11VirtSpecifierSeq(DeclaratorInfo, - VS); + // Requires clause can't follow 'override' but contracts can follow + // 'override'. Even if we don't support virtual functions with contractcs, + // we should diagnose at the sema stage instead of the parser stage. + if (Tok.isNot(tok::kw_requires)) { + ParseOptionalCXX11VirtSpecifierSeq( + VS, getCurrentClass().IsInterface, + DeclaratorInfo.getDeclSpec().getFriendSpecLoc()); + if (!VS.isUnset()) + MaybeParseAndDiagnoseDeclSpecAfterCXX11VirtSpecifierSeq(DeclaratorInfo, + VS); + } + + if (Tok.is(tok::kw_requires) || getContractSpecifierKind()) { + TemplateParameterDepthRAII CurTemplateDepthTracker( + TemplateParameterDepth); + // With abbreviated function templates - we need to explicitly add depth + // to account for the implicit template parameter list induced by the + // template. + if (DeclaratorInfo.getTemplateParameterLists().empty() && + DeclaratorInfo.getInventedTemplateParameterList()) + ++CurTemplateDepthTracker; + ParseFunctionDeclaratorTail(DeclaratorInfo); + } } // If a simple-asm-expr is present, parse it. @@ -4244,6 +4254,134 @@ void Parser::ParseTrailingRequiresClause(Declarator &D) { } } +std::optional<Parser::ContractSpecifierKind> +Parser::getContractSpecifierKind() { + if (!getLangOpts().Contracts || Tok.isNot(tok::identifier)) + return std::nullopt; + + IdentifierInfo *II = Tok.getIdentifierInfo(); + if (II->isStr("pre")) + return ContractSpecifierKind::Pre; + if (II->isStr("post")) + return ContractSpecifierKind::Post; + return std::nullopt; +} + +void Parser::ParseContractSpecifiers(Declarator &D) { + assert(getLangOpts().Contracts && "contracts are not enabled"); + + const bool IsFunction = D.isDeclarationOfFunction() && + (D.isFunctionDeclarationContext() || + D.getContext() == DeclaratorContext::LambdaExpr); + if (!IsFunction) + Diag(Tok, diag::err_contract_specifier_not_function); + + while (std::optional<ContractSpecifierKind> Kind = + getContractSpecifierKind()) { + const bool IsPost = *Kind == ContractSpecifierKind::Post; + ConsumeToken(); + + ParsedAttributes Attrs(AttrFactory); + MaybeParseCXX11Attributes(Attrs); + + BalancedDelimiterTracker T(*this, tok::l_paren); + if (T.expectAndConsume(diag::err_expected_lparen_after, + IsPost ? "post" : "pre")) + return; + + // !IsFunction is diagnosed before, avoid confusing diagnostics. + if (!IsFunction) { + T.skipToEnd(); + continue; + } + + // FIXME: We should accept attribute for the result binding, e.g., + // post(r [[attr]] : expr). + std::optional<ParseScope> ResultNameScope; + if (IsPost && Tok.is(tok::identifier) && NextToken().is(tok::colon)) { + IdentifierInfo *ResultName = Tok.getIdentifierInfo(); + SourceLocation ResultNameLoc = ConsumeToken(); + ConsumeToken(); + + ResultNameScope.emplace(this, Scope::DeclScope); + Actions.ActOnPostConditionResultName(getCurScope(), ResultName, + ResultNameLoc); + } + + EnterExpressionEvaluationContext Evaluated( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); + ExprResult Predicate = ParseConditionalExpression(); + ResultNameScope.reset(); + if (Predicate.isInvalid()) + T.skipToEnd(); + else + T.consumeClose(); + } +} + +// Parse the function tail where the require clause must appear first +// and the contracts later. +void Parser::ParseFunctionDeclaratorTail(Declarator &D, + bool ParametersAlreadyInScope) { + assert((Tok.is(tok::kw_requires) || getContractSpecifierKind()) && + "expected a function declarator tail"); + + // TODO: We can consider merging ParseTrailingRequiresClauseWithScope into + // this function. + if (!ParametersAlreadyInScope && Tok.is(tok::kw_requires)) { + ParseTrailingRequiresClauseWithScope(D); + if (!getContractSpecifierKind()) + return; + } + + auto ParseTail = [&] { + bool ParametersInScope = ParametersAlreadyInScope; + if (Tok.is(tok::kw_requires)) { + ParseTrailingRequiresClause(D); + ParametersInScope = true; + } + + while (getContractSpecifierKind()) { + if (!ParametersInScope) { + // FIXME: ActOnStartTrailingRequiresClause is not actually deal with + // require clause. It is actually adds the parameters to the scope + // chains. It needs a better name. + Actions.ActOnStartTrailingRequiresClause(getCurScope(), D); + ParametersInScope = true; + } + + const DeclSpec *MethodDS = &D.getDeclSpec(); + if (D.isFunctionDeclarator() && D.getFunctionTypeInfo().MethodQualifiers) + MethodDS = D.getFunctionTypeInfo().MethodQualifiers; + std::optional<Sema::CXXThisScopeRAII> ThisScope; + InitCXXThisScopeForDeclaratorIfRelevant(D, *MethodDS, ThisScope); + ParseContractSpecifiers(D); + + // If we meet requires after contracts, emit a nice diagnostics and + // recovering compilation. + if (Tok.is(tok::kw_requires)) { + Diag(Tok, diag::err_requires_clause_must_precede_contract_specifiers); + ParseTrailingRequiresClause(D); + } + } + }; + + if (ParametersAlreadyInScope) { + ParseTail(); + return; + } + + CXXScopeSpec &SS = D.getCXXScopeSpec(); + DeclaratorScopeObj DeclScopeObj(*this, SS); + if (SS.isValid() && Actions.ShouldEnterDeclaratorScope(getCurScope(), SS)) + DeclScopeObj.EnterDeclaratorScope(); + + ParseScope ParamScope(this, Scope::DeclScope | + Scope::FunctionDeclarationScope | + Scope::FunctionPrototypeScope); + ParseTail(); +} + Sema::ParsingClassState Parser::PushParsingClass(Decl *ClassDecl, bool NonNestedClass, bool IsInterface) { diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index ae741af7249cf..ad566c25a438d 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -1349,6 +1349,7 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( tok::kw___private, tok::kw___global, tok::kw___local, tok::kw___constant, tok::kw___generic, tok::kw_groupshared, tok::kw_requires, tok::kw_noexcept) || + getContractSpecifierKind().has_value() || Tok.isRegularKeywordAttribute() || (Tok.is(tok::l_square) && NextToken().is(tok::l_square)); @@ -1446,8 +1447,9 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( TrailingReturnType, TrailingReturnTypeLoc, &DS), std::move(Attributes), DeclEndLoc); - if (HasParentheses && Tok.is(tok::kw_requires)) - ParseTrailingRequiresClause(D); + if ((HasParentheses && Tok.is(tok::kw_requires)) || + getContractSpecifierKind()) + ParseFunctionDeclaratorTail(D, /*ParametersAlreadyInScope=*/true); } // Emit a warning if we see a CUDA host/device/global attribute diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 5e67cd551bff8..898e6bf3cf120 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -158,6 +158,11 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( getCurScope(), SemaCodeCompletion::PCC_Statement); return StmtError(); + case tok::kw_contract_assert: + Res = ParseContractAssertStatement(); + SemiError = "contract_assert"; + break; + case tok::identifier: ParseIdentifier: { Token Next = NextToken(); @@ -2462,6 +2467,33 @@ StmtResult Parser::ParseBreakStatement() { return ParseBreakOrContinueStatement(/*IsContinue=*/false); } +StmtResult Parser::ParseContractAssertStatement() { + assert(Tok.is(tok::kw_contract_assert) && "expected contract_assert"); + SourceLocation ContractAssertLoc = ConsumeToken(); + + ParsedAttributes Attrs(AttrFactory); + MaybeParseCXX11Attributes(Attrs); + + BalancedDelimiterTracker T(*this, tok::l_paren); + if (T.expectAndConsume(diag::err_expected_lparen_after, "contract_assert")) { + SkipUntil(tok::semi, StopBeforeMatch); + return Actions.ActOnNullStmt(ContractAssertLoc, + /*HasLeadingEmptyMacro=*/false); + } + + EnterExpressionEvaluationContext Evaluated( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); + ExprResult Predicate = ParseConditionalExpression(); + if (Predicate.isInvalid()) + T.skipToEnd(); + else + T.consumeClose(); + + // TODO: Now we don't build AST node for contracts. + return Actions.ActOnNullStmt(ContractAssertLoc, + /*HasLeadingEmptyMacro=*/false); +} + StmtResult Parser::ParseReturnStatement() { assert((Tok.is(tok::kw_return) || Tok.is(tok::kw_co_return)) && "Not a return stmt!"); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 4457ec58902d0..3a33e77efb85b 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -4220,6 +4220,21 @@ void Sema::ActOnStartTrailingRequiresClause(Scope *S, Declarator &D) { } } +VarDecl *Sema::ActOnPostConditionResultName(Scope *S, + IdentifierInfo *ResultName, + SourceLocation ResultNameLoc) { + // FIXME: We use a dependent type here to allow parsing the result name + // binding in post contracts. We should use the real result type when the + // support gets better Keeping this declaration out of the DeclContext makes + // it a predicate-local lookup aid rather than a program declaration. + VarDecl *ResultVar = VarDecl::Create( + Context, Context.getTranslationUnitDecl(), ResultNameLoc, ResultNameLoc, + ResultName, Context.DependentTy, /*TInfo=*/nullptr, SC_None); + ResultVar->setImplicit(); + PushOnScopeChains(ResultVar, S, /*AddToContext=*/false); + return ResultVar; +} + ExprResult Sema::ActOnFinishTrailingRequiresClause(ExprResult ConstraintExpr) { return ActOnRequiresClause(ConstraintExpr); } diff --git a/clang/test/Parser/cxx26-contracts-declarators.cpp b/clang/test/Parser/cxx26-contracts-declarators.cpp new file mode 100644 index 0000000000000..2161fc0dcb394 --- /dev/null +++ b/clang/test/Parser/cxx26-contracts-declarators.cpp @@ -0,0 +1,77 @@ +// RUN: %clang_cc1 -std=c++2c -fcontracts -fsyntax-only -verify %s + +// This file exercises the declaration paths which can lead to a function +// contract specifier. Contract predicates are parsed but not retained yet. + +template <typename> +concept Always = true; + +int declaration(int value) pre(value > 0) post(result: value >= 0); + +int definition(int value) pre(value > 0) post(result: value >= 0) { + return value; +} + +auto trailing_return(int value) -> int pre(value > 0) post(value >= 0) { + return value; +} + +decltype(auto) reference_return(int &value) pre(value > 0) post(value > 0) { + return (value); +} + +int with_defaults(int value = 1) noexcept pre(value > 0) { + return value; +} + +template <typename T> +auto constrained(T value) -> T requires Always<T> pre(value > T{}) { + return value; +} + +int first(int value) pre(value > 0), + second(int value) post(result: value > 0); + +struct Widget { + int value; + + Widget(int input) pre(input > 0); + ~Widget() pre(true); + + static int static_member(int input) noexcept pre(input > 0) { + return input; + } + + friend int inspect(const Widget &, int input) pre(input > 0); + + explicit operator bool() const pre(this->value >= 0); + int operator()(int input) const & noexcept pre(input > 0) post(input >= 0); + + virtual int pure(int input) const pre(input > 0) = 0; + + template <typename T> + T member_template(T input) requires Always<T> pre(input > T{}) { + return input; + } +}; + +Widget::Widget(int input) pre(input > 0) : value(input) {} +Widget::~Widget() pre(true) {} + +namespace API { +struct Service { + int limit; + int call(int input) const; +}; +} // namespace API + +int API::Service::call(int input) const + pre(this->limit >= 0 && input > 0) post(input > 0) { + return input; +} + +int object pre(true); // expected-error {{contract specifiers can only be applied to function declarations}} +int (*function_pointer)(int) pre(true); // expected-error {{contract specifiers can only be applied to function declarations}} +extern int (&function_reference)(int) pre(true); // expected-error {{contract specifiers can only be applied to function declarations}} + +typedef int FunctionTypedef(int) pre(true); // expected-error {{contract specifiers can only be applied to function declarations}} diff --git a/clang/test/Parser/cxx26-contracts-keyword.cpp b/clang/test/Parser/cxx26-contracts-keyword.cpp new file mode 100644 index 0000000000000..a6f6b22503564 --- /dev/null +++ b/clang/test/Parser/cxx26-contracts-keyword.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -std=c++2c -fcontracts -DENABLED=1 -fsyntax-only -verify=enabled %s +// RUN: %clang_cc1 -std=c++2c -fno-contracts -DENABLED=0 -fsyntax-only -verify=disabled %s + +// disabled-no-diagnostics + +#ifdef __cplusplus +void test() { +#if ENABLED + int contract_assert; // enabled-error {{expected unqualified-id}} +#else + int contract_assert; + ++contract_assert; +#endif +} +#endif diff --git a/clang/test/Parser/cxx26-contracts-lambda.cpp b/clang/test/Parser/cxx26-contracts-lambda.cpp new file mode 100644 index 0000000000000..cb290c54319b0 --- /dev/null +++ b/clang/test/Parser/cxx26-contracts-lambda.cpp @@ -0,0 +1,57 @@ +// RUN: %clang_cc1 -std=c++2c -fcontracts -fsyntax-only -verify %s + +template <typename> +concept LambdaConstraint = true; + +void lambda_contracts() { + auto no_parentheses = [] pre(true) { return 0; }; + auto explicit_return = [](int value) -> int + pre(value > 0) post(result: result >= value) { return value; }; + auto qualified = [](int value) mutable noexcept -> int + pre(value > 0) post(value >= 0) { return value; }; + auto constexpr_lambda = [](int value) constexpr pre(value > 0) { + return value; + }; + auto static_lambda = [](int value) static noexcept pre(value > 0) { + return value; + }; + auto with_default = [](int value = 1) pre(value > 0) { return value; }; + + auto generic = []<typename T>(T value) pre(value > T{}) { return value; }; + auto constrained = []<typename T>(T value) + requires LambdaConstraint<T> + pre(value > T{}) post(value >= T{}) { return value; }; + auto doubly_constrained = []<typename T> + requires LambdaConstraint<T> + (T value) -> T requires LambdaConstraint<T> + pre(value > T{}) post(value >= T{}) { return value; }; + auto non_type_parameter = []<int N>() pre(N > 0) { return N; }; + auto parameter_pack = []<typename... T>(T... values) + pre(sizeof...(values) > 0) { return sizeof...(values); }; + + auto nested = [](int outer) pre(outer > 0) { + return [outer](int inner) pre(inner > outer) { return inner; }; + }; + + (void)no_parentheses; + (void)explicit_return; + (void)qualified; + (void)constexpr_lambda; + (void)static_lambda; + (void)with_default; + (void)generic; + (void)constrained; + (void)doubly_constrained; + (void)non_type_parameter; + (void)parameter_pack; + (void)nested; +} + +void lambda_order_recovery() { + auto wrong_order = []<typename T>(T value) + pre(value > T{}) requires LambdaConstraint<T> { + // expected-error@-1 {{trailing requires clause must appear before contract specifiers}} + return value; + }; + (void)wrong_order; +} diff --git a/clang/test/Parser/cxx26-contracts-language.c b/clang/test/Parser/cxx26-contracts-language.c new file mode 100644 index 0000000000000..2b80d4e79f2bc --- /dev/null +++ b/clang/test/Parser/cxx26-contracts-language.c @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -std=c17 -fcontracts -fsyntax-only -verify %s + +// Contracts are a C++ feature. Enabling the parser option in C mode must not +// turn its spellings into keywords or contextual keywords. + +// expected-no-diagnostics + +int contract_assert; +int pre; +int post; + +void use_contract_spellings(void) { + ++contract_assert; + ++pre; + ++post; +} diff --git a/clang/test/Parser/cxx26-contracts-parser.cpp b/clang/test/Parser/cxx26-contracts-parser.cpp new file mode 100644 index 0000000000000..7b08e702b9450 --- /dev/null +++ b/clang/test/Parser/cxx26-contracts-parser.cpp @@ -0,0 +1,59 @@ +// RUN: %clang_cc1 -std=c++2c -fcontracts -fsyntax-only -verify %s + +// This test intentionally exercises parser-only support. Contract predicates +// are parsed as ordinary expressions, but are not retained in the AST yet. + +int pre = 42; +int post(int value) { return value; } + +int divide(int a, int b) pre(b != 0); +int square(int x) post(result: result >= x); +int clamp(int x) pre(x >= 0) pre(x <= 100) + post(result: x >= 0) post(other_result: x <= 100); + +struct OuterResult {}; +OuterResult shadowed_result; +int result_shadowing(int x) + post(shadowed_result: shadowed_result >= x); + +template <typename T> +concept C = true; + +template <typename T> +int constrained(T value) requires C<T> pre(value > T{}); + +struct S { + int member(int value) pre(value > 0) post(result: result > value); + virtual int virtual_member(int value) const final pre(value > 0); +}; + +int S::member(int value) pre(value > 0) post(result: result > value) { + contract_assert(value > 0); + contract_assert [[maybe_unused]] (value < 100); + return value; +} + +void lambdas() { + auto a = [](int value) pre(value > 0) { return value; }; + auto b = [] pre(true) { return 0; }; + (void)a; + (void)b; +} + +int attributed(int value) + pre [[maybe_unused]] (value > 0) + post [[maybe_unused]] (result: value > 0); + +int not_a_function pre(true); // expected-error {{contract specifiers can only be applied to function declarations}} + +template <typename T> +int wrong_order(T value) pre(value > T{}) requires C<T>; +// expected-error@-1 {{trailing requires clause must appear before contract specifiers}} + +void missing_assert_lparen() { + contract_assert true; // expected-error {{expected '(' after 'contract_assert'}} +} + +int missing_pre_lparen() pre true; +// expected-error@-1 {{expected '(' after 'pre'}} +// expected-error@-2 {{expected function body after function declarator}} diff --git a/clang/test/Parser/cxx26-contracts-recovery.cpp b/clang/test/Parser/cxx26-contracts-recovery.cpp new file mode 100644 index 0000000000000..9ffc2bc878f1c --- /dev/null +++ b/clang/test/Parser/cxx26-contracts-recovery.cpp @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -std=c++2c -fcontracts -fsyntax-only -verify %s + +template <typename> +concept Recoverable = true; + +int empty_pre(int value) pre(); // expected-error {{expected expression}} +int after_empty_pre(int value) pre(value > 0); + +int empty_post(int value) post(result:); // expected-error {{expected expression}} +int after_empty_post(int value) post(value > 0); + +int missing_pre_close(int value) pre(value > 0; // expected-error {{expected ')'}} expected-note {{to match this '('}} +int after_missing_pre_close(int value) pre(value > 0); + +void assertion_recovery(int value) { + contract_assert(); // expected-error {{expected expression}} + int after_empty = value; + + contract_assert value > 0; // expected-error {{expected '(' after 'contract_assert'}} + int after_missing_open = after_empty; + + contract_assert((value > 0) && ((value + 1) > 1)); + + contract_assert(value > 0; // expected-error {{expected ')'}} expected-note {{to match this '('}} + int after_missing_close = value; + (void)after_missing_close; + (void)after_missing_open; +} + +template <typename T> +int wrong_order_declaration(T value) pre(value > T{}) requires Recoverable<T>; +// expected-error@-1 {{trailing requires clause must appear before contract specifiers}} + +template <typename T> +int wrong_order_definition(T value) pre(value > T{}) requires Recoverable<T> { + // expected-error@-1 {{trailing requires clause must appear before contract specifiers}} + return value; +} + +struct RecoveryMember { + template <typename T> + int wrong_order(T value) pre(value > T{}) requires Recoverable<T>; + // expected-error@-1 {{trailing requires clause must appear before contract specifiers}} + + int after_error(int value) pre(value > 0); +}; + +int missing_pre_lparen(int value) pre value > 0; +// expected-error@-1 {{expected '(' after 'pre'}} +// expected-error@-2 {{expected function body after function declarator}} + +int after_missing_pre_lparen(int value) post(value > 0); >From d5e0dbaee60bcf9a4e256dd42f91b4bf4fb001f8 Mon Sep 17 00:00:00 2001 From: Chuanqi Xu <[email protected]> Date: Fri, 4 Sep 2026 17:06:28 +0800 Subject: [PATCH 2/2] Address comments --- clang/include/clang/Basic/BuiltinTraits.td | 7 +- .../include/clang/Basic/DiagnosticLexKinds.td | 2 + .../clang/Basic/DiagnosticParseKinds.td | 4 + clang/include/clang/Basic/TokenKinds.def | 13 +- clang/include/clang/Options/Options.td | 4 +- clang/include/clang/Parse/Parser.h | 17 ++- clang/include/clang/Sema/Sema.h | 3 +- clang/lib/Basic/IdentifierTable.cpp | 11 +- clang/lib/Parse/ParseDecl.cpp | 4 +- clang/lib/Parse/ParseDeclCXX.cpp | 120 +++++++----------- clang/lib/Parse/ParseExprCXX.cpp | 3 +- clang/lib/Parse/ParseStmt.cpp | 2 + clang/lib/Parse/Parser.cpp | 2 + clang/lib/Sema/SemaDeclCXX.cpp | 3 +- clang/test/Lexer/keywords_test.cpp | 13 +- clang/test/Parser/cxx26-contracts-keyword.cpp | 24 ++-- 16 files changed, 124 insertions(+), 108 deletions(-) diff --git a/clang/include/clang/Basic/BuiltinTraits.td b/clang/include/clang/Basic/BuiltinTraits.td index c424ea42bc7cd..0eb64f38310f7 100644 --- a/clang/include/clang/Basic/BuiltinTraits.td +++ b/clang/include/clang/Basic/BuiltinTraits.td @@ -41,10 +41,11 @@ def KEYHLSL : TokenKey<0x8000000>; def KEYFIXEDPOINT : TokenKey<0x10000000>; def KEYDEFERTS : TokenKey<0x20000000>; def KEYNOHLSL : TokenKey<0x40000000>; -def KEYCONTRACTS : TokenKey<0x80000000>; +def KEYCXX26 : TokenKey<0x80000000>; -def KEYMAX : TokenKey<KEYCONTRACTS.Value>; -def KEYALLCXX : TokenKey<!or(KEYCXX.Value, KEYCXX11.Value, KEYCXX20.Value)>; +def KEYMAX : TokenKey<KEYCXX26.Value>; +def KEYALLCXX : TokenKey<!or(KEYCXX.Value, KEYCXX11.Value, KEYCXX20.Value, + KEYCXX26.Value)>; def KEYALL : TokenKey<!and(!or(KEYMAX.Value, !sub(KEYMAX.Value, 1)), !xor(KEYNOMS18.Value, -1), !xor(KEYNOOPENCL.Value, -1), diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index c7f4856b03a1e..9e3f7ea842bfe 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -85,6 +85,8 @@ def warn_cxx11_keyword : Warning<"'%0' is a keyword in C++11">, InGroup<CXX11Compat>, DefaultIgnore; def warn_cxx20_keyword : Warning<"'%0' is a keyword in C++20">, InGroup<CXX20Compat>, DefaultIgnore; +def warn_cxx26_keyword : Warning<"'%0' is a keyword in C++26">, + InGroup<CXX26Compat>, DefaultIgnore; def warn_c99_keyword : Warning<"'%0' is a keyword in C99">, InGroup<C99Compat>, DefaultIgnore; def warn_c23_keyword : Warning<"'%0' is a keyword in C23">, diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index bc46b00e5f4c2..09ef2e013a3e5 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -12,6 +12,10 @@ let Component = "Parse" in { let CategoryName = "Parse Issue" in { +def err_contracts_require_cxx26 : Error<"contracts are a C++26 feature">; +def err_contracts_disabled : Error< + "contracts support is disabled; pass '-fcontracts' to enable it">; + // C23 compatibility with C17 and earlier. defm c_label_at_end_of_compound_statement : C23Compat< "label at end of compound statement is", /*ext_warn*/true>; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 70c6cae42d642..22aed14ff6369 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -28,15 +28,15 @@ #ifndef CXX20_KEYWORD #define CXX20_KEYWORD(X,Y) KEYWORD(X,KEYCXX20|(Y)) #endif +#ifndef CXX26_KEYWORD +#define CXX26_KEYWORD(X,Y) KEYWORD(X,KEYCXX26|(Y)) +#endif #ifndef C99_KEYWORD #define C99_KEYWORD(X,Y) KEYWORD(X,KEYC99|(Y)) #endif #ifndef C23_KEYWORD #define C23_KEYWORD(X,Y) KEYWORD(X,KEYC23|(Y)) #endif -#ifndef CONTRACTS_KEYWORD -#define CONTRACTS_KEYWORD(X) KEYWORD(X,KEYCONTRACTS) -#endif #ifndef COROUTINES_KEYWORD #define COROUTINES_KEYWORD(X) CXX20_KEYWORD(X,KEYCOROUTINES) #endif @@ -281,6 +281,7 @@ PUNCTUATOR(greatergreatergreater, ">>>") // KEYNOCXX - This is a keyword in every non-C++ dialect. // KEYCXX11 - This is a C++ keyword introduced to C++ in C++11 // KEYCXX20 - This is a C++ keyword introduced to C++ in C++20 +// KEYCXX26 - This is a C++ keyword introduced to C++ in C++26 // KEYMODULES - This is a keyword if the C++ extensions for modules // are enabled. // KEYGNU - This is a keyword if GNU extensions are enabled @@ -424,8 +425,8 @@ CXX11_KEYWORD(nullptr , KEYC23) CXX11_KEYWORD(static_assert , KEYMSCOMPAT|KEYC23) CXX11_KEYWORD(thread_local , KEYC23) -// C++26 Contracts keyword. -CONTRACTS_KEYWORD(contract_assert) +// C++26 keyword. +CXX26_KEYWORD(contract_assert , 0) // C++20 / coroutines keywords COROUTINES_KEYWORD(co_await) @@ -937,7 +938,7 @@ ANNOTATION(embed) #undef TYPE_TRAIT_1 #undef TYPE_TRAIT #undef MODULES_KEYWORD -#undef CONTRACTS_KEYWORD +#undef CXX26_KEYWORD #undef CXX20_KEYWORD #undef CXX11_KEYWORD #undef KEYWORD diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 5c06c8ecbf3db..ff7714edd116a 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -1835,9 +1835,9 @@ defm coroutines : BoolFOption<"coroutines", defm contracts : BoolFOption<"contracts", LangOpts<"Contracts">, DefaultFalse, - PosFlag<SetTrue, [], [ClangOption, CC1Option], + PosFlag<SetTrue, [], [CC1Option], "Enable support for C++ Contracts">, - NegFlag<SetFalse, [], [ClangOption, CC1Option], + NegFlag<SetFalse, [], [CC1Option], "Disable support for C++ Contracts">>, ShouldParseIf<cplusplus.KeyPath>; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 13b01f9cc1c1b..429e7789e9696 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2766,13 +2766,13 @@ class Parser : public CodeCompletionHandler { enum class ContractSpecifierKind { Pre, Post }; std::optional<ContractSpecifierKind> getContractSpecifierKind(); void ParseContractSpecifiers(Declarator &D); - /// Parse the function tails. e.g., requires clause and contracts. + /// Parse the trailing requires-clause and function contract specifiers. /// - /// \param ParametersAlreadyInScope whether the paramers are arealdy in an - /// active function prototype scope. This can be true for lambda as lambda - /// would always build its function prototype scope. - void ParseFunctionDeclaratorTail(Declarator &D, - bool ParametersAlreadyInScope = false); + /// \param ParametersAlreadyInScope whether the parameters are already in an + /// active function prototype scope. This can be true for lambdas, which + /// always build their function prototype scope. + void ParseFunctionContractSpecifiersAndConstraints( + Declarator &D, bool ParametersAlreadyInScope = false); /// ParseRefQualifier - Parses a member function ref-qualifier. Returns /// true if a ref-qualifier is found. @@ -2898,6 +2898,10 @@ class Parser : public CodeCompletionHandler { mutable IdentifierInfo *Ident_GNU_final; mutable IdentifierInfo *Ident_override; + /// C++26 contextual keywords. + mutable IdentifierInfo *Ident_pre; + mutable IdentifierInfo *Ident_post; + /// Representation of a class that has been parsed, including /// any member function declarations or definitions that need to be /// parsed after the corresponding top-level class is complete. @@ -3003,7 +3007,6 @@ class Parser : public CodeCompletionHandler { bool MayBeFollowedByDirectInit); /// Parse a requires-clause as part of a function declaration. - void ParseTrailingRequiresClauseWithScope(Declarator &D); void ParseTrailingRequiresClause(Declarator &D); void ParseMicrosoftIfExistsClassDeclaration(DeclSpec::TST TagType, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index fff66a45fc2a2..0aa0f682283df 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -6500,7 +6500,8 @@ class Sema final : public SemaBase { void SetFunctionBodyKind(Decl *D, SourceLocation Loc, FnBodyKind BodyKind, StringLiteral *DeletedMessage = nullptr); - void ActOnStartTrailingRequiresClause(Scope *S, Declarator &D); + void ActOnStartTrailingRequiresClauseOrContractSpecifier(Scope *S, + Declarator &D); /// Create a result variable for postconditions and make it visible in the /// predicate's scope. VarDecl *ActOnPostConditionResultName(Scope *S, IdentifierInfo *ResultName, diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index b4a22b3f597b9..08d1265323072 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -108,6 +108,10 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts, if (LangOpts.CPlusPlus20) return KS_Enabled; return LangOpts.CPlusPlus ? KS_Future : KS_Unknown; + case KEYCXX26: + if (LangOpts.CPlusPlus26) + return KS_Enabled; + return LangOpts.CPlusPlus ? KS_Future : KS_Unknown; case KEYGNU: return LangOpts.GNUKeywords ? KS_Extension : KS_Unknown; case KEYMS: @@ -137,8 +141,6 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts, return LangOpts.ObjC ? KS_Enabled : KS_Unknown; case KEYZVECTOR: return LangOpts.ZVector ? KS_Enabled : KS_Unknown; - case KEYCONTRACTS: - return LangOpts.CPlusPlus && LangOpts.Contracts ? KS_Enabled : KS_Unknown; case KEYCOROUTINES: return LangOpts.Coroutines ? KS_Enabled : KS_Unknown; case KEYMODULES: @@ -201,7 +203,7 @@ KeywordStatus clang::getKeywordStatus(const LangOptions &LangOpts, } static bool IsKeywordInCpp(unsigned Flags) { - return (Flags & (KEYCXX | KEYCXX11 | KEYCXX20 | KEYCONTRACTS | BOOLSUPPORT | + return (Flags & (KEYCXX | KEYCXX11 | KEYCXX20 | KEYCXX26 | BOOLSUPPORT | WCHARSUPPORT | CHAR8SUPPORT)) != 0; } @@ -849,6 +851,9 @@ IdentifierTable::getFutureCompatDiagKind(const IdentifierInfo &II, if (((Flags & KEYCXX20) == KEYCXX20) || ((Flags & CHAR8SUPPORT) == CHAR8SUPPORT)) return diag::warn_cxx20_keyword; + + if ((Flags & KEYCXX26) == KEYCXX26) + return diag::warn_cxx26_keyword; } else { if ((Flags & KEYC99) == KEYC99) return diag::warn_c99_keyword; diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 6e3fd2d5a4fc0..270d0e98c1f11 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2192,7 +2192,7 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, // account for the implicit template parameter list induced by the template. if (!TemplateInfo.TemplateParams && D.getInventedTemplateParameterList()) ++CurTemplateDepthTracker; - ParseFunctionDeclaratorTail(D); + ParseFunctionContractSpecifiersAndConstraints(D); } // Save late-parsed attributes for now; they need to be parsed in the @@ -2442,7 +2442,7 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, // declarator initializer[opt] // declarator requires-clause if (Tok.is(tok::kw_requires) || getContractSpecifierKind()) - ParseFunctionDeclaratorTail(D); + ParseFunctionContractSpecifiersAndConstraints(D); Decl *ThisDecl = ParseDeclarationAfterDeclarator(D, TemplateInfo); D.complete(ThisDecl); if (ThisDecl) diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index b60dd203635df..87b33761abc0d 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2614,7 +2614,7 @@ bool Parser::ParseCXXMemberDeclaratorBeforeInitializer( if (DeclaratorInfo.getTemplateParameterLists().empty() && DeclaratorInfo.getInventedTemplateParameterList()) ++CurTemplateDepthTracker; - ParseFunctionDeclaratorTail(DeclaratorInfo); + ParseFunctionContractSpecifiersAndConstraints(DeclaratorInfo); } } @@ -4172,49 +4172,17 @@ TypeResult Parser::ParseTrailingReturnType(SourceRange &Range, : DeclaratorContext::TrailingReturn); } -void Parser::ParseTrailingRequiresClauseWithScope(Declarator &D) { - assert(Tok.is(tok::kw_requires) && "expected requires"); - - // C++23 [basic.scope.namespace]p1: - // For each non-friend redeclaration or specialization whose target scope - // is or is contained by the scope, the portion after the declarator-id, - // class-head-name, or enum-head-name is also included in the scope. - // C++23 [basic.scope.class]p1: - // For each non-friend redeclaration or specialization whose target scope - // is or is contained by the scope, the portion after the declarator-id, - // class-head-name, or enum-head-name is also included in the scope. - // - // FIXME: We should really be calling ParseTrailingRequiresClause in - // ParseDirectDeclarator, when we are already in the declarator scope. - // This would also correctly suppress access checks for specializations - // and explicit instantiations, which we currently do not do. - CXXScopeSpec &SS = D.getCXXScopeSpec(); - DeclaratorScopeObj DeclScopeObj(*this, SS); - if (SS.isValid() && Actions.ShouldEnterDeclaratorScope(getCurScope(), SS)) - DeclScopeObj.EnterDeclaratorScope(); - - ParseScope ParamScope(this, Scope::DeclScope | - Scope::FunctionDeclarationScope | - Scope::FunctionPrototypeScope); - - ParseTrailingRequiresClause(D); -} - void Parser::ParseTrailingRequiresClause(Declarator &D) { + // The caller need to set up the scope for the parameters and init 'this' + // scope fro declarator if relevant. + assert(Tok.is(tok::kw_requires) && "expected requires"); assert( getCurScope()->isFunctionPrototypeScope() && "trailing requires-clause must be parsed in a function prototype scope"); SourceLocation RequiresKWLoc = ConsumeToken(); - - ExprResult TrailingRequiresClause; - Actions.ActOnStartTrailingRequiresClause(getCurScope(), D); - - std::optional<Sema::CXXThisScopeRAII> ThisScope; - InitCXXThisScopeForDeclaratorIfRelevant(D, D.getDeclSpec(), ThisScope); - - TrailingRequiresClause = + ExprResult TrailingRequiresClause = ParseConstraintLogicalOrExpression(/*IsTrailingRequiresClause=*/true); TrailingRequiresClause = @@ -4256,19 +4224,29 @@ void Parser::ParseTrailingRequiresClause(Declarator &D) { std::optional<Parser::ContractSpecifierKind> Parser::getContractSpecifierKind() { - if (!getLangOpts().Contracts || Tok.isNot(tok::identifier)) + if (!getLangOpts().CPlusPlus || Tok.isNot(tok::identifier)) return std::nullopt; - IdentifierInfo *II = Tok.getIdentifierInfo(); - if (II->isStr("pre")) + if (!Ident_pre) { + Ident_pre = &PP.getIdentifierTable().get("pre"); + Ident_post = &PP.getIdentifierTable().get("post"); + } + + const IdentifierInfo *II = Tok.getIdentifierInfo(); + if (II == Ident_pre) return ContractSpecifierKind::Pre; - if (II->isStr("post")) + if (II == Ident_post) return ContractSpecifierKind::Post; return std::nullopt; } void Parser::ParseContractSpecifiers(Declarator &D) { - assert(getLangOpts().Contracts && "contracts are not enabled"); + assert(getContractSpecifierKind() && "expected a contract specifier"); + + if (!getLangOpts().CPlusPlus26) + Diag(Tok, diag::err_contracts_require_cxx26); + else if (!getLangOpts().Contracts) + Diag(Tok, diag::err_contracts_disabled); const bool IsFunction = D.isDeclarationOfFunction() && (D.isFunctionDeclarationContext() || @@ -4319,42 +4297,27 @@ void Parser::ParseContractSpecifiers(Declarator &D) { } } -// Parse the function tail where the require clause must appear first -// and the contracts later. -void Parser::ParseFunctionDeclaratorTail(Declarator &D, - bool ParametersAlreadyInScope) { +void Parser::ParseFunctionContractSpecifiersAndConstraints( + Declarator &D, bool ParametersAlreadyInScope) { assert((Tok.is(tok::kw_requires) || getContractSpecifierKind()) && - "expected a function declarator tail"); + "expected a trailing requires-clause or contract specifier"); - // TODO: We can consider merging ParseTrailingRequiresClauseWithScope into - // this function. - if (!ParametersAlreadyInScope && Tok.is(tok::kw_requires)) { - ParseTrailingRequiresClauseWithScope(D); - if (!getContractSpecifierKind()) - return; - } + auto ParseSpecifiersAndConstraints = [&] { + if (!ParametersAlreadyInScope) + Actions.ActOnStartTrailingRequiresClauseOrContractSpecifier(getCurScope(), + D); + + const DeclSpec *MethodDS = &D.getDeclSpec(); + if (D.isFunctionDeclarator() && D.getFunctionTypeInfo().MethodQualifiers) + MethodDS = D.getFunctionTypeInfo().MethodQualifiers; + std::optional<Sema::CXXThisScopeRAII> ThisScope; + InitCXXThisScopeForDeclaratorIfRelevant(D, *MethodDS, ThisScope); - auto ParseTail = [&] { - bool ParametersInScope = ParametersAlreadyInScope; if (Tok.is(tok::kw_requires)) { ParseTrailingRequiresClause(D); - ParametersInScope = true; } while (getContractSpecifierKind()) { - if (!ParametersInScope) { - // FIXME: ActOnStartTrailingRequiresClause is not actually deal with - // require clause. It is actually adds the parameters to the scope - // chains. It needs a better name. - Actions.ActOnStartTrailingRequiresClause(getCurScope(), D); - ParametersInScope = true; - } - - const DeclSpec *MethodDS = &D.getDeclSpec(); - if (D.isFunctionDeclarator() && D.getFunctionTypeInfo().MethodQualifiers) - MethodDS = D.getFunctionTypeInfo().MethodQualifiers; - std::optional<Sema::CXXThisScopeRAII> ThisScope; - InitCXXThisScopeForDeclaratorIfRelevant(D, *MethodDS, ThisScope); ParseContractSpecifiers(D); // If we meet requires after contracts, emit a nice diagnostics and @@ -4367,10 +4330,23 @@ void Parser::ParseFunctionDeclaratorTail(Declarator &D, }; if (ParametersAlreadyInScope) { - ParseTail(); + ParseSpecifiersAndConstraints(); return; } + // C++23 [basic.scope.namespace]p1: + // For each non-friend redeclaration or specialization whose target scope + // is or is contained by the scope, the portion after the declarator-id, + // class-head-name, or enum-head-name is also included in the scope. + // C++23 [basic.scope.class]p1: + // For each non-friend redeclaration or specialization whose target scope + // is or is contained by the scope, the portion after the declarator-id, + // class-head-name, or enum-head-name is also included in the scope. + // + // FIXME: We should really parse these in ParseDirectDeclarator, when we are + // already in the declarator scope. This would also correctly suppress access + // checks for specializations and explicit instantiations, which we currently + // do not do. CXXScopeSpec &SS = D.getCXXScopeSpec(); DeclaratorScopeObj DeclScopeObj(*this, SS); if (SS.isValid() && Actions.ShouldEnterDeclaratorScope(getCurScope(), SS)) @@ -4379,7 +4355,7 @@ void Parser::ParseFunctionDeclaratorTail(Declarator &D, ParseScope ParamScope(this, Scope::DeclScope | Scope::FunctionDeclarationScope | Scope::FunctionPrototypeScope); - ParseTail(); + ParseSpecifiersAndConstraints(); } Sema::ParsingClassState Parser::PushParsingClass(Decl *ClassDecl, diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index ad566c25a438d..f80fdd6256ccb 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -1449,7 +1449,8 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( if ((HasParentheses && Tok.is(tok::kw_requires)) || getContractSpecifierKind()) - ParseFunctionDeclaratorTail(D, /*ParametersAlreadyInScope=*/true); + ParseFunctionContractSpecifiersAndConstraints( + D, /*ParametersAlreadyInScope=*/true); } // Emit a warning if we see a CUDA host/device/global attribute diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 898e6bf3cf120..feb228c1af984 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -2469,6 +2469,8 @@ StmtResult Parser::ParseBreakStatement() { StmtResult Parser::ParseContractAssertStatement() { assert(Tok.is(tok::kw_contract_assert) && "expected contract_assert"); + if (!getLangOpts().Contracts) + Diag(Tok, diag::err_contracts_disabled); SourceLocation ContractAssertLoc = ConsumeToken(); ParsedAttributes Attrs(AttrFactory); diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index af40d59e51bd4..ea733932f037b 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -521,6 +521,8 @@ void Parser::Initialize() { Ident_sealed = nullptr; Ident_abstract = nullptr; Ident_override = nullptr; + Ident_pre = nullptr; + Ident_post = nullptr; Ident_GNU_final = nullptr; Ident_super = &PP.getIdentifierTable().get("super"); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 3a33e77efb85b..631f47859cfe1 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -4206,7 +4206,8 @@ void Sema::ActOnStartCXXInClassMemberInitializer() { PushFunctionScope(); } -void Sema::ActOnStartTrailingRequiresClause(Scope *S, Declarator &D) { +void Sema::ActOnStartTrailingRequiresClauseOrContractSpecifier(Scope *S, + Declarator &D) { if (!D.isFunctionDeclarator()) return; auto &FTI = D.getFunctionTypeInfo(); diff --git a/clang/test/Lexer/keywords_test.cpp b/clang/test/Lexer/keywords_test.cpp index 62a2aef8ff66c..987baa46610c6 100644 --- a/clang/test/Lexer/keywords_test.cpp +++ b/clang/test/Lexer/keywords_test.cpp @@ -1,6 +1,7 @@ // RUN: %clang_cc1 -std=c++03 -fsyntax-only %s // RUN: %clang_cc1 -std=c++11 -DCXX11 -fsyntax-only %s // RUN: %clang_cc1 -std=c++20 -DCXX11 -DCXX20 -fsyntax-only %s +// RUN: %clang_cc1 -std=c++26 -DCXX11 -DCXX20 -DCXX26 -fsyntax-only %s // RUN: %clang_cc1 -std=c++03 -fdeclspec -DDECLSPEC -fsyntax-only %s // RUN: %clang_cc1 -std=c++03 -fms-extensions -DDECLSPEC -fsyntax-only %s // RUN: %clang_cc1 -std=c++03 -fborland-extensions -DDECLSPEC -fsyntax-only %s @@ -15,7 +16,7 @@ // RUN: %clang -std=c++03 -target i686-windows-msvc -DMS -fno-declspec -fsyntax-only %s // RUN: %clang -std=c++03 -target x86_64-scei-ps4 -fno-declspec -fsyntax-only %s -// RUN: %clang_cc1 -std=c++98 -DFutureKeyword -fsyntax-only -Wc++11-compat -Wc++20-compat -verify=cxx98 %s +// RUN: %clang_cc1 -std=c++98 -DFutureKeyword -fsyntax-only -Wc++11-compat -Wc++20-compat -Wc++2c-compat -verify=cxx98 %s #define IS_KEYWORD(NAME) _Static_assert(!__is_identifier(NAME), #NAME) #define NOT_KEYWORD(NAME) _Static_assert(__is_identifier(NAME), #NAME) @@ -27,6 +28,12 @@ #define CXX20_KEYWORD(NAME) NOT_KEYWORD(NAME) #endif +#if defined(CXX26) +#define CXX26_KEYWORD(NAME) IS_KEYWORD(NAME) +#else +#define CXX26_KEYWORD(NAME) NOT_KEYWORD(NAME) +#endif + #ifdef DECLSPEC #define DECLSPEC_KEYWORD(NAME) IS_KEYWORD(NAME) #else @@ -71,6 +78,9 @@ CXX20_KEYWORD(co_await); CXX20_KEYWORD(co_return); CXX20_KEYWORD(co_yield); +// C++26 keywords +CXX26_KEYWORD(contract_assert); + // __declspec extension DECLSPEC_KEYWORD(__declspec); @@ -100,5 +110,6 @@ int constinit; // cxx98-warning {{'constinit' is a keyword in C++20}} int consteval; // cxx98-warning {{'consteval' is a keyword in C++20}} int requires; // cxx98-warning {{'requires' is a keyword in C++20}} int concept; // cxx98-warning {{'concept' is a keyword in C++20}} +int contract_assert; // cxx98-warning {{'contract_assert' is a keyword in C++26}} #endif diff --git a/clang/test/Parser/cxx26-contracts-keyword.cpp b/clang/test/Parser/cxx26-contracts-keyword.cpp index a6f6b22503564..d7539ae2de408 100644 --- a/clang/test/Parser/cxx26-contracts-keyword.cpp +++ b/clang/test/Parser/cxx26-contracts-keyword.cpp @@ -1,15 +1,21 @@ -// RUN: %clang_cc1 -std=c++2c -fcontracts -DENABLED=1 -fsyntax-only -verify=enabled %s -// RUN: %clang_cc1 -std=c++2c -fno-contracts -DENABLED=0 -fsyntax-only -verify=disabled %s - -// disabled-no-diagnostics +// RUN: %clang_cc1 -std=c++2c -fcontracts -DCXX26 -fsyntax-only -verify=enabled,expected %s +// RUN: %clang_cc1 -std=c++2c -fno-contracts -DCXX26 -fsyntax-only -verify=disabled,expected %s +// RUN: %clang_cc1 -std=c++23 -fcontracts -fsyntax-only -verify=precxx26,expected %s #ifdef __cplusplus +#ifdef CXX26 void test() { -#if ENABLED - int contract_assert; // enabled-error {{expected unqualified-id}} + int contract_assert; // expected-error {{expected unqualified-id}} +} + +void assertion() { + contract_assert(true); // disabled-error {{contracts support is disabled; pass '-fcontracts' to enable it}} +} + +int specifier(int value) pre(value > 0); +// disabled-error@-1 {{contracts support is disabled; pass '-fcontracts' to enable it}} #else - int contract_assert; - ++contract_assert; +int old_standard(int value) pre(value > 0); +// precxx26-error@-1 {{contracts are a C++26 feature}} #endif -} #endif _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
