diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3861,6 +3861,35 @@ +**RequiresExpressionIndentation** (``RequiresExpressionIndentationKind``) :versionbadge:`clang-format 16` + The indentation used for requires expression bodies. + + Possible values: + + * ``REI_OuterScope`` (in configuration: ``OuterScope``) + Align requires expression body relative to the indentation level of the + outer scope the requires expression resides in. + This is the default. + + .. code-block:: c++ + + template + concept C = requires(T t) { + ... + } + + * ``REI_Keyword`` (in configuration: ``Keyword``) + Align requires expression body relative to the `requires` keyword. + + .. 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/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -629,7 +629,10 @@ clang-format ------------ -- Add `RemoveSemicolon` option for removing `;` after a non-empty function definition. +- Add ``RemoveSemicolon`` option for removing ``;`` after a non-empty function definition. +- Add ``RequiresExpressionIndentation`` option for configuring the alignment of requires-expressions. + The default value of this option is ``OuterScope``, which differs in behavior from clang-format 15. + To match the default behavior of clang-format 15, use the ``Keyword`` value. clang-extdef-mapping -------------------- 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 @@ -3153,6 +3153,32 @@ /// \version 15 RequiresClausePositionStyle RequiresClausePosition; + /// Indentation logic for requires expression bodies. + enum RequiresExpressionIndentationKind : int8_t { + /// Align requires expression body relative to the indentation level of the + /// outer scope the requires expression resides in. + /// This is the default. + /// \code + /// template + /// concept C = requires(T t) { + /// ... + /// } + /// \endcode + REI_OuterScope, + /// Align requires expression body relative to the `requires` keyword. + /// \code + /// template + /// concept C = requires(T t) { + /// ... + /// } + /// \endcode + REI_Keyword, + }; + + /// 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. @@ -3988,6 +4014,7 @@ RemoveBracesLLVM == R.RemoveBracesLLVM && RemoveSemicolon == R.RemoveSemicolon && 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 @@ -1406,8 +1406,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); @@ -854,6 +863,8 @@ IO.mapOptional("RemoveBracesLLVM", Style.RemoveBracesLLVM); IO.mapOptional("RemoveSemicolon", Style.RemoveSemicolon); 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); @@ -1290,6 +1301,7 @@ LLVMStyle.PointerAlignment = FormatStyle::PAS_Right; LLVMStyle.ReferenceAlignment = FormatStyle::RAS_Pointer; LLVMStyle.RequiresClausePosition = FormatStyle::RCPS_OwnLine; + LLVMStyle.RequiresExpressionIndentation = FormatStyle::REI_OuterScope; 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 @@ -24334,6 +24334,12 @@ TEST_F(FormatTest, Concepts) { EXPECT_EQ(getLLVMStyle().BreakBeforeConceptDeclarations, FormatStyle::BBCDS_Always); + + // The default in LLVM style is REI_OuterScope, but these tests were written + // when the default was REI_Keyword. + FormatStyle Style = getLLVMStyle(); + Style.RequiresExpressionIndentation = FormatStyle::REI_Keyword; + verifyFormat("template \n" "concept True = true;"); @@ -24350,13 +24356,15 @@ "concept DelayedCheck = true && requires(T t) {\n" " t.bar();\n" " t.baz();\n" - " } && sizeof(T) <= 8;"); + " } && sizeof(T) <= 8;", + Style); verifyFormat("template \n" "concept DelayedCheck = true && requires(T t) { // Comment\n" " t.bar();\n" " t.baz();\n" - " } && sizeof(T) <= 8;"); + " } && sizeof(T) <= 8;", + Style); verifyFormat("template \n" "concept DelayedCheck = false || requires(T t) { t.bar(); } && " @@ -24458,26 +24466,30 @@ "concept Hashable = requires(T a) {\n" " { std::hash{}(a) } -> " "std::convertible_to;\n" - " };"); + " };", + Style); verifyFormat( "template \n" "concept EqualityComparable = requires(T a, T b) {\n" " { a == b } -> std::same_as;\n" - " };"); + " };", + Style); verifyFormat( "template \n" "concept EqualityComparable = requires(T a, T b) {\n" " { a == b } -> std::same_as;\n" " { a != b } -> std::same_as;\n" - " };"); + " };", + Style); verifyFormat("template \n" "concept WeakEqualityComparable = requires(T a, T b) {\n" " { a == b };\n" " { a != b };\n" - " };"); + " };", + Style); verifyFormat("template \n" "concept HasSizeT = requires { typename T::size_t; };"); @@ -24493,7 +24505,8 @@ " requires Same;\n" " { delete new T; };\n" " { delete new T[n]; };\n" - " };"); + " };", + Style); verifyFormat("template \n" "concept Semiregular =\n" @@ -24506,7 +24519,8 @@ " { delete new T[n]; };\n" " { new T } -> std::same_as;\n" " } && DefaultConstructible && CopyConstructible && " - "CopyAssignable;"); + "CopyAssignable;", + Style); verifyFormat( "template \n" @@ -24520,14 +24534,16 @@ " { delete new T; };\n" " { delete new T[n]; };\n" " } && CopyConstructible && " - "CopyAssignable;"); + "CopyAssignable;", + Style); 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" - " };"); + " };", + Style); verifyFormat( "template \n" @@ -24535,7 +24551,8 @@ " { *x } -> std::convertible_to;\n" " { x + 1 } noexcept -> std::same_as;\n" " { x * 1 } -> std::convertible_to;\n" - " };"); + " };", + Style); verifyFormat("template \n" "concept C = requires(T x) {\n" @@ -24545,33 +24562,38 @@ " {\n" " long_long_long_function_call(1, 2, 3, 4, 5)\n" " } noexcept -> long_long_concept_name;\n" - " };"); + " };", + Style); 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" - " };"); + " };", + Style); verifyFormat("template \n" "concept Common = requires(T &&t, U &&u) {\n" " typename CommonType;\n" " { CommonType(std::forward(t)) };\n" - " };"); + " };", + Style); verifyFormat("template \n" "concept Common = requires(T &&t, U &&u) {\n" " typename CommonType;\n" " { CommonType{std::forward(t)} };\n" - " };"); + " };", + Style); verifyFormat( "template \n" "concept C = requires(T t) {\n" " requires Bar && Foo;\n" " requires((trait && Baz) || (T2 && Foo));\n" - " };"); + " };", + Style); verifyFormat("template \n" "concept HasFoo = requires(T t) {\n" @@ -24582,7 +24604,8 @@ "concept HasBar = requires(T t) {\n" " { t.bar() };\n" " t.bar();\n" - " };"); + " };", + Style); verifyFormat("template \n" "concept Large = sizeof(T) > 10;"); @@ -24593,7 +24616,8 @@ " { t.foo(u) } -> typename T::foo_type;\n" " t++;\n" " };\n" - "void doFoo(FooableWith auto t) { t.foo(3); }"); + "void doFoo(FooableWith auto t) { t.foo(3); }", + Style); verifyFormat("template \n" "concept Context = is_specialization_of_v;"); @@ -24619,7 +24643,6 @@ " std::forward_iterator<_OutIt> &&\n" " std::same_as, std::iter_value_t<_OutIt>>;"); - auto Style = getLLVMStyle(); Style.BreakBeforeConceptDeclarations = FormatStyle::BBCDS_Allowed; verifyFormat( @@ -24730,6 +24753,10 @@ EXPECT_EQ(Style.RequiresClausePosition, FormatStyle::RCPS_OwnLine); EXPECT_EQ(Style.IndentRequiresClause, true); + // The default in LLVM style is REI_OuterScope, but these tests were written + // when the default was REI_Keyword. + Style.RequiresExpressionIndentation = FormatStyle::REI_Keyword; + verifyFormat("template \n" " requires(Foo && std::trait)\n" "struct Bar;", @@ -25021,6 +25048,123 @@ "bar(requires);"); } +TEST_F(FormatTest, RequiresExpressionIndentation) { + auto Style = getLLVMStyle(); + EXPECT_EQ(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); + + 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); +} + TEST_F(FormatTest, StatementAttributeLikeMacros) { FormatStyle Style = getLLVMStyle(); StringRef Source = "void Foo::slot() {\n"