diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3844,6 +3844,35 @@ +**RequiresExpressionIndentation** (``RequiresExpressionIndentationKind``) :versionbadge:`clang-format 16` + The indentation used for requires expression bodies. + + Possible values: + + * ``REI_Keyword`` (in configuration: ``Keyword``) + Align requires expression body relative to the `requires` keyword. + This is the default. + + .. code-block:: c++ + + template + concept C = requires(T t) { + ... + } + + * ``REI_OuterScope`` (in configuration: ``OuterScope``) + Align requires expression body relative to the indentation level of the + outer scope the requires expression resides in. + + .. code-block:: c++ + + template + concept C = requires(T t) { + ... + } + + + **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/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 @@ -3136,6 +3136,32 @@ /// \version 15 RequiresClausePositionStyle RequiresClausePosition; + /// Indentation logic for requires expression bodies. + enum RequiresExpressionIndentationKind : int8_t { + /// Align requires expression body relative to the `requires` keyword. + /// This is the default. + /// \code + /// template + /// concept C = requires(T t) { + /// ... + /// } + /// \endcode + REI_Keyword, + /// Align requires expression body relative to the indentation level of the + /// outer scope the requires expression resides in. + /// \code + /// template + /// concept C = requires(T t) { + /// ... + /// } + /// \endcode + REI_OuterScope, + }; + + /// The indentation used for requires expression bodies. + /// \version 16 + RequiresExpressionIndentationKind RequiresExpressionIndentation; + /// \brief The style if definition blocks should be separated. enum SeparateDefinitionStyle : int8_t { /// Leave definition blocks as they are. @@ -3970,6 +3996,7 @@ ReferenceAlignment == R.ReferenceAlignment && RemoveBracesLLVM == R.RemoveBracesLLVM && RequiresClausePosition == R.RequiresClausePosition && + RequiresExpressionIndentation == R.RequiresExpressionIndentation && 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 @@ -1403,8 +1403,10 @@ CurrentState.NestedBlockIndent = State.Column + Current.ColumnWidth + 1; if (Current.isOneOf(TT_LambdaLSquare, TT_LambdaArrow)) CurrentState.LastSpace = State.Column; - if (Current.is(TT_RequiresExpression)) + if (Current.is(TT_RequiresExpression) && + Style.RequiresExpressionIndentation == FormatStyle::REI_Keyword) { CurrentState.NestedBlockIndent = State.Column; + } // Insert scopes created by fake parenthesis. const FormatToken *Previous = Current.getPreviousNonComment(); 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 @@ -99,6 +99,15 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void + enumeration(IO &IO, FormatStyle::RequiresExpressionIndentationKind &Value) { + IO.enumCase(Value, "Keyword", FormatStyle::REI_Keyword); + IO.enumCase(Value, "OuterScope", FormatStyle::REI_OuterScope); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::UseTabStyle &Value) { IO.enumCase(Value, "Never", FormatStyle::UT_Never); @@ -853,6 +862,8 @@ IO.mapOptional("ReflowComments", Style.ReflowComments); IO.mapOptional("RemoveBracesLLVM", Style.RemoveBracesLLVM); IO.mapOptional("RequiresClausePosition", Style.RequiresClausePosition); + IO.mapOptional("RequiresExpressionIndentation", + Style.RequiresExpressionIndentation); IO.mapOptional("SeparateDefinitionBlocks", Style.SeparateDefinitionBlocks); IO.mapOptional("ShortNamespaceLines", Style.ShortNamespaceLines); IO.mapOptional("SortIncludes", Style.SortIncludes); @@ -1289,6 +1300,7 @@ LLVMStyle.PointerAlignment = FormatStyle::PAS_Right; LLVMStyle.ReferenceAlignment = FormatStyle::RAS_Pointer; LLVMStyle.RequiresClausePosition = FormatStyle::RCPS_OwnLine; + LLVMStyle.RequiresExpressionIndentation = FormatStyle::REI_Keyword; LLVMStyle.SeparateDefinitionBlocks = FormatStyle::SDS_Leave; LLVMStyle.ShortNamespaceLines = 1; LLVMStyle.SpacesBeforeTrailingComments = 1; 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 @@ -24984,6 +24984,123 @@ "bar(requires);"); } +TEST_F(FormatTest, RequiresExpressionIndentation) { + auto Style = getLLVMStyle(); + EXPECT_EQ(Style.RequiresExpressionIndentation, FormatStyle::REI_Keyword); + + verifyFormat("template \n" + "concept C = requires(T t) {\n" + " typename T::value;\n" + " requires requires(typename T::value v) {\n" + " { t == v } -> std::same_as;\n" + " };\n" + " };", + Style); + + verifyFormat( + "template \n" + "void bar(T)\n" + " requires Foo && requires(T t) {\n" + " { t.foo() } -> std::same_as;\n" + " } && requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " --t;\n" + " };", + Style); + + verifyFormat("template \n" + " requires Foo &&\n" + " requires(T t) {\n" + " { t.foo() } -> std::same_as;\n" + " } && requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " --t;\n" + " }\n" + "void bar(T);", + Style); + + verifyFormat("template void f() {\n" + " if constexpr (requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " }) {\n" + " }\n" + "}", + Style); + + verifyFormat( + "template void f() {\n" + " if constexpr (condition && requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " }) {\n" + " }\n" + "}", + Style); + + verifyFormat("template struct C {\n" + " void f()\n" + " requires requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " };\n" + "};", + Style); + + Style.RequiresExpressionIndentation = FormatStyle::REI_OuterScope; + + verifyFormat("template \n" + "concept C = requires(T t) {\n" + " typename T::value;\n" + " requires requires(typename T::value v) {\n" + " { t == v } -> std::same_as;\n" + " };\n" + "};", + Style); + + verifyFormat("template \n" + "void bar(T)\n" + " requires Foo && requires(T t) {\n" + " { t.foo() } -> std::same_as;\n" + " } && requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " --t;\n" + " };", + Style); + + verifyFormat("template \n" + " requires Foo &&\n" + " requires(T t) {\n" + " { t.foo() } -> std::same_as;\n" + " } && requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " --t;\n" + " }\n" + "void bar(T);", + Style); + + verifyFormat("template void f() {\n" + " if constexpr (requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " }) {\n" + " }\n" + "}", + Style); + + verifyFormat("template void f() {\n" + " if constexpr (condition && requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " }) {\n" + " }\n" + "}", + Style); + + verifyFormat("template struct C {\n" + " void f()\n" + " requires requires(T t) {\n" + " { t.bar() } -> std::same_as;\n" + " };\n" + "};", + Style); +} + TEST_F(FormatTest, StatementAttributeLikeMacros) { FormatStyle Style = getLLVMStyle(); StringRef Source = "void Foo::slot() {\n"