diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -1988,17 +1988,33 @@ -**BreakBeforeConceptDeclarations** (``Boolean``) :versionbadge:`clang-format 13` - If ``true``, concept will be placed on a new line. +**BreakBeforeConceptDeclarations** (``BreakBeforeConceptDeclarationsStyle``) :versionbadge:`clang-format 13` + The concept declaration style to use. - .. code-block:: c++ + Possible values: + + * ``BBCDS_Never`` (in configuration: ``Never``) + Keep the template declaration line together with ``concept``. + + .. code-block:: c++ + + template concept C = ...; + + * ``BBCDS_Allowed`` (in configuration: ``Allowed``) + Breaking between template declaration and ``concept`` is allowed. The + actual behavior depends on the content and line breaking rules and + penalities. + + * ``BBCDS_Always`` (in configuration: ``Always``) + Always break before ``concept``, putting it in the line after the + template declaration. + + .. code-block:: c++ + + template + concept C = ...; - true: - template - concept ... - false: - template concept ... **BreakBeforeTernaryOperators** (``Boolean``) :versionbadge:`clang-format 3.7` If ``true``, ternary operators will be placed after line breaks. @@ -2690,8 +2706,9 @@ -**IndentRequires** (``Boolean``) :versionbadge:`clang-format 13` - Indent the requires clause in a template +**IndentRequiresClause** (``Boolean``) :versionbadge:`clang-format 13` + Indent the requires clause in a template. This only applies when + ``RequiresClausePosition`` is ``OwnLine``, or ``WithFollowing``. .. code-block:: c++ @@ -3474,6 +3491,92 @@ } } +**RequiresClausePosition** (``RequiresClausePositionStyle``) :versionbadge:`clang-format 15` + The position of the ``requires`` clause. + + Possible values: + + * ``RCPS_OwnLine`` (in configuration: ``OwnLine``) + Always put the ``requires`` clause on 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_WithPreceding`` (in configuration: ``WithPreceding``) + Try to put the clause together with the preceding part of a declaration. + For class templates: stick to the template declaration. + For function templates: stick to the template declaration. + For function declaration followed by a requires clause: stick to the + parameter list. + + .. code-block:: c++ + + template requires C + struct Foo {... + + template requires C + void bar(T t) {... + + template + void baz(T t) requires C + {... + + * ``RCPS_WithFollowing`` (in configuration: ``WithFollowing``) + Try to put the ``requires`` clause together with the class or 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``) + Try to put everything in the same line if possible. Otherwise normal + line breaking rules take over. + + .. code-block:: c++ + + // Fitting: + template requires C struct Foo {... + + template requires C void bar(T t) {... + + template void bar(T t) requires C {... + + // Not fitting, one possible example: + template + requires C + struct Foo {... + + template + requires C + void bar(LongName ln) { + + template + void bar(LongName ln) + requires C { + + + **SeparateDefinitionBlocks** (``SeparateDefinitionStyle``) :versionbadge:`clang-format 14` Specifies the use of empty lines to separate definition blocks, including classes, structs, enums, and functions. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -167,6 +167,14 @@ clang-format ------------ +- **Important change**: Renamed ``IndentRequires`` to ``IndentRequiresClause`` + and changed the default for all styles from ``false`` to ``true``. + +- Reworked and improved handling of concepts and requires. Added the + ``RequiresClausePosition`` option as part of that. + +- Changed ``BreakBeforeConceptDeclarations`` from ``Boolean`` to an enum. + 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 @@ -1770,17 +1770,29 @@ /// \version 3.8 BraceWrappingFlags BraceWrapping; - /// If ``true``, concept will be placed on a new line. - /// \code - /// true: - /// template - /// concept ... - /// - /// false: - /// template concept ... - /// \endcode + /// Different ways to break before concept declarations. + enum BreakBeforeConceptDeclarationsStyle { + /// Keep the template declaration line together with ``concept``. + /// \code + /// template concept C = ...; + /// \endcode + BBCDS_Never, + /// Breaking between template declaration and ``concept`` is allowed. The + /// actual behavior depends on the content and line breaking rules and + /// penalities. + BBCDS_Allowed, + /// Always break before ``concept``, putting it in the line after the + /// template declaration. + /// \code + /// template + /// concept C = ...; + /// \endcode + BBCDS_Always, + }; + + /// The concept declaration style to use. /// \version 13 - bool BreakBeforeConceptDeclarations; + BreakBeforeConceptDeclarationsStyle BreakBeforeConceptDeclarations; /// If ``true``, ternary operators will be placed after line breaks. /// \code @@ -2509,7 +2521,8 @@ /// \version 12 IndentExternBlockStyle IndentExternBlock; - /// Indent the requires clause in a template + /// Indent the requires clause in a template. This only applies when + /// ``RequiresClausePosition`` is ``OwnLine``, or ``WithFollowing``. /// \code /// true: /// template @@ -2526,7 +2539,7 @@ /// } /// \endcode /// \version 13 - bool IndentRequires; + bool IndentRequiresClause; /// The number of columns to use for indentation. /// \code @@ -3116,6 +3129,87 @@ /// \version 14 bool RemoveBracesLLVM; + /// \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 { + /// Always put the ``requires`` clause on 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, + /// Try to put the clause together with the preceding part of a declaration. + /// For class templates: stick to the template declaration. + /// For function templates: stick to the template declaration. + /// For function declaration followed by a requires clause: stick to the + /// parameter list. + /// \code + /// template requires C + /// struct Foo {... + /// + /// template requires C + /// void bar(T t) {... + /// + /// template + /// void baz(T t) requires C + /// {... + /// \endcode + RCPS_WithPreceding, + /// Try to put the ``requires`` clause together with the class or function + /// declaration. + /// \code + /// template + /// requires C struct Foo {... + /// + /// template + /// requires C void bar(T t) {... + /// + /// template + /// void baz(T t) + /// requires C {... + /// \endcode + RCPS_WithFollowing, + /// Try to put everything in the same line if possible. Otherwise normal + /// line breaking rules take over. + /// \code + /// // Fitting: + /// template requires C struct Foo {... + /// + /// template requires C void bar(T t) {... + /// + /// template void bar(T t) requires C {... + /// + /// // Not fitting, one possible example: + /// template + /// requires C + /// struct Foo {... + /// + /// template + /// requires C + /// void bar(LongName ln) { + /// + /// template + /// void bar(LongName ln) + /// requires C { + /// \endcode + RCPS_SingleLine, + }; + + /// \brief The position of the ``requires`` clause. + /// \version 15 + RequiresClausePositionStyle RequiresClausePosition; + /// \brief The style if definition blocks should be separated. enum SeparateDefinitionStyle { /// Leave definition blocks as they are. @@ -3889,8 +3983,8 @@ IndentGotoLabels == R.IndentGotoLabels && IndentPPDirectives == R.IndentPPDirectives && IndentExternBlock == R.IndentExternBlock && - IndentRequires == R.IndentRequires && IndentWidth == R.IndentWidth && - Language == R.Language && + IndentRequiresClause == R.IndentRequiresClause && + IndentWidth == R.IndentWidth && Language == R.Language && IndentWrappedFunctionNames == R.IndentWrappedFunctionNames && JavaImportGroups == R.JavaImportGroups && JavaScriptQuotes == R.JavaScriptQuotes && @@ -3926,6 +4020,7 @@ RawStringFormats == R.RawStringFormats && ReferenceAlignment == R.ReferenceAlignment && RemoveBracesLLVM == R.RemoveBracesLLVM && + RequiresClausePosition == R.RequiresClausePosition && SeparateDefinitionBlocks == R.SeparateDefinitionBlocks && ShortNamespaceLines == R.ShortNamespaceLines && SortIncludes == R.SortIncludes && diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -478,11 +478,32 @@ return true; if (Current.NestingLevel == 0 && !Current.isTrailingComment()) { - // Always break after "template <...>" and leading annotations. This is only - // for cases where the entire line does not fit on a single line as a + // Always break after "template <...>"(*) and leading annotations. This is + // only for cases where the entire line does not fit on a single line as a // different LineFormatter would be used otherwise. - if (Previous.ClosesTemplateDeclaration) + // *: Except when another option interferes with that, like concepts. + if (Previous.ClosesTemplateDeclaration) { + if (Current.is(tok::kw_concept)) { + switch (Style.BreakBeforeConceptDeclarations) { + case FormatStyle::BBCDS_Allowed: + break; + case FormatStyle::BBCDS_Always: + return true; + case FormatStyle::BBCDS_Never: + return false; + } + } + if (Current.is(TT_RequiresClause)) { + switch (Style.RequiresClausePosition) { + case FormatStyle::RCPS_SingleLine: + case FormatStyle::RCPS_WithPreceding: + return false; + default: + return true; + } + } return Style.AlwaysBreakTemplateDeclarations != FormatStyle::BTDS_No; + } if (Previous.is(TT_FunctionAnnotationRParen) && State.Line->Type != LT_PreprocessorDirective) return true; @@ -669,6 +690,7 @@ if (Style.AlignAfterOpenBracket != FormatStyle::BAS_DontAlign && !State.Stack.back().IsCSharpGenericTypeConstraint && Previous.opensScope() && Previous.isNot(TT_ObjCMethodExpr) && + Previous.isNot(TT_RequiresClause) && (Current.isNot(TT_LineComment) || Previous.is(BK_BracedInit))) { State.Stack.back().Indent = State.Column + Spaces; State.Stack.back().IsAligned = true; @@ -880,7 +902,8 @@ Previous.is(TT_BinaryOperator)) State.Stack.back().BreakBeforeParameter = false; if (PreviousNonComment && - PreviousNonComment->isOneOf(TT_TemplateCloser, TT_JavaAnnotation) && + (PreviousNonComment->isOneOf(TT_TemplateCloser, TT_JavaAnnotation) || + PreviousNonComment->ClosesRequiresClause) && Current.NestingLevel == 0) State.Stack.back().BreakBeforeParameter = false; if (NextNonComment->is(tok::question) || @@ -927,13 +950,19 @@ State.Stack[State.Stack.size() - 2].NestedBlockInlined) || (Style.Language == FormatStyle::LK_ObjC && Current.is(tok::r_brace) && State.Stack.size() > 1 && !Style.ObjCBreakBeforeNestedBlockParam); + // Do not force parameter break for statements with requires expressions. + NestedBlockSpecialCase = + NestedBlockSpecialCase || + (Current.MatchingParen && + Current.MatchingParen->is(TT_RequiresExpressionLBrace)); if (!NestedBlockSpecialCase) for (unsigned i = 0, e = State.Stack.size() - 1; i != e; ++i) State.Stack[i].BreakBeforeParameter = true; if (PreviousNonComment && !PreviousNonComment->isOneOf(tok::comma, tok::colon, tok::semi) && - (PreviousNonComment->isNot(TT_TemplateCloser) || + ((PreviousNonComment->isNot(TT_TemplateCloser) && + !PreviousNonComment->ClosesRequiresClause) || Current.NestingLevel != 0) && !PreviousNonComment->isOneOf( TT_BinaryOperator, TT_FunctionAnnotationRParen, TT_JavaAnnotation, @@ -1096,8 +1125,20 @@ } if (Previous.is(tok::comma) && State.Stack.back().VariablePos != 0) return State.Stack.back().VariablePos; + if (Current.is(TT_RequiresClause)) { + if (Style.IndentRequiresClause) + return State.Stack.back().Indent + Style.IndentWidth; + switch (Style.RequiresClausePosition) { + case FormatStyle::RCPS_OwnLine: + case FormatStyle::RCPS_WithFollowing: + return State.Stack.back().Indent; + default: + break; + } + } if ((PreviousNonComment && (PreviousNonComment->ClosesTemplateDeclaration || + PreviousNonComment->ClosesRequiresClause || PreviousNonComment->isOneOf( TT_AttributeParen, TT_AttributeSquare, TT_FunctionAnnotationRParen, TT_JavaAnnotation, TT_LeadingJavaAnnotation))) || @@ -1288,6 +1329,8 @@ State.Column + Current.ColumnWidth + 1; if (Current.isOneOf(TT_LambdaLSquare, TT_LambdaArrow)) State.Stack.back().LastSpace = State.Column; + if (Current.is(TT_RequiresExpression)) + State.Stack.back().NestedBlockIndent = State.Column; // Insert scopes created by fake parenthesis. const FormatToken *Previous = Current.getPreviousNonComment(); @@ -1298,8 +1341,8 @@ // foo(); // bar(); // }, a, b, c); - if (Current.isNot(tok::comment) && Previous && - Previous->isOneOf(tok::l_brace, TT_ArrayInitializerLSquare) && + if (Current.isNot(tok::comment) && !Current.ClosesRequiresClause && + Previous && Previous->isOneOf(tok::l_brace, TT_ArrayInitializerLSquare) && !Previous->is(TT_DictLiteral) && State.Stack.size() > 1 && !State.Stack.back().HasMultipleNestedBlocks) { if (State.Stack[State.Stack.size() - 2].NestedBlockInlined && Newline) @@ -1359,14 +1402,15 @@ 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->getPrecedence() == prec::Assignment && - Style.AlignOperands != FormatStyle::OAS_DontAlign) || - Previous->is(TT_ObjCMethodExpr))); + Previous && + (Previous->opensScope() || + Previous->isOneOf(tok::semi, tok::kw_return, TT_RequiresClause) || + (Previous->getPrecedence() == prec::Assignment && + Style.AlignOperands != FormatStyle::OAS_DontAlign) || + Previous->is(TT_ObjCMethodExpr)); for (const auto &PrecedenceLevel : llvm::reverse(Current.FakeLParens)) { ParenState NewParenState = State.Stack.back(); NewParenState.Tok = nullptr; @@ -1399,7 +1443,7 @@ if (Previous && (Previous->getPrecedence() == prec::Assignment || - Previous->is(tok::kw_return) || + Previous->isOneOf(tok::kw_return, TT_RequiresClause) || (PrecedenceLevel == prec::Conditional && Previous->is(tok::question) && Previous->is(TT_ConditionalExpr))) && !Newline) { @@ -1457,6 +1501,12 @@ State.Stack.pop_back(); State.Stack.back().VariablePos = VariablePos; } + + if (State.NextToken->ClosesRequiresClause && Style.IndentRequiresClause) { + // Remove the indentation of the requires clauses (which is not in Indent, + // but in LastSpace). + State.Stack.back().LastSpace -= Style.IndentWidth; + } } void ContinuationIndenter::moveStatePastScopeOpener(LineState &State, 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 @@ -257,6 +257,21 @@ } }; +template <> +struct ScalarEnumerationTraits< + FormatStyle::BreakBeforeConceptDeclarationsStyle> { + static void + enumeration(IO &IO, FormatStyle::BreakBeforeConceptDeclarationsStyle &Value) { + IO.enumCase(Value, "Never", FormatStyle::BBCDS_Never); + IO.enumCase(Value, "Allowed", FormatStyle::BBCDS_Allowed); + IO.enumCase(Value, "Always", FormatStyle::BBCDS_Always); + + // For backward compatibility. + IO.enumCase(Value, "true", FormatStyle::BBCDS_Always); + IO.enumCase(Value, "false", FormatStyle::BBCDS_Allowed); + } +}; + template <> struct ScalarEnumerationTraits { static void @@ -463,6 +478,17 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, + FormatStyle::RequiresClausePositionStyle &Value) { + IO.enumCase(Value, "OwnLine", FormatStyle::RCPS_OwnLine); + IO.enumCase(Value, "WithPreceding", FormatStyle::RCPS_WithPreceding); + IO.enumCase(Value, "WithFollowing", FormatStyle::RCPS_WithFollowing); + IO.enumCase(Value, "SingleLine", FormatStyle::RCPS_SingleLine); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::SpaceBeforeParensStyle &Value) { @@ -565,6 +591,7 @@ IO.mapOptional("DerivePointerBinding", Style.DerivePointerAlignment); IO.mapOptional("IndentFunctionDeclarationAfterType", Style.IndentWrappedFunctionNames); + IO.mapOptional("IndentRequires", Style.IndentRequiresClause); IO.mapOptional("PointerBindsToType", Style.PointerAlignment); IO.mapOptional("SpaceAfterControlStatementKeyword", Style.SpaceBeforeParens); @@ -737,7 +764,7 @@ IO.mapOptional("IndentGotoLabels", Style.IndentGotoLabels); IO.mapOptional("IndentPPDirectives", Style.IndentPPDirectives); IO.mapOptional("IndentExternBlock", Style.IndentExternBlock); - IO.mapOptional("IndentRequires", Style.IndentRequires); + IO.mapOptional("IndentRequiresClause", Style.IndentRequiresClause); IO.mapOptional("IndentWidth", Style.IndentWidth); IO.mapOptional("IndentWrappedFunctionNames", Style.IndentWrappedFunctionNames); @@ -782,6 +809,7 @@ IO.mapOptional("ReferenceAlignment", Style.ReferenceAlignment); IO.mapOptional("ReflowComments", Style.ReflowComments); IO.mapOptional("RemoveBracesLLVM", Style.RemoveBracesLLVM); + IO.mapOptional("RequiresClausePosition", Style.RequiresClausePosition); IO.mapOptional("SeparateDefinitionBlocks", Style.SeparateDefinitionBlocks); IO.mapOptional("ShortNamespaceLines", Style.ShortNamespaceLines); IO.mapOptional("SortIncludes", Style.SortIncludes); @@ -1130,7 +1158,7 @@ LLVMStyle.BinPackArguments = true; LLVMStyle.BinPackParameters = true; LLVMStyle.BreakBeforeBinaryOperators = FormatStyle::BOS_None; - LLVMStyle.BreakBeforeConceptDeclarations = true; + LLVMStyle.BreakBeforeConceptDeclarations = FormatStyle::BBCDS_Always; LLVMStyle.BreakBeforeTernaryOperators = true; LLVMStyle.BreakBeforeBraces = FormatStyle::BS_Attach; LLVMStyle.BraceWrapping = {/*AfterCaseLabel=*/false, @@ -1188,7 +1216,7 @@ LLVMStyle.IndentCaseBlocks = false; LLVMStyle.IndentGotoLabels = true; LLVMStyle.IndentPPDirectives = FormatStyle::PPDIS_None; - LLVMStyle.IndentRequires = false; + LLVMStyle.IndentRequiresClause = true; LLVMStyle.IndentWrappedFunctionNames = false; LLVMStyle.IndentWidth = 2; LLVMStyle.PPIndentWidth = -1; @@ -1207,6 +1235,7 @@ LLVMStyle.ObjCSpaceBeforeProtocolList = true; LLVMStyle.PointerAlignment = FormatStyle::PAS_Right; LLVMStyle.ReferenceAlignment = FormatStyle::RAS_Pointer; + LLVMStyle.RequiresClausePosition = FormatStyle::RCPS_OwnLine; LLVMStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Leave; LLVMStyle.ShortNamespaceLines = 1; LLVMStyle.SpacesBeforeTrailingComments = 1; @@ -3048,6 +3077,15 @@ FormatStyle Expanded = Style; expandPresetsBraceWrapping(Expanded); expandPresetsSpaceBeforeParens(Expanded); + switch (Expanded.RequiresClausePosition) { + case FormatStyle::RCPS_SingleLine: + case FormatStyle::RCPS_WithPreceding: + Expanded.IndentRequiresClause = false; + break; + default: + break; + } + if (Expanded.DisableFormat) return {tooling::Replacements(), 0}; if (isLikelyXml(Code)) 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 @@ -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) \ @@ -98,6 +99,11 @@ TYPE(RangeBasedForLoopColon) \ TYPE(RecordLBrace) \ TYPE(RegexLiteral) \ + TYPE(RequiresClause) \ + TYPE(RequiresClauseInARequiresExpression) \ + TYPE(RequiresExpression) \ + TYPE(RequiresExpressionLBrace) \ + TYPE(RequiresExpressionLParen) \ TYPE(SelectorName) \ TYPE(StartOfName) \ TYPE(StatementAttributeLikeMacro) \ @@ -245,8 +251,9 @@ CanBreakBefore(false), ClosesTemplateDeclaration(false), StartsBinaryExpression(false), EndsBinaryExpression(false), PartOfMultiVariableDeclStmt(false), ContinuesLineCommentSection(false), - Finalized(false), BlockKind(BK_Unknown), Decision(FD_Unformatted), - PackingKind(PPK_Inconclusive), Type(TT_Unknown) {} + Finalized(false), ClosesRequiresClause(false), BlockKind(BK_Unknown), + Decision(FD_Unformatted), PackingKind(PPK_Inconclusive), + Type(TT_Unknown) {} /// The \c Token. Token Tok; @@ -312,6 +319,9 @@ /// changes. unsigned Finalized : 1; + /// \c true if this is the last token within requires clause. + unsigned ClosesRequiresClause : 1; + private: /// Contains the kind of block if this token is a brace. unsigned BlockKind : 2; 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 @@ -1019,7 +1019,7 @@ return false; if (Line.MustBeDeclaration && Contexts.size() == 1 && !Contexts.back().IsExpression && !Line.startsWith(TT_ObjCProperty) && - !Tok->is(TT_TypeDeclarationParen) && + !Tok->isOneOf(TT_TypeDeclarationParen, TT_RequiresExpressionLParen) && (!Tok->Previous || !Tok->Previous->isOneOf(tok::kw___attribute, TT_LeadingJavaAnnotation))) Line.MightBeFunctionDecl = true; @@ -1152,6 +1152,10 @@ parseCSharpGenericTypeConstraint(); } break; + case tok::arrow: + if (Tok->Previous && Tok->Previous->is(tok::kw_noexcept)) + Tok->setType(TT_TrailingReturnArrow); + break; default: break; } @@ -1412,9 +1416,12 @@ 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_FunctionLikeOrFreestandingMacro, - TT_RecordLBrace)) + TT_UntouchableMacroFunc, TT_StatementAttributeLikeMacro, + TT_FunctionLikeOrFreestandingMacro, TT_RecordLBrace, + TT_RequiresClause, TT_RequiresClauseInARequiresExpression, + TT_RequiresExpression, TT_RequiresExpressionLParen, + TT_RequiresExpressionLBrace, TT_BinaryOperator, + TT_CompoundRequirementLBrace, TT_BracedListLBrace)) CurrentToken->setType(TT_Unknown); CurrentToken->Role.reset(); CurrentToken->MatchingParen = nullptr; @@ -1609,7 +1616,8 @@ PriorLeadingIdentifier = PriorLeadingIdentifier->Previous; return (PriorLeadingIdentifier && - PriorLeadingIdentifier->is(TT_TemplateCloser) && + (PriorLeadingIdentifier->is(TT_TemplateCloser) || + PriorLeadingIdentifier->ClosesRequiresClause) && LeadingIdentifier->TokenText == Current.Next->TokenText); } } @@ -1826,6 +1834,9 @@ if (!PreviousNotConst) return false; + if (PreviousNotConst->ClosesRequiresClause) + return false; + bool IsPPKeyword = PreviousNotConst->is(tok::identifier) && PreviousNotConst->Previous && PreviousNotConst->Previous->is(tok::hash); @@ -2164,7 +2175,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) { @@ -2219,7 +2230,11 @@ break; // 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_RequiresClause, + TT_RequiresClauseInARequiresExpression)) { // 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())) { @@ -2241,12 +2256,26 @@ } 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 = + (Start->Previous && + Start->Previous->isOneOf(TT_RequiresClause, + TT_RequiresClauseInARequiresExpression)) + ? [this](){ + auto Ret = Current ? Current : Line.Last; + while (!Ret->ClosesRequiresClause && 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); } } } @@ -2295,17 +2324,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; } } @@ -2350,6 +2379,7 @@ const FormatStyle &Style; const AdditionalKeywords &Keywords; + const AnnotatedLine &Line; FormatToken *Current; }; @@ -2920,6 +2950,8 @@ } if (Left.ClosesTemplateDeclaration) return Style.PenaltyBreakTemplateDeclaration; + if (Left.ClosesRequiresClause) + return 0; if (Left.is(TT_ConditionalExpr)) return prec::Conditional; prec::Level Level = Left.getPrecedence(); @@ -2987,9 +3019,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) || @@ -3892,6 +3921,15 @@ if (Right.is(tok::lessless) && Right.Next && Left.is(tok::string_literal) && Right.Next->is(tok::string_literal)) return true; + if (Right.is(TT_RequiresClause)) { + switch (Style.RequiresClausePosition) { + case FormatStyle::RCPS_OwnLine: + case FormatStyle::RCPS_WithFollowing: + return true; + default: + break; + } + } // Can break after template<> declaration if (Left.ClosesTemplateDeclaration && Left.MatchingParen && Left.MatchingParen->NestingLevel == 0) { @@ -3899,9 +3937,18 @@ // template // concept ... if (Right.is(tok::kw_concept)) - return Style.BreakBeforeConceptDeclarations; + return Style.BreakBeforeConceptDeclarations == FormatStyle::BBCDS_Always; return (Style.AlwaysBreakTemplateDeclarations == FormatStyle::BTDS_Yes); } + if (Left.ClosesRequiresClause) { + switch (Style.RequiresClausePosition) { + case FormatStyle::RCPS_OwnLine: + case FormatStyle::RCPS_WithPreceding: + return true; + default: + break; + } + } if (Style.PackConstructorInitializers == FormatStyle::PCIS_Never) { if (Style.BreakConstructorInitializers == FormatStyle::BCIS_BeforeColon && (Left.is(TT_CtorInitializerComma) || Right.is(TT_CtorInitializerColon))) @@ -4296,8 +4343,14 @@ return Left.isNot(tok::period); // FIXME: Properly parse ObjC calls. if (Left.is(tok::r_paren) && Line.Type == LT_ObjCProperty) return true; + if (Right.is(tok::kw_concept)) + return Style.BreakBeforeConceptDeclarations != FormatStyle::BBCDS_Never; + if (Right.is(TT_RequiresClause)) + return true; if (Left.ClosesTemplateDeclaration || Left.is(TT_FunctionAnnotationRParen)) return true; + if (Left.ClosesRequiresClause) + return true; if (Right.isOneOf(TT_RangeBasedForLoopColon, TT_OverloadedOperatorLParen, TT_OverloadedOperator)) return false; 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 @@ -92,11 +92,16 @@ void reset(); void parseFile(); bool precededByCommentOrPPDirective() const; - bool parseLevel(bool HasOpeningBrace, IfStmtKind *IfKind = nullptr); + bool parseLevel(bool HasOpeningBrace, bool CanContainBracedList, + IfStmtKind *IfKind = nullptr, + TokenType NextLBracesType = TT_Unknown); IfStmtKind parseBlock(bool MustBeDeclaration = false, unsigned AddLevels = 1u, bool MunchSemi = true, - bool UnindentWhitesmithsBraces = false); - void parseChildBlock(); + bool UnindentWhitesmithsBraces = false, + bool CanContainBracedList = true, + TokenType NextLBracesType = TT_Unknown); + void parseChildBlock(bool CanContainBracedList = true, + TokenType NextLBracesType = TT_Unknown); void parsePPDirective(); void parsePPDefine(); void parsePPIf(bool IfDef); @@ -106,11 +111,12 @@ void parsePPUnknown(); void readTokenWithJavaScriptASI(); void parseStructuralElement(IfStmtKind *IfKind = nullptr, - bool IsTopLevel = false); + 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 keepAncestorBraces(); FormatToken *parseIfThenElse(IfStmtKind *IfKind, bool KeepBraces = false); @@ -127,9 +133,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 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 @@ -369,7 +369,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 @@ -436,10 +436,20 @@ return Previous && Previous->is(tok::comment) && (Previous->IsMultiline || Previous->NewlinesBefore > 0); } - -// Returns true if a simple block, or false otherwise. (A simple block has a -// single statement.) -bool UnwrappedLineParser::parseLevel(bool HasOpeningBrace, IfStmtKind *IfKind) { +/// \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. +/// \returns true if a simple block, or false otherwise. (A simple block has a +/// single statement.) +bool UnwrappedLineParser::parseLevel(bool HasOpeningBrace, + bool CanContainBracedList, + IfStmtKind *IfKind, + TokenType NextLBracesType) { + auto NextLevelLBracesType = NextLBracesType == TT_CompoundRequirementLBrace + ? TT_BracedListLBrace + : TT_Unknown; const bool IsPrecededByCommentOrPPDirective = !Style.RemoveBracesLLVM || precededByCommentOrPPDirective(); unsigned StatementCount = 0; @@ -451,17 +461,36 @@ else if (FormatTok->getType() == TT_MacroBlockEnd) kind = tok::r_brace; + auto ParseDefault = [this, HasOpeningBrace, IfKind, NextLevelLBracesType, + &StatementCount] { + parseStructuralElement(IfKind, /*IsTopLevel=*/!HasOpeningBrace, + /*NextLBracesType=*/NextLevelLBracesType); + ++StatementCount; + assert(StatementCount > 0 && "StatementCount overflow!"); + }; + switch (kind) { case tok::comment: nextToken(); 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); + else if (FormatTok->Previous && + FormatTok->Previous->ClosesRequiresClause) { + // We need the 'default' case here to correctly parse a function + // l_brace. + ParseDefault(); continue; - parseBlock(); + } + if (CanContainBracedList && !FormatTok->is(TT_MacroBlockBegin) && + tryToParseBracedList()) + continue; + parseBlock(/*MustBeDeclaration=*/false, /*AddLevels=*/1u, + /*MunchSemi=*/true, /*UnindentWhitesmithBraces=*/false, + CanContainBracedList, + /*NextLBracesType=*/NextLBracesType); ++StatementCount; assert(StatementCount > 0 && "StatementCount overflow!"); addUnwrappedLine(); @@ -517,9 +546,7 @@ } LLVM_FALLTHROUGH; default: - parseStructuralElement(IfKind, !HasOpeningBrace); - ++StatementCount; - assert(StatementCount > 0 && "StatementCount overflow!"); + ParseDefault(); break; } } while (!eof()); @@ -594,27 +621,46 @@ 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.isJavaScript() && + 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.isJavaScript() && - 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. @@ -680,8 +726,9 @@ UnwrappedLineParser::IfStmtKind UnwrappedLineParser::parseBlock(bool MustBeDeclaration, unsigned AddLevels, - bool MunchSemi, - bool UnindentWhitesmithsBraces) { + bool MunchSemi, bool UnindentWhitesmithsBraces, + bool CanContainBracedList, + TokenType NextLBracesType) { assert(FormatTok->isOneOf(tok::l_brace, TT_MacroBlockBegin) && "'{' or macro block token expected"); FormatToken *Tok = FormatTok; @@ -721,7 +768,8 @@ Line->Level += AddLevels; IfStmtKind IfKind = IfStmtKind::NotIf; - const bool SimpleBlock = parseLevel(/*HasOpeningBrace=*/true, &IfKind); + const bool SimpleBlock = parseLevel( + /*HasOpeningBrace=*/true, CanContainBracedList, &IfKind, NextLBracesType); if (eof()) return IfKind; @@ -751,8 +799,13 @@ 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(); @@ -826,7 +879,8 @@ return false; } -void UnwrappedLineParser::parseChildBlock() { +void UnwrappedLineParser::parseChildBlock( + bool CanContainBracedList, clang::format::TokenType NextLBracesType) { FormatTok->setBlockKind(BK_Block); nextToken(); { @@ -836,7 +890,8 @@ ScopedDeclarationState DeclarationState(*Line, DeclarationScopeStack, /*MustBeDeclaration=*/false); Line->Level += SkipIndent ? 0 : 1; - parseLevel(/*HasOpeningBrace=*/true); + parseLevel(/*HasOpeningBrace=*/true, CanContainBracedList, + /*IfKind=*/nullptr, NextLBracesType); flushComments(isOnNewLine(*FormatTok)); Line->Level -= SkipIndent ? 0 : 1; } @@ -1231,7 +1286,8 @@ } void UnwrappedLineParser::parseStructuralElement(IfStmtKind *IfKind, - bool IsTopLevel) { + bool IsTopLevel, + TokenType NextLBracesType) { if (Style.Language == FormatStyle::LK_TableGen && FormatTok->is(tok::pp_include)) { nextToken(); @@ -1482,7 +1538,7 @@ parseConcept(); return; case tok::kw_requires: - parseRequires(); + parseRequiresClause(); return; 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. @@ -2095,7 +2153,10 @@ return false; } -void UnwrappedLineParser::parseParens() { +/// \brief Parses a pair of parentheses (and everything between them). +/// \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 { @@ -2145,6 +2206,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; @@ -2695,6 +2763,11 @@ } } +/// \brief Parses a concept definition. +/// \pre The current token has to be the concept keyword. +/// +/// Returns if either the concept has been completely parsed, or if it detects +/// that the concept definition is incorrect. void UnwrappedLineParser::parseConcept() { assert(FormatTok->Tok.is(tok::kw_concept) && "'concept' expected"); nextToken(); @@ -2704,100 +2777,179 @@ 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(); } -void UnwrappedLineParser::parseRequiresExpression(unsigned int OriginalLevel) { - // requires (R range) - if (FormatTok->Tok.is(tok::l_paren)) { +/// \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); + + // If there is no previous token, we are within a requires expression, + // otherwise we will always have the template or function declaration in front + // of it. + bool InRequiresExpression = + !FormatTok->Previous || + FormatTok->Previous->is(TT_RequiresExpressionLBrace); + + FormatTok->setType(InRequiresExpression + ? TT_RequiresClauseInARequiresExpression + : TT_RequiresClause); + + nextToken(); + parseConstraintExpression(); + + if (!InRequiresExpression) + FormatTok->Previous->ClosesRequiresClause = true; +} + +/// \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); + + FormatTok->setType(TT_RequiresExpression); + nextToken(); + + if (FormatTok->is(tok::l_paren)) { + FormatTok->setType(TT_RequiresExpressionLParen); 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)) { + FormatTok->setType(TT_RequiresExpressionLBrace); + parseChildBlock(/*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: + // We need to differentiate identifiers for a template deduction guide, + // variables, or function return types (the constraint expression has + // ended before that), and basically all other cases. But it's easier to + // check the other way around. + assert(FormatTok->Previous); + switch (FormatTok->Previous->Tok.getKind()) { + case tok::coloncolon: // Nested identifier. + case tok::ampamp: // Start of a function or variable for the + case tok::pipepipe: // constraint expression. + case tok::kw_requires: // Initial identifier of a requires clause. + case tok::equal: // Initial identifier of a concept declaration. + break; + default: + return; + } + + // 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::kw_const: + 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: + case tok::pipepipe: + 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. + case tok::kw_true: + case tok::kw_false: + case tok::kw_sizeof: + case tok::greater: + case tok::greaterequal: + case tok::greatergreater: + case tok::less: + case tok::lessequal: + case tok::lessless: + case tok::equalequal: + case tok::exclaim: + case tok::exclaimequal: + case tok::plus: + case tok::minus: + case tok::star: + case tok::slash: + case tok::numeric_constant: + case tok::kw_decltype: + case tok::comment: + case tok::comma: + case tok::coloncolon: + // 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(); - } -} + case tok::kw_static_cast: + case tok::kw_const_cast: + case tok::kw_reinterpret_cast: + case tok::kw_dynamic_cast: + nextToken(); + if (!FormatTok->is(tok::less)) + return; -void UnwrappedLineParser::parseRequires() { - assert(FormatTok->Tok.is(tok::kw_requires) && "'requires' expected"); + parseBracedList(/*ContinueOnSemicolons=*/false, /*IsEnum=*/false, + /*ClosingBraceKind=*/tok::greater); + break; - unsigned OriginalLevel = Line->Level; - if (FormatTok->Previous && FormatTok->Previous->is(tok::greater)) { - addUnwrappedLine(); - if (Style.IndentRequires) - ++Line->Level; - } - nextToken(); + case tok::kw_bool: + // bool is only allowed if it is directly followed by a paren for a cast: + // concept C = bool(...); + // and bool is the only type, all other types as cast must be inside a + // cast to bool an thus are handled by the other cases. + nextToken(); + if (FormatTok->isNot(tok::l_paren)) + return; + parseParens(); + break; - parseRequiresExpression(OriginalLevel); + default: + return; + } + } while (!eof()); } bool UnwrappedLineParser::parseEnum() { @@ -2993,7 +3145,7 @@ } // Parse the class body after the enum's ";" if any. - parseLevel(/*HasOpeningBrace=*/true); + parseLevel(/*HasOpeningBrace=*/true, /*CanContainBracedList=*/true); nextToken(); --Line->Level; addUnwrappedLine(); 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 @@ -3826,7 +3826,10 @@ "struct b_struct {};\n" "} // namespace B\n", Style); - verifyFormat("template constexpr void foo requires(I == 42) {}\n" + verifyFormat("template \n" + "constexpr void foo()\n" + " requires(I == 42)\n" + "{}\n" "namespace ns {\n" "void foo() {}\n" "} // namespace ns\n", @@ -19254,7 +19257,6 @@ 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); @@ -19266,7 +19268,8 @@ CHECK_PARSE_BOOL(IndentCaseLabels); CHECK_PARSE_BOOL(IndentCaseBlocks); CHECK_PARSE_BOOL(IndentGotoLabels); - CHECK_PARSE_BOOL(IndentRequires); + CHECK_PARSE_BOOL_FIELD(IndentRequiresClause, "IndentRequires"); + CHECK_PARSE_BOOL(IndentRequiresClause); CHECK_PARSE_BOOL(IndentWrappedFunctionNames); CHECK_PARSE_BOOL(KeepEmptyLinesAtTheStartOfBlocks); CHECK_PARSE_BOOL(ObjCSpaceAfterProperty); @@ -19932,6 +19935,27 @@ // For backward compatibility: CHECK_PARSE("SpacesInAngles: false", SpacesInAngles, FormatStyle::SIAS_Never); CHECK_PARSE("SpacesInAngles: true", SpacesInAngles, FormatStyle::SIAS_Always); + + CHECK_PARSE("RequiresClausePosition: WithPreceding", RequiresClausePosition, + FormatStyle::RCPS_WithPreceding); + CHECK_PARSE("RequiresClausePosition: WithFollowing", RequiresClausePosition, + FormatStyle::RCPS_WithFollowing); + CHECK_PARSE("RequiresClausePosition: SingleLine", RequiresClausePosition, + FormatStyle::RCPS_SingleLine); + CHECK_PARSE("RequiresClausePosition: OwnLine", RequiresClausePosition, + FormatStyle::RCPS_OwnLine); + + CHECK_PARSE("BreakBeforeConceptDeclarations: Never", + BreakBeforeConceptDeclarations, FormatStyle::BBCDS_Never); + CHECK_PARSE("BreakBeforeConceptDeclarations: Always", + BreakBeforeConceptDeclarations, FormatStyle::BBCDS_Always); + CHECK_PARSE("BreakBeforeConceptDeclarations: Allowed", + BreakBeforeConceptDeclarations, FormatStyle::BBCDS_Allowed); + // For backward compatibility: + CHECK_PARSE("BreakBeforeConceptDeclarations: true", + BreakBeforeConceptDeclarations, FormatStyle::BBCDS_Always); + CHECK_PARSE("BreakBeforeConceptDeclarations: false", + BreakBeforeConceptDeclarations, FormatStyle::BBCDS_Allowed); } TEST_F(FormatTest, ParsesConfigurationWithLanguages) { @@ -23200,275 +23224,584 @@ Style); } -TEST_F(FormatTest, ConceptsAndRequires) { - FormatStyle Style = getLLVMStyle(); - Style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None; +TEST_F(FormatTest, Concepts) { + EXPECT_EQ(getLLVMStyle().BreakBeforeConceptDeclarations, + FormatStyle::BBCDS_Always); + verifyFormat("template \n" + "concept True = true;"); verifyFormat("template \n" - "concept Hashable = requires(T a) {\n" - " { std::hash{}(a) } -> std::convertible_to;\n" - "};", - Style); + "concept C = ((false || foo()) && C2) ||\n" + " (std::trait::value && Baz) || sizeof(T) >= 6;", + getLLVMStyleWithColumns(60)); + verifyFormat("template \n" - "concept EqualityComparable = requires(T a, T b) {\n" - " { a == b } -> bool;\n" - "};", - Style); + "concept DelayedCheck = true && requires(T t) { t.bar(); } && " + "sizeof(T) <= 8;"); + verifyFormat("template \n" - "concept EqualityComparable = requires(T a, T b) {\n" - " { a == b } -> bool;\n" - " { a != b } -> bool;\n" - "};", - Style); + "concept DelayedCheck = true && requires(T t) {\n" + " t.bar();\n" + " t.baz();\n" + " } && sizeof(T) <= 8;"); + verifyFormat("template \n" - "concept EqualityComparable = requires(T a, T b) {\n" - " { a == b } -> bool;\n" - " { a != b } -> bool;\n" - "};", - Style); + "concept DelayedCheck = true && requires(T t) { // Comment\n" + " t.bar();\n" + " t.baz();\n" + " } && sizeof(T) <= 8;"); - verifyFormat("template \n" - "requires Iterator\n" - "void sort(It begin, It end) {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept DelayedCheck = false || requires(T t) { t.bar(); } && " + "sizeof(T) <= 8;"); verifyFormat("template \n" - "concept Large = sizeof(T) > 10;", - Style); + "concept DelayedCheck = !!false || requires(T t) { t.bar(); } " + "&& sizeof(T) <= 8;"); + + verifyFormat( + "template \n" + "concept DelayedCheck = static_cast(0) ||\n" + " requires(T t) { t.bar(); } && sizeof(T) <= 8;"); - 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); + "concept DelayedCheck = bool(0) || requires(T t) { t.bar(); } " + "&& sizeof(T) <= 8;"); + + verifyFormat( + "template \n" + "concept DelayedCheck = (bool)(0) ||\n" + " requires(T t) { t.bar(); } && sizeof(T) <= 8;"); + verifyFormat("template \n" - "concept Context = is_specialization_of_v;", - Style); + "concept DelayedCheck = (bool)0 || requires(T t) { t.bar(); } " + "&& sizeof(T) <= 8;"); + verifyFormat("template \n" - "concept Node = std::is_object_v;", - Style); + "concept Size = sizeof(T) >= 5 && requires(T t) { t.bar(); } && " + "sizeof(T) <= 8;"); + verifyFormat("template \n" - "concept Tree = true;", - Style); + "concept Size = 2 < 5 && 2 <= 5 && 8 >= 5 && 8 > 5 &&\n" + " requires(T t) {\n" + " t.bar();\n" + " t.baz();\n" + " } && sizeof(T) <= 8 && !(4 < 3);", + getLLVMStyleWithColumns(60)); - verifyFormat("template int g(T i) requires Concept1 {\n" - " //...\n" - "}", - Style); + 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 int g(T i) requires Concept1 && Concept2 {\n" - " //...\n" - "}", - Style); + "template \n" + "concept C = []() { return true; }() && requires(T t) { t.bar(); } &&\n" + " sizeof(T) <= 8;"); + + // FIXME: This is misformatted because the fake l paren starts at bool, not at + // the lambda l square. + verifyFormat("template \n" + "concept C = [] -> bool { return true; }() && requires(T t) { " + "t.bar(); } &&\n" + " sizeof(T) <= 8;"); verifyFormat( - "template int g(T i) requires Concept1 || Concept2 {\n" - " //...\n" - "}", - Style); + "template \n" + "concept C = decltype([]() { return std::true_type{}; }())::value &&\n" + " requires(T t) { t.bar(); } && sizeof(T) <= 8;"); verifyFormat("template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 || " - "Concept2 {\n" - " //...\n" - "}", - Style); + "concept C = decltype([]() { return std::true_type{}; " + "}())::value && requires(T t) { t.bar(); } && sizeof(T) <= 8;", + getLLVMStyleWithColumns(120)); verifyFormat("template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 && " - "Concept2 {\n" - " //...\n" - "}", - Style); + "concept C = decltype([]() -> std::true_type { return {}; " + "}())::value &&\n" + " requires(T t) { t.bar(); } && 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" + " };"); verifyFormat( "template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 && Concept2 {\n" - " //...\n" - "}", - Style); + "concept EqualityComparable = requires(T a, T b) {\n" + " { a == b } -> std::same_as;\n" + " };"); verifyFormat( "template \n" - "veryveryvery_long_return_type g(T i) requires Concept1 || Concept2 {\n" - " //...\n" - "}", - Style); + "concept EqualityComparable = requires(T a, T b) {\n" + " { a == b } -> std::same_as;\n" + " { a != b } -> std::same_as;\n" + " };"); - verifyFormat("template \n" - "requires Foo() && Bar {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept WeakEqualityComparable = requires(T a, T b) {\n" + " { a == b };\n" + " { a != b };\n" + " };"); - verifyFormat("template \n" - "requires Foo>() && Bar> {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept HasSizeT = requires { typename T::size_t; };"); - verifyFormat("template \n" - "requires Foo>() && Bar> {\n" - " //....\n" - "}", - Style); + verifyFormat("template \n" + "concept Semiregular =\n" + " DefaultConstructible && CopyConstructible && " + "CopyAssignable &&\n" + " 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 =\n" + " 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" - "requires Foo, Baz>() && Bar, Baz> {\n" - " //....\n" - "}", + "template \n" + "concept Semiregular =\n" + " 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" + " typename T::foo_type;\n" + " { t.foo(u) } -> typename T::foo_type;\n" + " t++;\n" + " };\n" + "void doFoo(FooableWith auto t) { t.foo(3); }"); + + verifyFormat("template \n" + "concept Context = is_specialization_of_v;"); + + verifyFormat("template \n" + "concept Node = std::is_object_v;"); + + auto Style = getLLVMStyle(); + Style.BreakBeforeConceptDeclarations = FormatStyle::BBCDS_Allowed; + + verifyFormat( + "template \n" + "concept C = requires(T t) {\n" + " requires Bar && Foo;\n" + " requires((trait && Baz) || (T2 && Foo));\n" + " };", Style); - Style.IndentRequires = true; - verifyFormat("template \n" - " requires Iterator\n" - "void sort(It begin, It end) {\n" - " //....\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" + " };", Style); - verifyFormat("template \n" - " requires(index_ < sizeof...(Children_))\n" - "Tree auto &child() {\n" - " // ...\n" - "}", + + verifyFormat("template concept True = true;", Style); + + verifyFormat("template \n" + "concept C = decltype([]() -> std::true_type { return {}; " + "}())::value &&\n" + " requires(T t) { t.bar(); } && sizeof(T) <= 8;", Style); - Style.SpaceBeforeParens = FormatStyle::SBPO_Always; verifyFormat("template \n" - "concept Hashable = requires (T a) {\n" - " { std::hash{}(a) } -> std::convertible_to;\n" - "};", + "concept Semiregular =\n" + " DefaultConstructible && CopyConstructible && " + "CopyAssignable &&\n" + " 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" - " requires EqualityComparable || Same\n" - "struct equal_to;", + Style.BreakBeforeConceptDeclarations = FormatStyle::BBCDS_Never; + + verifyFormat("template concept C =\n" + " requires(T t) {\n" + " requires Bar && Foo;\n" + " requires((trait && Baz) || (T2 && Foo));\n" + " };", Style); - verifyFormat("template \n" - " requires requires {\n" - " T{};\n" - " T (int);\n" - " }\n", + 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); - Style.ColumnLimit = 78; + verifyFormat("template concept True = true;", Style); + + verifyFormat( + "template concept C = decltype([]() -> std::true_type {\n" + " return {};\n" + " }())::value\n" + " && requires(T t) { t.bar(); } &&\n" + " sizeof(T) <= 8;", + Style); + + verifyFormat("template concept Semiregular =\n" + " DefaultConstructible && CopyConstructible && " + "CopyAssignable &&\n" + " 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); + + // The following tests are invalid C++, we just want to make sure we don't + // assert. 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", + "concept C = requires C2;"); + + verifyFormat("template \n" + "concept C = 5 + 4;"); + + verifyFormat("template \n" + "concept C =\n" + "class X;"); + + verifyFormat("template \n" + "concept C = [] && true;"); + + verifyFormat("template \n" + "concept C = [] && requires(T t) { typename T::size_type; };"); +} + +TEST_F(FormatTest, RequiresClauses) { + auto Style = getLLVMStyle(); + EXPECT_EQ(Style.RequiresClausePosition, FormatStyle::RCPS_OwnLine); + EXPECT_EQ(Style.IndentRequiresClause, true); + + verifyFormat("template \n" + " requires(Foo && std::trait)\n" + "struct Bar;", 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(Foo && std::trait)\n" + "class Bar {\n" + "public:\n" + " Bar(T t);\n" + " bool baz();\n" + "};", Style); + verifyFormat( + "template \n" + " requires requires(T &&t) {\n" + " typename T::I;\n" + " requires(F && std::trait);\n" + " }\n" + "Bar(T) -> Bar;", + 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(Foo && std::trait)\n" + "constexpr T MyGlobal;", Style); - verifyFormat("template \nconcept someConcept = Constraint1 && " - "Constraint2;"); + verifyFormat("template \n" + " requires Foo && requires(T t) {\n" + " { t.baz() } -> std::same_as;\n" + " requires std::same_as;\n" + " }\n" + "inline int bar(T t) {\n" + " return t.baz() ? T::Factor : 5;\n" + "}", + 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" + verifyFormat("template \n" + "inline int bar(T t)\n" + " requires Foo && requires(T t) {\n" + " { t.baz() } -> std::same_as;\n" + " requires std::same_as;\n" + " }\n" "{\n" - " return\n" - "}\n", + " return t.baz() ? T::Factor : 5;\n" + "}", Style); - verifyFormat("void Foo () requires std::copyable\n" - "{\n" - " return\n" - "}\n", + verifyFormat("template \n" + " requires F\n" + "int bar(T t) {\n" + " return 5;\n" + "}", Style); - verifyFormat("template \n" - " requires (std::invocable...>)\n" - "struct constant;", + verifyFormat("template \n" + "int bar(T t)\n" + " requires F\n" + "{\n" + " return 5;\n" + "}", Style); - verifyFormat("template \n" - " requires std::invocable...>\n" - "struct constant;", + Style.IndentRequiresClause = false; + verifyFormat("template \n" + "requires F\n" + "int bar(T t) {\n" + " return 5;\n" + "}", Style); - verifyFormat("template \n" - "class plane_with_very_very_very_long_name\n" + verifyFormat("template \n" + "int bar(T t)\n" + "requires F\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", + " return 5;\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.RequiresClausePosition = FormatStyle::RCPS_SingleLine; + verifyFormat("template requires Foo struct Bar {};\n" + "template requires Foo void bar() {}\n" + "template void bar() requires Foo {}\n" + "template requires Foo Bar(T) -> Bar;", + Style); + + auto ColumnStyle = Style; + ColumnStyle.ColumnLimit = 40; + verifyFormat("template \n" + "requires Foo struct Bar {};\n" + "template \n" + "requires Foo void bar() {}\n" + "template \n" + "void bar() requires Foo {}\n" + "template \n" + "requires Foo Baz(T) -> Baz;", + ColumnStyle); + + verifyFormat("template \n" + "requires Foo struct Bar {};\n" + "template \n" + "requires Foo void bar() {}\n" + "template \n" + "void bar() requires Foo {}\n" + "template \n" + "requires Foo Bar(T) -> Bar;", + ColumnStyle); + + verifyFormat("template \n" + "requires Foo\n" + "struct Bar {};\n" + "template \n" + "requires Foo\n" + "void bar() {}\n" + "template \n" + "void bar()\n" + " requires Foo {}\n" + "template \n" + "requires Foo Bar(T) -> Bar;\n" + "template \n" + "requires Foo\n" + "Bar(T) -> Bar;", + ColumnStyle); + + Style.RequiresClausePosition = FormatStyle::RCPS_WithFollowing; + ColumnStyle.RequiresClausePosition = FormatStyle::RCPS_WithFollowing; + + verifyFormat("template \n" + "requires Foo struct Bar {};\n" + "template \n" + "requires Foo void bar() {}\n" + "template \n" + "void bar()\n" + "requires Foo {}\n" + "template \n" + "requires Foo Bar(T) -> Bar;", Style); - Style.BreakBeforeConceptDeclarations = false; - verifyFormat("template concept Tree = true;", Style); + verifyFormat("template \n" + "requires Foo\n" + "struct Bar {};\n" + "template \n" + "requires Foo\n" + "void bar() {}\n" + "template \n" + "void bar()\n" + "requires Foo {}\n" + "template \n" + "requires Foo Bar(T) -> Bar;\n" + "template \n" + "requires Foo\n" + "Bar(T) -> Bar;", + ColumnStyle); - Style.IndentRequires = false; - verifyFormat("template \n" - "requires (std::invocable...>) " - "struct constant;", + Style.IndentRequiresClause = true; + ColumnStyle.IndentRequiresClause = true; + + verifyFormat("template \n" + " requires Foo struct Bar {};\n" + "template \n" + " requires Foo void bar() {}\n" + "template \n" + "void bar()\n" + " requires Foo {}\n" + "template \n" + " requires Foo Bar(T) -> Bar;", Style); + + verifyFormat("template \n" + " requires Foo\n" + "struct Bar {};\n" + "template \n" + " requires Foo\n" + "void bar() {}\n" + "template \n" + "void bar()\n" + " requires Foo {}\n" + "template \n" + " requires Foo Bar(T) -> Bar;\n" + "template \n" + " requires Foo\n" + "Bar(T) -> Bar;", + ColumnStyle); + + Style.RequiresClausePosition = FormatStyle::RCPS_WithPreceding; + ColumnStyle.RequiresClausePosition = FormatStyle::RCPS_WithPreceding; + + verifyFormat("template requires Foo\n" + "struct Bar {};\n" + "template requires Foo\n" + "void bar() {}\n" + "template \n" + "void bar() requires Foo\n" + "{}\n" + "template requires Foo\n" + "Bar(T) -> Bar;", + Style); + + verifyFormat("template \n" + "requires Foo\n" + "struct Bar {};\n" + "template \n" + "requires Foo\n" + "void bar() {}\n" + "template \n" + "void bar()\n" + " requires Foo\n" + "{}\n" + "template \n" + "requires Foo\n" + "Bar(T) -> Bar;\n" + "template \n" + "requires Foo\n" + "Bar(T) -> Bar;", + ColumnStyle); } TEST_F(FormatTest, StatementAttributeLikeMacros) { diff --git a/clang/unittests/Format/TokenAnnotatorTest.cpp b/clang/unittests/Format/TokenAnnotatorTest.cpp --- a/clang/unittests/Format/TokenAnnotatorTest.cpp +++ b/clang/unittests/Format/TokenAnnotatorTest.cpp @@ -14,6 +14,14 @@ namespace clang { namespace format { + +// Not really the equality, but everything we need. +static bool operator==(const FormatToken &LHS, + const FormatToken &RHS) noexcept { + return LHS.Tok.getKind() == RHS.Tok.getKind() && + LHS.getType() == RHS.getType(); +} + namespace { class TokenAnnotatorTest : public ::testing::Test { @@ -119,6 +127,261 @@ EXPECT_TOKEN(Tokens[7], tok::r_paren, TT_CastRParen); } +TEST_F(TokenAnnotatorTest, UnderstandsRequiresClausesAndConcepts) { + auto Tokens = annotate("template \n" + "concept C = (Foo && Bar) && (Bar && Baz);"); + + ASSERT_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;"); + ASSERT_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;"); + ASSERT_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); + EXPECT_TRUE(Tokens[31]->ClosesRequiresClause); + + Tokens = + annotate("template\n" + "requires (C1 && (C21 || C22 && C2e) && C3)\n" + "struct Foo;"); + ASSERT_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); + EXPECT_TOKEN(Tokens[33], tok::r_paren, TT_Unknown); + EXPECT_TRUE(Tokens[33]->ClosesRequiresClause); +} + +TEST_F(TokenAnnotatorTest, RequiresDoesNotChangeParsingOfTheRest) { + auto NumberOfAdditionalRequiresClauseTokens = 5u; + auto NumberOfTokensBeforeRequires = 5u; + + auto BaseTokens = annotate("template\n" + "T Pi = 3.14;"); + auto ConstrainedTokens = annotate("template\n" + " requires Foo\n" + "T Pi = 3.14;"); + + auto NumberOfBaseTokens = 11u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "struct Bar;"); + ConstrainedTokens = annotate("template\n" + " requires Foo\n" + "struct Bar;"); + NumberOfBaseTokens = 9u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "struct Bar {" + " T foo();\n" + " T bar();\n" + "};"); + ConstrainedTokens = annotate("template\n" + " requires Foo\n" + "struct Bar {" + " T foo();\n" + " T bar();\n" + "};"); + NumberOfBaseTokens = 21u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "Bar(T) -> Bar;"); + ConstrainedTokens = annotate("template\n" + " requires Foo\n" + "Bar(T) -> Bar;"); + NumberOfBaseTokens = 16u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "T foo();"); + ConstrainedTokens = annotate("template\n" + " requires Foo\n" + "T foo();"); + NumberOfBaseTokens = 11u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "T foo() {\n" + " auto bar = baz();\n" + " return bar + T{};\n" + "}"); + ConstrainedTokens = annotate("template\n" + " requires Foo\n" + "T foo() {\n" + " auto bar = baz();\n" + " return bar + T{};\n" + "}"); + NumberOfBaseTokens = 26u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "T foo();"); + ConstrainedTokens = annotate("template\n" + "T foo() requires Foo;"); + NumberOfBaseTokens = 11u; + NumberOfTokensBeforeRequires = 9u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "T foo() {\n" + " auto bar = baz();\n" + " return bar + T{};\n" + "}"); + ConstrainedTokens = annotate("template\n" + "T foo() requires Foo {\n" + " auto bar = baz();\n" + " return bar + T{};\n" + "}"); + NumberOfBaseTokens = 26u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; + + BaseTokens = annotate("template\n" + "Bar(T) -> Bar;"); + ConstrainedTokens = annotate("template\n" + " requires requires(T &&t) {\n" + " typename T::I;\n" + " }\n" + "Bar(T) -> Bar;"); + NumberOfBaseTokens = 19u; + NumberOfAdditionalRequiresClauseTokens = 14u; + NumberOfTokensBeforeRequires = 5u; + + ASSERT_EQ(BaseTokens.size(), NumberOfBaseTokens) << BaseTokens; + ASSERT_EQ(ConstrainedTokens.size(), + NumberOfBaseTokens + NumberOfAdditionalRequiresClauseTokens) + << ConstrainedTokens; + + for (auto I = 0u; I < NumberOfBaseTokens; ++I) + if (I < NumberOfTokensBeforeRequires) + EXPECT_EQ(*BaseTokens[I], *ConstrainedTokens[I]) << I; + else + EXPECT_EQ(*BaseTokens[I], + *ConstrainedTokens[I + NumberOfAdditionalRequiresClauseTokens]) + << I; +} + } // namespace } // namespace format } // namespace clang