diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -236,6 +236,9 @@ on the formatted new lines and not on the new lines in the file. (Fixes https://llvm.org/PR41870.) +- Option ``SpacesInAngles`` has been improved, it now accepts ``Leave`` value + that allows to keep spaces where they are already present. + 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 @@ -2994,14 +2994,27 @@ /// \endcode unsigned SpacesBeforeTrailingComments; - /// If ``true``, spaces will be inserted after ``<`` and before ``>`` - /// in template argument lists. - /// \code - /// true: false: - /// static_cast< int >(arg); vs. static_cast(arg); - /// std::function< void(int) > fct; std::function fct; - /// \endcode - bool SpacesInAngles; + /// Styles for adding spacing after ``<`` and before ``>` + /// in template argument lists. + enum SpacesInAnglesStyle : unsigned char { + /// Remove spaces after ``<`` and before ``>``. + /// \code + /// static_cast(arg); + /// std::function fct; + /// \endcode + SIAS_Never, + /// Add spaces after ``<`` and before ``>``. + /// \code + /// static_cast< int >(arg); + /// std::function< void(int) > fct; + /// \endcode + SIAS_Always, + /// Keep a single space after ``<`` and before ``>`` if any spaces were + /// present. Option ``Standard: Cpp03`` takes precedence. + SIAS_Leave + }; + /// The SpacesInAnglesStyle to use for template argument lists. + SpacesInAnglesStyle SpacesInAngles; /// If ``true``, spaces will be inserted around if/for/switch/while /// conditions. 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 @@ -446,6 +446,18 @@ } }; +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FormatStyle::SpacesInAnglesStyle &Value) { + IO.enumCase(Value, "Never", FormatStyle::SIAS_Never); + IO.enumCase(Value, "Always", FormatStyle::SIAS_Always); + IO.enumCase(Value, "Leave", FormatStyle::SIAS_Leave); + + // For backward compatibility. + IO.enumCase(Value, "false", FormatStyle::SIAS_Never); + IO.enumCase(Value, "true", FormatStyle::SIAS_Always); + } +}; + template <> struct MappingTraits { static void mapping(IO &IO, FormatStyle &Style) { // When reading, read the language first, we need it for getPredefinedStyle. @@ -1046,7 +1058,7 @@ LLVMStyle.SpaceBeforeCpp11BracedList = false; LLVMStyle.SpaceBeforeSquareBrackets = false; LLVMStyle.BitFieldColonSpacing = FormatStyle::BFCS_Both; - LLVMStyle.SpacesInAngles = false; + LLVMStyle.SpacesInAngles = FormatStyle::SIAS_Never; LLVMStyle.SpacesInConditionalStatement = false; LLVMStyle.PenaltyBreakAssignment = prec::Assignment; 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 @@ -3094,6 +3094,9 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line, const FormatToken &Right) { const FormatToken &Left = *Right.Previous; + auto HasExistingWhitespace = [&Whitespace = Right.WhitespaceRange]() { + return Whitespace.getBegin() != Whitespace.getEnd(); + }; if (Right.Tok.getIdentifierInfo() && Left.Tok.getIdentifierInfo()) return true; // Never ever merge two identifiers. if (Style.isCpp()) { @@ -3126,7 +3129,7 @@ // Preserve the existence of a space before a percent for cases like 0x%04x // and "%d %d" if (Left.is(tok::numeric_constant) && Right.is(tok::percent)) - return Right.WhitespaceRange.getEnd() != Right.WhitespaceRange.getBegin(); + return HasExistingWhitespace(); } else if (Style.isCSharp()) { // Require spaces around '{' and before '}' unless they appear in // interpolated strings. Interpolated strings are merged into a single token @@ -3319,7 +3322,7 @@ return true; } if (Left.is(TT_ImplicitStringLiteral)) - return Right.WhitespaceRange.getBegin() != Right.WhitespaceRange.getEnd(); + return HasExistingWhitespace(); if (Line.Type == LT_ObjCMethodDecl) { if (Left.is(TT_ObjCMethodSpecifier)) return true; @@ -3406,12 +3409,22 @@ return Style.SpaceAfterCStyleCast || Right.isOneOf(TT_BinaryOperator, TT_SelectorName); + auto ShouldAddSpacesInAngles = [&Style = this->Style, + &HasExistingWhitespace]() { + if (Style.SpacesInAngles == FormatStyle::SIAS_Always) + return true; + if (Style.SpacesInAngles == FormatStyle::SIAS_Leave) + return HasExistingWhitespace(); + return false; + }; + if (Left.is(tok::greater) && Right.is(tok::greater)) { if (Style.Language == FormatStyle::LK_TextProto || (Style.Language == FormatStyle::LK_Proto && Left.is(TT_DictLiteral))) return !Style.Cpp11BracedListStyle; return Right.is(TT_TemplateCloser) && Left.is(TT_TemplateCloser) && - (Style.Standard < FormatStyle::LS_Cpp11 || Style.SpacesInAngles); + ((Style.Standard < FormatStyle::LS_Cpp11) || + ShouldAddSpacesInAngles()); } if (Right.isOneOf(tok::arrow, tok::arrowstar, tok::periodstar) || Left.isOneOf(tok::arrow, tok::period, tok::arrowstar, tok::periodstar) || @@ -3427,18 +3440,19 @@ // Generally don't remove existing spaces between an identifier and "::". // The identifier might actually be a macro name such as ALWAYS_INLINE. If // this turns out to be too lenient, add analysis of the identifier itself. - return Right.WhitespaceRange.getBegin() != Right.WhitespaceRange.getEnd(); + return HasExistingWhitespace(); if (Right.is(tok::coloncolon) && !Left.isOneOf(tok::l_brace, tok::comment, tok::l_paren)) // Put a space between < and :: in vector< ::std::string > return (Left.is(TT_TemplateOpener) && - (Style.Standard < FormatStyle::LS_Cpp11 || Style.SpacesInAngles)) || + ((Style.Standard < FormatStyle::LS_Cpp11) || + ShouldAddSpacesInAngles())) || !(Left.isOneOf(tok::l_paren, tok::r_paren, tok::l_square, tok::kw___super, TT_TemplateOpener, TT_TemplateCloser)) || (Left.is(tok::l_paren) && Style.SpacesInParentheses); if ((Left.is(TT_TemplateOpener)) != (Right.is(TT_TemplateCloser))) - return Style.SpacesInAngles; + return ShouldAddSpacesInAngles(); // Space before TT_StructuredBindingLSquare. if (Right.is(TT_StructuredBindingLSquare)) return !Left.isOneOf(tok::amp, tok::ampamp) || 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 @@ -10286,7 +10286,7 @@ verifyFormat("f({}, {{}, {}}, MyMap[{k, v}]);", SpaceBeforeBrace); FormatStyle SpaceBetweenBraces = getLLVMStyle(); - SpaceBetweenBraces.SpacesInAngles = true; + SpaceBetweenBraces.SpacesInAngles = FormatStyle::SIAS_Always; SpaceBetweenBraces.SpacesInParentheses = true; SpaceBetweenBraces.SpacesInSquareBrackets = true; verifyFormat("vector< int > x{ 1, 2, 3, 4 };", SpaceBetweenBraces); @@ -16308,7 +16308,6 @@ CHECK_PARSE_BOOL(SortUsingDeclarations); CHECK_PARSE_BOOL(SpacesInParentheses); CHECK_PARSE_BOOL(SpacesInSquareBrackets); - CHECK_PARSE_BOOL(SpacesInAngles); CHECK_PARSE_BOOL(SpacesInConditionalStatement); CHECK_PARSE_BOOL(SpaceInEmptyBlock); CHECK_PARSE_BOOL(SpaceInEmptyParentheses); @@ -16865,6 +16864,15 @@ " Maximum: 1", SpacesInLineCommentPrefix.Maximum, 1u); EXPECT_EQ(Style.SpacesInLineCommentPrefix.Minimum, 1u); + + Style.SpacesInAngles = FormatStyle::SIAS_Always; + CHECK_PARSE("SpacesInAngles: Never", SpacesInAngles, FormatStyle::SIAS_Never); + CHECK_PARSE("SpacesInAngles: Always", SpacesInAngles, + FormatStyle::SIAS_Always); + CHECK_PARSE("SpacesInAngles: Leave", SpacesInAngles, FormatStyle::SIAS_Leave); + // For backward compatibility: + CHECK_PARSE("SpacesInAngles: false", SpacesInAngles, FormatStyle::SIAS_Never); + CHECK_PARSE("SpacesInAngles: true", SpacesInAngles, FormatStyle::SIAS_Always); } TEST_F(FormatTest, ParsesConfigurationWithLanguages) { @@ -18442,7 +18450,7 @@ TEST_F(FormatTest, SpacesInAngles) { FormatStyle Spaces = getLLVMStyle(); - Spaces.SpacesInAngles = true; + Spaces.SpacesInAngles = FormatStyle::SIAS_Always; verifyFormat("vector< ::std::string > x1;", Spaces); verifyFormat("Foo< int, Bar > x2;", Spaces); @@ -18458,23 +18466,48 @@ Spaces); Spaces.Standard = FormatStyle::LS_Cpp03; - Spaces.SpacesInAngles = true; + Spaces.SpacesInAngles = FormatStyle::SIAS_Always; verifyFormat("A< A< int > >();", Spaces); - Spaces.SpacesInAngles = false; + Spaces.SpacesInAngles = FormatStyle::SIAS_Never; + verifyFormat("A >();", Spaces); + + Spaces.SpacesInAngles = FormatStyle::SIAS_Leave; + verifyFormat("vector< ::std::string> x4;", "vector<::std::string> x4;", + Spaces); + verifyFormat("vector< ::std::string > x4;", "vector<::std::string > x4;", + Spaces); + verifyFormat("A >();", Spaces); + verifyFormat("A >();", "A>();", Spaces); + verifyFormat("A< A< int > >();", Spaces); Spaces.Standard = FormatStyle::LS_Cpp11; - Spaces.SpacesInAngles = true; + Spaces.SpacesInAngles = FormatStyle::SIAS_Always; verifyFormat("A< A< int > >();", Spaces); - Spaces.SpacesInAngles = false; + Spaces.SpacesInAngles = FormatStyle::SIAS_Never; verifyFormat("vector<::std::string> x4;", Spaces); verifyFormat("vector x5;", Spaces); verifyFormat("Foo x6;", Spaces); verifyFormat("Foo<::int, ::Bar> x7;", Spaces); verifyFormat("A>();", Spaces); + + Spaces.SpacesInAngles = FormatStyle::SIAS_Leave; + verifyFormat("vector<::std::string> x4;", Spaces); + verifyFormat("vector< ::std::string > x4;", Spaces); + verifyFormat("vector x5;", Spaces); + verifyFormat("vector< int > x5;", Spaces); + verifyFormat("Foo x6;", Spaces); + verifyFormat("Foo< int, Bar > x6;", Spaces); + verifyFormat("Foo<::int, ::Bar> x7;", Spaces); + verifyFormat("Foo< ::int, ::Bar > x7;", Spaces); + + verifyFormat("A>();", Spaces); + verifyFormat("A< A< int > >();", Spaces); + verifyFormat("A >();", Spaces); + verifyFormat("A< A< int>>();", Spaces); } TEST_F(FormatTest, SpaceAfterTemplateKeyword) {