[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-03-20 Thread Owen Pan via Phabricator via cfe-commits
owenpan added a comment.

@kadircet thanks for reporting the crash and reverting the commit. I will fix 
it and reland the patch.




Comment at: clang/lib/Format/TokenAnnotator.cpp:1229
+  consumeToken();
+assert(CurrentToken);
+auto Previous = CurrentToken->getPreviousNonComment();

After `next()` or `consumeToken()`, `CurrentToken` would advance and could 
become null.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143755/new/

https://reviews.llvm.org/D143755

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-03-20 Thread Kadir Cetinkaya via Phabricator via cfe-commits
kadircet added a comment.

Hi @owenpan, this seems to be crashing for:

  struct Foo { operator enum foo{} };

with stack trace:

  $ ~/repos/llvm/build/bin/clang-format format_crash.cc --dry-run
  clang-format: 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/TokenAnnotator.cpp:1229:
 bool clang::format::(anonymous namespace)::AnnotatingParser::consumeToken(): 
Assertion `CurrentToken' failed.
  PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ 
and include the crash backtrace.
  Stack dump:
  0.  Program arguments: 
/usr/local/google/home/kadircet/repos/llvm/build/bin/clang-format 
format_crash.cc --dry-run
   #0 0x00397687 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) 
/usr/local/google/home/kadircet/repos/llvm/llvm/lib/Support/Unix/Signals.inc:567:13
   #1 0x0039583e llvm::sys::RunSignalHandlers() 
/usr/local/google/home/kadircet/repos/llvm/llvm/lib/Support/Signals.cpp:105:18
   #2 0x00397faa SignalHandler(int) 
/usr/local/google/home/kadircet/repos/llvm/llvm/lib/Support/Unix/Signals.inc:412:1
   #3 0x7f49d145af90 (/lib/x86_64-linux-gnu/libc.so.6+0x3bf90)
   #4 0x7f49d14a9ccc __pthread_kill_implementation 
./nptl/pthread_kill.c:44:76
   #5 0x7f49d145aef2 raise ./signal/../sysdeps/posix/raise.c:27:6
   #6 0x7f49d1445472 abort ./stdlib/abort.c:81:7
   #7 0x7f49d1445395 _nl_load_domain ./intl/loadmsgcat.c:1177:9
   #8 0x7f49d1453df2 (/lib/x86_64-linux-gnu/libc.so.6+0x34df2)
   #9 0x0043a7c1 parseBrace 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/TokenAnnotator.cpp:867:9
  #10 0x0043a7c1 clang::format::(anonymous 
namespace)::AnnotatingParser::consumeToken() 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/TokenAnnotator.cpp:1170:12
  #11 0x0042e1f8 clang::format::(anonymous 
namespace)::AnnotatingParser::parseLine() 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/TokenAnnotator.cpp:1558:11
  #12 0x0042dc37 
clang::format::TokenAnnotator::annotate(clang::format::AnnotatedLine&) 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/TokenAnnotator.cpp:2837:13
  #13 0x0041b910 clang::format::TokenAnalyzer::process(bool) 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/TokenAnalyzer.cpp:0:19
  #14 0x003d0576 ~TokenAnalyzer 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/TokenAnalyzer.h:88:7
  #15 0x003d0576 operator() 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/Format.cpp:3492:9
  #16 0x003d0576 __invoke_impl, (lambda at 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/Format.cpp:3491:27) 
&, const clang::format::Environment &> 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/invoke.h:61:14
  #17 0x003d0576 __invoke_r, (lambda at 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/Format.cpp:3491:27) 
&, const clang::format::Environment &> 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/invoke.h:114:9
  #18 0x003d0576 
std::_Function_handler 
(clang::format::Environment const&), 
clang::format::internal::reformat(clang::format::FormatStyle const&, 
llvm::StringRef, llvm::ArrayRef, unsigned int, unsigned 
int, unsigned int, llvm::StringRef, 
clang::format::FormattingAttemptStatus*)::$_8>::_M_invoke(std::_Any_data 
const&, clang::format::Environment const&) 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/std_function.h:290:9
  #19 0x003b8a18 _M_is_engaged 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/optional:471:58
  #20 0x003b8a18 operator bool 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/optional:985:22
  #21 0x003b8a18 
clang::format::internal::reformat(clang::format::FormatStyle const&, 
llvm::StringRef, llvm::ArrayRef, unsigned int, unsigned 
int, unsigned int, llvm::StringRef, clang::format::FormattingAttemptStatus*) 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/Format.cpp:3533:9
  #22 0x003b9c73 _Rb_tree_impl 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_tree.h:687:12
  #23 0x003b9c73 _Rb_tree 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_tree.h:954:7
  #24 0x003b9c73 set 
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_set.h:231:7
  #25 0x003b9c73 Replacements 
/usr/local/google/home/kadircet/repos/llvm/clang/include/clang/Tooling/Core/Replacement.h:212:7
  #26 0x003b9c73 clang::format::reformat(clang::format::FormatStyle 
const&, llvm::StringRef, llvm::ArrayRef, 
llvm::StringRef, clang::format::FormattingAttemptStatus*) 
/usr/local/google/home/kadircet/repos/llvm/clang/lib/Format/Format.cpp:3557:10
  #27 0x0033da7f clang::format::format(llvm::StringRef) 

[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-02-16 Thread Owen Pan via Phabricator via cfe-commits
This revision was landed with ongoing or failed builds.
This revision was automatically updated to reflect the committed changes.
Closed by commit rGb05dc1b8766a: [clang-format] Add a space between an 
overloaded operator and  (authored by owenpan).

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143755/new/

https://reviews.llvm.org/D143755

Files:
  clang/lib/Format/FormatToken.h
  clang/lib/Format/FormatTokenLexer.cpp
  clang/lib/Format/FormatTokenLexer.h
  clang/lib/Format/TokenAnnotator.cpp
  clang/unittests/Format/FormatTest.cpp
  clang/unittests/Format/TokenAnnotatorTest.cpp

Index: clang/unittests/Format/TokenAnnotatorTest.cpp
===
--- clang/unittests/Format/TokenAnnotatorTest.cpp
+++ clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -574,6 +574,71 @@
   EXPECT_TOKEN(Tokens[4], tok::l_paren, TT_OverloadedOperatorLParen);
 }
 
+TEST_F(TokenAnnotatorTest, OverloadedOperatorInTemplate) {
+  struct {
+const char *Text;
+tok::TokenKind Kind;
+  } Operators[] = {{"+", tok::plus},
+   {"-", tok::minus},
+   // FIXME:
+   // {"*", tok::star},
+   {"/", tok::slash},
+   {"%", tok::percent},
+   {"^", tok::caret},
+   // FIXME:
+   // {"&", tok::amp},
+   {"|", tok::pipe},
+   {"~", tok::tilde},
+   {"!", tok::exclaim},
+   {"=", tok::equal},
+   // FIXME:
+   // {"<", tok::less},
+   {">", tok::greater},
+   {"+=", tok::plusequal},
+   {"-=", tok::minusequal},
+   {"*=", tok::starequal},
+   {"/=", tok::slashequal},
+   {"%=", tok::percentequal},
+   {"^=", tok::caretequal},
+   {"&=", tok::ampequal},
+   {"|=", tok::pipeequal},
+   {"<<", tok::lessless},
+   {">>", tok::greatergreater},
+   {">>=", tok::greatergreaterequal},
+   {"<<=", tok::lesslessequal},
+   {"==", tok::equalequal},
+   {"!=", tok::exclaimequal},
+   {"<=", tok::lessequal},
+   {">=", tok::greaterequal},
+   {"<=>", tok::spaceship},
+   {"&&", tok::ampamp},
+   {"||", tok::pipepipe},
+   {"++", tok::plusplus},
+   {"--", tok::minusminus},
+   {",", tok::comma},
+   {"->*", tok::arrowstar},
+   {"->", tok::arrow}};
+
+  for (const auto  : Operators) {
+std::string Input("C<");
+Input += Operator.Text;
+Input += " > a;";
+auto Tokens = annotate(std::string(Input));
+ASSERT_EQ(Tokens.size(), 9u) << Tokens;
+EXPECT_TOKEN(Tokens[1], tok::less, TT_TemplateOpener);
+EXPECT_TOKEN(Tokens[4], Operator.Kind, TT_OverloadedOperator);
+EXPECT_TOKEN(Tokens[5], tok::greater, TT_TemplateCloser);
+  }
+
+  auto Tokens = annotate("C<< > lt;");
+  ASSERT_EQ(Tokens.size(), 12u) << Tokens;
+  EXPECT_TOKEN(Tokens[1], tok::less, TT_TemplateOpener);
+  EXPECT_TOKEN(Tokens[4], tok::less, TT_OverloadedOperator);
+  EXPECT_TOKEN(Tokens[5], tok::less, TT_TemplateOpener);
+  EXPECT_TOKEN(Tokens[7], tok::greater, TT_TemplateCloser);
+  EXPECT_TOKEN(Tokens[8], tok::greater, TT_TemplateCloser);
+}
+
 TEST_F(TokenAnnotatorTest, UnderstandsRequiresClausesAndConcepts) {
   auto Tokens = annotate("template \n"
  "concept C = (Foo && Bar) && (Bar && Baz);");
Index: clang/unittests/Format/FormatTest.cpp
===
--- clang/unittests/Format/FormatTest.cpp
+++ clang/unittests/Format/FormatTest.cpp
@@ -10663,6 +10663,14 @@
   verifyFormat("foo() { ::operator new(n * sizeof(foo)); }");
 }
 
+TEST_F(FormatTest, SpaceBeforeTemplateCloser) {
+  verifyFormat("C< > minus;");
+  verifyFormat("C<> > gt;");
+  verifyFormat("C<>= > ge;");
+  verifyFormat("C<<= > le;");
+  verifyFormat("C<< > lt;");
+}
+
 TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
   verifyFormat("void A::b() && {}");
   verifyFormat("void A::b() && noexcept {}");
Index: clang/lib/Format/TokenAnnotator.cpp
===
--- clang/lib/Format/TokenAnnotator.cpp
+++ clang/lib/Format/TokenAnnotator.cpp
@@ -1219,19 +1219,25 @@
  !CurrentToken->isOneOf(tok::l_paren, tok::semi, tok::r_paren)) {
 if (CurrentToken->isOneOf(tok::star, tok::amp))
   CurrentToken->setType(TT_PointerOrReference);
-consumeToken();
-if (!CurrentToken)
-  continue;
-if (CurrentToken->is(tok::comma) &&
-CurrentToken->Previous->isNot(tok::kw_operator)) {
+auto Next = 

[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-02-14 Thread MyDeveloperDay via Phabricator via cfe-commits
MyDeveloperDay accepted this revision.
MyDeveloperDay added a comment.
This revision is now accepted and ready to land.

LGTM


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143755/new/

https://reviews.llvm.org/D143755

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-02-11 Thread Owen Pan via Phabricator via cfe-commits
owenpan added a comment.

In D143755#4118802 , @vedgy wrote:

> Hi @owenpan. Thank you for fixing this bug!
> Have you noticed this paragraph in my bug report?
>
>> I believe `clang_getTypeSpelling()`, or more likely `QualType::print()` used 
>> by it, should insert a tab character between such tokens to pretty-print 
>> compilable code. The tab character is preferable to the space character 
>> here, because the users may rely on the fact that pretty-printed binary 
>> operators are surrounded by spaces to distinguish them from angle brackets.

Yes, but I'm not familiar with these functions.

> KDevelop parses the result of `clang_getTypeSpelling()` when libclang API is 
> lacking. Since this recent commit 
> 
>  KDevelop's parsing relies on the empirical fact that only operators are 
> surrounded by spaces to distinguish them from angle brackets. Does this 
> revision introduce angle brackets surrounded by spaces? Can tab characters be 
> used instead? If not, do you know how else such angle brackets can be 
> distinguished from operators?

This patch should fix the reported invalid-code-generation bug by leaving a 
space between the overloaded operator and the closing angle bracket. 
clang-format doesn't insert tabs in the middle of a line (except when doing 
alignment if `UseTab` is set to `Always`).


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143755/new/

https://reviews.llvm.org/D143755

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-02-11 Thread Owen Pan via Phabricator via cfe-commits
owenpan updated this revision to Diff 496670.
owenpan added a comment.

Added `tryMergeGreaterGreater()` to and fixed `tryMergerLessLess()` of 
`FormatTokenLexer`.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143755/new/

https://reviews.llvm.org/D143755

Files:
  clang/lib/Format/FormatToken.h
  clang/lib/Format/FormatTokenLexer.cpp
  clang/lib/Format/FormatTokenLexer.h
  clang/lib/Format/TokenAnnotator.cpp
  clang/unittests/Format/FormatTest.cpp
  clang/unittests/Format/TokenAnnotatorTest.cpp

Index: clang/unittests/Format/TokenAnnotatorTest.cpp
===
--- clang/unittests/Format/TokenAnnotatorTest.cpp
+++ clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -574,6 +574,71 @@
   EXPECT_TOKEN(Tokens[4], tok::l_paren, TT_OverloadedOperatorLParen);
 }
 
+TEST_F(TokenAnnotatorTest, OverloadedOperatorInTemplate) {
+  struct {
+const char *Text;
+tok::TokenKind Kind;
+  } Operators[] = {{"+", tok::plus},
+   {"-", tok::minus},
+   // FIXME:
+   // {"*", tok::star},
+   {"/", tok::slash},
+   {"%", tok::percent},
+   {"^", tok::caret},
+   // FIXME:
+   // {"&", tok::amp},
+   {"|", tok::pipe},
+   {"~", tok::tilde},
+   {"!", tok::exclaim},
+   {"=", tok::equal},
+   // FIXME:
+   // {"<", tok::less},
+   {">", tok::greater},
+   {"+=", tok::plusequal},
+   {"-=", tok::minusequal},
+   {"*=", tok::starequal},
+   {"/=", tok::slashequal},
+   {"%=", tok::percentequal},
+   {"^=", tok::caretequal},
+   {"&=", tok::ampequal},
+   {"|=", tok::pipeequal},
+   {"<<", tok::lessless},
+   {">>", tok::greatergreater},
+   {">>=", tok::greatergreaterequal},
+   {"<<=", tok::lesslessequal},
+   {"==", tok::equalequal},
+   {"!=", tok::exclaimequal},
+   {"<=", tok::lessequal},
+   {">=", tok::greaterequal},
+   {"<=>", tok::spaceship},
+   {"&&", tok::ampamp},
+   {"||", tok::pipepipe},
+   {"++", tok::plusplus},
+   {"--", tok::minusminus},
+   {",", tok::comma},
+   {"->*", tok::arrowstar},
+   {"->", tok::arrow}};
+
+  for (const auto  : Operators) {
+std::string Input("C<");
+Input += Operator.Text;
+Input += " > a;";
+auto Tokens = annotate(std::string(Input));
+ASSERT_EQ(Tokens.size(), 9u) << Tokens;
+EXPECT_TOKEN(Tokens[1], tok::less, TT_TemplateOpener);
+EXPECT_TOKEN(Tokens[4], Operator.Kind, TT_OverloadedOperator);
+EXPECT_TOKEN(Tokens[5], tok::greater, TT_TemplateCloser);
+  }
+
+  auto Tokens = annotate("C<< > lt;");
+  ASSERT_EQ(Tokens.size(), 12u) << Tokens;
+  EXPECT_TOKEN(Tokens[1], tok::less, TT_TemplateOpener);
+  EXPECT_TOKEN(Tokens[4], tok::less, TT_OverloadedOperator);
+  EXPECT_TOKEN(Tokens[5], tok::less, TT_TemplateOpener);
+  EXPECT_TOKEN(Tokens[7], tok::greater, TT_TemplateCloser);
+  EXPECT_TOKEN(Tokens[8], tok::greater, TT_TemplateCloser);
+}
+
 TEST_F(TokenAnnotatorTest, UnderstandsRequiresClausesAndConcepts) {
   auto Tokens = annotate("template \n"
  "concept C = (Foo && Bar) && (Bar && Baz);");
Index: clang/unittests/Format/FormatTest.cpp
===
--- clang/unittests/Format/FormatTest.cpp
+++ clang/unittests/Format/FormatTest.cpp
@@ -10663,6 +10663,14 @@
   verifyFormat("foo() { ::operator new(n * sizeof(foo)); }");
 }
 
+TEST_F(FormatTest, SpaceBeforeTemplateCloser) {
+  verifyFormat("C< > minus;");
+  verifyFormat("C<> > gt;");
+  verifyFormat("C<>= > ge;");
+  verifyFormat("C<<= > le;");
+  verifyFormat("C<< > lt;");
+}
+
 TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
   verifyFormat("void A::b() && {}");
   verifyFormat("void A::b() && noexcept {}");
Index: clang/lib/Format/TokenAnnotator.cpp
===
--- clang/lib/Format/TokenAnnotator.cpp
+++ clang/lib/Format/TokenAnnotator.cpp
@@ -1219,19 +1219,25 @@
  !CurrentToken->isOneOf(tok::l_paren, tok::semi, tok::r_paren)) {
 if (CurrentToken->isOneOf(tok::star, tok::amp))
   CurrentToken->setType(TT_PointerOrReference);
-consumeToken();
-if (!CurrentToken)
-  continue;
-if (CurrentToken->is(tok::comma) &&
-CurrentToken->Previous->isNot(tok::kw_operator)) {
+auto Next = CurrentToken->getNextNonComment();
+if (!Next)
   break;
-}
-if 

[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-02-10 Thread Igor Kushnir via Phabricator via cfe-commits
vedgy added a comment.

Hi @owenpan. Thank you for fixing this bug!
Have you noticed this paragraph in my bug report?

> I believe `clang_getTypeSpelling()`, or more likely `QualType::print()` used 
> by it, should insert a tab character between such tokens to pretty-print 
> compilable code. The tab character is preferable to the space character here, 
> because the users may rely on the fact that pretty-printed binary operators 
> are surrounded by spaces to distinguish them from angle brackets.

KDevelop parses the result of `clang_getTypeSpelling()` when libclang API is 
lacking. Since this recent commit 

 KDevelop's parsing relies on the empirical fact that only operators are 
surrounded by spaces to distinguish them from angle brackets. Does this 
revision introduce angle brackets surrounded by spaces? Can tab characters be 
used instead? If not, do you know how else such angle brackets can be 
distinguished from operators?


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D143755/new/

https://reviews.llvm.org/D143755

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D143755: [clang-format] Add a space between an overloaded operator and '>'

2023-02-10 Thread Owen Pan via Phabricator via cfe-commits
owenpan created this revision.
owenpan added reviewers: MyDeveloperDay, HazardyKnusperkeks, rymiel.
owenpan added a project: clang-format.
Herald added a project: All.
owenpan requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

The token annotator doesn't annotate the template opener and closer as such if 
they enclose an overloaded operator. This causes the space between the operator 
and the closer to be removed, resulting
in invalid C++ code.

Fixes https://github.com/llvm/llvm-project/issues/58602.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D143755

Files:
  clang/lib/Format/TokenAnnotator.cpp
  clang/unittests/Format/FormatTest.cpp
  clang/unittests/Format/TokenAnnotatorTest.cpp

Index: clang/unittests/Format/TokenAnnotatorTest.cpp
===
--- clang/unittests/Format/TokenAnnotatorTest.cpp
+++ clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -574,6 +574,72 @@
   EXPECT_TOKEN(Tokens[4], tok::l_paren, TT_OverloadedOperatorLParen);
 }
 
+TEST_F(TokenAnnotatorTest, OverloadedOperatorInTemplate) {
+  struct {
+const char *Text;
+tok::TokenKind Kind;
+  } Operators[] = {{"+", tok::plus},
+   {"-", tok::minus},
+   // FIXME:
+   // {"*", tok::star},
+   {"/", tok::slash},
+   {"%", tok::percent},
+   {"^", tok::caret},
+   // FIXME:
+   // {"&", tok::amp},
+   {"|", tok::pipe},
+   {"~", tok::tilde},
+   {"!", tok::exclaim},
+   {"=", tok::equal},
+   // FIXME:
+   // {"<", tok::less},
+   {">", tok::greater},
+   {"+=", tok::plusequal},
+   {"-=", tok::minusequal},
+   {"*=", tok::starequal},
+   {"/=", tok::slashequal},
+   {"%=", tok::percentequal},
+   {"^=", tok::caretequal},
+   {"&=", tok::ampequal},
+   {"|=", tok::pipeequal},
+   // FIXME:
+   // {"<<", tok::lessless},
+   // {">>", tok::greatergreater},
+   {">>=", tok::greatergreaterequal},
+   {"<<=", tok::lesslessequal},
+   {"==", tok::equalequal},
+   {"!=", tok::exclaimequal},
+   {"<=", tok::lessequal},
+   {">=", tok::greaterequal},
+   {"<=>", tok::spaceship},
+   {"&&", tok::ampamp},
+   {"||", tok::pipepipe},
+   {"++", tok::plusplus},
+   {"--", tok::minusminus},
+   {",", tok::comma},
+   {"->*", tok::arrowstar},
+   {"->", tok::arrow}};
+
+  for (const auto  : Operators) {
+std::string Input("C< ");
+Input += Operator.Text;
+Input += " > a;";
+auto Tokens = annotate(std::string(Input));
+ASSERT_EQ(Tokens.size(), 9u) << Tokens;
+EXPECT_TOKEN(Tokens[1], tok::less, TT_TemplateOpener);
+EXPECT_TOKEN(Tokens[4], Operator.Kind, TT_OverloadedOperator);
+EXPECT_TOKEN(Tokens[5], tok::greater, TT_TemplateCloser);
+  }
+
+  auto Tokens = annotate("C<< > lt;");
+  ASSERT_EQ(Tokens.size(), 12u) << Tokens;
+  EXPECT_TOKEN(Tokens[1], tok::less, TT_TemplateOpener);
+  EXPECT_TOKEN(Tokens[4], tok::less, TT_OverloadedOperator);
+  EXPECT_TOKEN(Tokens[5], tok::less, TT_TemplateOpener);
+  EXPECT_TOKEN(Tokens[7], tok::greater, TT_TemplateCloser);
+  EXPECT_TOKEN(Tokens[8], tok::greater, TT_TemplateCloser);
+}
+
 TEST_F(TokenAnnotatorTest, UnderstandsRequiresClausesAndConcepts) {
   auto Tokens = annotate("template \n"
  "concept C = (Foo && Bar) && (Bar && Baz);");
Index: clang/unittests/Format/FormatTest.cpp
===
--- clang/unittests/Format/FormatTest.cpp
+++ clang/unittests/Format/FormatTest.cpp
@@ -10663,6 +10663,14 @@
   verifyFormat("foo() { ::operator new(n * sizeof(foo)); }");
 }
 
+TEST_F(FormatTest, SpaceBeforeTemplateCloser) {
+  verifyFormat("C< > minus;");
+  verifyFormat("C<> > gt;");
+  verifyFormat("C<>= > ge;");
+  verifyFormat("C<<= > le;");
+  verifyFormat("C<< > lt;");
+}
+
 TEST_F(FormatTest, UnderstandsFunctionRefQualification) {
   verifyFormat("void A::b() && {}");
   verifyFormat("void A::b() && noexcept {}");
Index: clang/lib/Format/TokenAnnotator.cpp
===
--- clang/lib/Format/TokenAnnotator.cpp
+++ clang/lib/Format/TokenAnnotator.cpp
@@ -1219,9 +1219,14 @@
  !CurrentToken->isOneOf(tok::l_paren, tok::semi, tok::r_paren)) {
 if (CurrentToken->isOneOf(tok::star, tok::amp))
   CurrentToken->setType(TT_PointerOrReference);
-consumeToken();
+if