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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits