HazardyKnusperkeks created this revision.
HazardyKnusperkeks added reviewers: MyDeveloperDay, curdeius, owenpan.
HazardyKnusperkeks added a project: clang-format.
HazardyKnusperkeks requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Detect requires expressions in more unusable contexts. This is far from
perfect, but currently we have no good metric to decide between a
requires expression and a trailing requires clause.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D119138

Files:
  clang/lib/Format/UnwrappedLineParser.cpp
  clang/lib/Format/UnwrappedLineParser.h
  clang/unittests/Format/TokenAnnotatorTest.cpp

Index: clang/unittests/Format/TokenAnnotatorTest.cpp
===================================================================
--- clang/unittests/Format/TokenAnnotatorTest.cpp
+++ clang/unittests/Format/TokenAnnotatorTest.cpp
@@ -119,6 +119,9 @@
                     "  { t.foo() };\n"
                     "} && Bar<T> && Baz<T>;");
   ASSERT_EQ(Tokens.size(), 35u) << Tokens;
+  EXPECT_TOKEN(Tokens[8], tok::kw_requires, TT_RequiresExpression);
+  EXPECT_TOKEN(Tokens[9], tok::l_paren, TT_RequiresExpressionLParen);
+  EXPECT_TOKEN(Tokens[13], tok::l_brace, TT_RequiresExpressionLBrace);
   EXPECT_TOKEN(Tokens[23], tok::ampamp, TT_BinaryOperator);
   EXPECT_TOKEN(Tokens[28], tok::ampamp, TT_BinaryOperator);
 
@@ -126,6 +129,7 @@
                     "requires C1<T> && (C21<T> || C22<T> && C2e<T>) && C3<T>\n"
                     "struct Foo;");
   ASSERT_EQ(Tokens.size(), 36u) << Tokens;
+  EXPECT_TOKEN(Tokens[5], tok::kw_requires, TT_RequiresClause);
   EXPECT_TOKEN(Tokens[6], tok::identifier, TT_Unknown);
   EXPECT_EQ(Tokens[6]->FakeLParens.size(), 1u);
   EXPECT_TOKEN(Tokens[10], tok::ampamp, TT_BinaryOperator);
@@ -141,6 +145,7 @@
                "requires (C1<T> && (C21<T> || C22<T> && C2e<T>) && C3<T>)\n"
                "struct Foo;");
   ASSERT_EQ(Tokens.size(), 38u) << Tokens;
+  EXPECT_TOKEN(Tokens[5], tok::kw_requires, TT_RequiresClause);
   EXPECT_TOKEN(Tokens[7], tok::identifier, TT_Unknown);
   EXPECT_EQ(Tokens[7]->FakeLParens.size(), 1u);
   EXPECT_TOKEN(Tokens[11], tok::ampamp, TT_BinaryOperator);
@@ -151,6 +156,72 @@
   EXPECT_EQ(Tokens[32]->FakeRParens, 1u);
   EXPECT_TOKEN(Tokens[33], tok::r_paren, TT_Unknown);
   EXPECT_TRUE(Tokens[33]->ClosesRequiresClause);
