diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3827,6 +3827,35 @@ +**RequiresExpressionIndentation** (``RequiresExpressionIndentationKind``) :versionbadge:`clang-format 15` + 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 @@ -369,6 +369,32 @@ /// \version 3.5 OperandAlignmentStyle AlignOperands; + /// 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 15 + RequiresExpressionIndentationKind RequiresExpressionIndentation; + /// If ``true``, aligns trailing comments. /// \code /// true: false: @@ -3856,6 +3882,7 @@ AlignConsecutiveMacros == R.AlignConsecutiveMacros && AlignEscapedNewlines == R.AlignEscapedNewlines && AlignOperands == R.AlignOperands && + RequiresExpressionIndentation == R.RequiresExpressionIndentation && AlignTrailingComments == R.AlignTrailingComments && AllowAllArgumentsOnNextLine == R.AllowAllArgumentsOnNextLine && AllowAllParametersOfDeclarationOnNextLine == 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 @@ -1398,7 +1398,8 @@ 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. 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); @@ -646,6 +655,8 @@ IO.mapOptional("AlignConsecutiveMacros", Style.AlignConsecutiveMacros); IO.mapOptional("AlignEscapedNewlines", Style.AlignEscapedNewlines); IO.mapOptional("AlignOperands", Style.AlignOperands); + IO.mapOptional("RequiresExpressionIndentation", + Style.RequiresExpressionIndentation); IO.mapOptional("AlignTrailingComments", Style.AlignTrailingComments); IO.mapOptional("AllowAllArgumentsOnNextLine", Style.AllowAllArgumentsOnNextLine); @@ -1287,6 +1298,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 @@ -24719,6 +24719,106 @@ "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 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 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"