diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -1382,6 +1382,18 @@ +**BreakBeforeConceptDeclarations** (``bool``) + If ``true``, concept will be placed on a new line. + + .. code-block:: c++ + + true: + template + concept ... + + false: + template concept ... + **BreakBeforeTernaryOperators** (``bool``) If ``true``, ternary operators will be placed after line breaks. @@ -1901,6 +1913,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. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -271,6 +271,14 @@ }; +- Experimental 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 ``BreakBeforeConceptDeclarations`` has been added to aid the formatting of concepts. + + libclang -------- diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1160,6 +1160,17 @@ /// \endcode BraceWrappingFlags BraceWrapping; + /// If ``true``, concept will be placed on a new line. + /// \code + /// true: + /// template + /// concept ... + /// + /// false: + /// template concept ... + /// \endcode + bool BreakBeforeConceptDeclarations; + /// If ``true``, ternary operators will be placed after line breaks. /// \code /// true: @@ -1590,6 +1601,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 @@ -2435,6 +2464,7 @@ BinPackParameters == R.BinPackParameters && BreakBeforeBinaryOperators == R.BreakBeforeBinaryOperators && BreakBeforeBraces == R.BreakBeforeBraces && + BreakBeforeConceptDeclarations == R.BreakBeforeConceptDeclarations && BreakBeforeTernaryOperators == R.BreakBeforeTernaryOperators && BreakConstructorInitializers == R.BreakConstructorInitializers && CompactNamespaces == R.CompactNamespaces && @@ -2466,7 +2496,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 && diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -501,6 +501,8 @@ IO.mapOptional("BraceWrapping", Style.BraceWrapping); IO.mapOptional("BreakBeforeBinaryOperators", Style.BreakBeforeBinaryOperators); + IO.mapOptional("BreakBeforeConceptDeclarations", + Style.BreakBeforeConceptDeclarations); IO.mapOptional("BreakBeforeBraces", Style.BreakBeforeBraces); bool BreakBeforeInheritanceComma = false; @@ -557,6 +559,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); @@ -872,6 +875,7 @@ LLVMStyle.BinPackArguments = true; LLVMStyle.BinPackParameters = true; LLVMStyle.BreakBeforeBinaryOperators = FormatStyle::BOS_None; + LLVMStyle.BreakBeforeConceptDeclarations = true; LLVMStyle.BreakBeforeTernaryOperators = true; LLVMStyle.BreakBeforeBraces = FormatStyle::BS_Attach; LLVMStyle.BraceWrapping = {/*AfterCaseLabel=*/false, @@ -921,6 +925,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; diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -40,6 +40,7 @@ TYPE(ConflictAlternative) \ TYPE(ConflictEnd) \ TYPE(ConflictStart) \ + TYPE(ConstraintJunctions) \ TYPE(CtorInitializerColon) \ TYPE(CtorInitializerComma) \ TYPE(DesignatedInitializerLSquare) \ @@ -590,6 +591,7 @@ case tok::kw__Atomic: case tok::kw___attribute: case tok::kw___underlying_type: + case tok::kw_requires: return true; default: return false; diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -1367,7 +1367,7 @@ TT_ImplicitStringLiteral, TT_InlineASMBrace, TT_JsFatArrow, TT_LambdaArrow, TT_NamespaceMacro, TT_OverloadedOperator, TT_RegexLiteral, TT_TemplateString, TT_ObjCStringLiteral, - TT_UntouchableMacroFunc)) + TT_UntouchableMacroFunc, TT_ConstraintJunctions)) CurrentToken->setType(TT_Unknown); CurrentToken->Role.reset(); CurrentToken->MatchingParen = nullptr; @@ -1621,7 +1621,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); @@ -1722,8 +1726,8 @@ // colon after this, this is the only place which annotates the identifier // as a selector.) Current.setType(TT_SelectorName); - } else if (Current.isOneOf(tok::identifier, tok::kw_const, - tok::kw_noexcept) && + } else if (Current.isOneOf(tok::identifier, tok::kw_const, tok::kw_noexcept, + tok::kw_requires) && Current.Previous && !Current.Previous->isOneOf(tok::equal, tok::at) && Line.MightBeFunctionDecl && Contexts.size() == 1) { @@ -1839,8 +1843,8 @@ // Functions which end with decorations like volatile, noexcept are unlikely // to be casts. if (Tok.Next->isOneOf(tok::kw_noexcept, tok::kw_volatile, tok::kw_const, - tok::kw_throw, tok::arrow, Keywords.kw_override, - Keywords.kw_final) || + tok::kw_requires, tok::kw_throw, tok::arrow, + Keywords.kw_override, Keywords.kw_final) || isCpp11AttributeSpecifier(*Tok.Next)) return false; @@ -2817,6 +2821,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))) @@ -3594,11 +3606,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.BreakBeforeConceptDeclarations; + return (Style.AlwaysBreakTemplateDeclarations == FormatStyle::BTDS_Yes); + } if (Right.is(TT_CtorInitializerComma) && Style.BreakConstructorInitializers == FormatStyle::BCIS_BeforeComma && !Style.ConstructorInitializerAllOnOneLineOrOnePerLine) diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h --- a/clang/lib/Format/UnwrappedLineParser.h +++ b/clang/lib/Format/UnwrappedLineParser.h @@ -113,6 +113,10 @@ void parseNew(); void parseAccessSpecifier(); bool parseEnum(); + void parseConcept(); + void parseRequires(); + void parseRequiresExpression(unsigned int OriginalLevel); + void parseConstraintExpression(unsigned int 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 diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -626,8 +626,16 @@ if (MacroBlock && FormatTok->is(tok::l_paren)) parseParens(); + if (FormatTok->is(tok::arrow)) { + // Following the } we can find a trailing return type arrow + // as part of an implicit conversion constraint. + nextToken(); + parseStructuralElement(); + } + if (MunchSemi && FormatTok->Tok.is(tok::semi)) nextToken(); + Line->Level = InitialLevel; if (PPStartHash == PPEndHash) { @@ -1262,6 +1270,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)) { @@ -2279,6 +2293,117 @@ 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 int 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)) { + if (Style.BraceWrapping.AfterFunction) + addUnwrappedLine(); + FormatTok->setType(TT_FunctionLBrace); + parseBlock(/*MustBeDeclaration=*/false); + addUnwrappedLine(); + } else { + parseConstraintExpression(OriginalLevel); + } +} + +void UnwrappedLineParser::parseConstraintExpression( + unsigned int 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, tok::ellipsis)) { + 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)) { + if (Style.BraceWrapping.AfterFunction) + addUnwrappedLine(); + FormatTok->setType(TT_FunctionLBrace); + parseBlock(/*MustBeDeclaration=*/false); + } + if (FormatTok->Tok.is(tok::semi)) { + // Eat any trailing semi. + nextToken(); + addUnwrappedLine(); + } + if (FormatTok->Tok.is(tok::colon)) { + return; + } + 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)) diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -14104,6 +14104,7 @@ CHECK_PARSE_BOOL(BinPackArguments); CHECK_PARSE_BOOL(BinPackParameters); CHECK_PARSE_BOOL(BreakAfterJavaFieldAnnotations); + CHECK_PARSE_BOOL(BreakBeforeConceptDeclarations); CHECK_PARSE_BOOL(BreakBeforeTernaryOperators); CHECK_PARSE_BOOL(BreakStringLiterals); CHECK_PARSE_BOOL(CompactNamespaces); @@ -14115,6 +14116,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); @@ -17288,6 +17290,277 @@ "}", 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.BreakBeforeBraces = FormatStyle::BS_Custom; + Style.BraceWrapping.AfterFunction = true; + Style.BraceWrapping.AfterClass = true; + Style.AlwaysBreakTemplateDeclarations = FormatStyle::BTDS_Yes; + Style.BreakConstructorInitializers = FormatStyle::BCIS_BeforeColon; + verifyFormat("void Foo () requires (std::copyable)\n" + "{\n" + " return\n" + "}\n", + Style); + + verifyFormat("void Foo () requires std::copyable\n" + "{\n" + " return\n" + "}\n", + Style); + + verifyFormat("template \n" + " requires (std::invocable...>)\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires std::invocable...>\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "class plane_with_very_very_very_long_name\n" + "{\n" + " constexpr plane_with_very_very_very_long_name () requires " + "std::copyable\n" + " : plane_with_very_very_very_long_name (1)\n" + " {\n" + " }\n" + "}\n", + Style); + + verifyFormat("template \n" + "class plane_with_long_name\n" + "{\n" + " constexpr plane_with_long_name () requires std::copyable\n" + " : plane_with_long_name (1)\n" + " {\n" + " }\n" + "}\n", + Style); + + Style.BreakBeforeConceptDeclarations = false; + verifyFormat("template concept Tree = true;", Style); + + Style.IndentRequires = false; + verifyFormat("template \n" + "requires (std::invocable...>) " + "struct constant;", + Style); +} } // namespace } // namespace format } // namespace clang