forked from OSchip/llvm-project
[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
This commit is contained in:
parent
53b7abdd23
commit
427c1dc4f4
|
@ -65,6 +65,7 @@ public:
|
||||||
ET_RegistryNotBindable = 4,
|
ET_RegistryNotBindable = 4,
|
||||||
ET_RegistryAmbiguousOverload = 5,
|
ET_RegistryAmbiguousOverload = 5,
|
||||||
ET_RegistryValueNotFound = 6,
|
ET_RegistryValueNotFound = 6,
|
||||||
|
ET_RegistryUnknownEnumWithReplace = 7,
|
||||||
|
|
||||||
ET_ParserStringError = 100,
|
ET_ParserStringError = 100,
|
||||||
ET_ParserNoOpenParen = 101,
|
ET_ParserNoOpenParen = 101,
|
||||||
|
|
|
@ -14,9 +14,10 @@ endif()
|
||||||
|
|
||||||
add_clang_library(clangDynamicASTMatchers
|
add_clang_library(clangDynamicASTMatchers
|
||||||
Diagnostics.cpp
|
Diagnostics.cpp
|
||||||
VariantValue.cpp
|
Marshallers.cpp
|
||||||
Parser.cpp
|
Parser.cpp
|
||||||
Registry.cpp
|
Registry.cpp
|
||||||
|
VariantValue.cpp
|
||||||
|
|
||||||
LINK_LIBS
|
LINK_LIBS
|
||||||
clangAST
|
clangAST
|
||||||
|
|
|
@ -98,6 +98,8 @@ static StringRef errorTypeToFormatString(Diagnostics::ErrorType Type) {
|
||||||
return "Ambiguous matcher overload.";
|
return "Ambiguous matcher overload.";
|
||||||
case Diagnostics::ET_RegistryValueNotFound:
|
case Diagnostics::ET_RegistryValueNotFound:
|
||||||
return "Value not found: $0";
|
return "Value not found: $0";
|
||||||
|
case Diagnostics::ET_RegistryUnknownEnumWithReplace:
|
||||||
|
return "Unknown value '$1' for arg $0; did you mean '$2'";
|
||||||
|
|
||||||
case Diagnostics::ET_ParserStringError:
|
case Diagnostics::ET_ParserStringError:
|
||||||
return "Error parsing string token: <$0>";
|
return "Error parsing string token: <$0>";
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
#include "Marshallers.h"
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/ADT/Optional.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static llvm::Optional<std::string>
|
||||||
|
getBestGuess(llvm::StringRef Search, llvm::ArrayRef<llvm::StringRef> Allowed,
|
||||||
|
llvm::StringRef DropPrefix = "", unsigned MaxEditDistance = 3) {
|
||||||
|
if (MaxEditDistance != ~0U)
|
||||||
|
++MaxEditDistance;
|
||||||
|
llvm::StringRef Res;
|
||||||
|
for (const llvm::StringRef &Item : Allowed) {
|
||||||
|
if (Item.equals_lower(Search)) {
|
||||||
|
assert(!Item.equals(Search) && "This should be handled earlier on.");
|
||||||
|
MaxEditDistance = 1;
|
||||||
|
Res = Item;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
unsigned Distance = Item.edit_distance(Search);
|
||||||
|
if (Distance < MaxEditDistance) {
|
||||||
|
MaxEditDistance = Distance;
|
||||||
|
Res = Item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Res.empty())
|
||||||
|
return Res.str();
|
||||||
|
if (!DropPrefix.empty()) {
|
||||||
|
--MaxEditDistance; // Treat dropping the prefix as 1 edit
|
||||||
|
for (const llvm::StringRef &Item : Allowed) {
|
||||||
|
auto NoPrefix = Item;
|
||||||
|
if (!NoPrefix.consume_front(DropPrefix))
|
||||||
|
continue;
|
||||||
|
if (NoPrefix.equals_lower(Search)) {
|
||||||
|
if (NoPrefix.equals(Search))
|
||||||
|
return Item.str();
|
||||||
|
MaxEditDistance = 1;
|
||||||
|
Res = Item;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
unsigned Distance = NoPrefix.edit_distance(Search);
|
||||||
|
if (Distance < MaxEditDistance) {
|
||||||
|
MaxEditDistance = Distance;
|
||||||
|
Res = Item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Res.empty())
|
||||||
|
return Res.str();
|
||||||
|
}
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::Optional<std::string>
|
||||||
|
clang::ast_matchers::dynamic::internal::ArgTypeTraits<
|
||||||
|
clang::attr::Kind>::getBestGuess(const VariantValue &Value) {
|
||||||
|
static constexpr llvm::StringRef Allowed[] = {
|
||||||
|
#define ATTR(X) "attr::" #X,
|
||||||
|
#include "clang/Basic/AttrList.inc"
|
||||||
|
};
|
||||||
|
if (Value.isString())
|
||||||
|
return ::getBestGuess(Value.getString(), llvm::makeArrayRef(Allowed),
|
||||||
|
"attr::");
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::Optional<std::string>
|
||||||
|
clang::ast_matchers::dynamic::internal::ArgTypeTraits<
|
||||||
|
clang::CastKind>::getBestGuess(const VariantValue &Value) {
|
||||||
|
static constexpr llvm::StringRef Allowed[] = {
|
||||||
|
#define CAST_OPERATION(Name) #Name,
|
||||||
|
#include "clang/AST/OperationKinds.def"
|
||||||
|
};
|
||||||
|
if (Value.isString())
|
||||||
|
return ::getBestGuess(Value.getString(), llvm::makeArrayRef(Allowed));
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::Optional<std::string>
|
||||||
|
clang::ast_matchers::dynamic::internal::ArgTypeTraits<
|
||||||
|
clang::OpenMPClauseKind>::getBestGuess(const VariantValue &Value) {
|
||||||
|
static constexpr llvm::StringRef Allowed[] = {
|
||||||
|
#define OPENMP_CLAUSE(TextualSpelling, Class) "OMPC_" #TextualSpelling,
|
||||||
|
#include "clang/Basic/OpenMPKinds.def"
|
||||||
|
};
|
||||||
|
if (Value.isString())
|
||||||
|
return ::getBestGuess(Value.getString(), llvm::makeArrayRef(Allowed),
|
||||||
|
"OMPC_");
|
||||||
|
return llvm::None;
|
||||||
|
}
|
|
@ -29,6 +29,7 @@
|
||||||
#include "clang/Basic/OpenMPKinds.h"
|
#include "clang/Basic/OpenMPKinds.h"
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/None.h"
|
#include "llvm/ADT/None.h"
|
||||||
|
#include "llvm/ADT/Optional.h"
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
#include "llvm/ADT/StringRef.h"
|
||||||
#include "llvm/ADT/StringSwitch.h"
|
#include "llvm/ADT/StringSwitch.h"
|
||||||
|
@ -64,6 +65,10 @@ template <> struct ArgTypeTraits<std::string> {
|
||||||
static ArgKind getKind() {
|
static ArgKind getKind() {
|
||||||
return ArgKind(ArgKind::AK_String);
|
return ArgKind(ArgKind::AK_String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
@ -82,6 +87,10 @@ template <class T> struct ArgTypeTraits<ast_matchers::internal::Matcher<T>> {
|
||||||
static ArgKind getKind() {
|
static ArgKind getKind() {
|
||||||
return ArgKind(ASTNodeKind::getFromNodeKind<T>());
|
return ArgKind(ASTNodeKind::getFromNodeKind<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct ArgTypeTraits<bool> {
|
template <> struct ArgTypeTraits<bool> {
|
||||||
|
@ -94,6 +103,10 @@ template <> struct ArgTypeTraits<bool> {
|
||||||
static ArgKind getKind() {
|
static ArgKind getKind() {
|
||||||
return ArgKind(ArgKind::AK_Boolean);
|
return ArgKind(ArgKind::AK_Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct ArgTypeTraits<double> {
|
template <> struct ArgTypeTraits<double> {
|
||||||
|
@ -106,6 +119,10 @@ template <> struct ArgTypeTraits<double> {
|
||||||
static ArgKind getKind() {
|
static ArgKind getKind() {
|
||||||
return ArgKind(ArgKind::AK_Double);
|
return ArgKind(ArgKind::AK_Double);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct ArgTypeTraits<unsigned> {
|
template <> struct ArgTypeTraits<unsigned> {
|
||||||
|
@ -118,6 +135,10 @@ template <> struct ArgTypeTraits<unsigned> {
|
||||||
static ArgKind getKind() {
|
static ArgKind getKind() {
|
||||||
return ArgKind(ArgKind::AK_Unsigned);
|
return ArgKind(ArgKind::AK_Unsigned);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &) {
|
||||||
|
return llvm::None;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct ArgTypeTraits<attr::Kind> {
|
template <> struct ArgTypeTraits<attr::Kind> {
|
||||||
|
@ -141,6 +162,8 @@ public:
|
||||||
static ArgKind getKind() {
|
static ArgKind getKind() {
|
||||||
return ArgKind(ArgKind::AK_String);
|
return ArgKind(ArgKind::AK_String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &Value);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct ArgTypeTraits<CastKind> {
|
template <> struct ArgTypeTraits<CastKind> {
|
||||||
|
@ -164,6 +187,8 @@ public:
|
||||||
static ArgKind getKind() {
|
static ArgKind getKind() {
|
||||||
return ArgKind(ArgKind::AK_String);
|
return ArgKind(ArgKind::AK_String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &Value);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct ArgTypeTraits<OpenMPClauseKind> {
|
template <> struct ArgTypeTraits<OpenMPClauseKind> {
|
||||||
|
@ -185,6 +210,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
static ArgKind getKind() { return ArgKind(ArgKind::AK_String); }
|
static ArgKind getKind() { return ArgKind(ArgKind::AK_String); }
|
||||||
|
|
||||||
|
static llvm::Optional<std::string> getBestGuess(const VariantValue &Value);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Matcher descriptor interface.
|
/// Matcher descriptor interface.
|
||||||
|
@ -318,7 +345,7 @@ static void mergePolyMatchers(const PolyMatcher &Poly,
|
||||||
/// polymorphic matcher. For the former, we just construct the VariantMatcher.
|
/// polymorphic matcher. For the former, we just construct the VariantMatcher.
|
||||||
/// For the latter, we instantiate all the possible Matcher<T> of the poly
|
/// For the latter, we instantiate all the possible Matcher<T> of the poly
|
||||||
/// matcher.
|
/// matcher.
|
||||||
static VariantMatcher outvalueToVariantMatcher(const DynTypedMatcher &Matcher) {
|
inline VariantMatcher outvalueToVariantMatcher(const DynTypedMatcher &Matcher) {
|
||||||
return VariantMatcher::SingleMatcher(Matcher);
|
return VariantMatcher::SingleMatcher(Matcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,9 +522,16 @@ private:
|
||||||
|
|
||||||
#define CHECK_ARG_TYPE(index, type) \
|
#define CHECK_ARG_TYPE(index, type) \
|
||||||
if (!ArgTypeTraits<type>::is(Args[index].Value)) { \
|
if (!ArgTypeTraits<type>::is(Args[index].Value)) { \
|
||||||
Error->addError(Args[index].Range, Error->ET_RegistryWrongArgType) \
|
if (llvm::Optional<std::string> BestGuess = \
|
||||||
<< (index + 1) << ArgTypeTraits<type>::getKind().asString() \
|
ArgTypeTraits<type>::getBestGuess(Args[index].Value)) { \
|
||||||
<< Args[index].Value.getTypeAsString(); \
|
Error->addError(Args[index].Range, \
|
||||||
|
Error->ET_RegistryUnknownEnumWithReplace) \
|
||||||
|
<< index + 1 << Args[index].Value.getString() << *BestGuess; \
|
||||||
|
} else { \
|
||||||
|
Error->addError(Args[index].Range, Error->ET_RegistryWrongArgType) \
|
||||||
|
<< (index + 1) << ArgTypeTraits<type>::getKind().asString() \
|
||||||
|
<< Args[index].Value.getTypeAsString(); \
|
||||||
|
} \
|
||||||
return VariantMatcher(); \
|
return VariantMatcher(); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -317,6 +317,20 @@ TEST(ParserTest, Errors) {
|
||||||
EXPECT_EQ("Input value has unresolved overloaded type: "
|
EXPECT_EQ("Input value has unresolved overloaded type: "
|
||||||
"Matcher<DoStmt|ForStmt|WhileStmt|CXXForRangeStmt|FunctionDecl>",
|
"Matcher<DoStmt|ForStmt|WhileStmt|CXXForRangeStmt|FunctionDecl>",
|
||||||
ParseMatcherWithError("hasBody(stmt())"));
|
ParseMatcherWithError("hasBody(stmt())"));
|
||||||
|
EXPECT_EQ(
|
||||||
|
"1:1: Error parsing argument 1 for matcher decl.\n"
|
||||||
|
"1:6: Error building matcher hasAttr.\n"
|
||||||
|
"1:14: Unknown value 'attr::Fnal' for arg 1; did you mean 'attr::Final'",
|
||||||
|
ParseMatcherWithError(R"query(decl(hasAttr("attr::Fnal")))query"));
|
||||||
|
EXPECT_EQ("1:1: Error parsing argument 1 for matcher decl.\n"
|
||||||
|
"1:6: Error building matcher hasAttr.\n"
|
||||||
|
"1:14: Unknown value 'Final' for arg 1; did you mean 'attr::Final'",
|
||||||
|
ParseMatcherWithError(R"query(decl(hasAttr("Final")))query"));
|
||||||
|
EXPECT_EQ("1:1: Error parsing argument 1 for matcher decl.\n"
|
||||||
|
"1:6: Error building matcher hasAttr.\n"
|
||||||
|
"1:14: Incorrect type for arg 1. (Expected = string) != (Actual = "
|
||||||
|
"String)",
|
||||||
|
ParseMatcherWithError(R"query(decl(hasAttr("unrelated")))query"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ParserTest, OverloadErrors) {
|
TEST(ParserTest, OverloadErrors) {
|
||||||
|
|
Loading…
Reference in New Issue