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,6 +14,7 @@ VariantValue.cpp Parser.cpp Registry.cpp + Marshallers.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 { @@ -91,8 +100,10 @@ return Value.getBoolean(); } - static ArgKind getKind() { - return ArgKind(ArgKind::AK_Boolean); + static ArgKind getKind() { return ArgKind(ArgKind::AK_Boolean); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; } }; @@ -103,8 +114,10 @@ return Value.getDouble(); } - static ArgKind getKind() { - return ArgKind(ArgKind::AK_Double); + static ArgKind getKind() { return ArgKind(ArgKind::AK_Double); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; } }; @@ -115,8 +128,10 @@ return Value.getUnsigned(); } - static ArgKind getKind() { - return ArgKind(ArgKind::AK_Unsigned); + static ArgKind getKind() { return ArgKind(ArgKind::AK_Unsigned); } + + static llvm::Optional getBestGuess(const VariantValue &) { + return llvm::None; } }; @@ -141,6 +156,8 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_String); } + + static llvm::Optional getBestGuess(const VariantValue &Value); }; template <> struct ArgTypeTraits { @@ -164,6 +181,8 @@ static ArgKind getKind() { return ArgKind(ArgKind::AK_String); } + + static llvm::Optional getBestGuess(const VariantValue &Value); }; template <> struct ArgTypeTraits { @@ -186,6 +205,8 @@ } static ArgKind getKind() { return ArgKind(ArgKind::AK_String); } + + static llvm::Optional getBestGuess(const VariantValue &Value); }; /// Matcher descriptor interface. @@ -319,7 +340,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); } @@ -496,9 +517,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,91 @@ +#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 != -1u) { + ++MaxEditDistance; + } + llvm::StringRef Res; + for (const llvm::StringRef &Item : Allowed) { + if (Item.equals_lower(Search)) { + if (Item.equals(Search)) + return Item.str(); + 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; +}