Skip to content

Commit 427c1dc

Browse files
committed
[ASTMatchers] Matchers that take enumerations args provide hints with invalid arguments
Summary: This adds support for giving hints when using dynamic matchers with enum args. Take this query, I couldn't figure out why the matcher wasn't working: (Turns out it needed to be "IntegralToBoolean", but thats another bug itself) ``` clang-query> match implicitCastExpr(hasCastKind("CK_IntegralToBoolean")) 1:1: Error parsing argument 1 for matcher implicitCastExpr. 1:18: Error building matcher hasCastKind. 1:30: Incorrect type for arg 1. (Expected = string) != (Actual = String) ``` With this patch the new behaviour looks like this: ``` clang-query> match implicitCastExpr(hasCastKind("CK_IntegralToBoolean")) 1:1: Error parsing argument 1 for matcher implicitCastExpr. 1:18: Error building matcher hasCastKind. 1:30: Unknown value 'CK_IntegralToBoolean' for arg 1; did you mean 'IntegralToBoolean' ``` There are no test cases for this yet as there simply isn't any infrastructure for testing errors reported when parsing args that I can see. Reviewers: klimek, jdoerfert, aaron.ballman Reviewed By: aaron.ballman Subscribers: aaron.ballman, dexonsmith, mgorny, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D77499
1 parent 53b7abd commit 427c1dc

File tree

6 files changed

+146
-5
lines changed

6 files changed

+146
-5
lines changed

clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class Diagnostics {
6565
ET_RegistryNotBindable = 4,
6666
ET_RegistryAmbiguousOverload = 5,
6767
ET_RegistryValueNotFound = 6,
68+
ET_RegistryUnknownEnumWithReplace = 7,
6869

6970
ET_ParserStringError = 100,
7071
ET_ParserNoOpenParen = 101,

clang/lib/ASTMatchers/Dynamic/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ endif()
1414

1515
add_clang_library(clangDynamicASTMatchers
1616
Diagnostics.cpp
17-
VariantValue.cpp
17+
Marshallers.cpp
1818
Parser.cpp
1919
Registry.cpp
20+
VariantValue.cpp
2021

2122
LINK_LIBS
2223
clangAST

clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ static StringRef errorTypeToFormatString(Diagnostics::ErrorType Type) {
9898
return "Ambiguous matcher overload.";
9999
case Diagnostics::ET_RegistryValueNotFound:
100100
return "Value not found: $0";
101+
case Diagnostics::ET_RegistryUnknownEnumWithReplace:
102+
return "Unknown value '$1' for arg $0; did you mean '$2'";
101103

102104
case Diagnostics::ET_ParserStringError:
103105
return "Error parsing string token: <$0>";
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#include "Marshallers.h"
2+
#include "llvm/ADT/ArrayRef.h"
3+
#include "llvm/ADT/Optional.h"
4+
#include "llvm/ADT/StringRef.h"
5+
#include <string>
6+
7+
static llvm::Optional<std::string>
8+
getBestGuess(llvm::StringRef Search, llvm::ArrayRef<llvm::StringRef> Allowed,
9+
llvm::StringRef DropPrefix = "", unsigned MaxEditDistance = 3) {
10+
if (MaxEditDistance != ~0U)
11+
++MaxEditDistance;
12+
llvm::StringRef Res;
13+
for (const llvm::StringRef &Item : Allowed) {
14+
if (Item.equals_lower(Search)) {
15+
assert(!Item.equals(Search) && "This should be handled earlier on.");
16+
MaxEditDistance = 1;
17+
Res = Item;
18+
continue;
19+
}
20+
unsigned Distance = Item.edit_distance(Search);
21+
if (Distance < MaxEditDistance) {
22+
MaxEditDistance = Distance;
23+
Res = Item;
24+
}
25+
}
26+
if (!Res.empty())
27+
return Res.str();
28+
if (!DropPrefix.empty()) {
29+
--MaxEditDistance; // Treat dropping the prefix as 1 edit
30+
for (const llvm::StringRef &Item : Allowed) {
31+
auto NoPrefix = Item;
32+
if (!NoPrefix.consume_front(DropPrefix))
33+
continue;
34+
if (NoPrefix.equals_lower(Search)) {
35+
if (NoPrefix.equals(Search))
36+
return Item.str();
37+
MaxEditDistance = 1;
38+
Res = Item;
39+
continue;
40+
}
41+
unsigned Distance = NoPrefix.edit_distance(Search);
42+
if (Distance < MaxEditDistance) {
43+
MaxEditDistance = Distance;
44+
Res = Item;
45+
}
46+
}
47+
if (!Res.empty())
48+
return Res.str();
49+
}
50+
return llvm::None;
51+
}
52+
53+
llvm::Optional<std::string>
54+
clang::ast_matchers::dynamic::internal::ArgTypeTraits<
55+
clang::attr::Kind>::getBestGuess(const VariantValue &Value) {
56+
static constexpr llvm::StringRef Allowed[] = {
57+
#define ATTR(X) "attr::" #X,
58+
#include "clang/Basic/AttrList.inc"
59+
};
60+
if (Value.isString())
61+
return ::getBestGuess(Value.getString(), llvm::makeArrayRef(Allowed),
62+
"attr::");
63+
return llvm::None;
64+
}
65+
66+
llvm::Optional<std::string>
67+
clang::ast_matchers::dynamic::internal::ArgTypeTraits<
68+
clang::CastKind>::getBestGuess(const VariantValue &Value) {
69+
static constexpr llvm::StringRef Allowed[] = {
70+
#define CAST_OPERATION(Name) #Name,
71+
#include "clang/AST/OperationKinds.def"
72+
};
73+
if (Value.isString())
74+
return ::getBestGuess(Value.getString(), llvm::makeArrayRef(Allowed));
75+
return llvm::None;
76+
}
77+
78+
llvm::Optional<std::string>
79+
clang::ast_matchers::dynamic::internal::ArgTypeTraits<
80+
clang::OpenMPClauseKind>::getBestGuess(const VariantValue &Value) {
81+
static constexpr llvm::StringRef Allowed[] = {
82+
#define OPENMP_CLAUSE(TextualSpelling, Class) "OMPC_" #TextualSpelling,
83+
#include "clang/Basic/OpenMPKinds.def"
84+
};
85+
if (Value.isString())
86+
return ::getBestGuess(Value.getString(), llvm::makeArrayRef(Allowed),
87+
"OMPC_");
88+
return llvm::None;
89+
}

clang/lib/ASTMatchers/Dynamic/Marshallers.h

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "clang/Basic/OpenMPKinds.h"
3030
#include "llvm/ADT/ArrayRef.h"
3131
#include "llvm/ADT/None.h"
32+
#include "llvm/ADT/Optional.h"
3233
#include "llvm/ADT/STLExtras.h"
3334
#include "llvm/ADT/StringRef.h"
3435
#include "llvm/ADT/StringSwitch.h"
@@ -64,6 +65,10 @@ template <> struct ArgTypeTraits<std::string> {
6465
static ArgKind getKind() {
6566
return ArgKind(ArgKind::AK_String);
6667
}
68+
69+
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
70+
return llvm::None;
71+
}
6772
};
6873

6974
template <>
@@ -82,6 +87,10 @@ template <class T> struct ArgTypeTraits<ast_matchers::internal::Matcher<T>> {
8287
static ArgKind getKind() {
8388
return ArgKind(ASTNodeKind::getFromNodeKind<T>());
8489
}
90+
91+
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
92+
return llvm::None;
93+
}
8594
};
8695

8796
template <> struct ArgTypeTraits<bool> {
@@ -94,6 +103,10 @@ template <> struct ArgTypeTraits<bool> {
94103
static ArgKind getKind() {
95104
return ArgKind(ArgKind::AK_Boolean);
96105
}
106+
107+
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
108+
return llvm::None;
109+
}
97110
};
98111

99112
template <> struct ArgTypeTraits<double> {
@@ -106,6 +119,10 @@ template <> struct ArgTypeTraits<double> {
106119
static ArgKind getKind() {
107120
return ArgKind(ArgKind::AK_Double);
108121
}
122+
123+
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
124+
return llvm::None;
125+
}
109126
};
110127

111128
template <> struct ArgTypeTraits<unsigned> {
@@ -118,6 +135,10 @@ template <> struct ArgTypeTraits<unsigned> {
118135
static ArgKind getKind() {
119136
return ArgKind(ArgKind::AK_Unsigned);
120137
}
138+
139+
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
140+
return llvm::None;
141+
}
121142
};
122143

123144
template <> struct ArgTypeTraits<attr::Kind> {
@@ -141,6 +162,8 @@ template <> struct ArgTypeTraits<attr::Kind> {
141162
static ArgKind getKind() {
142163
return ArgKind(ArgKind::AK_String);
143164
}
165+
166+
static llvm::Optional<std::string> getBestGuess(const VariantValue &Value);
144167
};
145168

146169
template <> struct ArgTypeTraits<CastKind> {
@@ -164,6 +187,8 @@ template <> struct ArgTypeTraits<CastKind> {
164187
static ArgKind getKind() {
165188
return ArgKind(ArgKind::AK_String);
166189
}
190+
191+
static llvm::Optional<std::string> getBestGuess(const VariantValue &Value);
167192
};
168193

169194
template <> struct ArgTypeTraits<OpenMPClauseKind> {
@@ -185,6 +210,8 @@ template <> struct ArgTypeTraits<OpenMPClauseKind> {
185210
}
186211

187212
static ArgKind getKind() { return ArgKind(ArgKind::AK_String); }
213+
214+
static llvm::Optional<std::string> getBestGuess(const VariantValue &Value);
188215
};
189216

190217
/// Matcher descriptor interface.
@@ -318,7 +345,7 @@ static void mergePolyMatchers(const PolyMatcher &Poly,
318345
/// polymorphic matcher. For the former, we just construct the VariantMatcher.
319346
/// For the latter, we instantiate all the possible Matcher<T> of the poly
320347
/// matcher.
321-
static VariantMatcher outvalueToVariantMatcher(const DynTypedMatcher &Matcher) {
348+
inline VariantMatcher outvalueToVariantMatcher(const DynTypedMatcher &Matcher) {
322349
return VariantMatcher::SingleMatcher(Matcher);
323350
}
324351

@@ -495,9 +522,16 @@ class DynCastAllOfMatcherDescriptor : public VariadicFuncMatcherDescriptor {
495522

496523
#define CHECK_ARG_TYPE(index, type) \
497524
if (!ArgTypeTraits<type>::is(Args[index].Value)) { \
498-
Error->addError(Args[index].Range, Error->ET_RegistryWrongArgType) \
499-
<< (index + 1) << ArgTypeTraits<type>::getKind().asString() \
500-
<< Args[index].Value.getTypeAsString(); \
525+
if (llvm::Optional<std::string> BestGuess = \
526+
ArgTypeTraits<type>::getBestGuess(Args[index].Value)) { \
527+
Error->addError(Args[index].Range, \
528+
Error->ET_RegistryUnknownEnumWithReplace) \
529+
<< index + 1 << Args[index].Value.getString() << *BestGuess; \
530+
} else { \
531+
Error->addError(Args[index].Range, Error->ET_RegistryWrongArgType) \
532+
<< (index + 1) << ArgTypeTraits<type>::getKind().asString() \
533+
<< Args[index].Value.getTypeAsString(); \
534+
} \
501535
return VariantMatcher(); \
502536
}
503537

clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,20 @@ TEST(ParserTest, Errors) {
317317
EXPECT_EQ("Input value has unresolved overloaded type: "
318318
"Matcher<DoStmt|ForStmt|WhileStmt|CXXForRangeStmt|FunctionDecl>",
319319
ParseMatcherWithError("hasBody(stmt())"));
320+
EXPECT_EQ(
321+
"1:1: Error parsing argument 1 for matcher decl.\n"
322+
"1:6: Error building matcher hasAttr.\n"
323+
"1:14: Unknown value 'attr::Fnal' for arg 1; did you mean 'attr::Final'",
324+
ParseMatcherWithError(R"query(decl(hasAttr("attr::Fnal")))query"));
325+
EXPECT_EQ("1:1: Error parsing argument 1 for matcher decl.\n"
326+
"1:6: Error building matcher hasAttr.\n"
327+
"1:14: Unknown value 'Final' for arg 1; did you mean 'attr::Final'",
328+
ParseMatcherWithError(R"query(decl(hasAttr("Final")))query"));
329+
EXPECT_EQ("1:1: Error parsing argument 1 for matcher decl.\n"
330+
"1:6: Error building matcher hasAttr.\n"
331+
"1:14: Incorrect type for arg 1. (Expected = string) != (Actual = "
332+
"String)",
333+
ParseMatcherWithError(R"query(decl(hasAttr("unrelated")))query"));
320334
}
321335

322336
TEST(ParserTest, OverloadErrors) {

0 commit comments

Comments
 (0)