Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -700,6 +700,9 @@ +**AlwaysBreakBeforeConceptDeclarations** (``bool``) + If ``true``, always break before concept declarations + **AlwaysBreakBeforeMultilineStrings** (``bool``) If ``true``, always break before multiline string literals. @@ -1845,6 +1848,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 @@ -349,6 +349,13 @@ foo(); } while(1); +- Support in clang-format for concepts has been improved, to aid this the follow options have been added + +- Option ``IndentRequires`` has been added to indent the ``requires`` keyword + in templates. +- Option ``AlwaysBreakBeforeConceptDeclarations`` has been added to aid the formatting of concepts. + + libclang -------- Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -530,6 +530,9 @@ /// The function declaration return type breaking style to use. ReturnTypeBreakingStyle AlwaysBreakAfterReturnType; + /// If ``true``, always break before concept declarations + bool AlwaysBreakBeforeConceptDeclarations; + /// If ``true``, always break before multiline string literals. /// /// This flag is mean to make cases where there are multiple multiline strings @@ -1556,6 +1559,24 @@ /// IndentExternBlockStyle is the type of indenting of extern blocks. IndentExternBlockStyle IndentExternBlock; + /// 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 @@ -2303,6 +2324,8 @@ AllowShortLambdasOnASingleLine == R.AllowShortLambdasOnASingleLine && AllowShortLoopsOnASingleLine == R.AllowShortLoopsOnASingleLine && AlwaysBreakAfterReturnType == R.AlwaysBreakAfterReturnType && + AlwaysBreakBeforeConceptDeclarations == + R.AlwaysBreakBeforeConceptDeclarations && AlwaysBreakBeforeMultilineStrings == R.AlwaysBreakBeforeMultilineStrings && AlwaysBreakTemplateDeclarations == @@ -2342,7 +2365,8 @@ IndentGotoLabels == R.IndentGotoLabels && IndentPPDirectives == R.IndentPPDirectives && IndentExternBlock == R.IndentExternBlock && - 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 @@ -459,6 +459,8 @@ FormatStyle::RTBS_TopLevelDefinitions; } + IO.mapOptional("AlwaysBreakBeforeConceptDeclarations", + Style.AlwaysBreakBeforeConceptDeclarations); IO.mapOptional("AlwaysBreakBeforeMultilineStrings", Style.AlwaysBreakBeforeMultilineStrings); IO.mapOptional("AlwaysBreakTemplateDeclarations", @@ -524,6 +526,7 @@ IO.mapOptional("IndentGotoLabels", Style.IndentGotoLabels); IO.mapOptional("IndentPPDirectives", Style.IndentPPDirectives); IO.mapOptional("IndentExternBlock", Style.IndentExternBlock); + IO.mapOptional("IndentRequires", Style.IndentRequires); IO.mapOptional("IndentWidth", Style.IndentWidth); IO.mapOptional("IndentWrappedFunctionNames", Style.IndentWrappedFunctionNames); @@ -825,6 +828,7 @@ LLVMStyle.AllowShortLoopsOnASingleLine = false; LLVMStyle.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None; LLVMStyle.AlwaysBreakAfterDefinitionReturnType = FormatStyle::DRTBS_None; + LLVMStyle.AlwaysBreakBeforeConceptDeclarations = true; LLVMStyle.AlwaysBreakBeforeMultilineStrings = false; LLVMStyle.AlwaysBreakTemplateDeclarations = FormatStyle::BTDS_MultiLine; LLVMStyle.BinPackArguments = true; @@ -879,6 +883,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))) @@ -3483,11 +3495,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 Style.AlwaysBreakBeforeConceptDeclarations; + 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,10 @@ void parseNew(); void parseAccessSpecifier(); bool parseEnum(); + void parseConcept(); + void parseRequires(); + void parseRequiresExpression(unsigned OriginalLevel); + void parseConstraintExpression(unsigned OriginalLevel); 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) { @@ -1262,6 +1269,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)) { @@ -2268,6 +2281,107 @@ 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)) { + nextToken(); + parseRequiresExpression(Line->Level); + } else { + parseConstraintExpression(Line->Level); + } +} + +void UnwrappedLineParser::parseRequiresExpression(unsigned OriginalLevel) { + // 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 { + parseConstraintExpression(OriginalLevel); + } +} + +void UnwrappedLineParser::parseConstraintExpression(unsigned OriginalLevel) { + // requires Id && Id || Id + while ( + FormatTok->isOneOf(tok::identifier, tok::kw_requires, tok::coloncolon)) { + nextToken(); + while (FormatTok->isOneOf(tok::identifier, tok::coloncolon, tok::less, + tok::greater, tok::comma)) { + if (FormatTok->Tok.is(tok::less)) { + parseBracedList(/*ContinueOnSemicolons=*/false, /*IsEnum=*/false, + /*ClosingBraceKind=*/tok::greater); + continue; + } + nextToken(); + } + if (FormatTok->Tok.is(tok::kw_requires)) { + parseRequiresExpression(OriginalLevel); + } + if (FormatTok->Tok.is(tok::less)) { + parseBracedList(/*ContinueOnSemicolons=*/false, /*IsEnum=*/false, + /*ClosingBraceKind=*/tok::greater); + } + + 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->isOneOf(tok::identifier, tok::kw_requires, + tok::coloncolon)) { + addUnwrappedLine(); + } + if (Style.IndentRequires && OriginalLevel != Line->Level) { + --Line->Level; + } + break; + } else { + FormatTok->setType(TT_ConstraintJunctions); + } + + nextToken(); + } +} + +void UnwrappedLineParser::parseRequires() { + assert(FormatTok->Tok.is(tok::kw_requires) && "'requires' expected"); + + unsigned OriginalLevel = Line->Level; + if (FormatTok->Previous && FormatTok->Previous->is(tok::greater)) { + addUnwrappedLine(); + if (Style.IndentRequires) { + Line->Level++; + } + } + nextToken(); + + parseRequiresExpression(OriginalLevel); +} + 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 @@ -13537,6 +13537,7 @@ CHECK_PARSE_BOOL(AlignConsecutiveBitFields); CHECK_PARSE_BOOL(AlignConsecutiveDeclarations); CHECK_PARSE_BOOL(AlignConsecutiveMacros); + CHECK_PARSE_BOOL(AlwaysBreakBeforeConceptDeclarations); CHECK_PARSE_BOOL(AllowAllArgumentsOnNextLine); CHECK_PARSE_BOOL(AllowAllConstructorInitializersOnNextLine); CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine); @@ -13557,6 +13558,7 @@ CHECK_PARSE_BOOL(IndentCaseLabels); CHECK_PARSE_BOOL(IndentCaseBlocks); CHECK_PARSE_BOOL(IndentGotoLabels); + CHECK_PARSE_BOOL(IndentRequires); CHECK_PARSE_BOOL(IndentWrappedFunctionNames); CHECK_PARSE_BOOL(KeepEmptyLinesAtTheStartOfBlocks); CHECK_PARSE_BOOL(ObjCSpaceAfterProperty); @@ -16567,6 +16569,223 @@ "}", 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 EqualityComparable = requires(T a, T b) {\n" + " { a == b } -> bool;\n" + "};", + Style); + verifyFormat("template \n" + "concept EqualityComparable = requires(T a, T b) {\n" + " { a == b } -> bool;\n" + " { a != b } -> bool;\n" + "};", + Style); + verifyFormat("template \n" + "concept 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); + + verifyFormat("template \n" + "requires Foo() && Bar {\n" + " //....\n" + "}", + Style); + + verifyFormat("template \n" + "requires Foo>() && Bar> {\n" + " //....\n" + "}", + Style); + + verifyFormat("template \n" + "requires Foo>() && Bar> {\n" + " //....\n" + "}", + Style); + + verifyFormat( + "template \n" + "requires Foo, Baz>() && Bar, Baz> {\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); + + Style.ColumnLimit = 78; + verifyFormat("template \n" + "concept Context = Traits and\n" + " Interface and\n" + " Request and\n" + " Response and\n" + " ContextExtension and\n" + " ::std::is_copy_constructable and " + "::std::is_move_constructable and\n" + " requires (T c) {\n" + " { c.response; } -> Response;\n" + "} and requires (T c) {\n" + " { c.request; } -> Request;\n" + "}\n", + Style); + + verifyFormat("template \n" + "concept Context = Traits or\n" + " Interface or\n" + " Request or\n" + " Response or\n" + " ContextExtension or\n" + " ::std::is_copy_constructable or " + "::std::is_move_constructable or\n" + " requires (T c) {\n" + " { c.response; } -> Response;\n" + "} or requires (T c) {\n" + " { c.request; } -> Request;\n" + "}\n", + Style); + + verifyFormat("template \n" + "concept Context = Traits &&\n" + " Interface &&\n" + " Request &&\n" + " Response &&\n" + " ContextExtension &&\n" + " ::std::is_copy_constructable && " + "::std::is_move_constructable &&\n" + " requires (T c) {\n" + " { c.response; } -> Response;\n" + "} && requires (T c) {\n" + " { c.request; } -> Request;\n" + "}\n", + Style); + + verifyFormat("template \nconcept someConcept = Constraint1 && " + "Constraint2;"); + + Style.AlwaysBreakBeforeConceptDeclarations = false; + verifyFormat("template concept Tree = true;", Style); +} } // namespace } // namespace format } // namespace clang