Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -2671,7 +2671,8 @@ **IndentRequires** (``Boolean``) :versionbadge:`clang-format 13` - Indent the requires clause in a template + Indent the requires clause in a template, this only applies if the + ``requires`` is at the beginning of a line. .. code-block:: c++ @@ -3236,7 +3237,7 @@ **QualifierAlignment** (``QualifierAlignmentStyle``) :versionbadge:`clang-format 14` Different ways to arrange specifiers and qualifiers (e.g. const/volatile). - .. warning:: + .. warning:: Setting ``QualifierAlignment`` to something other than `Leave`, COULD lead to incorrect code formatting due to incorrect decisions made due to @@ -3395,6 +3396,182 @@ /* second veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of * information */ +**RequiresClausePositionForClasses** (``RequiresClausePositionStyle``) :versionbadge:`clang-format 14` + The position of the ``requires`` clause for class templates. + + Possible values: + + * ``RCPS_OwnLine`` (in configuration: ``OwnLine``) + The clause always gets its own line. + + .. code-block:: c++ + + template + requires C + struct Foo {... + + template + requires C + void bar(T t) {... + + template + void baz(T t) + requires C {... + + * ``RCPS_ToPreceeding`` (in configuration: ``ToPreceeding``) + The clause tries to stick to the template declaration in case of class + templates or between template and function delcarations. In case of + after the function delcaration it tries to stick to this. + + .. code-block:: c++ + + template requires C + struct Foo {... + + template requires C + void bar(T t) {... + + template + void baz(T t) requires C {... + + * ``RCPS_ToFollowing`` (in configuration: ``ToFollowing``) + The clause tries to stick to the class respectively function + declaration. + + .. code-block:: c++ + + template + requires C struct Foo {... + + template + requires C void bar(T t) {... + + template + void baz(T t) requires C {... + + * ``RCPS_SingleLine`` (in configuration: ``SingleLine``) + The clause tries to get everything in the same line, if it doesn't fit + normal line breaking rules decide to which part it sticks. + + .. code-block:: c++ + + template requires C struct Foo {... + + template requires C void bar(T t) {... + + template void bar(T t) requires C {... + + * ``RCPS_TwoLines`` (in configuration: ``TwoLines``) + The clause always gets its own line, and the content of the clause go + into the next line with some indentation. + + .. code-block:: c++ + + template + requires + C + struct Foo {... + + template + requires + C + void bar(T t) {... + + template + void bar(T t) + requires + C {... + + + +**RequiresClausePositionForFunctions** (``RequiresClausePositionStyle``) :versionbadge:`clang-format 14` + The position of the ``requires`` clause for function templates. + + Possible values: + + * ``RCPS_OwnLine`` (in configuration: ``OwnLine``) + The clause always gets its own line. + + .. code-block:: c++ + + template + requires C + struct Foo {... + + template + requires C + void bar(T t) {... + + template + void baz(T t) + requires C {... + + * ``RCPS_ToPreceeding`` (in configuration: ``ToPreceeding``) + The clause tries to stick to the template declaration in case of class + templates or between template and function delcarations. In case of + after the function delcaration it tries to stick to this. + + .. code-block:: c++ + + template requires C + struct Foo {... + + template requires C + void bar(T t) {... + + template + void baz(T t) requires C {... + + * ``RCPS_ToFollowing`` (in configuration: ``ToFollowing``) + The clause tries to stick to the class respectively function + declaration. + + .. code-block:: c++ + + template + requires C struct Foo {... + + template + requires C void bar(T t) {... + + template + void baz(T t) requires C {... + + * ``RCPS_SingleLine`` (in configuration: ``SingleLine``) + The clause tries to get everything in the same line, if it doesn't fit + normal line breaking rules decide to which part it sticks. + + .. code-block:: c++ + + template requires C struct Foo {... + + template requires C void bar(T t) {... + + template void bar(T t) requires C {... + + * ``RCPS_TwoLines`` (in configuration: ``TwoLines``) + The clause always gets its own line, and the content of the clause go + into the next line with some indentation. + + .. code-block:: c++ + + template + requires + C + struct Foo {... + + template + requires + C + void bar(T t) {... + + template + void bar(T t) + requires + C {... + + + **ShortNamespaceLines** (``Unsigned``) :versionbadge:`clang-format 14` The maximal number of unwrapped lines that a short namespace spans. Defaults to 1. Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -267,6 +267,10 @@ - Improved C++20 Modules and Coroutines support. +- Reworked and improved handling of concepts and requires. Added the + ``RequiresClausePositionForClasses`` and + ``RequiresClausePositionForFunctions`` options as part of that. + libclang -------- Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -2501,7 +2501,8 @@ /// \version 12 IndentExternBlockStyle IndentExternBlock; - /// Indent the requires clause in a template + /// Indent the requires clause in a template, this only applies if the + /// ``requires`` is at the beginning of a line. /// \code /// true: /// template @@ -3049,6 +3050,91 @@ bool ReflowComments; // clang-format on + /// \brief The possible positions for the requires clause. The + /// ``IndentRequires`` option is only used if the ``requires`` is put on the + /// start of a line. + enum RequiresClausePositionStyle { + /// The clause always gets its own line. + /// \code + /// template + /// requires C + /// struct Foo {... + /// + /// template + /// requires C + /// void bar(T t) {... + /// + /// template + /// void baz(T t) + /// requires C {... + /// \endcode + RCPS_OwnLine, + /// The clause tries to stick to the template declaration in case of class + /// templates or between template and function delcarations. In case of + /// after the function delcaration it tries to stick to this. + /// \code + /// template requires C + /// struct Foo {... + /// + /// template requires C + /// void bar(T t) {... + /// + /// template + /// void baz(T t) requires C {... + /// \endcode + RCPS_ToPreceeding, + /// The clause tries to stick to the class respectively function + /// declaration. + /// \code + /// template + /// requires C struct Foo {... + /// + /// template + /// requires C void bar(T t) {... + /// + /// template + /// void baz(T t) requires C {... + /// \endcode + RCPS_ToFollowing, + /// The clause tries to get everything in the same line, if it doesn't fit + /// normal line breaking rules decide to which part it sticks. + /// \code + /// template requires C struct Foo {... + /// + /// template requires C void bar(T t) {... + /// + /// template void bar(T t) requires C {... + /// \endcode + RCPS_SingleLine, + /// The clause always gets its own line, and the content of the clause go + /// into the next line with some indentation. + /// \code + /// template + /// requires + /// C + /// struct Foo {... + /// + /// template + /// requires + /// C + /// void bar(T t) {... + /// + /// template + /// void bar(T t) + /// requires + /// C {... + /// \endcode + RCPS_TwoLines, + }; + + /// \brief The position of the ``requires`` clause for class templates. + /// \version 14 + RequiresClausePositionStyle RequiresClausePositionForClasses; + + /// \brief The position of the ``requires`` clause for function templates. + /// \version 14 + RequiresClausePositionStyle RequiresClausePositionForFunctions; + /// The maximal number of unwrapped lines that a short namespace spans. /// Defaults to 1. /// @@ -3790,6 +3876,10 @@ QualifierOrder == R.QualifierOrder && RawStringFormats == R.RawStringFormats && ReferenceAlignment == R.ReferenceAlignment && + RequiresClausePositionForClasses == + R.RequiresClausePositionForClasses && + RequiresClausePositionForFunctions == + R.RequiresClausePositionForFunctions && ShortNamespaceLines == R.ShortNamespaceLines && SortIncludes == R.SortIncludes && SortJavaStaticImport == R.SortJavaStaticImport && Index: clang/lib/Format/ContinuationIndenter.cpp =================================================================== --- clang/lib/Format/ContinuationIndenter.cpp +++ clang/lib/Format/ContinuationIndenter.cpp @@ -800,6 +800,18 @@ State.Column = getNewLineColumn(State); + if (Current.is(TT_RequiresClauseForClasses)) { + State.Stack.back().Indent = State.Column; + if (Style.RequiresClausePositionForClasses == FormatStyle::RCPS_TwoLines) + State.Stack.back().Indent += Current.ColumnWidth; + } + + if (Current.is(TT_RequiresClauseForFunctions)) { + State.Stack.back().Indent = State.Column; + if (Style.RequiresClausePositionForFunctions == FormatStyle::RCPS_TwoLines) + State.Stack.back().Indent += Current.ColumnWidth; + } + // Add Penalty proportional to amount of whitespace away from FirstColumn // This tends to penalize several lines that are far-right indented, // and prefers a line-break prior to such a block, e.g: @@ -1086,6 +1098,10 @@ } if (Previous.is(tok::comma) && State.Stack.back().VariablePos != 0) return State.Stack.back().VariablePos; + if (Current.isOneOf(TT_RequiresClauseForClasses, + TT_RequiresClauseForFunctions) && + Style.IndentRequires) + return State.Stack.back().Indent + Style.IndentWidth; if ((PreviousNonComment && (PreviousNonComment->ClosesTemplateDeclaration || PreviousNonComment->isOneOf( @@ -1343,11 +1359,13 @@ const FormatToken *Previous = Current.getPreviousNonComment(); // Don't add extra indentation for the first fake parenthesis after - // 'return', assignments or opening <({[. The indentation for these cases - // is special cased. + // 'return', assignments, opening <({[, or requires clauses. The indentation + // for these cases is special cased. bool SkipFirstExtraIndent = (Previous && (Previous->opensScope() || - Previous->isOneOf(tok::semi, tok::kw_return) || + Previous->isOneOf(tok::semi, tok::kw_return, + TT_RequiresClauseForClasses, + TT_RequiresClauseForFunctions) || (Previous->getPrecedence() == prec::Assignment && Style.AlignOperands != FormatStyle::OAS_DontAlign) || Previous->is(TT_ObjCMethodExpr))); @@ -1383,7 +1401,8 @@ if (Previous && (Previous->getPrecedence() == prec::Assignment || - Previous->is(tok::kw_return) || + Previous->isOneOf(tok::kw_return, TT_RequiresClauseForClasses, + TT_RequiresClauseForFunctions) || (PrecedenceLevel == prec::Conditional && Previous->is(tok::question) && Previous->is(TT_ConditionalExpr))) && !Newline) { Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ clang/lib/Format/Format.cpp @@ -450,6 +450,18 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, + FormatStyle::RequiresClausePositionStyle &Value) { + IO.enumCase(Value, "OwnLine", FormatStyle::RCPS_OwnLine); + IO.enumCase(Value, "ToPreceeding", FormatStyle::RCPS_ToPreceeding); + IO.enumCase(Value, "ToFollowing", FormatStyle::RCPS_ToFollowing); + IO.enumCase(Value, "SingleLine", FormatStyle::RCPS_SingleLine); + IO.enumCase(Value, "TwoLines", FormatStyle::RCPS_TwoLines); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::SpaceBeforeParensStyle &Value) { @@ -769,6 +781,10 @@ IO.mapOptional("RawStringFormats", Style.RawStringFormats); IO.mapOptional("ReferenceAlignment", Style.ReferenceAlignment); IO.mapOptional("ReflowComments", Style.ReflowComments); + IO.mapOptional("RequiresClausePositionForClasses", + Style.RequiresClausePositionForClasses); + IO.mapOptional("RequiresClausePositionForFunctions", + Style.RequiresClausePositionForFunctions); IO.mapOptional("ShortNamespaceLines", Style.ShortNamespaceLines); IO.mapOptional("SortIncludes", Style.SortIncludes); IO.mapOptional("SortJavaStaticImport", Style.SortJavaStaticImport); @@ -1193,6 +1209,9 @@ LLVMStyle.ObjCSpaceBeforeProtocolList = true; LLVMStyle.PointerAlignment = FormatStyle::PAS_Right; LLVMStyle.ReferenceAlignment = FormatStyle::RAS_Pointer; + // This is open for discussions! When will LLVM adapt C++20? + LLVMStyle.RequiresClausePositionForClasses = FormatStyle::RCPS_OwnLine; + LLVMStyle.RequiresClausePositionForFunctions = FormatStyle::RCPS_OwnLine; LLVMStyle.ShortNamespaceLines = 1; LLVMStyle.SpacesBeforeTrailingComments = 1; LLVMStyle.Standard = FormatStyle::LS_Latest; Index: clang/lib/Format/FormatToken.h =================================================================== --- clang/lib/Format/FormatToken.h +++ clang/lib/Format/FormatToken.h @@ -35,12 +35,13 @@ TYPE(BinaryOperator) \ TYPE(BitFieldColon) \ TYPE(BlockComment) \ + TYPE(BracedListLBrace) \ TYPE(CastRParen) \ + TYPE(CompoundRequirementLBrace) \ TYPE(ConditionalExpr) \ TYPE(ConflictAlternative) \ TYPE(ConflictEnd) \ TYPE(ConflictStart) \ - TYPE(ConstraintJunctions) \ TYPE(CtorInitializerColon) \ TYPE(CtorInitializerComma) \ TYPE(DesignatedInitializerLSquare) \ @@ -96,6 +97,8 @@ TYPE(PureVirtualSpecifier) \ TYPE(RangeBasedForLoopColon) \ TYPE(RegexLiteral) \ + TYPE(RequiresClauseForClasses) \ + TYPE(RequiresClauseForFunctions) \ TYPE(SelectorName) \ TYPE(StartOfName) \ TYPE(StatementAttributeLikeMacro) \ Index: clang/lib/Format/TokenAnnotator.cpp =================================================================== --- clang/lib/Format/TokenAnnotator.cpp +++ clang/lib/Format/TokenAnnotator.cpp @@ -251,7 +251,9 @@ Contexts.back().IsExpression = false; } else if (Left->Previous && (Left->Previous->isOneOf(tok::kw_static_assert, tok::kw_while, - tok::l_paren, tok::comma) || + tok::l_paren, tok::comma, + TT_RequiresClauseForClasses, + TT_RequiresClauseForFunctions) || Left->Previous->isIf() || Left->Previous->is(TT_BinaryOperator))) { // static_assert, if and while usually contain expressions. @@ -1162,6 +1164,10 @@ parseCSharpGenericTypeConstraint(); } break; + case tok::arrow: + if (Tok->Previous && Tok->Previous->is(tok::kw_noexcept)) + Tok->setType(TT_TrailingReturnArrow); + break; default: break; } @@ -1425,8 +1431,10 @@ TT_ImplicitStringLiteral, TT_InlineASMBrace, TT_FatArrow, TT_LambdaArrow, TT_NamespaceMacro, TT_OverloadedOperator, TT_RegexLiteral, TT_TemplateString, TT_ObjCStringLiteral, - TT_UntouchableMacroFunc, TT_ConstraintJunctions, - TT_StatementAttributeLikeMacro)) + TT_UntouchableMacroFunc, TT_StatementAttributeLikeMacro, + TT_RequiresClauseForClasses, TT_RequiresClauseForFunctions, + TT_BinaryOperator, TT_CompoundRequirementLBrace, + TT_BracedListLBrace)) CurrentToken->setType(TT_Unknown); CurrentToken->Role.reset(); CurrentToken->MatchingParen = nullptr; @@ -2136,7 +2144,7 @@ public: ExpressionParser(const FormatStyle &Style, const AdditionalKeywords &Keywords, AnnotatedLine &Line) - : Style(Style), Keywords(Keywords), Current(Line.First) {} + : Style(Style), Keywords(Keywords), Line(Line), Current(Line.First) {} /// Parse expressions with the given operator precedence. void parse(int Precedence = 0) { @@ -2192,7 +2200,11 @@ } // Consume scopes: (), [], <> and {} - if (Current->opensScope()) { + // In addition to that we handle require clauses as scope, so that the + // constraints in that are correctly indented. + if (Current->opensScope() || + Current->isOneOf(TT_RequiresClauseForClasses, + TT_RequiresClauseForFunctions)) { // In fragment of a JavaScript template string can look like '}..${' and // thus close a scope and open a new one at the same time. while (Current && (!Current->closesScope() || Current->opensScope())) { @@ -2214,12 +2226,25 @@ } if (LatestOperator && (Current || Precedence > 0)) { - // LatestOperator->LastOperator = true; + // The requires clauses do not neccessarily end in a semicolon or a brace, + // but just go over to struct/class or a function declaration, we need to + // intervene so that the fake right paren is inserted correctly. + auto End = (!Current && Start->Previous && + Start->Previous->isOneOf(TT_RequiresClauseForClasses, + TT_RequiresClauseForFunctions)) + ? [this](){ + auto Ret = Line.Last; + while (Ret->is(tok::comment) && Ret->Previous) + Ret = Ret->Previous; + return Ret; + }() + : nullptr; + if (Precedence == PrecedenceArrowAndPeriod) { // Call expressions don't have a binary operator precedence. - addFakeParenthesis(Start, prec::Unknown); + addFakeParenthesis(Start, prec::Unknown, End); } else { - addFakeParenthesis(Start, prec::Level(Precedence)); + addFakeParenthesis(Start, prec::Level(Precedence), End); } } } @@ -2270,17 +2295,17 @@ return -1; } - void addFakeParenthesis(FormatToken *Start, prec::Level Precedence) { + void addFakeParenthesis(FormatToken *Start, prec::Level Precedence, + FormatToken *End = nullptr) { Start->FakeLParens.push_back(Precedence); if (Precedence > prec::Unknown) Start->StartsBinaryExpression = true; - if (Current) { - FormatToken *Previous = Current->Previous; - while (Previous->is(tok::comment) && Previous->Previous) - Previous = Previous->Previous; - ++Previous->FakeRParens; + if (!End && Current) + End = Current->getPreviousNonComment(); + if (End) { + ++End->FakeRParens; if (Precedence > prec::Unknown) - Previous->EndsBinaryExpression = true; + End->EndsBinaryExpression = true; } } @@ -2326,6 +2351,7 @@ const FormatStyle &Style; const AdditionalKeywords &Keywords; + const AnnotatedLine &Line; FormatToken *Current; }; @@ -2959,9 +2985,6 @@ if (Left.isOneOf(tok::kw_co_await, tok::kw_co_yield, tok::kw_co_return) && Right.isNot(tok::semi)) return true; - // 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) || @@ -3832,6 +3855,28 @@ // concept ... if (Right.is(tok::kw_concept)) return Style.BreakBeforeConceptDeclarations; + + if (Right.is(TT_RequiresClauseForClasses)) { + switch (Style.RequiresClausePositionForClasses) { + case FormatStyle::RCPS_OwnLine: + case FormatStyle::RCPS_ToFollowing: + case FormatStyle::RCPS_TwoLines: + return true; + default: + break; + } + } + + if (Right.is(TT_RequiresClauseForFunctions)) { + switch (Style.RequiresClausePositionForFunctions) { + case FormatStyle::RCPS_OwnLine: + case FormatStyle::RCPS_ToFollowing: + case FormatStyle::RCPS_TwoLines: + return true; + default: + break; + } + } return (Style.AlwaysBreakTemplateDeclarations == FormatStyle::BTDS_Yes); } if (Style.PackConstructorInitializers == FormatStyle::PCIS_Never) { @@ -4043,6 +4088,13 @@ return true; } + if (Left.is(TT_RequiresClauseForClasses)) + return Style.RequiresClausePositionForClasses == FormatStyle::RCPS_TwoLines; + + if (Left.is(TT_RequiresClauseForFunctions)) + return Style.RequiresClausePositionForFunctions == + FormatStyle::RCPS_TwoLines; + return false; } Index: clang/lib/Format/UnwrappedLineParser.h =================================================================== --- clang/lib/Format/UnwrappedLineParser.h +++ clang/lib/Format/UnwrappedLineParser.h @@ -83,10 +83,12 @@ private: void reset(); void parseFile(); - void parseLevel(bool HasOpeningBrace); + void parseLevel(bool HasOpeningBrace, bool CanContainBracedList, + TokenType NextLBracesType = TT_Unknown); void parseBlock(bool MustBeDeclaration = false, unsigned AddLevels = 1u, - bool MunchSemi = true, - bool UnindentWhitesmithsBraces = false); + bool MunchSemi = true, bool UnindentWhitesmithsBraces = false, + bool CanContainBracedList = true, + TokenType NextLBracesType = TT_Unknown); void parseChildBlock(); void parsePPDirective(); void parsePPDefine(); @@ -96,11 +98,12 @@ void parsePPEndIf(); void parsePPUnknown(); void readTokenWithJavaScriptASI(); - void parseStructuralElement(bool IsTopLevel = false); + void parseStructuralElement(bool IsTopLevel = false, + TokenType NextLBracesType = TT_Unknown); bool tryToParseBracedList(); bool parseBracedList(bool ContinueOnSemicolons = false, bool IsEnum = false, tok::TokenKind ClosingBraceKind = tok::r_brace); - void parseParens(); + void parseParens(TokenType AmpAmpTokenType = TT_Unknown); void parseSquare(bool LambdaIntroducer = false); void parseIfThenElse(); void parseTryCatch(); @@ -116,9 +119,9 @@ bool parseEnum(); bool parseStructLike(); void parseConcept(); - void parseRequires(); - void parseRequiresExpression(unsigned int OriginalLevel); - void parseConstraintExpression(unsigned int OriginalLevel); + void parseRequiresClause(); + void parseRequiresExpression(); + void parseConstraintExpression(); 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 @@ -373,7 +373,7 @@ if (Style.Language == FormatStyle::LK_TextProto) parseBracedList(); else - parseLevel(/*HasOpeningBrace=*/false); + parseLevel(/*HasOpeningBrace=*/false, /*CanContainBracedList=*/true); // Make sure to format the remaining tokens. // // LK_TextProto is special since its top-level is parsed as the body of a @@ -432,7 +432,17 @@ } while (!eof()); } -void UnwrappedLineParser::parseLevel(bool HasOpeningBrace) { +/// \brief Parses a level, that is ???. +/// \param HasOpeningBrace If that level is started by an opening brace. +/// \param CanContainBracedList If the content can contain (at any level) a +/// braced list. +/// \param NextLBracesType The type for left brace found in this level. +void UnwrappedLineParser::parseLevel(bool HasOpeningBrace, + bool CanContainBracedList, + TokenType NextLBracesType) { + auto NextLevelLBracesType = NextLBracesType == TT_CompoundRequirementLBrace + ? TT_BracedListLBrace + : TT_Unknown; bool SwitchLabelEncountered = false; do { tok::TokenKind kind = FormatTok->Tok.getKind(); @@ -448,11 +458,15 @@ addUnwrappedLine(); break; case tok::l_brace: - // FIXME: Add parameter whether this can happen - if this happens, we must - // be in a non-declaration context. - if (!FormatTok->is(TT_MacroBlockBegin) && tryToParseBracedList()) + if (NextLBracesType != TT_Unknown) + FormatTok->setType(NextLBracesType); + if (CanContainBracedList && !FormatTok->is(TT_MacroBlockBegin) && + tryToParseBracedList()) continue; - parseBlock(); + parseBlock(/*MustBeDeclaration=*/false, /*AddLevels=*/1u, + /*MunchSemi=*/true, /*UnindentWhitesmithBraces=*/false, + CanContainBracedList, + /*NextLBracesType=*/NextLBracesType); addUnwrappedLine(); break; case tok::r_brace: @@ -498,7 +512,8 @@ } LLVM_FALLTHROUGH; default: - parseStructuralElement(!HasOpeningBrace); + parseStructuralElement(/*IsTopLevel=*/!HasOpeningBrace, + /*NextLBracesType=*/NextLevelLBracesType); break; } } while (!eof()); @@ -572,27 +587,47 @@ bool NextIsObjCMethod = NextTok->isOneOf(tok::plus, tok::minus) && NextTok->OriginalColumn == 0; + // Try to detect a braced list. Note that regardless how we mark inner + // braces here, we will overwrite the BlockKind later if we parse a + // braced list (where all blocks inside are by default braced lists), + // or when we explicitly detect blocks (for example while parsing + // lambdas). + + // If we already marked the opening brace as braced list, the closing + // must also be part of it. + ProbablyBracedList = LBraceStack.back()->is(TT_BracedListLBrace); + + ProbablyBracedList = ProbablyBracedList || + (Style.Language == FormatStyle::LK_JavaScript && + NextTok->isOneOf(Keywords.kw_of, Keywords.kw_in, + Keywords.kw_as)); + ProbablyBracedList = + ProbablyBracedList || + (Style.isCpp() && NextTok->is(tok::l_paren)); + // If there is a comma, semicolon or right paren after the closing - // brace, we assume this is a braced initializer list. Note that - // regardless how we mark inner braces here, we will overwrite the - // BlockKind later if we parse a braced list (where all blocks - // inside are by default braced lists), or when we explicitly detect - // blocks (for example while parsing lambdas). + // brace, we assume this is a braced initializer list. // FIXME: Some of these do not apply to JS, e.g. "} {" can never be a // braced list in JS. ProbablyBracedList = - (Style.Language == FormatStyle::LK_JavaScript && - NextTok->isOneOf(Keywords.kw_of, Keywords.kw_in, - Keywords.kw_as)) || - (Style.isCpp() && NextTok->is(tok::l_paren)) || + ProbablyBracedList || NextTok->isOneOf(tok::comma, tok::period, tok::colon, tok::r_paren, tok::r_square, tok::l_brace, - tok::ellipsis) || + tok::ellipsis); + + ProbablyBracedList = + ProbablyBracedList || (NextTok->is(tok::identifier) && - !PrevTok->isOneOf(tok::semi, tok::r_brace, tok::l_brace)) || - (NextTok->is(tok::semi) && - (!ExpectClassBody || LBraceStack.size() != 1)) || + !PrevTok->isOneOf(tok::semi, tok::r_brace, tok::l_brace)); + + ProbablyBracedList = ProbablyBracedList || + (NextTok->is(tok::semi) && + (!ExpectClassBody || LBraceStack.size() != 1)); + + ProbablyBracedList = + ProbablyBracedList || (NextTok->isBinaryOperator() && !NextIsObjCMethod); + if (!Style.isCSharp() && NextTok->is(tok::l_square)) { // We can have an array subscript after a braced init // list, but C++11 attributes are expected after blocks. @@ -659,7 +694,9 @@ void UnwrappedLineParser::parseBlock(bool MustBeDeclaration, unsigned AddLevels, bool MunchSemi, - bool UnindentWhitesmithsBraces) { + bool UnindentWhitesmithsBraces, + bool CanContainBracedList, + TokenType NextLBracesType) { assert(FormatTok->isOneOf(tok::l_brace, TT_MacroBlockBegin) && "'{' or macro block token expected"); const bool MacroBlock = FormatTok->is(TT_MacroBlockBegin); @@ -696,7 +733,7 @@ MustBeDeclaration); if (AddLevels > 0u && Style.BreakBeforeBraces != FormatStyle::BS_Whitesmiths) Line->Level += AddLevels; - parseLevel(/*HasOpeningBrace=*/true); + parseLevel(/*HasOpeningBrace=*/true, CanContainBracedList, NextLBracesType); if (eof()) return; @@ -716,8 +753,12 @@ if (MacroBlock && FormatTok->is(tok::l_paren)) parseParens(); + if (FormatTok->is(tok::kw_noexcept)) + // A noexcept in a requires expression. + nextToken(); + if (FormatTok->is(tok::arrow)) { - // Following the } we can find a trailing return type arrow + // Following the } or noexcept we can find a trailing return type arrow // as part of an implicit conversion constraint. nextToken(); parseStructuralElement(); @@ -797,7 +838,7 @@ ScopedDeclarationState DeclarationState(*Line, DeclarationScopeStack, /*MustBeDeclaration=*/false); Line->Level += SkipIndent ? 0 : 1; - parseLevel(/*HasOpeningBrace=*/true); + parseLevel(/*HasOpeningBrace=*/true, /*CanContainBracedList=*/true); flushComments(isOnNewLine(*FormatTok)); Line->Level -= SkipIndent ? 0 : 1; } @@ -1189,7 +1230,8 @@ return addUnwrappedLine(); } -void UnwrappedLineParser::parseStructuralElement(bool IsTopLevel) { +void UnwrappedLineParser::parseStructuralElement(bool IsTopLevel, + TokenType NextLBracesType) { if (Style.Language == FormatStyle::LK_TableGen && FormatTok->is(tok::pp_include)) { nextToken(); @@ -1439,7 +1481,7 @@ parseConcept(); break; case tok::kw_requires: - parseRequires(); + parseRequiresClause(); break; case tok::kw_enum: // Ignore if this is part of "template setType(NextLBracesType); if (!tryToParsePropertyAccessor() && !tryToParseBracedList()) { // A block outside of parentheses must be the last part of a // structural element. @@ -2063,7 +2107,10 @@ return false; } -void UnwrappedLineParser::parseParens() { +/// \brief Parses a parens pair. +/// \param AmpAmpTokenType If different than TT_Unknown sets this type for all +/// double ampersands. This only counts for the current parens scope. +void UnwrappedLineParser::parseParens(TokenType AmpAmpTokenType) { assert(FormatTok->Tok.is(tok::l_paren) && "'(' expected."); nextToken(); do { @@ -2113,6 +2160,13 @@ else nextToken(); break; + case tok::kw_requires: + parseRequiresExpression(); + break; + case tok::ampamp: + if (AmpAmpTokenType != TT_Unknown) + FormatTok->setType(AmpAmpTokenType); + LLVM_FALLTHROUGH; default: nextToken(); break; @@ -2522,6 +2576,11 @@ addUnwrappedLine(); } +/// \brief Parses a concept definition. +/// \pre The current token has to be the concept keyword. +/// +/// Returns if it either has finished parsing the concept, or it detects, that +/// the concept definition is incorrect. void UnwrappedLineParser::parseConcept() { assert(FormatTok->Tok.is(tok::kw_concept) && "'concept' expected"); nextToken(); @@ -2531,107 +2590,168 @@ if (!FormatTok->Tok.is(tok::equal)) return; nextToken(); - if (FormatTok->Tok.is(tok::kw_requires)) { + parseConstraintExpression(); + if (FormatTok->Tok.is(tok::semi)) nextToken(); - parseRequiresExpression(Line->Level); - } else { - parseConstraintExpression(Line->Level); + addUnwrappedLine(); +} + +/// \brief Parses a requires clause. +/// \pre The current token needs to be the requires keyword. +/// \sa parseRequiresExpression +/// +/// Returns if it either has finished parsing the clause, or it detects, that +/// the clause is incorrect. +void UnwrappedLineParser::parseRequiresClause() { + assert(FormatTok->Tok.is(tok::kw_requires) && "'requires' expected"); + assert(FormatTok->getType() == TT_Unknown); + + auto RequiresToken = FormatTok; + + nextToken(); + parseConstraintExpression(); + + auto setTypeAndMaybeAddLine = + [this, + RequiresToken](TokenType Type, + FormatStyle::RequiresClausePositionStyle PositionStyle) { + RequiresToken->setType(Type); + + switch (PositionStyle) { + case FormatStyle::RCPS_OwnLine: + case FormatStyle::RCPS_ToPreceeding: + case FormatStyle::RCPS_TwoLines: + addUnwrappedLine(); + break; + default: + break; + } + }; + + switch (FormatTok->Tok.getKind()) { + case tok::kw_class: + case tok::kw_struct: + case tok::kw_union: + setTypeAndMaybeAddLine(TT_RequiresClauseForClasses, + Style.RequiresClausePositionForClasses); + break; + + case tok::semi: + if (RequiresToken->Previous) + // We are a trailing requires clause for a function. + RequiresToken->setType(TT_RequiresClauseForFunctions); + // Else: We are a requires clause within a requires expression. + // The Semi is eaten by the caller, he also adds the unwrapped line. + break; + + default: + setTypeAndMaybeAddLine(TT_RequiresClauseForFunctions, + Style.RequiresClausePositionForFunctions); + break; } } -void UnwrappedLineParser::parseRequiresExpression(unsigned int OriginalLevel) { - // requires (R range) - if (FormatTok->Tok.is(tok::l_paren)) { +/// \brief Parses a requires expression. +/// \pre The current token needs to be the requires keyword. +/// \sa parseRequiresClause +/// +/// Returns if it either has finished parsing the expression, or it detects, +/// that the expression is incorrect. +void UnwrappedLineParser::parseRequiresExpression() { + assert(FormatTok->Tok.is(tok::kw_requires) && "'requires' expected"); + assert(FormatTok->getType() == TT_Unknown); + nextToken(); + + if (FormatTok->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(); - addUnwrappedLine(); - } else { - parseConstraintExpression(OriginalLevel); - } + if (FormatTok->is(tok::l_brace)) + parseBlock(/*MustBeDeclaration=*/false, /*AddLevels=*/1u, + /*MunchSemi=*/false, /*UnindentWhitesmithsBraces=*/false, + /*CanContainBracedList=*/false, + /*NextLBracesType=*/TT_CompoundRequirementLBrace); } -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)) { +/// \brief Parses a constraint expression. +/// +/// This is either the definition of a concept, or the body of a requires +/// clause. It returns, when the parsing is complete, or the expression is +/// incorrect. +void UnwrappedLineParser::parseConstraintExpression() { + do { + switch (FormatTok->Tok.getKind()) { + case tok::kw_requires: + parseRequiresExpression(); + break; + + case tok::l_paren: + parseParens(/*AmpAmpTokenType=*/TT_BinaryOperator); + break; + + case tok::l_square: + if (!tryToParseLambda()) + return; + break; + + case tok::identifier: + // Read identifier with optional template declaration. + nextToken(); + if (FormatTok->Tok.is(tok::less)) parseBracedList(/*ContinueOnSemicolons=*/false, /*IsEnum=*/false, /*ClosingBraceKind=*/tok::greater); - continue; - } + break; + + case tok::semi: + case tok::kw_class: + case tok::kw_struct: + case tok::kw_union: + return; + + case tok::l_brace: + // Potential function body. + return; + + case tok::ampamp: + FormatTok->setType(TT_BinaryOperator); nextToken(); - } - if (FormatTok->Tok.is(tok::kw_requires)) { - parseRequiresExpression(OriginalLevel); - } - if (FormatTok->Tok.is(tok::less)) { - parseBracedList(/*ContinueOnSemicolons=*/false, /*IsEnum=*/false, - /*ClosingBraceKind=*/tok::greater); - } + break; - 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(); - } - if (FormatTok->Tok.is(tok::semi)) { - // Eat any trailing semi. + default: + // Just eat them. 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(); - } + } while (!eof()); } -void UnwrappedLineParser::parseRequires() { +/*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++; + bool IsClause = false; + if (FormatTok->Previous) { + if (FormatTok->Previous->is(tok::greater)) { + FormatTok->setType(TT_RequiresClause); + IsClause = true; + addUnwrappedLine(); + if (Style.IndentRequires) { + Line->Level++; + } + } else if (FormatTok->Previous->is(tok::r_paren)) { + FormatTok->setType(TT_RequiresClause); + IsClause = true; } } + if (!IsClause) + FormatTok->setType(TT_RequiresExpression); + nextToken(); - parseRequiresExpression(OriginalLevel); -} + if (IsClause) + parseRequiresClause(OriginalLevel); + else + parseRequiresExpression(OriginalLevel); +}*/ bool UnwrappedLineParser::parseEnum() { // Won't be 'enum' for NS_ENUMs. @@ -2830,7 +2950,7 @@ } // Parse the class body after the enum's ";" if any. - parseLevel(/*HasOpeningBrace=*/true); + parseLevel(/*HasOpeningBrace=*/true, /*CanContainBracedList=*/true); nextToken(); --Line->Level; addUnwrappedLine(); Index: clang/unittests/Format/FormatTest.cpp =================================================================== --- clang/unittests/Format/FormatTest.cpp +++ clang/unittests/Format/FormatTest.cpp @@ -19143,6 +19143,30 @@ // For backward compatibility: CHECK_PARSE("SpacesInAngles: false", SpacesInAngles, FormatStyle::SIAS_Never); CHECK_PARSE("SpacesInAngles: true", SpacesInAngles, FormatStyle::SIAS_Always); + + CHECK_PARSE("RequiresClausePositionForClasses: ToPreceeding", + RequiresClausePositionForClasses, FormatStyle::RCPS_ToPreceeding); + CHECK_PARSE("RequiresClausePositionForClasses: ToFollowing", + RequiresClausePositionForClasses, FormatStyle::RCPS_ToFollowing); + CHECK_PARSE("RequiresClausePositionForClasses: SingleLine", + RequiresClausePositionForClasses, FormatStyle::RCPS_SingleLine); + CHECK_PARSE("RequiresClausePositionForClasses: TwoLines", + RequiresClausePositionForClasses, FormatStyle::RCPS_TwoLines); + CHECK_PARSE("RequiresClausePositionForClasses: OwnLine", + RequiresClausePositionForClasses, FormatStyle::RCPS_OwnLine); + + CHECK_PARSE("RequiresClausePositionForFunctions: ToPreceeding", + RequiresClausePositionForFunctions, + FormatStyle::RCPS_ToPreceeding); + CHECK_PARSE("RequiresClausePositionForFunctions: ToFollowing", + RequiresClausePositionForFunctions, + FormatStyle::RCPS_ToFollowing); + CHECK_PARSE("RequiresClausePositionForFunctions: SingleLine", + RequiresClausePositionForFunctions, FormatStyle::RCPS_SingleLine); + CHECK_PARSE("RequiresClausePositionForFunctions: TwoLines", + RequiresClausePositionForFunctions, FormatStyle::RCPS_TwoLines); + CHECK_PARSE("RequiresClausePositionForFunctions: OwnLine", + RequiresClausePositionForFunctions, FormatStyle::RCPS_OwnLine); } TEST_F(FormatTest, ParsesConfigurationWithLanguages) { @@ -22257,43 +22281,234 @@ Style); } -TEST_F(FormatTest, ConceptsAndRequires) { - FormatStyle Style = getLLVMStyle(); - Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; +TEST_F(FormatTest, Concepts) { + verifyFormat("template \n" + "concept True = true;"); + + verifyFormat("template \n" + "concept C = ((false || foo()) && C2) || " + "(std::trait::value && Baz) ||\n " + " sizeof(T) >= 6;"); + + verifyFormat("template \n" + "concept DelayedCheck = true && requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept DelayedCheck = false || requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept DelayedCheck = !!false || requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat( + "template \n" + "concept DelayedCheck = static_cast(0) || requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept DelayedCheck = bool(0) || requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept DelayedCheck = (bool)(0) || requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept DelayedCheck = (bool)0 || requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept Size = sizeof(T) >= 5 && requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat( + "template \n" + "concept Size = 2 < 5 && 2 <= 5 && 8 >= 5 && 8 > 5 && requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8 && !(4 < 3);"); + + verifyFormat("template \n" + "concept TrueOrNot = IsAlwaysTrue || IsNeverTrue;"); + + verifyFormat("template \n" + "concept C = foo();"); + + verifyFormat("template \n" + "concept C = foo(T());"); + + verifyFormat("template \n" + "concept C = foo(T{});"); + + verifyFormat("template \n" + "concept Size = V::Value > 5;"); + + verifyFormat("template \n" + "concept True = S::Value;"); + + verifyFormat("template \n" + "concept C = []() { return true; }() && requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept C = [] { return true; }() && requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept C = [] -> bool { return true; }() && requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat( + "template \n" + "concept C = decltype([]() { return std::true_type{}; }())::value &&\n" + " requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept C = decltype([]() { return std::true_type{}; " + "}())::value && requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;", + getLLVMStyleWithColumns(120)); + + verifyFormat("template \n" + "concept C = decltype([]() -> std::true_type { return {}; " + "}())::value &&\n" + " requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;"); + + verifyFormat("template \n" + "concept C = true;\n" + "Foo Bar;"); 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); + " { a == b } -> std::same_as;\n" + "};"); + verifyFormat("template \n" "concept EqualityComparable = requires(T a, T b) {\n" - " { a == b } -> bool;\n" - " { a != b } -> bool;\n" - "};", - Style); + " { a == b } -> std::same_as;\n" + " { a != b } -> std::same_as;\n" + "};"); + verifyFormat("template \n" - "concept EqualityComparable = requires(T a, T b) {\n" - " { a == b } -> bool;\n" - " { a != b } -> bool;\n" - "};", - Style); + "concept WeakEqualityComparable = requires(T a, T b) {\n" + " { a == b };\n" + " { a != b };\n" + "};"); - verifyFormat("template \n" - "requires Iterator\n" - "void sort(It begin, It end) {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept HasSizeT = requires {\n" + " typename T::size_t;\n" + "};"); verifyFormat("template \n" - "concept Large = sizeof(T) > 10;", - Style); + "concept Semiregular = DefaultConstructible && " + "CopyConstructible &&\n" + " CopyAssignable && requires(T a, " + "std::size_t n) {\n" + " requires Same;\n" + " { a.~T() } noexcept;\n" + " requires Same;\n" + " requires Same;\n" + " { delete new T; };\n" + " { delete new T[n]; };\n" + "};"); + + verifyFormat("template \n" + "concept Semiregular = requires(T a, std::size_t n) {\n" + " requires Same;\n" + " { a.~T() } noexcept;\n" + " requires Same;\n" + " requires Same;\n" + " { delete new T; };\n" + " { delete new T[n]; };\n" + " { new T } -> std::same_as;\n" + "} && DefaultConstructible && CopyConstructible && " + "CopyAssignable;"); + + verifyFormat("template \n" + "concept Semiregular = DefaultConstructible requires(T a, " + "std::size_t n) {\n" + " requires Same;\n" + " { a.~T() } noexcept;\n" + " requires Same;\n" + " requires Same;\n" + " { delete new T; };\n" + " { delete new T[n]; };\n" + "} && CopyConstructible && CopyAssignable;"); + + verifyFormat("template \n" + "concept Two = requires(T t) {\n" + " { t.foo() } -> std::same_as;\n" + "} && requires(T &&t) {\n" + " { t.foo() } -> std::same_as;\n" + "}"); + + verifyFormat("template \n" + "concept C = requires(T x) {\n" + " { *x } -> std::convertible_to;\n" + " { x + 1 } noexcept -> std::same_as;\n" + " { x * 1 } -> std::convertible_to;\n" + "};"); + + verifyFormat("template \n" + "concept Swappable = requires(T &&t, U &&u) {\n" + " swap(std::forward(t), std::forward(u));\n" + " swap(std::forward(u), std::forward(t));\n" + "};"); + + verifyFormat("template \n" + "concept Common = requires(T &&t, U &&u) {\n" + " typename CommonType;\n" + " { CommonType(std::forward(t)) };\n" + "};"); + + verifyFormat("template \n" + "concept Common = requires(T &&t, U &&u) {\n" + " typename CommonType;\n" + " { CommonType{std::forward(t)} };\n" + "};"); + + verifyFormat("template \n" + "concept C = requires(T t) {\n" + " requires Bar && Foo;\n" + " requires((trait && Baz) || (T2 && Foo));\n" + "};"); + + verifyFormat("template \n" + "concept HasFoo = requires(T t) {\n" + " { t.foo() };\n" + " t.foo();\n" + "};\n" + "template \n" + "concept HasBar = requires(T t) {\n" + " { t.bar() };\n" + " t.bar();\n" + "};"); + + verifyFormat("template \n" + "concept Large = sizeof(T) > 10;"); verifyFormat("template \n" "concept FooableWith = requires(T t, U u) {\n" @@ -22301,199 +22516,881 @@ " { 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); + "void doFoo(FooableWith auto t) { t.foo(3); }"); + verifyFormat("template \n" - "concept Node = std::is_object_v;", - Style); + "concept Context = is_specialization_of_v;"); + verifyFormat("template \n" - "concept Tree = true;", - Style); + "concept Node = std::is_object_v;"); - verifyFormat("template int g(T i) requires Concept1 {\n" - " //...\n" - "}", + auto Style = getLLVMStyle(); + Style.BreakBeforeConceptDeclarations = false; + + verifyFormat("template concept C = requires(T t) {\n" + " requires Bar && Foo;\n" + " requires((trait && Baz) || (T2 && Foo));\n" + "};", Style); - verifyFormat( - "template int g(T i) requires Concept1 && Concept2 {\n" - " //...\n" - "}", - Style); + verifyFormat("template concept HasFoo = requires(T t) {\n" + " { t.foo() };\n" + " t.foo();\n" + "};\n" + "template concept HasBar = requires(T t) {\n" + " { t.bar() };\n" + " t.bar();\n" + "};", + Style); - verifyFormat( - "template int g(T i) requires Concept1 || Concept2 {\n" - " //...\n" - "}", - Style); + verifyFormat("template concept True = true;", Style); verifyFormat("template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 || " - "Concept2 {\n" - " //...\n" - "}", + "concept C = decltype([]() -> std::true_type { return {}; " + "}())::value &&\n" + " requires(T t) {\n" + " t.bar();\n" + "} && sizeof(T) <= 8;", Style); verifyFormat("template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 && " - "Concept2 {\n" - " //...\n" - "}", + "concept Semiregular = DefaultConstructible && " + "CopyConstructible &&\n" + " CopyAssignable && requires(T a, " + "std::size_t n) {\n" + " requires Same;\n" + " { a.~T() } noexcept;\n" + " requires Same;\n" + " requires Same;\n" + " { delete new T; };\n" + " { delete new T[n]; };\n" + "};", Style); - verifyFormat( - "template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 && Concept2 {\n" - " //...\n" - "}", - Style); + // The following tests are invalid C++, we just want to make sure we don't + // assert. + verifyFormat("template \n" + "concept C = requires C2;"); - verifyFormat( - "template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 || Concept2 {\n" - " //...\n" - "}", - Style); + verifyFormat("template \n" + "concept C = 5 + 4;"); - verifyFormat("template \n" - "requires Foo() && Bar {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept C =\n" + "class X;"); - verifyFormat("template \n" - "requires Foo>() && Bar> {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept C = [] && true;"); - verifyFormat("template \n" - "requires Foo>() && Bar> {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept C = [] && requires(T t) {\n" + " typename T::size_type;\n" + "};"); +} - verifyFormat( - "template \n" - "requires Foo, Baz>() && Bar, Baz> {\n" - " //....\n" - "}", - Style); +TEST_F(FormatTest, RequiresClauseBeforeClass) { + auto Style = getLLVMStyle(); + EXPECT_EQ(Style.RequiresClausePositionForClasses, FormatStyle::RCPS_OwnLine); + EXPECT_EQ(Style.IndentRequires, false); - Style.IndentRequires = true; - verifyFormat("template \n" - " requires Iterator\n" - "void sort(It begin, It end) {\n" - " //....\n" - "}", + verifyFormat("template \n" + "requires(std::invocable...>)\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "requires(std::invocable...>)\n" + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", Style); - verifyFormat("template \n" - " requires(index_ < sizeof...(Children_))\n" - "Tree auto &child() {\n" - " // ...\n" - "}", + + verifyFormat("template \n" + "requires true\n" + "struct constant;", Style); - Style.SpaceBeforeParens = FormatStyle::SBPO_Always; - verifyFormat("template \n" - "concept Hashable = requires (T a) {\n" - " { std::hash{}(a) } -> std::convertible_to;\n" - "};", + verifyFormat("template \n" + "requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept\n" + "struct constant;", Style); - verifyFormat("template \n" - " requires EqualityComparable || Same\n" - "struct equal_to;", + verifyFormat("template \n" + "requires(Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept)\n" + "struct constant;", Style); - verifyFormat("template \n" - " requires requires {\n" - " T{};\n" - " T (int);\n" - " }\n", + verifyFormat("template \n" + "requires(C && C2)\n" + "class Foo;", 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", + "requires(C && C2)\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\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", + "requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "}\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\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", + "requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", Style); - verifyFormat("template \nconcept someConcept = Constraint1 && " - "Constraint2;"); + Style.IndentRequires = true; + verifyFormat("template \n" + " requires(std::invocable...>)\n" + "struct constant;", + Style); - 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", + verifyFormat("template \n" + " requires(std::invocable...>)\n" + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", Style); - verifyFormat("void Foo () requires std::copyable\n" - "{\n" - " return\n" - "}\n", + verifyFormat("template \n" + " requires true\n" + "struct constant;", Style); verifyFormat("template \n" - " requires (std::invocable...>)\n" + " requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept\n" "struct constant;", Style); verifyFormat("template \n" - " requires std::invocable...>\n" + " requires(Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept)\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires(C && C2)\n" + "class Foo;", + Style); + + verifyFormat("template \n" + " requires(C && C2)\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + " requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "}\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + " requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.RequiresClausePositionForClasses = FormatStyle::RCPS_SingleLine; + Style.IndentRequires = false; + verifyFormat("template \n" + "requires(std::invocable...>) " + "struct constant;", + Style); + + verifyFormat("template \n" + "requires(std::invocable...>) " + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat("template \n" + "requires true struct constant;", + Style); + + verifyFormat("template \n" + "requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept struct constant;", + Style); + + verifyFormat("template \n" + "requires(Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept) struct constant;", + Style); + + verifyFormat("template requires(C && C2) class Foo;", + Style); + + verifyFormat("template requires(C && C2) class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.IndentRequires = true; + verifyFormat("template \n" + " requires(std::invocable...>) " + "struct constant;", + Style); + + verifyFormat("template \n" + " requires(std::invocable...>) " + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat("template \n" + " requires true struct constant;", + Style); + + verifyFormat("template \n" + " requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept struct constant;", + Style); + + verifyFormat("template \n" + " requires(Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept) struct constant;", + Style); + + verifyFormat("template requires(C && C2) class Foo;", + Style); + + verifyFormat("template requires(C && C2) class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.RequiresClausePositionForClasses = FormatStyle::RCPS_ToPreceeding; + Style.IndentRequires = false; + verifyFormat("template \n" + "requires(std::invocable...>)\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "requires(std::invocable...>)\n" + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat( + "template requires true\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "requires(Concept1 && Concept2 && ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept)\n" + "struct constant;", + Style); + + verifyFormat("template requires(C && C2)\n" + "class Foo;", + Style); + + verifyFormat("template requires(C && C2)\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "}\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.IndentRequires = true; + verifyFormat("template \n" + " requires(std::invocable...>)\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires(std::invocable...>)\n" + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat( + "template requires true\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires(Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept)\n" + "struct constant;", + Style); + + verifyFormat("template requires(C && C2)\n" + "class Foo;", + Style); + + verifyFormat("template requires(C && C2)\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "}\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.RequiresClausePositionForClasses = FormatStyle::RCPS_ToFollowing; + Style.IndentRequires = false; + verifyFormat("template \n" + "requires(std::invocable...>) " + "struct constant;", + Style); + + verifyFormat("template \n" + "requires(std::invocable...>) " + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat("template \n" + "requires true struct constant;", + Style); + + verifyFormat("template \n" + "requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept struct constant;", + Style); + + verifyFormat("template \n" + "requires(Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept) struct constant;", + Style); + + verifyFormat("template \n" + "requires(C && C2) class Foo;", + Style); + + verifyFormat("template \n" + "requires(C && C2) class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + "requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + "requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.IndentRequires = true; + verifyFormat("template \n" + " requires(std::invocable...>) " + "struct constant;", + Style); + + verifyFormat("template \n" + " requires(std::invocable...>) " + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat("template \n" + " requires true struct constant;", + Style); + + verifyFormat("template \n" + " requires Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept struct constant;", + Style); + + verifyFormat("template \n" + " requires(Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept) struct constant;", + Style); + + verifyFormat("template \n" + " requires(C && C2) class Foo;", + Style); + + verifyFormat("template \n" + " requires(C && C2) class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + " requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + " requires requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.RequiresClausePositionForClasses = FormatStyle::RCPS_TwoLines; + Style.IndentRequires = false; + verifyFormat("template \n" + "requires\n" + " (std::invocable...>)\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "requires\n" + " (std::invocable...>)\n" + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat("template \n" + "requires\n" + " true\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "requires\n" + " Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept\n" + "struct constant;", + Style); + + verifyFormat("template \n" + "requires\n" + " (C && C2)\n" + "class Foo;", + Style); + + verifyFormat("template \n" + "requires\n" + " (C && C2)\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + "requires\n" + " requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "}\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + "requires\n" + " requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + Style.IndentRequires = true; + verifyFormat("template \n" + " requires\n" + " (std::invocable...>)\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires\n" + " (std::invocable...>)\n" + "struct constant {\n" + " int Foo;\n" + " double Bar;\n" + "};", + Style); + + verifyFormat("template \n" + " requires\n" + " true\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires\n" + " Concept1 && Concept2 && " + "ThisIsAVeryLongConceptName &&\n" + " AndAnotherConcept\n" + "struct constant;", + Style); + + verifyFormat("template \n" + " requires\n" + " (C && C2)\n" + "class Foo;", + Style); + + verifyFormat("template \n" + " requires\n" + " (C && C2)\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + " requires\n" + " requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "}\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); + + verifyFormat("template \n" + " requires\n" + " requires(T t) {\n" + " typename T::Bar;\n" + " { t.bar() } -> std::same_as;\n" + "} && C\n" + "class Foo {\n" + "public:\n" + " Foo();\n" + " void foo() const;\n" + "};", + Style); +} + +TEST_F(FormatTest, ConceptsAndRequires) { + return; + FormatStyle Style = getLLVMStyle(); + Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; + + verifyFormat("template \n" + "requires Iterator\n" + "void sort(It begin, It end) {\n" + " //....\n" + "}", + 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" + " requires EqualityComparable || Same\n" + "struct equal_to;", + Style); + + 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); @@ -22518,13 +23415,114 @@ "}\n", Style); - Style.BreakBeforeConceptDeclarations = false; - verifyFormat("template concept Tree = true;", Style); - Style.IndentRequires = false; - verifyFormat("template \n" - "requires (std::invocable...>) " - "struct constant;", + + verifyFormat("template void func(T);"); + + verifyFormat("template \n" + "requires std::signed_integral && std::signed_integral\n" + "void func(T);"); + verifyFormat("template \n" + "requires(std::is_integral_v && std::is_signed_v)\n" + "void func(T);"); + verifyFormat("template \n" + "requires requires(T &&t) {\n" + " typename T::size_type;\n" + " { t.size() } -> std::same_as;\n" + "}\n" + "func(T);"); + + verifyFormat("template \n" + "requires std::signed_integral && std::signed_integral\n" + "void func(T) {}"); + verifyFormat("template \n" + "requires(std::is_integral_v && std::is_signed_v)\n" + "void func(T) {}"); + verifyFormat("template \n" + "requires requires(T &&t) {\n" + " typename T::size_type;\n" + " { t.size() } -> std::same_as;\n" + "}\n" + "func(T) {}"); + + verifyFormat( + "template void func(T) requires std::signed_integral;"); + verifyFormat("template \n" + "void func(T) requires std::signed_integral && " + "std::signed_integral;"); + verifyFormat( + "template \n" + "void func(T) requires(std::is_integral_v && std::is_signed_v);"); + verifyFormat("template void func(T) requires requires(T &&t) {\n" + " typename T::size_type;\n" + " { t.size() } -> std::same_as;\n" + "};"); + + verifyFormat( + "template void func(T) requires std::signed_integral {}"); + verifyFormat("template \n" + "void func(T) requires std::signed_integral && " + "std::signed_integral {}"); + verifyFormat( + "template \n" + "void func(T) requires(std::is_integral_v && std::is_signed_v) {}"); + verifyFormat("template void func(T) requires requires(T &&t) {\n" + " typename T::size_type;\n" + " { t.size() } -> std::same_as;\n" + "}\n" + "{}"); + + Style = getLLVMStyle(); + Style.IndentRequires = true; + + verifyFormat("template \n" + " requires std::signed_integral && std::signed_integral\n" + "void func(T);", + Style); + verifyFormat("template \n" + " requires(std::is_integral_v && std::is_signed_v)\n" + "void func(T);", + Style); + verifyFormat("template \n" + " requires requires(T &&t) {\n" + " typename T::size_type;\n" + " { t.size() } -> std::same_as;\n" + " }\n" + "func(T);", + Style); + + verifyFormat("template \n" + " requires std::signed_integral && std::signed_integral\n" + "void func(T) {}", + Style); + verifyFormat("template \n" + " requires(std::is_integral_v && std::is_signed_v)\n" + "void func(T) {}", + Style); + verifyFormat("template \n" + " requires requires(T &&t) {\n" + " typename T::size_type;\n" + " { t.size() } -> std::same_as;\n" + " }\n" + "func(T) {}", + Style); + + verifyFormat( + "template void func(T) requires std::signed_integral {}", + Style); + verifyFormat("template \n" + "void func(T) requires std::signed_integral && " + "std::signed_integral {}", + Style); + verifyFormat( + "template \n" + "void func(T) requires(std::is_integral_v && std::is_signed_v) {}", + Style); + verifyFormat("template void func(T) requires requires(T &&t) {\n" + " typename T::size_type;\n" + " { t.size() } -> std::same_as;\n" + "}\n" + "{}", Style); } Index: clang/unittests/Format/TokenAnnotatorTest.cpp =================================================================== --- clang/unittests/Format/TokenAnnotatorTest.cpp +++ clang/unittests/Format/TokenAnnotatorTest.cpp @@ -67,6 +67,51 @@ EXPECT_TOKEN(Tokens[11], tok::star, TT_PointerOrReference); } +TEST_F(TokenAnnotatorTest, UnderstandsRequiresClauses) { + auto Tokens = annotate("template \n" + "concept C = (Foo && Bar) && (Bar && Baz);"); + + EXPECT_EQ(Tokens.size(), 21u) << Tokens; + EXPECT_TOKEN(Tokens[10], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[13], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[16], tok::ampamp, TT_BinaryOperator); + + Tokens = annotate("template \n" + "concept C = requires(T t) {\n" + " { t.foo() };\n" + "} && Bar && Baz;"); + EXPECT_EQ(Tokens.size(), 35u) << Tokens; + EXPECT_TOKEN(Tokens[23], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[28], tok::ampamp, TT_BinaryOperator); + + Tokens = annotate("template\n" + "requires C1 && (C21 || C22 && C2e) && C3\n" + "struct Foo;"); + EXPECT_EQ(Tokens.size(), 36u) << Tokens; + EXPECT_TOKEN(Tokens[6], tok::identifier, TT_Unknown); + EXPECT_EQ(Tokens[6]->FakeLParens.size(), 1u); + EXPECT_TOKEN(Tokens[10], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[16], tok::pipepipe, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[21], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[27], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[31], tok::greater, TT_TemplateCloser); + EXPECT_EQ(Tokens[31]->FakeRParens, 1u); + + Tokens = + annotate("template\n" + "requires (C1 && (C21 || C22 && C2e) && C3)\n" + "struct Foo;"); + EXPECT_EQ(Tokens.size(), 38u) << Tokens; + EXPECT_TOKEN(Tokens[7], tok::identifier, TT_Unknown); + EXPECT_EQ(Tokens[7]->FakeLParens.size(), 1u); + EXPECT_TOKEN(Tokens[11], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[17], tok::pipepipe, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[22], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[28], tok::ampamp, TT_BinaryOperator); + EXPECT_TOKEN(Tokens[32], tok::greater, TT_TemplateCloser); + EXPECT_EQ(Tokens[32]->FakeRParens, 1u); +} + } // namespace } // namespace format } // namespace clang