[clang] [C++26] [Contracts] Initial parser support (PR #221139)
Chuanqi Xu via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 7 01:54:27 PDT 2026
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 <chuanqi.xcq at alibaba-inc.com>
Date: Fri, 4 Sep 2026 11:19:00 +0800
Subject: [PATCH 1/3] [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 at -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 at -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 at -1 {{expected '(' after 'pre'}}
+// expected-error at -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 at -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 at -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 at -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 at -1 {{expected '(' after 'pre'}}
+// expected-error at -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 <chuanqi.xcq at alibaba-inc.com>
Date: Fri, 4 Sep 2026 17:06:28 +0800
Subject: [PATCH 2/3] 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 at -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 at -1 {{contracts are a C++26 feature}}
#endif
-}
#endif
>From 493f0821543e6103366572a86780fa40609811a5 Mon Sep 17 00:00:00 2001
From: Chuanqi Xu <chuanqi.xcq at alibaba-inc.com>
Date: Mon, 7 Sep 2026 16:53:58 +0800
Subject: [PATCH 3/3] Address comments
---
clang/include/clang/Basic/TokenKinds.def | 6 +++---
clang/lib/Parse/ParseDecl.cpp | 7 +++++++
clang/lib/Parse/ParseDeclCXX.cpp | 3 ---
clang/lib/Parse/ParseStmt.cpp | 4 +++-
.../test/Parser/cxx26-contracts-declarators.cpp | 5 +++++
clang/test/Parser/cxx26-contracts-parser.cpp | 17 +++++++++++++++++
6 files changed, 35 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 22aed14ff6369..34242dc494a5f 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -425,9 +425,6 @@ CXX11_KEYWORD(nullptr , KEYC23)
CXX11_KEYWORD(static_assert , KEYMSCOMPAT|KEYC23)
CXX11_KEYWORD(thread_local , KEYC23)
-// C++26 keyword.
-CXX26_KEYWORD(contract_assert , 0)
-
// C++20 / coroutines keywords
COROUTINES_KEYWORD(co_await)
COROUTINES_KEYWORD(co_return)
@@ -443,6 +440,9 @@ CXX20_KEYWORD(constinit , 0)
CXX20_KEYWORD(concept , 0)
CXX20_KEYWORD(requires , 0)
+// C++26 keyword.
+CXX26_KEYWORD(contract_assert , 0)
+
// Not a CXX20_KEYWORD because it is disabled by -fno-char8_t.
KEYWORD(char8_t , CHAR8SUPPORT)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 270d0e98c1f11..5a12ec0db5b26 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -7641,6 +7641,13 @@ void Parser::ParseParameterDeclarationClause(
: DeclaratorContext::Prototype);
ParseDeclarator(ParmDeclarator);
+ // A parameter declarator can spell a function type, but it is adjusted to
+ // a pointer-to-function type. Parse contract specifiers here to diagnose
+ // them and recover instead of treating them as an unexpected parameter
+ // list token.
+ if (getContractSpecifierKind())
+ ParseContractSpecifiers(ParmDeclarator);
+
if (ThisLoc.isValid())
ParmDeclarator.SetRangeBegin(ThisLoc);
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 87b33761abc0d..296ef9bdc8c3e 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -2593,9 +2593,6 @@ bool Parser::ParseCXXMemberDeclaratorBeforeInitializer(
if (BitfieldSize.isInvalid())
SkipUntil(tok::comma, StopAtSemi | StopBeforeMatch);
} else {
- // 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,
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index feb228c1af984..71f929f774a46 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2469,7 +2469,9 @@ StmtResult Parser::ParseBreakStatement() {
StmtResult Parser::ParseContractAssertStatement() {
assert(Tok.is(tok::kw_contract_assert) && "expected contract_assert");
- if (!getLangOpts().Contracts)
+ if (!getLangOpts().CPlusPlus26)
+ Diag(Tok, diag::err_contracts_require_cxx26);
+ else if (!getLangOpts().Contracts)
Diag(Tok, diag::err_contracts_disabled);
SourceLocation ContractAssertLoc = ConsumeToken();
diff --git a/clang/test/Parser/cxx26-contracts-declarators.cpp b/clang/test/Parser/cxx26-contracts-declarators.cpp
index 2161fc0dcb394..7cbba2447f867 100644
--- a/clang/test/Parser/cxx26-contracts-declarators.cpp
+++ b/clang/test/Parser/cxx26-contracts-declarators.cpp
@@ -75,3 +75,8 @@ int (*function_pointer)(int) pre(true); // expected-error {{contract specifiers
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}}
+
+void foo(int bar() pre(true)) {} // expected-error {{contract specifiers can only be applied to function declarations}}
+
+using ftype = void();
+ftype foo pre(true);
diff --git a/clang/test/Parser/cxx26-contracts-parser.cpp b/clang/test/Parser/cxx26-contracts-parser.cpp
index 7b08e702b9450..be4d8da0605f0 100644
--- a/clang/test/Parser/cxx26-contracts-parser.cpp
+++ b/clang/test/Parser/cxx26-contracts-parser.cpp
@@ -24,6 +24,7 @@ int constrained(T value) requires C<T> pre(value > T{});
struct S {
int member(int value) pre(value > 0) post(result: result > value);
+ int member2(int value) post(result: result > value) pre(value > 0);
virtual int virtual_member(int value) const final pre(value > 0);
};
@@ -33,6 +34,12 @@ int S::member(int value) pre(value > 0) post(result: result > value) {
return value;
}
+int test_contextual_keyword() {
+ int pre = 32;
+ int post = 34;
+ return pre + post;
+}
+
void lambdas() {
auto a = [](int value) pre(value > 0) { return value; };
auto b = [] pre(true) { return 0; };
@@ -57,3 +64,13 @@ void missing_assert_lparen() {
int missing_pre_lparen() pre true;
// expected-error at -1 {{expected '(' after 'pre'}}
// expected-error at -2 {{expected function body after function declarator}}
+
+class base {
+public:
+ virtual int member(int v) pre(v > 0) post(r: r > 0);
+};
+
+class inherited : public base {
+public:
+ int member(int v) override pre(v > 0) post(r: r > 0);
+};
More information about the cfe-commits
mailing list