+
+  Tokens = annotate("template <typename T>\n"
+                    "void foo(T) noexcept requires Bar<T>;");
+  ASSERT_EQ(Tokens.size(), 18u) << Tokens;
+  EXPECT_TOKEN(Tokens[11], tok::kw_requires, TT_RequiresClause);
+
+  Tokens = annotate("template <typename T>\n"
+                    "struct S {\n"
+                    "  void foo() const requires Bar<T>;\n"
+                    "  void bar() const & requires Baz<T>;\n"
+                    "  void bar() && requires Baz2<T>;\n"
+                    "  void baz() const & noexcept requires Baz<T>;\n"
+                    "  void baz() && noexcept requires Baz2<T>;\n"
+                    "};\n"
+                    "\n"
+                    "void S::bar() const & requires Baz<T> { }");
+  ASSERT_EQ(Tokens.size(), 85u) << Tokens;
+  EXPECT_TOKEN(Tokens[13], tok::kw_requires, TT_RequiresClause);
+  EXPECT_TOKEN(Tokens[25], tok::kw_requires, TT_RequiresClause);
+  EXPECT_TOKEN(Tokens[36], tok::kw_requires, TT_RequiresClause);
+  EXPECT_TOKEN(Tokens[49], tok::kw_requires, TT_RequiresClause);
+  EXPECT_TOKEN(Tokens[61], tok::kw_requires, TT_RequiresClause);
+  EXPECT_TOKEN(Tokens[77], tok::kw_requires, TT_RequiresClause);
+}
+
+TEST_F(TokenAnnotatorTest, UnderstandsRequiresExpressions) {
+  auto Tokens = annotate("bool b = requires(int i) { i + 5; };");
+  ASSERT_EQ(Tokens.size(), 16u) << Tokens;
+  EXPECT_TOKEN(Tokens[3], tok::kw_requires, TT_RequiresExpression);
+  EXPECT_TOKEN(Tokens[4], tok::l_paren, TT_RequiresExpressionLParen);
+  EXPECT_TOKEN(Tokens[8], tok::l_brace, TT_RequiresExpressionLBrace);
+
+  Tokens = annotate("if (requires(int i) { i + 5; }) return;");
+  ASSERT_EQ(Tokens.size(), 17u) << Tokens;
+  EXPECT_TOKEN(Tokens[2], tok::kw_requires, TT_RequiresExpression);
+  EXPECT_TOKEN(Tokens[3], tok::l_paren, TT_RequiresExpressionLParen);
+  EXPECT_TOKEN(Tokens[7], tok::l_brace, TT_RequiresExpressionLBrace);
+
+  Tokens = annotate("if (func() && requires(int i) { i + 5; }) return;");
+  ASSERT_EQ(Tokens.size(), 21u) << Tokens;
+  EXPECT_TOKEN(Tokens[6], tok::kw_requires, TT_RequiresExpression);
+  EXPECT_TOKEN(Tokens[7], tok::l_paren, TT_RequiresExpressionLParen);
+  EXPECT_TOKEN(Tokens[11], tok::l_brace, TT_RequiresExpressionLBrace);
+
+  Tokens = annotate("template <typename T>\n"
+                    "concept C = requires(T T) {\n"
+                    "  requires Bar<T> && Foo<T>;\n"
+                    "};");
+  ASSERT_EQ(Tokens.size(), 28u) << Tokens;
+  EXPECT_TOKEN(Tokens[8], tok::kw_requires, TT_RequiresExpression);
+  EXPECT_TOKEN(Tokens[9], tok::l_paren, TT_RequiresExpressionLParen);
+  EXPECT_TOKEN(Tokens[13], tok::l_brace, TT_RequiresExpressionLBrace);
+  EXPECT_TOKEN(Tokens[14], tok::kw_requires,
+               TT_RequiresClauseInARequiresExpression);
+
+  Tokens = annotate("template <typename T>\n"
+                    "concept C = requires(T T) {\n"
+                    "  { t.func() } -> std::same_as<int>;"
+                    "  requires Bar<T> && Foo<T>;\n"
+                    "};");
+  ASSERT_EQ(Tokens.size(), 43u) << Tokens;
+  EXPECT_TOKEN(Tokens[8], tok::kw_requires, TT_RequiresExpression);
+  EXPECT_TOKEN(Tokens[9], tok::l_paren, TT_RequiresExpressionLParen);
+  EXPECT_TOKEN(Tokens[13], tok::l_brace, TT_RequiresExpressionLBrace);
+  EXPECT_TOKEN(Tokens[29], tok::kw_requires,
+               TT_RequiresClauseInARequiresExpression);
 }
 
 TEST_F(TokenAnnotatorTest, RequiresDoesNotChangeParsingOfTheRest) {
Index: clang/lib/Format/UnwrappedLineParser.h
===================================================================
--- clang/lib/Format/UnwrappedLineParser.h
+++ clang/lib/Format/UnwrappedLineParser.h
@@ -133,6 +133,7 @@
   bool parseEnum();
   bool parseStructLike();
   void parseConcept();
+  bool parseRequires();
   void parseRequiresClause();
   void parseRequiresExpression();
   void parseConstraintExpression();
Index: clang/lib/Format/UnwrappedLineParser.cpp
===================================================================
--- clang/lib/Format/UnwrappedLineParser.cpp
+++ clang/lib/Format/UnwrappedLineParser.cpp
@@ -1537,9 +1537,12 @@
     case tok::kw_concept:
       parseConcept();
       return;
-    case tok::kw_requires:
-      parseRequiresClause();
-      return;
+    case tok::kw_requires: {
+      bool Return = parseRequires();
+      if (Return)
+        return;
+      break;
+    }
     case tok::kw_enum:
       // Ignore if this is part of "template <enum ...".
       if (Previous && Previous->is(tok::less)) {
@@ -2783,6 +2786,120 @@
   addUnwrappedLine();
 }
 
+/// \brief Parses a requires, decides if it is a clause or an expression.
+/// \pre The current token has to be the requires keyword.
+/// \returns If it parsed a clause.
+bool clang::format::UnwrappedLineParser::parseRequires() {
+  assert(FormatTok->Tok.is(tok::kw_requires) && "'requires' expected");
+  auto *PreviousNonComment = FormatTok->getPreviousNonComment();
+
+  auto ProbablyExpressionAfterAmpOrAmpAmp = [PreviousNonComment] {
+    assert(PreviousNonComment->isOneOf(tok::amp, tok::ampamp));
+    // This one is really tricky, since most tokens doesn't have a type yet.
+    // The & or && can be binary operators, then we have a requires
+    // expression. But they also can be r-value overload indicators, then
+    // we have a trailing requires clause. We try to detect the latter and
+    // default to the expressions.
+
+    auto PrevPrev = PreviousNonComment->getPreviousNonComment();
+    if (!PrevPrev) {
+      // No Token is invalid code, just do whatever you want.
+      return true;
+    }
+
+    switch (PrevPrev->Tok.getKind()) {
+    case tok::kw_const:
+      // void foo() const & requires...
+      return false;
+    case tok::r_paren:
+      // We differentiate after the switch.
+      break;
+    default:
+      // Is most definitly an expression.
+      return true;
+    }
+
+    auto RParen = PrevPrev;
+    auto LastParenContent = RParen->getPreviousNonComment();
+
+    if (!LastParenContent) {
+      // No Token is invalid code, just do whatever you want.
+      return true;
+    }
+
+    switch (LastParenContent->Tok.getKind()) {
+    case tok::r_paren:
+      // Nested parens, probably not a clause.
+      return true;
+    case tok::l_paren:
+      // FIXME: We need an annotation on the paren to really know if it is a
+      // function call:
+      // ... foo() && requires ...
+      // or a declaration:
+      // void foo() && requires ...
+      // there is no distinction possible right now. We go for the latter,
+      // because it's more likely to appear in code.
+      return false;
+    case tok::amp:
+    case tok::ampamp:
+    case tok::star:
+      // A function declaration where the parameter name is omitted:
+      // void foo(Type&&) && requires ...
+      return false;
+    case tok::identifier:
+      // We have to look further...
+      break;
+    default:
+      if (LastParenContent->isSimpleTypeSpecifier()) {
+        // Definetly function delcaration.
+        return false;
+      }
+      return true;
+    }
+
+    auto BeforeLastParenContent = LastParenContent->getPreviousNonComment();
+    if (!BeforeLastParenContent) {
+      // No Token is invalid code, just do whatever you want.
+      return true;
+    }
+
+    switch (BeforeLastParenContent->Tok.getKind()) {
+    case tok::l_paren:
+      // FIXME: We need an annotation on the paren to really know if it is a
+      // function call:
+      // ... foo(variable) && requires ...
+      // or a declaration:
+      // void foo(Type /*with omitted*/) && requires ...
+      // there is no distinction possible right now. We go for the former,
+      // because it's more likely to appear in code.
+      return true;
+    case tok::identifier:
+      // Two identifiers is, without macros, a parameter type and value, thus a
+      // clause.
+      return false;
+    default:
+      if (BeforeLastParenContent->isSimpleTypeSpecifier()) {
+        // Definetly function delcaration.
+        return false;
+      }
+      break;
+    }
+    return true;
+  };
+
+  if (PreviousNonComment &&
+      !PreviousNonComment->isOneOf(tok::greater, tok::r_paren, tok::kw_noexcept,
+                                   tok::kw_const,
+                                   TT_RequiresExpressionLBrace) &&
+      (!PreviousNonComment->isOneOf(tok::amp, tok::ampamp) ||
+       ProbablyExpressionAfterAmpOrAmpAmp())) {
+    parseRequiresExpression();
+    return false;
+  }
+  parseRequiresClause();
+  return true;
+}
+
 /// \brief Parses a requires clause.
 /// \pre The current token needs to be the requires keyword.
 /// \sa parseRequiresExpression
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to