Skip to content

Commit ae1b785

Browse files
committed
[clang-format] Update noexcept reference qualifiers detection
Summary: r373165 fixed an issue where a templated noexcept member function with a reference qualifier would be indented more than expected: ``` // Formatting produced with LLVM style with AlwaysBreakTemplateDeclarations: Yes // before r373165: struct f { template <class T> void bar() && noexcept {} }; // after: struct f { template <class T> void bar() && noexcept {} }; ``` The way this is done is that in the AnnotatingParser in `lib/FormatTokenAnnotator.cpp` the determination of the usage of a `&` or `&&` (the line in determineTokenType ``` Current.Type = determineStarAmpUsage(... ``` is not performed in some cases anymore, combining with a few additional related checks afterwards. The net effect of these checks results in the `&` or `&&` token to start being classified as `TT_Unknown` in cases where before `r373165` it would be classified as `TT_UnaryOperator` or `TT_PointerOrReference` by `determineStarAmpUsage`. This inadvertently caused 2 classes of regressions I'm aware of: - The address-of `&` after a function assignment would be classified as `TT_Unknown`, causing spaces to surround it, disregarding style options: ``` // before r373165: void (*fun_ptr)(void) = &fun; // after: void (*fun_ptr)(void) = & fun; ``` - In cases where there is a function declaration list -- looking macro between a template line and the start of the function declaration, an `&` as part of the return type would be classified as `TT_Unknown`, causing spaces to surround it: ``` // before r373165: template <class T> DEPRECATED("lala") Type& foo(); // after: template <class T> DEPRECATED("lala") Type & foo(); ``` In these cases the problems are rooted in the skipping of the classification of a `&` (and similarly `&&`) by determineStarAmpUsage which effects the formatting decisions later in the pipeline. I've looked into the goal of r373165 and noticed that replacing `noexcept` with `const` in the given example produces no extra indentation with the old code: ``` // before r373165: struct f { template <class T> int foo() & const {} }; struct f { template <class T> int foo() & noexcept {} }; ``` I investigated how clang-format annotated these two examples differently to determine the places where the processing of both diverges in the pipeline. There were two places where the processing diverges, causing the extra indent in the `noexcept` case: 1. The `const` is annotated as a `TT_TrailingAnnotation`, whereas `noexcept` is annotated as `TT_Unknown`. I've updated the `determineTokenType` function to account for this by adding a missing `tok:kw_noexcept` to the clause that marks a token as `TT_TrailingAnnotation`. 2. The `&` in the second example is wrongly identified as `TT_BinaryOperator` in `determineStarAmpUsage`. This is the reason for the extra indentation -- clang-format gets confused and thinks this is an expression. I've updated `determineStarAmpUsage` to check for `tok:kw_noexcept`. With these two updates in place, the additional parsing introduced by r373165 becomes unnecessary and all added tests pass (with updates, as now clang-format respects the style configuration for spaces around the `&` in the test examples). I've removed these additions and added regression tests for the cases above. Reviewers: AndWass, MyDeveloperDay Reviewed By: MyDeveloperDay Subscribers: cfe-commits Tags: #clang, #clang-format Differential Revision: https://reviews.llvm.org/D68695 llvm-svn: 374172
1 parent 604b7c2 commit ae1b785

File tree

2 files changed

+33
-21
lines changed

2 files changed

+33
-21
lines changed

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "TokenAnnotator.h"
1616
#include "FormatToken.h"
1717
#include "clang/Basic/SourceManager.h"
18+
#include "clang/Basic/TokenKinds.h"
1819
#include "llvm/ADT/SmallPtrSet.h"
1920
#include "llvm/Support/Debug.h"
2021

@@ -65,7 +66,7 @@ class AnnotatingParser {
6566
AnnotatingParser(const FormatStyle &Style, AnnotatedLine &Line,
6667
const AdditionalKeywords &Keywords)
6768
: Style(Style), Line(Line), CurrentToken(Line.First), AutoFound(false),
68-
TrailingReturnFound(false), Keywords(Keywords) {
69+
Keywords(Keywords) {
6970
Contexts.push_back(Context(tok::unknown, 1, /*IsExpression=*/false));
7071
resetTokenMetadata(CurrentToken);
7172
}
@@ -1397,11 +1398,7 @@ class AnnotatingParser {
13971398
!Current.Previous->is(tok::kw_operator)) {
13981399
// not auto operator->() -> xxx;
13991400
Current.Type = TT_TrailingReturnArrow;
1400-
TrailingReturnFound = true;
1401-
} else if (Current.is(tok::star) ||
1402-
(Current.isOneOf(tok::amp, tok::ampamp) &&
1403-
(Current.NestingLevel != 0 || !Line.MightBeFunctionDecl ||
1404-
TrailingReturnFound))) {
1401+
} else if (Current.isOneOf(tok::star, tok::amp, tok::ampamp)) {
14051402
Current.Type = determineStarAmpUsage(Current,
14061403
Contexts.back().CanBeExpression &&
14071404
Contexts.back().IsExpression,
@@ -1424,8 +1421,6 @@ class AnnotatingParser {
14241421
Current.Type = TT_ConditionalExpr;
14251422
}
14261423
} else if (Current.isBinaryOperator() &&
1427-
!(Line.MightBeFunctionDecl && Current.NestingLevel == 0 &&
1428-
Current.isOneOf(tok::amp, tok::ampamp)) &&
14291424
(!Current.Previous || Current.Previous->isNot(tok::l_square)) &&
14301425
(!Current.is(tok::greater) &&
14311426
Style.Language != FormatStyle::LK_TextProto)) {
@@ -1500,12 +1495,11 @@ class AnnotatingParser {
15001495
// colon after this, this is the only place which annotates the identifier
15011496
// as a selector.)
15021497
Current.Type = TT_SelectorName;
1503-
} else if (Current.isOneOf(tok::identifier, tok::kw_const, tok::amp,
1504-
tok::ampamp) &&
1498+
} else if (Current.isOneOf(tok::identifier, tok::kw_const,
1499+
tok::kw_noexcept) &&
15051500
Current.Previous &&
15061501
!Current.Previous->isOneOf(tok::equal, tok::at) &&
1507-
Line.MightBeFunctionDecl && !TrailingReturnFound &&
1508-
Contexts.size() == 1) {
1502+
Line.MightBeFunctionDecl && Contexts.size() == 1) {
15091503
// Line.MightBeFunctionDecl can only be true after the parentheses of a
15101504
// function declaration have been found.
15111505
Current.Type = TT_TrailingAnnotation;
@@ -1689,7 +1683,8 @@ class AnnotatingParser {
16891683

16901684
const FormatToken *NextToken = Tok.getNextNonComment();
16911685
if (!NextToken ||
1692-
NextToken->isOneOf(tok::arrow, tok::equal, tok::kw_const) ||
1686+
NextToken->isOneOf(tok::arrow, tok::equal, tok::kw_const,
1687+
tok::kw_noexcept) ||
16931688
(NextToken->is(tok::l_brace) && !NextToken->getNextNonComment()))
16941689
return TT_PointerOrReference;
16951690

@@ -1790,7 +1785,6 @@ class AnnotatingParser {
17901785
AnnotatedLine &Line;
17911786
FormatToken *CurrentToken;
17921787
bool AutoFound;
1793-
bool TrailingReturnFound;
17941788
const AdditionalKeywords &Keywords;
17951789

17961790
// Set of "<" tokens that do not open a template parameter list. If parseAngle

clang/unittests/Format/FormatTest.cpp

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7037,31 +7037,31 @@ TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
70377037

70387038
verifyFormat("struct f {\n"
70397039
" template <class T>\n"
7040-
" int &foo(const std::string &str) & noexcept {}\n"
7040+
" int &foo(const std::string &str) &noexcept {}\n"
70417041
"};",
70427042
BreakTemplate);
70437043

70447044
verifyFormat("struct f {\n"
70457045
" template <class T>\n"
7046-
" int &foo(const std::string &str) && noexcept {}\n"
7046+
" int &foo(const std::string &str) &&noexcept {}\n"
70477047
"};",
70487048
BreakTemplate);
70497049

70507050
verifyFormat("struct f {\n"
70517051
" template <class T>\n"
7052-
" int &foo(const std::string &str) const & noexcept {}\n"
7052+
" int &foo(const std::string &str) const &noexcept {}\n"
70537053
"};",
70547054
BreakTemplate);
70557055

70567056
verifyFormat("struct f {\n"
70577057
" template <class T>\n"
7058-
" int &foo(const std::string &str) const & noexcept {}\n"
7058+
" int &foo(const std::string &str) const &noexcept {}\n"
70597059
"};",
70607060
BreakTemplate);
70617061

70627062
verifyFormat("struct f {\n"
70637063
" template <class T>\n"
7064-
" auto foo(const std::string &str) && noexcept -> int & {}\n"
7064+
" auto foo(const std::string &str) &&noexcept -> int & {}\n"
70657065
"};",
70667066
BreakTemplate);
70677067

@@ -7084,13 +7084,13 @@ TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
70847084

70857085
verifyFormat("struct f {\n"
70867086
" template <class T>\n"
7087-
" int& foo(const std::string& str) const & noexcept {}\n"
7087+
" int& foo(const std::string& str) const& noexcept {}\n"
70887088
"};",
70897089
AlignLeftBreakTemplate);
70907090

70917091
verifyFormat("struct f {\n"
70927092
" template <class T>\n"
7093-
" int& foo(const std::string& str) const & noexcept {}\n"
7093+
" int& foo(const std::string& str) const&& noexcept {}\n"
70947094
"};",
70957095
AlignLeftBreakTemplate);
70967096

@@ -7099,6 +7099,24 @@ TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
70997099
" auto foo(const std::string& str) && noexcept -> int& {}\n"
71007100
"};",
71017101
AlignLeftBreakTemplate);
7102+
7103+
// The `&` in `Type&` should not be confused with a trailing `&` of
7104+
// DEPRECATED(reason) member function.
7105+
verifyFormat("struct f {\n"
7106+
" template <class T>\n"
7107+
" DEPRECATED(reason)\n"
7108+
" Type &foo(arguments) {}\n"
7109+
"};",
7110+
BreakTemplate);
7111+
7112+
verifyFormat("struct f {\n"
7113+
" template <class T>\n"
7114+
" DEPRECATED(reason)\n"
7115+
" Type& foo(arguments) {}\n"
7116+
"};",
7117+
AlignLeftBreakTemplate);
7118+
7119+
verifyFormat("void (*foopt)(int) = &func;");
71027120
}
71037121

71047122
TEST_F(FormatTest, UnderstandsNewAndDelete) {

0 commit comments

Comments
 (0)