Index: lib/Format/WhitespaceManager.h =================================================================== --- lib/Format/WhitespaceManager.h +++ lib/Format/WhitespaceManager.h @@ -108,7 +108,7 @@ Change(bool CreateReplacement, SourceRange OriginalWhitespaceRange, unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore, StringRef PreviousLinePostfix, - StringRef CurrentLinePrefix, tok::TokenKind Kind, + StringRef CurrentLinePrefix, tok::TokenKind Kind, TokenType Type, bool ContinuesPPDirective, bool IsStartOfDeclName, bool IsInsideToken); @@ -125,6 +125,8 @@ // FIXME: Currently this is not set correctly for breaks inside comments, as // the \c BreakableToken is still doing its own alignment. tok::TokenKind Kind; + // Same comment applies as for Kind + TokenType Type; bool ContinuesPPDirective; bool IsStartOfDeclName; @@ -162,6 +164,13 @@ // the alignment process. const Change *StartOfBlockComment; int IndentationOffset; + // This is the same concept as FormatToken::NestingLevel, but we give + // it a different name, to signify that it is computed independently of + // NestingLevel. Ideally, we could just use NestingLevel, but braces + // are currently ignored by the NestingLevel, which makes them unusable. + // This is easier to use when signed (vs unsigned), so that it can go + // down to -1. + int ScopeLevel; }; private: @@ -170,6 +179,9 @@ /// \c EscapedNewlineColumn for the first tokens or token parts in a line. void calculateLineBreakInformation(); + /// \brief Calculate \c ScopeLevel for all \c Changes. + void calculateScopeLevel(); + /// \brief Align consecutive assignments over all \c Changes. void alignConsecutiveAssignments(); Index: lib/Format/WhitespaceManager.cpp =================================================================== --- lib/Format/WhitespaceManager.cpp +++ lib/Format/WhitespaceManager.cpp @@ -29,18 +29,18 @@ bool CreateReplacement, SourceRange OriginalWhitespaceRange, unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore, StringRef PreviousLinePostfix, - StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective, - bool IsStartOfDeclName, bool IsInsideToken) + StringRef CurrentLinePrefix, tok::TokenKind Kind, TokenType Type, + bool ContinuesPPDirective, bool IsStartOfDeclName, bool IsInsideToken) : CreateReplacement(CreateReplacement), OriginalWhitespaceRange(OriginalWhitespaceRange), StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore), PreviousLinePostfix(PreviousLinePostfix), - CurrentLinePrefix(CurrentLinePrefix), Kind(Kind), + CurrentLinePrefix(CurrentLinePrefix), Kind(Kind), Type(Type), ContinuesPPDirective(ContinuesPPDirective), IsStartOfDeclName(IsStartOfDeclName), IndentLevel(IndentLevel), Spaces(Spaces), IsInsideToken(IsInsideToken), IsTrailingComment(false), TokenLength(0), PreviousEndOfTokenColumn(0), EscapedNewlineColumn(0), - StartOfBlockComment(nullptr), IndentationOffset(0) {} + StartOfBlockComment(nullptr), IndentationOffset(0), ScopeLevel(0) {} void WhitespaceManager::reset() { Changes.clear(); @@ -57,7 +57,7 @@ Changes.push_back( Change(/*CreateReplacement=*/true, Tok.WhitespaceRange, IndentLevel, Spaces, StartOfTokenColumn, Newlines, "", "", Tok.Tok.getKind(), - InPPDirective && !Tok.IsFirst, + Tok.Type, InPPDirective && !Tok.IsFirst, Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName), /*IsInsideToken=*/false)); } @@ -69,7 +69,7 @@ Changes.push_back(Change( /*CreateReplacement=*/false, Tok.WhitespaceRange, /*IndentLevel=*/0, /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore, "", "", - Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst, + Tok.Tok.getKind(), Tok.Type, InPPDirective && !Tok.IsFirst, Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName), /*IsInsideToken=*/false)); } @@ -85,7 +85,7 @@ true, SourceRange(Start, Start.getLocWithOffset(ReplaceChars)), IndentLevel, Spaces, std::max(0, Spaces), Newlines, PreviousPostfix, CurrentPrefix, Tok.is(TT_LineComment) ? tok::comment : tok::unknown, - InPPDirective && !Tok.IsFirst, + Tok.Type, InPPDirective && !Tok.IsFirst, Tok.is(TT_StartOfName) || Tok.is(TT_FunctionDeclarationName), /*IsInsideToken=*/Newlines == 0)); } @@ -95,6 +95,7 @@ return Replaces; std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr)); + calculateScopeLevel(); calculateLineBreakInformation(); alignConsecutiveDeclarations(); alignConsecutiveAssignments(); @@ -160,6 +161,51 @@ } } +static tok::TokenKind OppositeNestingToken(tok::TokenKind t) { + switch (t) { + case tok::l_paren: + return tok::r_paren; + case tok::l_brace: + return tok::r_brace; + case tok::l_square: + return tok::r_square; + default: + assert(false && "Not a nesting token"); + return tok::unknown; + } +} + +struct TokenTypeAndLevel { + TokenType Type; + int ScopeLevel; +}; + +// See comment in Change::ScopeLevel for an explanation of why we need +// to run this function instead of just using FormatToken::NestingLevel +void WhitespaceManager::calculateScopeLevel() { + SmallVector ScopeStack; + for (auto &Change : Changes) { + Change.ScopeLevel = ScopeStack.size(); + + if (ScopeStack.size() != 0 && Change.Kind == ScopeStack.back()) + ScopeStack.pop_back(); + + switch (Change.Kind) { + case tok::l_paren: + case tok::l_square: + case tok::l_brace: + ScopeStack.push_back(OppositeNestingToken(Change.Kind)); + break; + case tok::less: + if (Change.Type == TT_TemplateOpener) + ScopeStack.push_back(tok::greater); + break; + default: + break; + } + } +} + // Align a single sequence of tokens, see AlignTokens below. template static void @@ -167,21 +213,51 @@ SmallVector &Changes) { bool FoundMatchOnLine = false; int Shift = 0; + + // ScopeStack keeps track of the current scope depth (aka NestingLevel). + // We only run the "Matches" function on tokens from the outer-most scope. + // However, we do need to adjust some special tokens within the entire + // Start..End range, regardless of their scope. This special rule applies + // only to function parameters which are split across multiple lines, and + // who's function names have been shifted for declaration alignment's sake. + // See "split function parameter alignment" in FormatTest.cpp for an example. + SmallVector ScopeStack; + for (unsigned i = Start; i != End; ++i) { - if (Changes[i].NewlinesBefore > 0) { - FoundMatchOnLine = false; + if (ScopeStack.size() != 0 && + Changes[i].ScopeLevel < ScopeStack.back().ScopeLevel) + ScopeStack.pop_back(); + + if (i != Start) { + if (Changes[i].ScopeLevel > Changes[i - 1].ScopeLevel) { + ScopeStack.push_back({Changes[i - 1].Type, Changes[i].ScopeLevel}); + if (i > Start + 1 && Changes[i - 2].Type == TT_FunctionDeclarationName) + ScopeStack.back().Type = Changes[i - 2].Type; + } + } + + bool InsideNestedScope = ScopeStack.size() != 0; + + if (Changes[i].NewlinesBefore > 0 && !InsideNestedScope) { Shift = 0; + FoundMatchOnLine = false; } // If this is the first matching token to be aligned, remember by how many // spaces it has to be shifted, so the rest of the changes on the line are // shifted by the same amount - if (!FoundMatchOnLine && Matches(Changes[i])) { + if (!FoundMatchOnLine && !InsideNestedScope && Matches(Changes[i])) { FoundMatchOnLine = true; Shift = Column - Changes[i].StartOfTokenColumn; Changes[i].Spaces += Shift; } + // This is for function parameters that are split across multiple lines, + // as mentioned in the ScopeStack comment. + if (InsideNestedScope && Changes[i].NewlinesBefore > 0 && + ScopeStack.back().Type == TT_FunctionDeclarationName) + Changes[i].Spaces += Shift; + assert(Shift >= 0); Changes[i].StartOfTokenColumn += Shift; if (i + 1 != Changes.size()) @@ -189,15 +265,39 @@ } } -// Walk through all of the changes and find sequences of matching tokens to -// align. To do so, keep track of the lines and whether or not a matching token -// was found on a line. If a matching token is found, extend the current -// sequence. If the current line cannot be part of a sequence, e.g. because -// there is an empty line before it or it contains only non-matching tokens, -// finalize the previous sequence. +// Walk through a subset of the changes, starting at StartAt, and find +// sequences of matching tokens to align. To do so, keep track of the lines and +// whether or not a matching token was found on a line. If a matching token is +// found, extend the current sequence. If the current line cannot be part of a +// sequence, e.g. because there is an empty line before it or it contains only +// non-matching tokens, finalize the previous sequence. +// The value returned is the token on which we stopped, either because we +// exhausted all items inside Changes, or because we hit a scope level higher +// than our initial scope. +// This function is recursive. Each invocation processes only the nesting level +// (aka ScopeLevel) equal to the initial level, which is the level of +// Changes[StartAt]. +// If we encounter a nesting level greater than the initial level, then we call +// ourselves recursively, thereby avoiding the pollution of the current state +// with the alignment requirements of the nested sub-level. This recursive +// behavior is necessary for aligning function prototypes that have one or more +// arguments. +// If this function encounters a nesting level less than the initial level, +// it exits. +// There is a non-obvious subtlety in the recursive behavior: Even though we +// defer processing of nested levels to recursive invocations of this +// function, when it comes time to align a sequence of tokens, we run the +// alignment on the entire sequence, including the nested levels. +// When doing so, most of the nested tokens are skipped, because their +// alignment was already handled by the recursive invocations of this function. +// However, the special exception is that we do NOT skip function parameters +// that are split across multiple lines. See the test case in FormatTest.cpp +// that mentions "split function parameter alignment" for an example of this. template -static void AlignTokens(const FormatStyle &Style, F &&Matches, - SmallVector &Changes) { +static unsigned +AlignTokens(const FormatStyle &Style, F &&Matches, + SmallVector &Changes, + unsigned StartAt) { unsigned MinColumn = 0; unsigned MaxColumn = UINT_MAX; @@ -205,14 +305,9 @@ unsigned StartOfSequence = 0; unsigned EndOfSequence = 0; - // Keep track of the nesting level of matching tokens, i.e. the number of - // surrounding (), [], or {}. We will only align a sequence of matching - // token that share the same scope depth. - // - // FIXME: This could use FormatToken::NestingLevel information, but there is - // an outstanding issue wrt the brace scopes. - unsigned NestingLevelOfLastMatch = 0; - unsigned NestingLevel = 0; + // Measure the ScopeLevel (i.e. depth of (), [], {}) of the first token, and abort + // when we hit any token in a higher scope than the starting one. + int ScopeLevel = StartAt < Changes.size() ? Changes[StartAt].ScopeLevel : -1; // Keep track of the number of commas before the matching tokens, we will only // align a sequence of matching tokens if they are preceded by the same number @@ -240,7 +335,10 @@ EndOfSequence = 0; }; - for (unsigned i = 0, e = Changes.size(); i != e; ++i) { + unsigned i = StartAt; + for (unsigned e = Changes.size(); + i != e && Changes[i].ScopeLevel >= ScopeLevel; ++i) { + if (Changes[i].NewlinesBefore != 0) { CommasBeforeMatch = 0; EndOfSequence = i; @@ -254,17 +352,11 @@ if (Changes[i].Kind == tok::comma) { ++CommasBeforeMatch; - } else if (Changes[i].Kind == tok::r_brace || - Changes[i].Kind == tok::r_paren || - Changes[i].Kind == tok::r_square) { - --NestingLevel; - } else if (Changes[i].Kind == tok::l_brace || - Changes[i].Kind == tok::l_paren || - Changes[i].Kind == tok::l_square) { - // We want sequences to skip over child scopes if possible, but not the - // other way around. - NestingLevelOfLastMatch = std::min(NestingLevelOfLastMatch, NestingLevel); - ++NestingLevel; + } else if (Changes[i].ScopeLevel != ScopeLevel) { + // Call AlignTokens recursively, skipping over this scope block. + unsigned StoppedAt = AlignTokens(Style, Matches, Changes, i); + i = StoppedAt - 1; + continue; } if (!Matches(Changes[i])) @@ -273,12 +365,10 @@ // If there is more than one matching token per line, or if the number of // preceding commas, or the scope depth, do not match anymore, end the // sequence. - if (FoundMatchOnLine || CommasBeforeMatch != CommasBeforeLastMatch || - NestingLevel != NestingLevelOfLastMatch) + if (FoundMatchOnLine || CommasBeforeMatch != CommasBeforeLastMatch) AlignCurrentSequence(); CommasBeforeLastMatch = CommasBeforeMatch; - NestingLevelOfLastMatch = NestingLevel; FoundMatchOnLine = true; if (StartOfSequence == 0) @@ -301,8 +391,10 @@ MaxColumn = std::min(MaxColumn, ChangeMaxColumn); } - EndOfSequence = Changes.size(); + unsigned StoppedAt = i; + EndOfSequence = i; AlignCurrentSequence(); + return StoppedAt; } void WhitespaceManager::alignConsecutiveAssignments() { @@ -321,7 +413,7 @@ return C.Kind == tok::equal; }, - Changes); + Changes, 0); } void WhitespaceManager::alignConsecutiveDeclarations() { @@ -334,9 +426,8 @@ // const char* const* v1; // float const* v2; // SomeVeryLongType const& v3; - AlignTokens(Style, [](Change const &C) { return C.IsStartOfDeclName; }, - Changes); + Changes, 0); } void WhitespaceManager::alignTrailingComments() { Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -9242,12 +9242,11 @@ "};", Alignment); - // FIXME: Should align all three assignments verifyFormat( "int i = 1;\n" "SomeType a = SomeFunction(looooooooooooooooooooooongParameterA,\n" " loooooooooooooooooooooongParameterB);\n" - "int j = 2;", + "int j = 2;", Alignment); verifyFormat("template