Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -1769,6 +1769,25 @@ +**IndentRequires** (``bool``) + Indent the requires clause in a template + + .. code-block:: c++ + + true: + template + requires Iterator + void sort(It begin, It end) { + //.... + } + + false: + template + requires Iterator + void sort(It begin, It end) { + //.... + } + **IndentWidth** (``unsigned``) The number of columns to use for indentation. Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -297,6 +297,11 @@ bar(); }); +- Option ``IndentRequires`` has been added to indent the ``requires`` keyword + in templates. + + Support in clang-format for concepts has been improved + libclang -------- Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -1492,6 +1492,24 @@ /// The preprocessor directive indenting style to use. PPDirectiveIndentStyle IndentPPDirectives; + /// Indent the requires clause in a template + /// \code + /// true: + /// template + /// requires Iterator + /// void sort(It begin, It end) { + /// //.... + /// } + /// + /// false: + /// template + /// requires Iterator + /// void sort(It begin, It end) { + /// //.... + /// } + /// \endcode + bool IndentRequires; + /// The number of columns to use for indentation. /// \code /// IndentWidth: 3 @@ -2011,8 +2029,8 @@ /// \endcode SBPO_ControlStatements, /// Same as ``SBPO_ControlStatements`` except this option doesn't apply to - /// ForEach macros. This is useful in projects where ForEach macros are - /// treated as function calls instead of control statements. + /// ForEach macros. This is useful in projects where ForEach macros are + /// treated as function calls instead of control statements. /// \code /// void f() { /// Q_FOREACH(...) { @@ -2276,7 +2294,8 @@ IndentCaseBlocks == R.IndentCaseBlocks && IndentGotoLabels == R.IndentGotoLabels && IndentPPDirectives == R.IndentPPDirectives && - IndentWidth == R.IndentWidth && Language == R.Language && + IndentRequires == R.IndentRequires && IndentWidth == R.IndentWidth && + Language == R.Language && IndentWrappedFunctionNames == R.IndentWrappedFunctionNames && JavaImportGroups == R.JavaImportGroups && JavaScriptQuotes == R.JavaScriptQuotes && Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -510,6 +510,7 @@ IO.mapOptional("IndentCaseBlocks", Style.IndentCaseBlocks); IO.mapOptional("IndentGotoLabels", Style.IndentGotoLabels); IO.mapOptional("IndentPPDirectives", Style.IndentPPDirectives); + IO.mapOptional("IndentRequires", Style.IndentRequires); IO.mapOptional("IndentWidth", Style.IndentWidth); IO.mapOptional("IndentWrappedFunctionNames", Style.IndentWrappedFunctionNames); @@ -821,6 +822,7 @@ LLVMStyle.IndentCaseBlocks = false; LLVMStyle.IndentGotoLabels = true; LLVMStyle.IndentPPDirectives = FormatStyle::PPDIS_None; + LLVMStyle.IndentRequires = false; LLVMStyle.IndentWrappedFunctionNames = false; LLVMStyle.IndentWidth = 2; LLVMStyle.InsertTrailingCommas = FormatStyle::TCS_None; Index: clang/lib/Format/FormatToken.h =================================================================== --- clang/lib/Format/FormatToken.h +++ clang/lib/Format/FormatToken.h @@ -39,6 +39,7 @@ TYPE(ConflictAlternative) \ TYPE(ConflictEnd) \ TYPE(ConflictStart) \ + TYPE(ConstraintJunctions) \ TYPE(CtorInitializerColon) \ TYPE(CtorInitializerComma) \ TYPE(DesignatedInitializerLSquare) \ @@ -469,6 +470,7 @@ case tok::kw_noexcept: case tok::kw_static_assert: case tok::kw___attribute: + case tok::kw_requires: return true; default: return false; Index: clang/lib/Format/TokenAnnotator.cpp =================================================================== --- clang/lib/Format/TokenAnnotator.cpp +++ clang/lib/Format/TokenAnnotator.cpp @@ -1309,7 +1309,7 @@ TT_TypenameMacro, TT_FunctionLBrace, TT_ImplicitStringLiteral, TT_InlineASMBrace, TT_JsFatArrow, TT_LambdaArrow, TT_NamespaceMacro, TT_OverloadedOperator, TT_RegexLiteral, TT_TemplateString, - TT_ObjCStringLiteral)) + TT_ObjCStringLiteral, TT_ConstraintJunctions)) CurrentToken->setType(TT_Unknown); CurrentToken->Role.reset(); CurrentToken->MatchingParen = nullptr; @@ -1563,7 +1563,11 @@ !Current.Previous->is(tok::kw_operator)) { // not auto operator->() -> xxx; Current.setType(TT_TrailingReturnArrow); - + } else if (Current.is(tok::arrow) && Current.Previous && + Current.Previous->is(tok::r_brace)) { + // Concept implicit conversion contraint needs to be treated like + // a trailing return type ... } -> . + Current.setType(TT_TrailingReturnArrow); } else if (isDeductionGuide(Current)) { // Deduction guides trailing arrow " A(...) -> A;". Current.setType(TT_TrailingReturnArrow); @@ -2739,6 +2743,14 @@ isKeywordWithCondition(*Right.MatchingParen->Previous)) return true; } + + // requires ( or requires( + if (Right.is(tok::l_paren) && Left.is(tok::kw_requires)) + return spaceRequiredBeforeParens(Right); + // requires clause Concept1 && Concept2 + if (Left.is(TT_ConstraintJunctions) && Right.is(tok::identifier)) + return true; + if (Left.is(tok::l_paren) || Right.is(tok::r_paren)) return (Right.is(TT_CastRParen) || (Left.MatchingParen && Left.MatchingParen->is(TT_CastRParen))) @@ -3478,11 +3490,17 @@ Right.Previous->is(tok::string_literal) && Right.Next->is(tok::string_literal)) return true; + // Can break after template<> declaration if (Right.Previous->ClosesTemplateDeclaration && Right.Previous->MatchingParen && - Right.Previous->MatchingParen->NestingLevel == 0 && - Style.AlwaysBreakTemplateDeclarations == FormatStyle::BTDS_Yes) - return true; + Right.Previous->MatchingParen->NestingLevel == 0) { + // Put concepts on the next line e.g. + // template + // concept ... + if (Right.is(tok::kw_concept)) + return true; + return (Style.AlwaysBreakTemplateDeclarations == FormatStyle::BTDS_Yes); + } if (Right.is(TT_CtorInitializerComma) && Style.BreakConstructorInitializers == FormatStyle::BCIS_BeforeComma && !Style.ConstructorInitializerAllOnOneLineOrOnePerLine) Index: clang/lib/Format/UnwrappedLineParser.h =================================================================== --- clang/lib/Format/UnwrappedLineParser.h +++ clang/lib/Format/UnwrappedLineParser.h @@ -113,6 +113,8 @@ void parseNew(); void parseAccessSpecifier(); bool parseEnum(); + void parseConcept(); + void parseRequires(); void parseJavaEnumBody(); // Parses a record (aka class) as a top level element. If ParseAsExpr is true, // parses the record as a child block, i.e. if the class declaration is an Index: clang/lib/Format/UnwrappedLineParser.cpp =================================================================== --- clang/lib/Format/UnwrappedLineParser.cpp +++ clang/lib/Format/UnwrappedLineParser.cpp @@ -628,6 +628,13 @@ if (MunchSemi && FormatTok->Tok.is(tok::semi)) nextToken(); + else if (FormatTok->is(tok::arrow)) { + // Following the } we can find a trailing return type arrow + // as part of an implicit conversion constraint. + nextToken(); + parseStructuralElement(); + } + Line->Level = InitialLevel; if (PPStartHash == PPEndHash) { @@ -1257,6 +1264,12 @@ break; } break; + case tok::kw_concept: + parseConcept(); + break; + case tok::kw_requires: + parseRequires(); + break; case tok::kw_enum: // Ignore if this is part of "template is(tok::less)) { @@ -2257,6 +2270,80 @@ addUnwrappedLine(); } +void UnwrappedLineParser::parseConcept() { + assert(FormatTok->Tok.is(tok::kw_concept) && "'concept' expected"); + nextToken(); + if (!FormatTok->Tok.is(tok::identifier)) + return; + nextToken(); + if (!FormatTok->Tok.is(tok::equal)) + return; + nextToken(); + if (FormatTok->Tok.is(tok::kw_requires)) + parseRequires(); +} + +void UnwrappedLineParser::parseRequires() { + assert(FormatTok->Tok.is(tok::kw_requires) && "'requires' expected"); + + unsigned int OriginalLevel = Line->Level; + if (FormatTok->Previous && FormatTok->Previous->is(tok::greater)) { + addUnwrappedLine(); + if (Style.IndentRequires) { + Line->Level++; + } + } + nextToken(); + + // requires (R range) + if (FormatTok->Tok.is(tok::l_paren)) { + parseParens(); + if (Style.IndentRequires && OriginalLevel != Line->Level) { + addUnwrappedLine(); + --Line->Level; + } + } + + if (FormatTok->Tok.is(tok::l_brace)) { + parseBlock(/*MustBeDeclaration=*/false); + addUnwrappedLine(); + } else { + // requires Id && Id || Id + while (FormatTok->is(tok::identifier)) { + nextToken(); + if (FormatTok->Tok.is(tok::less)) { + while (!FormatTok->Tok.is(tok::greater)) { + nextToken(); + } + nextToken(); + } + if (FormatTok->Tok.is(tok::l_paren)) { + parseParens(); + } + if (FormatTok->Tok.is(tok::l_brace)) { + parseBlock(/*MustBeDeclaration=*/false); + } + if (FormatTok->Tok.is(tok::semi)) { + // Eat any trailing semi. + nextToken(); + addUnwrappedLine(); + } + if (!FormatTok->Tok.isOneOf(tok::ampamp, tok::pipepipe)) { + if (FormatTok->Previous && !FormatTok->Previous->is(tok::identifier)) { + addUnwrappedLine(); + } + if (Style.IndentRequires && OriginalLevel != Line->Level) { + --Line->Level; + } + break; + } else + FormatTok->setType(TT_ConstraintJunctions); + + nextToken(); + } + } +} + bool UnwrappedLineParser::parseEnum() { // Won't be 'enum' for NS_ENUMs. if (FormatTok->Tok.is(tok::kw_enum)) Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -16306,6 +16306,145 @@ Style)); } +TEST_F(FormatTest, ConceptsAndRequires) { + FormatStyle Style = getLLVMStyle(); + Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; + + verifyFormat("template \n" + "concept Hashable = requires(T a) {\n" + " { std::hash{}(a) } -> std::convertible_to;\n" + "};", + Style); + verifyFormat("template \n" + "concept bool EqualityComparable = requires(T a, T b) {\n" + " { a == b } -> bool;\n" + "};", + Style); + verifyFormat("template \n" + "concept bool EqualityComparable = requires(T a, T b) {\n" + " { a == b } -> bool;\n" + " { a != b } -> bool;\n" + "};", + Style); + verifyFormat("template \n" + "concept bool EqualityComparable = requires(T a, T b) {\n" + " { a == b } -> bool;\n" + " { a != b } -> bool;\n" + "};", + Style); + + verifyFormat("template \n" + "requires Iterator\n" + "void sort(It begin, It end) {\n" + " //....\n" + "}", + Style); + + verifyFormat("template \n" + "concept Large = sizeof(T) > 10;", + Style); + + verifyFormat("template \n" + "concept FooableWith = requires(T t, U u) {\n" + " typename T::foo_type;\n" + " { t.foo(u) } -> typename T::foo_type;\n" + " t++;\n" + "};\n" + "void doFoo(FooableWith auto t) {\n" + " t.foo(3);\n" + "}", + Style); + verifyFormat("template \n" + "concept Context = sizeof(T) == 1;", + Style); + verifyFormat("template \n" + "concept Context = is_specialization_of_v;", + Style); + verifyFormat("template \n" + "concept Node = std::is_object_v;", + Style); + verifyFormat("template \n" + "concept Tree = true;", + Style); + + verifyFormat("template int g(T i) requires Concept1 {\n" + " //...\n" + "}", + Style); + + verifyFormat( + "template int g(T i) requires Concept1 && Concept2 {\n" + " //...\n" + "}", + Style); + + verifyFormat( + "template int g(T i) requires Concept1 || Concept2 {\n" + " //...\n" + "}", + Style); + + verifyFormat("template \n" + "veryveryvery_long_return_type g(T i) requires Concept1 || " + "Concept2 {\n" + " //...\n" + "}", + Style); + + verifyFormat("template \n" + "veryveryvery_long_return_type g(T i) requires Concept1 && " + "Concept2 {\n" + " //...\n" + "}", + Style); + + verifyFormat( + "template \n" + "veryveryvery_long_return_type g(T i) requires Concept1 && Concept2 {\n" + " //...\n" + "}", + Style); + + verifyFormat( + "template \n" + "veryveryvery_long_return_type g(T i) requires Concept1 || Concept2 {\n" + " //...\n" + "}", + Style); + + Style.IndentRequires = true; + verifyFormat("template \n" + " requires Iterator\n" + "void sort(It begin, It end) {\n" + " //....\n" + "}", + Style); + verifyFormat("template \n" + " requires(index_ < sizeof...(Children_))\n" + "Tree auto &child() {\n" + " // ...\n" + "}", + Style); + + Style.SpaceBeforeParens = FormatStyle::SBPO_Always; + verifyFormat("template \n" + "concept Hashable = requires (T a) {\n" + " { std::hash{}(a) } -> std::convertible_to;\n" + "};", + Style); + + verifyFormat("template \n" + " requires EqualityComparable || Same\n" + "struct equal_to;", + Style); + + verifyFormat("template \n" + " requires requires {\n" + " T{};\n" + " T (int);\n" + " }\n", + Style); +} } // namespace } // namespace format } // namespace clang