diff --git a/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h b/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h --- a/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h +++ b/clang/include/clang/ASTMatchers/Dynamic/Diagnostics.h @@ -65,6 +65,7 @@ ET_RegistryNotBindable = 4, ET_RegistryAmbiguousOverload = 5, ET_RegistryValueNotFound = 6, + ET_RegistryUnknownEnumWithReplace = 7, ET_ParserStringError = 100, ET_ParserNoOpenParen = 101, diff --git a/clang/lib/ASTMatchers/Dynamic/CMakeLists.txt b/clang/lib/ASTMatchers/Dynamic/CMakeLists.txt --- a/clang/lib/ASTMatchers/Dynamic/CMakeLists.txt +++ b/clang/lib/ASTMatchers/Dynamic/CMakeLists.txt @@ -14,9 +14,10 @@ add_clang_library(clangDynamicASTMatchers Diagnostics.cpp - VariantValue.cpp + Marshallers.cpp Parser.cpp Registry.cpp + VariantValue.cpp LINK_LIBS clangAST diff --git a/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp b/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp --- a/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Diagnostics.cpp @@ -98,6 +98,8 @@ return "Ambiguous matcher overload."; case Diagnostics::ET_RegistryValueNotFound: return "Value not found: $0"; + case Diagnostics::ET_RegistryUnknownEnumWithReplace: + return "Unknown value '$1' for arg $0; did you mean '$2'"; case Diagnostics::ET_ParserStringError: return "Error parsing string token: <$0>"; diff --git a/clang/lib/ASTMatchers/Dynamic/Marshallers.h b/clang/lib/ASTMatchers/Dynamic/Marshallers.h --- a/clang/lib/ASTMatchers/Dynamic/Marshallers.h +++ b/clang/lib/ASTMatchers/Dynamic/Marshallers.h @@ -29,6 +29,7 @@ #include "clang/Basic/OpenMPKinds.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" @@ -64,6 +65,10 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_String); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; + } }; template <> @@ -82,6 +87,10 @@ static ArgKind getKind() { return ArgKind(ASTNodeKind::getFromNodeKind()); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; + } }; template <> struct ArgTypeTraits { @@ -94,6 +103,10 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_Boolean); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; + } }; template <> struct ArgTypeTraits { @@ -106,6 +119,10 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_Double); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; + } }; template <> struct ArgTypeTraits { @@ -118,6 +135,10 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_Unsigned); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; + } }; template <> struct ArgTypeTraits { @@ -141,6 +162,8 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_String); } + + static llvm::Optional getBestGuess(const VariantValue &Value); }; template <> struct ArgTypeTraits { @@ -164,6 +187,8 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_String); } + + static llvm::Optional getBestGuess(const VariantValue &Value); }; template <> struct ArgTypeTraits { @@ -185,6 +210,8 @@ } static ArgKind getKind() { return ArgKind(ArgKind::AK_String); } + + static llvm::Optional getBestGuess(const VariantValue &Value); }; /// Matcher descriptor interface. @@ -318,7 +345,7 @@ /// polymorphic matcher. For the former, we just construct the VariantMatcher. /// For the latter, we instantiate all the possible Matcher of the poly /// matcher. -static VariantMatcher outvalueToVariantMatcher(const DynTypedMatcher &Matcher) { +inline VariantMatcher outvalueToVariantMatcher(const DynTypedMatcher &Matcher) { return VariantMatcher::SingleMatcher(Matcher); } @@ -495,9 +522,16 @@ #define CHECK_ARG_TYPE(index, type) \ if (!ArgTypeTraits::is(Args[index].Value)) { \ - Error->addError(Args[index].Range, Error->ET_RegistryWrongArgType) \ - << (index + 1) << ArgTypeTraits::getKind().asString() \ - << Args[index].Value.getTypeAsString(); \ + if (llvm::Optional BestGuess = \ + ArgTypeTraits::getBestGuess(Args[index].Value)) { \ + 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::getKind().asString() \ + << Args[index].Value.getTypeAsString(); \ + } \ return VariantMatcher(); \ } diff --git a/clang/lib/ASTMatchers/Dynamic/Marshallers.cpp b/clang/lib/ASTMatchers/Dynamic/Marshallers.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/ASTMatchers/Dynamic/Marshallers.cpp @@ -0,0 +1,89 @@ +#include "Marshallers.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include + +static llvm::Optional +getBestGuess(llvm::StringRef Search, llvm::ArrayRef 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 +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 +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 +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; +} diff --git a/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp b/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp --- a/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp +++ b/clang/unittests/ASTMatchers/Dynamic/ParserTest.cpp @@ -317,6 +317,20 @@ EXPECT_EQ("Input value has unresolved overloaded type: " "Matcher", 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) {