forked from OSchip/llvm-project
Support named values in the autocomplete feature.
Summary: This includes: - Passing a Sema to completeExpression to allow for named values in the expression. - Passing a map of names to values to the parser. - Update the Sema interface to include completion for matchers. - Change the parser to use the Sema for completion, instead of going directly to Registry. Reviewers: pcc Subscribers: klimek, cfe-commits Differential Revision: http://reviews.llvm.org/D3509 llvm-svn: 215472
This commit is contained in:
parent
4834ad2609
commit
646f23b809
|
@ -63,17 +63,6 @@ public:
|
||||||
public:
|
public:
|
||||||
virtual ~Sema();
|
virtual ~Sema();
|
||||||
|
|
||||||
/// \brief Lookup a value by name.
|
|
||||||
///
|
|
||||||
/// This can be used in the Sema layer to declare known constants or to
|
|
||||||
/// allow to split an expression in pieces.
|
|
||||||
///
|
|
||||||
/// \param Name The name of the value to lookup.
|
|
||||||
///
|
|
||||||
/// \return The named value. It could be any type that VariantValue
|
|
||||||
/// supports. An empty value means that the name is not recognized.
|
|
||||||
virtual VariantValue getNamedValue(StringRef Name);
|
|
||||||
|
|
||||||
/// \brief Process a matcher expression.
|
/// \brief Process a matcher expression.
|
||||||
///
|
///
|
||||||
/// All the arguments passed here have already been processed.
|
/// All the arguments passed here have already been processed.
|
||||||
|
@ -105,6 +94,29 @@ public:
|
||||||
/// found.
|
/// found.
|
||||||
virtual llvm::Optional<MatcherCtor>
|
virtual llvm::Optional<MatcherCtor>
|
||||||
lookupMatcherCtor(StringRef MatcherName) = 0;
|
lookupMatcherCtor(StringRef MatcherName) = 0;
|
||||||
|
|
||||||
|
/// \brief Compute the list of completion types for \p Context.
|
||||||
|
///
|
||||||
|
/// Each element of \p Context represents a matcher invocation, going from
|
||||||
|
/// outermost to innermost. Elements are pairs consisting of a reference to
|
||||||
|
/// the matcher constructor and the index of the next element in the
|
||||||
|
/// argument list of that matcher (or for the last element, the index of
|
||||||
|
/// the completion point in the argument list). An empty list requests
|
||||||
|
/// completion for the root matcher.
|
||||||
|
virtual std::vector<ArgKind> getAcceptedCompletionTypes(
|
||||||
|
llvm::ArrayRef<std::pair<MatcherCtor, unsigned>> Context);
|
||||||
|
|
||||||
|
/// \brief Compute the list of completions that match any of
|
||||||
|
/// \p AcceptedTypes.
|
||||||
|
///
|
||||||
|
/// \param All types accepted for this completion.
|
||||||
|
///
|
||||||
|
/// \return All completions for the specified types.
|
||||||
|
/// Completions should be valid when used in \c lookupMatcherCtor().
|
||||||
|
/// The matcher constructed from the return of \c lookupMatcherCtor()
|
||||||
|
/// should be convertible to some type in \p AcceptedTypes.
|
||||||
|
virtual std::vector<MatcherCompletion>
|
||||||
|
getMatcherCompletions(llvm::ArrayRef<ArgKind> AcceptedTypes);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Sema implementation that uses the matcher registry to process the
|
/// \brief Sema implementation that uses the matcher registry to process the
|
||||||
|
@ -121,58 +133,91 @@ public:
|
||||||
StringRef BindID,
|
StringRef BindID,
|
||||||
ArrayRef<ParserValue> Args,
|
ArrayRef<ParserValue> Args,
|
||||||
Diagnostics *Error) override;
|
Diagnostics *Error) override;
|
||||||
|
|
||||||
|
std::vector<ArgKind> getAcceptedCompletionTypes(
|
||||||
|
llvm::ArrayRef<std::pair<MatcherCtor, unsigned>> Context) override;
|
||||||
|
|
||||||
|
std::vector<MatcherCompletion>
|
||||||
|
getMatcherCompletions(llvm::ArrayRef<ArgKind> AcceptedTypes) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Parse a matcher expression, creating matchers from the registry.
|
typedef llvm::StringMap<VariantValue> NamedValueMap;
|
||||||
///
|
|
||||||
/// This overload creates matchers calling directly into the registry. If the
|
|
||||||
/// caller needs more control over how the matchers are created, then it can
|
|
||||||
/// use the overload below that takes a Sema.
|
|
||||||
///
|
|
||||||
/// \param MatcherCode The matcher expression to parse.
|
|
||||||
///
|
|
||||||
/// \return The matcher object constructed, or an empty Optional if an error
|
|
||||||
/// occurred.
|
|
||||||
/// In that case, \c Error will contain a description of the error.
|
|
||||||
/// The caller takes ownership of the DynTypedMatcher object returned.
|
|
||||||
static llvm::Optional<DynTypedMatcher>
|
|
||||||
parseMatcherExpression(StringRef MatcherCode, Diagnostics *Error);
|
|
||||||
|
|
||||||
/// \brief Parse a matcher expression.
|
/// \brief Parse a matcher expression.
|
||||||
///
|
///
|
||||||
/// \param MatcherCode The matcher expression to parse.
|
/// \param MatcherCode The matcher expression to parse.
|
||||||
///
|
///
|
||||||
/// \param S The Sema instance that will help the parser
|
/// \param S The Sema instance that will help the parser
|
||||||
/// construct the matchers.
|
/// construct the matchers. If null, it uses the default registry.
|
||||||
|
///
|
||||||
|
/// \param NamedValues A map of precomputed named values. This provides
|
||||||
|
/// the dictionary for the <NamedValue> rule of the grammar.
|
||||||
|
/// If null, it is ignored.
|
||||||
|
///
|
||||||
/// \return The matcher object constructed by the processor, or an empty
|
/// \return The matcher object constructed by the processor, or an empty
|
||||||
/// Optional if an error occurred. In that case, \c Error will contain a
|
/// Optional if an error occurred. In that case, \c Error will contain a
|
||||||
/// description of the error.
|
/// description of the error.
|
||||||
/// The caller takes ownership of the DynTypedMatcher object returned.
|
/// The caller takes ownership of the DynTypedMatcher object returned.
|
||||||
static llvm::Optional<DynTypedMatcher>
|
static llvm::Optional<DynTypedMatcher>
|
||||||
parseMatcherExpression(StringRef MatcherCode, Sema *S, Diagnostics *Error);
|
parseMatcherExpression(StringRef MatcherCode, Sema *S,
|
||||||
|
const NamedValueMap *NamedValues,
|
||||||
/// \brief Parse an expression, creating matchers from the registry.
|
Diagnostics *Error);
|
||||||
///
|
static llvm::Optional<DynTypedMatcher>
|
||||||
/// Parses any expression supported by this parser. In general, the
|
parseMatcherExpression(StringRef MatcherCode, Sema *S,
|
||||||
/// \c parseMatcherExpression function is a better approach to get a matcher
|
Diagnostics *Error) {
|
||||||
/// object.
|
return parseMatcherExpression(MatcherCode, S, nullptr, Error);
|
||||||
static bool parseExpression(StringRef Code, VariantValue *Value,
|
}
|
||||||
Diagnostics *Error);
|
static llvm::Optional<DynTypedMatcher>
|
||||||
|
parseMatcherExpression(StringRef MatcherCode, Diagnostics *Error) {
|
||||||
|
return parseMatcherExpression(MatcherCode, nullptr, Error);
|
||||||
|
}
|
||||||
|
|
||||||
/// \brief Parse an expression.
|
/// \brief Parse an expression.
|
||||||
///
|
///
|
||||||
/// Parses any expression supported by this parser. In general, the
|
/// Parses any expression supported by this parser. In general, the
|
||||||
/// \c parseMatcherExpression function is a better approach to get a matcher
|
/// \c parseMatcherExpression function is a better approach to get a matcher
|
||||||
/// object.
|
/// object.
|
||||||
|
///
|
||||||
|
/// \param S The Sema instance that will help the parser
|
||||||
|
/// construct the matchers. If null, it uses the default registry.
|
||||||
|
///
|
||||||
|
/// \param NamedValues A map of precomputed named values. This provides
|
||||||
|
/// the dictionary for the <NamedValue> rule of the grammar.
|
||||||
|
/// If null, it is ignored.
|
||||||
static bool parseExpression(StringRef Code, Sema *S,
|
static bool parseExpression(StringRef Code, Sema *S,
|
||||||
|
const NamedValueMap *NamedValues,
|
||||||
VariantValue *Value, Diagnostics *Error);
|
VariantValue *Value, Diagnostics *Error);
|
||||||
|
static bool parseExpression(StringRef Code, Sema *S,
|
||||||
|
VariantValue *Value, Diagnostics *Error) {
|
||||||
|
return parseExpression(Code, S, nullptr, Value, Error);
|
||||||
|
}
|
||||||
|
static bool parseExpression(StringRef Code, VariantValue *Value,
|
||||||
|
Diagnostics *Error) {
|
||||||
|
return parseExpression(Code, nullptr, Value, Error);
|
||||||
|
}
|
||||||
|
|
||||||
/// \brief Complete an expression at the given offset.
|
/// \brief Complete an expression at the given offset.
|
||||||
///
|
///
|
||||||
|
/// \param S The Sema instance that will help the parser
|
||||||
|
/// construct the matchers. If null, it uses the default registry.
|
||||||
|
///
|
||||||
|
/// \param NamedValues A map of precomputed named values. This provides
|
||||||
|
/// the dictionary for the <NamedValue> rule of the grammar.
|
||||||
|
/// If null, it is ignored.
|
||||||
|
///
|
||||||
/// \return The list of completions, which may be empty if there are no
|
/// \return The list of completions, which may be empty if there are no
|
||||||
/// available completions or if an error occurred.
|
/// available completions or if an error occurred.
|
||||||
static std::vector<MatcherCompletion>
|
static std::vector<MatcherCompletion>
|
||||||
completeExpression(StringRef Code, unsigned CompletionOffset);
|
completeExpression(StringRef Code, unsigned CompletionOffset, Sema *S,
|
||||||
|
const NamedValueMap *NamedValues);
|
||||||
|
static std::vector<MatcherCompletion>
|
||||||
|
completeExpression(StringRef Code, unsigned CompletionOffset, Sema *S) {
|
||||||
|
return completeExpression(Code, CompletionOffset, S, nullptr);
|
||||||
|
}
|
||||||
|
static std::vector<MatcherCompletion>
|
||||||
|
completeExpression(StringRef Code, unsigned CompletionOffset) {
|
||||||
|
return completeExpression(Code, CompletionOffset, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class CodeTokenizer;
|
class CodeTokenizer;
|
||||||
|
@ -180,6 +225,7 @@ private:
|
||||||
struct TokenInfo;
|
struct TokenInfo;
|
||||||
|
|
||||||
Parser(CodeTokenizer *Tokenizer, Sema *S,
|
Parser(CodeTokenizer *Tokenizer, Sema *S,
|
||||||
|
const NamedValueMap *NamedValues,
|
||||||
Diagnostics *Error);
|
Diagnostics *Error);
|
||||||
|
|
||||||
bool parseExpressionImpl(VariantValue *Value);
|
bool parseExpressionImpl(VariantValue *Value);
|
||||||
|
@ -187,12 +233,16 @@ private:
|
||||||
VariantValue *Value);
|
VariantValue *Value);
|
||||||
bool parseIdentifierPrefixImpl(VariantValue *Value);
|
bool parseIdentifierPrefixImpl(VariantValue *Value);
|
||||||
|
|
||||||
void addCompletion(const TokenInfo &CompToken, StringRef TypedText,
|
void addCompletion(const TokenInfo &CompToken,
|
||||||
StringRef Decl);
|
const MatcherCompletion &Completion);
|
||||||
void addExpressionCompletions();
|
void addExpressionCompletions();
|
||||||
|
|
||||||
|
std::vector<MatcherCompletion>
|
||||||
|
getNamedValueCompletions(ArrayRef<ArgKind> AcceptedTypes);
|
||||||
|
|
||||||
CodeTokenizer *const Tokenizer;
|
CodeTokenizer *const Tokenizer;
|
||||||
Sema *const S;
|
Sema *const S;
|
||||||
|
const NamedValueMap *const NamedValues;
|
||||||
Diagnostics *const Error;
|
Diagnostics *const Error;
|
||||||
|
|
||||||
typedef std::vector<std::pair<MatcherCtor, unsigned> > ContextStackTy;
|
typedef std::vector<std::pair<MatcherCtor, unsigned> > ContextStackTy;
|
||||||
|
|
|
@ -36,8 +36,10 @@ typedef const internal::MatcherDescriptor *MatcherCtor;
|
||||||
|
|
||||||
struct MatcherCompletion {
|
struct MatcherCompletion {
|
||||||
MatcherCompletion() {}
|
MatcherCompletion() {}
|
||||||
MatcherCompletion(StringRef TypedText, StringRef MatcherDecl)
|
MatcherCompletion(StringRef TypedText, StringRef MatcherDecl,
|
||||||
: TypedText(TypedText), MatcherDecl(MatcherDecl) {}
|
unsigned Specificity)
|
||||||
|
: TypedText(TypedText), MatcherDecl(MatcherDecl),
|
||||||
|
Specificity(Specificity) {}
|
||||||
|
|
||||||
/// \brief The text to type to select this matcher.
|
/// \brief The text to type to select this matcher.
|
||||||
std::string TypedText;
|
std::string TypedText;
|
||||||
|
@ -45,6 +47,13 @@ struct MatcherCompletion {
|
||||||
/// \brief The "declaration" of the matcher, with type information.
|
/// \brief The "declaration" of the matcher, with type information.
|
||||||
std::string MatcherDecl;
|
std::string MatcherDecl;
|
||||||
|
|
||||||
|
/// \brief Value corresponding to the "specificity" of the converted matcher.
|
||||||
|
///
|
||||||
|
/// Zero specificity indicates that this conversion would produce a trivial
|
||||||
|
/// matcher that will either always or never match.
|
||||||
|
/// Such matchers are excluded from code completion results.
|
||||||
|
unsigned Specificity;
|
||||||
|
|
||||||
bool operator==(const MatcherCompletion &Other) const {
|
bool operator==(const MatcherCompletion &Other) const {
|
||||||
return TypedText == Other.TypedText && MatcherDecl == Other.MatcherDecl;
|
return TypedText == Other.TypedText && MatcherDecl == Other.MatcherDecl;
|
||||||
}
|
}
|
||||||
|
@ -58,28 +67,28 @@ public:
|
||||||
/// constructor, or Optional<MatcherCtor>() if not found.
|
/// constructor, or Optional<MatcherCtor>() if not found.
|
||||||
static llvm::Optional<MatcherCtor> lookupMatcherCtor(StringRef MatcherName);
|
static llvm::Optional<MatcherCtor> lookupMatcherCtor(StringRef MatcherName);
|
||||||
|
|
||||||
/// \brief Compute the list of completions for \p Context.
|
/// \brief Compute the list of completion types for \p Context.
|
||||||
///
|
///
|
||||||
/// Each element of \p Context represents a matcher invocation, going from
|
/// Each element of \p Context represents a matcher invocation, going from
|
||||||
/// outermost to innermost. Elements are pairs consisting of a reference to the
|
/// outermost to innermost. Elements are pairs consisting of a reference to
|
||||||
/// matcher constructor and the index of the next element in the argument list
|
/// the matcher constructor and the index of the next element in the
|
||||||
/// of that matcher (or for the last element, the index of the completion
|
/// argument list of that matcher (or for the last element, the index of
|
||||||
/// point in the argument list). An empty list requests completion for the
|
/// the completion point in the argument list). An empty list requests
|
||||||
/// root matcher.
|
/// completion for the root matcher.
|
||||||
|
static std::vector<ArgKind> getAcceptedCompletionTypes(
|
||||||
|
llvm::ArrayRef<std::pair<MatcherCtor, unsigned>> Context);
|
||||||
|
|
||||||
|
/// \brief Compute the list of completions that match any of
|
||||||
|
/// \p AcceptedTypes.
|
||||||
///
|
///
|
||||||
/// The completions are ordered first by decreasing relevance, then
|
/// \param All types accepted for this completion.
|
||||||
/// alphabetically. Relevance is determined by how closely the matcher's
|
|
||||||
/// type matches that of the context. For example, if the innermost matcher
|
|
||||||
/// takes a FunctionDecl matcher, the FunctionDecl matchers are returned
|
|
||||||
/// first, followed by the ValueDecl matchers, then NamedDecl, then Decl, then
|
|
||||||
/// polymorphic matchers.
|
|
||||||
///
|
///
|
||||||
/// Matchers which are technically convertible to the innermost context but
|
/// \return All completions for the specified types.
|
||||||
/// which would match either all or no nodes are excluded. For example,
|
/// Completions should be valid when used in \c lookupMatcherCtor().
|
||||||
/// namedDecl and varDecl are excluded in a FunctionDecl context, because
|
/// The matcher constructed from the return of \c lookupMatcherCtor()
|
||||||
/// those matchers would match respectively all or no nodes in such a context.
|
/// should be convertible to some type in \p AcceptedTypes.
|
||||||
static std::vector<MatcherCompletion>
|
static std::vector<MatcherCompletion>
|
||||||
getCompletions(ArrayRef<std::pair<MatcherCtor, unsigned> > Context);
|
getMatcherCompletions(ArrayRef<ArgKind> AcceptedTypes);
|
||||||
|
|
||||||
/// \brief Construct a matcher from the registry.
|
/// \brief Construct a matcher from the registry.
|
||||||
///
|
///
|
||||||
|
|
|
@ -29,6 +29,50 @@ namespace clang {
|
||||||
namespace ast_matchers {
|
namespace ast_matchers {
|
||||||
namespace dynamic {
|
namespace dynamic {
|
||||||
|
|
||||||
|
/// \brief Kind identifier.
|
||||||
|
///
|
||||||
|
/// It supports all types that VariantValue can contain.
|
||||||
|
class ArgKind {
|
||||||
|
public:
|
||||||
|
enum Kind {
|
||||||
|
AK_Matcher,
|
||||||
|
AK_Unsigned,
|
||||||
|
AK_String
|
||||||
|
};
|
||||||
|
/// \brief Constructor for non-matcher types.
|
||||||
|
ArgKind(Kind K) : K(K) { assert(K != AK_Matcher); }
|
||||||
|
|
||||||
|
/// \brief Constructor for matcher types.
|
||||||
|
ArgKind(ast_type_traits::ASTNodeKind MatcherKind)
|
||||||
|
: K(AK_Matcher), MatcherKind(MatcherKind) {}
|
||||||
|
|
||||||
|
Kind getArgKind() const { return K; }
|
||||||
|
ast_type_traits::ASTNodeKind getMatcherKind() const {
|
||||||
|
assert(K == AK_Matcher);
|
||||||
|
return MatcherKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Determines if this type can be converted to \p To.
|
||||||
|
///
|
||||||
|
/// \param To the requested destination type.
|
||||||
|
///
|
||||||
|
/// \param Value corresponding to the "specificity" of the convertion.
|
||||||
|
bool isConvertibleTo(ArgKind To, unsigned *Specificity) const;
|
||||||
|
|
||||||
|
bool operator<(const ArgKind &Other) const {
|
||||||
|
if (K == AK_Matcher && Other.K == AK_Matcher)
|
||||||
|
return MatcherKind < Other.MatcherKind;
|
||||||
|
return K < Other.K;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief String representation of the type.
|
||||||
|
std::string asString() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Kind K;
|
||||||
|
ast_type_traits::ASTNodeKind MatcherKind;
|
||||||
|
};
|
||||||
|
|
||||||
using ast_matchers::internal::DynTypedMatcher;
|
using ast_matchers::internal::DynTypedMatcher;
|
||||||
|
|
||||||
/// \brief A variant matcher object.
|
/// \brief A variant matcher object.
|
||||||
|
@ -66,6 +110,8 @@ class VariantMatcher {
|
||||||
virtual llvm::Optional<DynTypedMatcher> getSingleMatcher() const = 0;
|
virtual llvm::Optional<DynTypedMatcher> getSingleMatcher() const = 0;
|
||||||
virtual std::string getTypeAsString() const = 0;
|
virtual std::string getTypeAsString() const = 0;
|
||||||
virtual void makeTypedMatcher(MatcherOps &Ops) const = 0;
|
virtual void makeTypedMatcher(MatcherOps &Ops) const = 0;
|
||||||
|
virtual bool isConvertibleTo(ast_type_traits::ASTNodeKind Kind,
|
||||||
|
unsigned *Specificity) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -116,6 +162,18 @@ public:
|
||||||
return Ops.hasMatcher();
|
return Ops.hasMatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \brief Determines if the contained matcher can be converted to \p Kind.
|
||||||
|
///
|
||||||
|
/// \param Kind the requested destination type.
|
||||||
|
///
|
||||||
|
/// \param Value corresponding to the "specificity" of the convertion.
|
||||||
|
bool isConvertibleTo(ast_type_traits::ASTNodeKind Kind,
|
||||||
|
unsigned *Specificity) const {
|
||||||
|
if (Value)
|
||||||
|
return Value->isConvertibleTo(Kind, Specificity);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// \brief Return this matcher as a \c Matcher<T>.
|
/// \brief Return this matcher as a \c Matcher<T>.
|
||||||
///
|
///
|
||||||
/// Handles the different types (Single, Polymorphic) accordingly.
|
/// Handles the different types (Single, Polymorphic) accordingly.
|
||||||
|
@ -228,6 +286,22 @@ public:
|
||||||
const VariantMatcher &getMatcher() const;
|
const VariantMatcher &getMatcher() const;
|
||||||
void setMatcher(const VariantMatcher &Matcher);
|
void setMatcher(const VariantMatcher &Matcher);
|
||||||
|
|
||||||
|
/// \brief Determines if the contained value can be converted to \p Kind.
|
||||||
|
///
|
||||||
|
/// \param Kind the requested destination type.
|
||||||
|
///
|
||||||
|
/// \param Value corresponding to the "specificity" of the convertion.
|
||||||
|
bool isConvertibleTo(ArgKind Kind, unsigned* Specificity) const;
|
||||||
|
|
||||||
|
/// \brief Determines if the contained value can be converted to any kind
|
||||||
|
/// in \p Kinds.
|
||||||
|
///
|
||||||
|
/// \param Kinds the requested destination types.
|
||||||
|
///
|
||||||
|
/// \param Value corresponding to the "specificity" of the convertion. It is
|
||||||
|
/// the maximum specificity of all the possible conversions.
|
||||||
|
bool isConvertibleTo(ArrayRef<ArgKind> Kinds, unsigned *Specificity) const;
|
||||||
|
|
||||||
/// \brief String representation of the type of the value.
|
/// \brief String representation of the type of the value.
|
||||||
std::string getTypeAsString() const;
|
std::string getTypeAsString() const;
|
||||||
|
|
||||||
|
|
|
@ -30,48 +30,8 @@
|
||||||
namespace clang {
|
namespace clang {
|
||||||
namespace ast_matchers {
|
namespace ast_matchers {
|
||||||
namespace dynamic {
|
namespace dynamic {
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
struct ArgKind {
|
|
||||||
enum Kind {
|
|
||||||
AK_Matcher,
|
|
||||||
AK_Unsigned,
|
|
||||||
AK_String
|
|
||||||
};
|
|
||||||
ArgKind(Kind K)
|
|
||||||
: K(K) {}
|
|
||||||
ArgKind(ast_type_traits::ASTNodeKind MatcherKind)
|
|
||||||
: K(AK_Matcher), MatcherKind(MatcherKind) {}
|
|
||||||
|
|
||||||
std::string asString() const {
|
|
||||||
switch (getArgKind()) {
|
|
||||||
case AK_Matcher:
|
|
||||||
return (Twine("Matcher<") + MatcherKind.asStringRef() + ">").str();
|
|
||||||
case AK_Unsigned:
|
|
||||||
return "unsigned";
|
|
||||||
case AK_String:
|
|
||||||
return "string";
|
|
||||||
}
|
|
||||||
llvm_unreachable("unhandled ArgKind");
|
|
||||||
}
|
|
||||||
|
|
||||||
Kind getArgKind() const { return K; }
|
|
||||||
ast_type_traits::ASTNodeKind getMatcherKind() const {
|
|
||||||
assert(K == AK_Matcher);
|
|
||||||
return MatcherKind;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator<(const ArgKind &Other) const {
|
|
||||||
if (K == AK_Matcher && Other.K == AK_Matcher)
|
|
||||||
return MatcherKind < Other.MatcherKind;
|
|
||||||
return K < Other.K;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Kind K;
|
|
||||||
ast_type_traits::ASTNodeKind MatcherKind;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// \brief Helper template class to just from argument type to the right is/get
|
/// \brief Helper template class to just from argument type to the right is/get
|
||||||
/// functions in VariantValue.
|
/// functions in VariantValue.
|
||||||
|
@ -161,16 +121,10 @@ inline bool isRetKindConvertibleTo(
|
||||||
ArrayRef<ast_type_traits::ASTNodeKind> RetKinds,
|
ArrayRef<ast_type_traits::ASTNodeKind> RetKinds,
|
||||||
ast_type_traits::ASTNodeKind Kind, unsigned *Specificity,
|
ast_type_traits::ASTNodeKind Kind, unsigned *Specificity,
|
||||||
ast_type_traits::ASTNodeKind *LeastDerivedKind) {
|
ast_type_traits::ASTNodeKind *LeastDerivedKind) {
|
||||||
for (ArrayRef<ast_type_traits::ASTNodeKind>::const_iterator
|
for (const ast_type_traits::ASTNodeKind &NodeKind : RetKinds) {
|
||||||
i = RetKinds.begin(),
|
if (ArgKind(NodeKind).isConvertibleTo(Kind, Specificity)) {
|
||||||
e = RetKinds.end();
|
|
||||||
i != e; ++i) {
|
|
||||||
unsigned Distance;
|
|
||||||
if (i->isBaseOf(Kind, &Distance)) {
|
|
||||||
if (Specificity)
|
|
||||||
*Specificity = 100 - Distance;
|
|
||||||
if (LeastDerivedKind)
|
if (LeastDerivedKind)
|
||||||
*LeastDerivedKind = *i;
|
*LeastDerivedKind = NodeKind;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "clang/Basic/CharInfo.h"
|
#include "clang/Basic/CharInfo.h"
|
||||||
#include "llvm/ADT/Optional.h"
|
#include "llvm/ADT/Optional.h"
|
||||||
#include "llvm/ADT/Twine.h"
|
#include "llvm/ADT/Twine.h"
|
||||||
|
#include "llvm/Support/ManagedStatic.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -258,8 +259,14 @@ private:
|
||||||
|
|
||||||
Parser::Sema::~Sema() {}
|
Parser::Sema::~Sema() {}
|
||||||
|
|
||||||
VariantValue Parser::Sema::getNamedValue(StringRef Name) {
|
std::vector<ArgKind> Parser::Sema::getAcceptedCompletionTypes(
|
||||||
return VariantValue();
|
llvm::ArrayRef<std::pair<MatcherCtor, unsigned>> Context) {
|
||||||
|
return std::vector<ArgKind>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<MatcherCompletion>
|
||||||
|
Parser::Sema::getMatcherCompletions(llvm::ArrayRef<ArgKind> AcceptedTypes) {
|
||||||
|
return std::vector<MatcherCompletion>();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Parser::ScopedContextEntry {
|
struct Parser::ScopedContextEntry {
|
||||||
|
@ -288,7 +295,9 @@ bool Parser::parseIdentifierPrefixImpl(VariantValue *Value) {
|
||||||
|
|
||||||
if (Tokenizer->nextTokenKind() != TokenInfo::TK_OpenParen) {
|
if (Tokenizer->nextTokenKind() != TokenInfo::TK_OpenParen) {
|
||||||
// Parse as a named value.
|
// Parse as a named value.
|
||||||
if (const VariantValue NamedValue = S->getNamedValue(NameToken.Text)) {
|
if (const VariantValue NamedValue =
|
||||||
|
NamedValues ? NamedValues->lookup(NameToken.Text)
|
||||||
|
: VariantValue()) {
|
||||||
*Value = NamedValue;
|
*Value = NamedValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -379,7 +388,7 @@ bool Parser::parseMatcherExpressionImpl(const TokenInfo &NameToken,
|
||||||
Tokenizer->consumeNextToken(); // consume the period.
|
Tokenizer->consumeNextToken(); // consume the period.
|
||||||
const TokenInfo BindToken = Tokenizer->consumeNextToken();
|
const TokenInfo BindToken = Tokenizer->consumeNextToken();
|
||||||
if (BindToken.Kind == TokenInfo::TK_CodeCompletion) {
|
if (BindToken.Kind == TokenInfo::TK_CodeCompletion) {
|
||||||
addCompletion(BindToken, "bind(\"", "bind");
|
addCompletion(BindToken, MatcherCompletion("bind(\"", "bind", 1));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,15 +436,30 @@ bool Parser::parseMatcherExpressionImpl(const TokenInfo &NameToken,
|
||||||
|
|
||||||
// If the prefix of this completion matches the completion token, add it to
|
// If the prefix of this completion matches the completion token, add it to
|
||||||
// Completions minus the prefix.
|
// Completions minus the prefix.
|
||||||
void Parser::addCompletion(const TokenInfo &CompToken, StringRef TypedText,
|
void Parser::addCompletion(const TokenInfo &CompToken,
|
||||||
StringRef Decl) {
|
const MatcherCompletion& Completion) {
|
||||||
if (TypedText.size() >= CompToken.Text.size() &&
|
if (StringRef(Completion.TypedText).startswith(CompToken.Text) &&
|
||||||
TypedText.substr(0, CompToken.Text.size()) == CompToken.Text) {
|
Completion.Specificity > 0) {
|
||||||
Completions.push_back(
|
Completions.emplace_back(Completion.TypedText.substr(CompToken.Text.size()),
|
||||||
MatcherCompletion(TypedText.substr(CompToken.Text.size()), Decl));
|
Completion.MatcherDecl, Completion.Specificity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<MatcherCompletion> Parser::getNamedValueCompletions(
|
||||||
|
ArrayRef<ArgKind> AcceptedTypes) {
|
||||||
|
if (!NamedValues) return std::vector<MatcherCompletion>();
|
||||||
|
std::vector<MatcherCompletion> Result;
|
||||||
|
for (const auto &Entry : *NamedValues) {
|
||||||
|
unsigned Specificity;
|
||||||
|
if (Entry.getValue().isConvertibleTo(AcceptedTypes, &Specificity)) {
|
||||||
|
std::string Decl =
|
||||||
|
(Entry.getValue().getTypeAsString() + " " + Entry.getKey()).str();
|
||||||
|
Result.emplace_back(Entry.getKey(), Decl, Specificity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
void Parser::addExpressionCompletions() {
|
void Parser::addExpressionCompletions() {
|
||||||
const TokenInfo CompToken = Tokenizer->consumeNextToken();
|
const TokenInfo CompToken = Tokenizer->consumeNextToken();
|
||||||
assert(CompToken.Kind == TokenInfo::TK_CodeCompletion);
|
assert(CompToken.Kind == TokenInfo::TK_CodeCompletion);
|
||||||
|
@ -449,12 +473,13 @@ void Parser::addExpressionCompletions() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<MatcherCompletion> RegCompletions =
|
auto AcceptedTypes = S->getAcceptedCompletionTypes(ContextStack);
|
||||||
Registry::getCompletions(ContextStack);
|
for (const auto &Completion : S->getMatcherCompletions(AcceptedTypes)) {
|
||||||
for (std::vector<MatcherCompletion>::iterator I = RegCompletions.begin(),
|
addCompletion(CompToken, Completion);
|
||||||
E = RegCompletions.end();
|
}
|
||||||
I != E; ++I) {
|
|
||||||
addCompletion(CompToken, I->TypedText, I->MatcherDecl);
|
for (const auto &Completion : getNamedValueCompletions(AcceptedTypes)) {
|
||||||
|
addCompletion(CompToken, Completion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,9 +519,12 @@ bool Parser::parseExpressionImpl(VariantValue *Value) {
|
||||||
llvm_unreachable("Unknown token kind.");
|
llvm_unreachable("Unknown token kind.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static llvm::ManagedStatic<Parser::RegistrySema> DefaultRegistrySema;
|
||||||
|
|
||||||
Parser::Parser(CodeTokenizer *Tokenizer, Sema *S,
|
Parser::Parser(CodeTokenizer *Tokenizer, Sema *S,
|
||||||
Diagnostics *Error)
|
const NamedValueMap *NamedValues, Diagnostics *Error)
|
||||||
: Tokenizer(Tokenizer), S(S), Error(Error) {}
|
: Tokenizer(Tokenizer), S(S ? S : &*DefaultRegistrySema),
|
||||||
|
NamedValues(NamedValues), Error(Error) {}
|
||||||
|
|
||||||
Parser::RegistrySema::~RegistrySema() {}
|
Parser::RegistrySema::~RegistrySema() {}
|
||||||
|
|
||||||
|
@ -516,16 +544,22 @@ VariantMatcher Parser::RegistrySema::actOnMatcherExpression(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::parseExpression(StringRef Code, VariantValue *Value,
|
std::vector<ArgKind> Parser::RegistrySema::getAcceptedCompletionTypes(
|
||||||
Diagnostics *Error) {
|
ArrayRef<std::pair<MatcherCtor, unsigned>> Context) {
|
||||||
RegistrySema S;
|
return Registry::getAcceptedCompletionTypes(Context);
|
||||||
return parseExpression(Code, &S, Value, Error);
|
}
|
||||||
|
|
||||||
|
std::vector<MatcherCompletion> Parser::RegistrySema::getMatcherCompletions(
|
||||||
|
ArrayRef<ArgKind> AcceptedTypes) {
|
||||||
|
return Registry::getMatcherCompletions(AcceptedTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::parseExpression(StringRef Code, Sema *S,
|
bool Parser::parseExpression(StringRef Code, Sema *S,
|
||||||
|
const NamedValueMap *NamedValues,
|
||||||
VariantValue *Value, Diagnostics *Error) {
|
VariantValue *Value, Diagnostics *Error) {
|
||||||
CodeTokenizer Tokenizer(Code, Error);
|
CodeTokenizer Tokenizer(Code, Error);
|
||||||
if (!Parser(&Tokenizer, S, Error).parseExpressionImpl(Value)) return false;
|
if (!Parser(&Tokenizer, S, NamedValues, Error).parseExpressionImpl(Value))
|
||||||
|
return false;
|
||||||
if (Tokenizer.peekNextToken().Kind != TokenInfo::TK_Eof) {
|
if (Tokenizer.peekNextToken().Kind != TokenInfo::TK_Eof) {
|
||||||
Error->addError(Tokenizer.peekNextToken().Range,
|
Error->addError(Tokenizer.peekNextToken().Range,
|
||||||
Error->ET_ParserTrailingCode);
|
Error->ET_ParserTrailingCode);
|
||||||
|
@ -535,28 +569,31 @@ bool Parser::parseExpression(StringRef Code, Sema *S,
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<MatcherCompletion>
|
std::vector<MatcherCompletion>
|
||||||
Parser::completeExpression(StringRef Code, unsigned CompletionOffset) {
|
Parser::completeExpression(StringRef Code, unsigned CompletionOffset, Sema *S,
|
||||||
|
const NamedValueMap *NamedValues) {
|
||||||
Diagnostics Error;
|
Diagnostics Error;
|
||||||
CodeTokenizer Tokenizer(Code, &Error, CompletionOffset);
|
CodeTokenizer Tokenizer(Code, &Error, CompletionOffset);
|
||||||
RegistrySema S;
|
Parser P(&Tokenizer, S, NamedValues, &Error);
|
||||||
Parser P(&Tokenizer, &S, &Error);
|
|
||||||
VariantValue Dummy;
|
VariantValue Dummy;
|
||||||
P.parseExpressionImpl(&Dummy);
|
P.parseExpressionImpl(&Dummy);
|
||||||
|
|
||||||
|
// Sort by specificity, then by name.
|
||||||
|
std::sort(P.Completions.begin(), P.Completions.end(),
|
||||||
|
[](const MatcherCompletion &A, const MatcherCompletion &B) {
|
||||||
|
if (A.Specificity != B.Specificity)
|
||||||
|
return A.Specificity > B.Specificity;
|
||||||
|
return A.TypedText < B.TypedText;
|
||||||
|
});
|
||||||
|
|
||||||
return P.Completions;
|
return P.Completions;
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm::Optional<DynTypedMatcher>
|
llvm::Optional<DynTypedMatcher>
|
||||||
Parser::parseMatcherExpression(StringRef Code, Diagnostics *Error) {
|
Parser::parseMatcherExpression(StringRef Code, Sema *S,
|
||||||
RegistrySema S;
|
const NamedValueMap *NamedValues,
|
||||||
return parseMatcherExpression(Code, &S, Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::Optional<DynTypedMatcher>
|
|
||||||
Parser::parseMatcherExpression(StringRef Code, Parser::Sema *S,
|
|
||||||
Diagnostics *Error) {
|
Diagnostics *Error) {
|
||||||
VariantValue Value;
|
VariantValue Value;
|
||||||
if (!parseExpression(Code, S, &Value, Error))
|
if (!parseExpression(Code, S, NamedValues, &Value, Error))
|
||||||
return llvm::Optional<DynTypedMatcher>();
|
return llvm::Optional<DynTypedMatcher>();
|
||||||
if (!Value.isMatcher()) {
|
if (!Value.isMatcher()) {
|
||||||
Error->addError(SourceRange(), Error->ET_ParserNotAMatcher);
|
Error->addError(SourceRange(), Error->ET_ParserNotAMatcher);
|
||||||
|
|
|
@ -353,77 +353,63 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
|
||||||
return OS;
|
return OS;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ReverseSpecificityThenName {
|
} // namespace
|
||||||
bool operator()(const std::pair<unsigned, std::string> &A,
|
|
||||||
const std::pair<unsigned, std::string> &B) const {
|
|
||||||
return A.first > B.first || (A.first == B.first && A.second < B.second);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
std::vector<ArgKind> Registry::getAcceptedCompletionTypes(
|
||||||
|
ArrayRef<std::pair<MatcherCtor, unsigned>> Context) {
|
||||||
std::vector<MatcherCompletion> Registry::getCompletions(
|
|
||||||
ArrayRef<std::pair<MatcherCtor, unsigned> > Context) {
|
|
||||||
ASTNodeKind InitialTypes[] = {
|
ASTNodeKind InitialTypes[] = {
|
||||||
ASTNodeKind::getFromNodeKind<Decl>(),
|
ASTNodeKind::getFromNodeKind<Decl>(),
|
||||||
ASTNodeKind::getFromNodeKind<QualType>(),
|
ASTNodeKind::getFromNodeKind<QualType>(),
|
||||||
ASTNodeKind::getFromNodeKind<Type>(),
|
ASTNodeKind::getFromNodeKind<Type>(),
|
||||||
ASTNodeKind::getFromNodeKind<Stmt>(),
|
ASTNodeKind::getFromNodeKind<Stmt>(),
|
||||||
ASTNodeKind::getFromNodeKind<NestedNameSpecifier>(),
|
ASTNodeKind::getFromNodeKind<NestedNameSpecifier>(),
|
||||||
ASTNodeKind::getFromNodeKind<NestedNameSpecifierLoc>(),
|
ASTNodeKind::getFromNodeKind<NestedNameSpecifierLoc>(),
|
||||||
ASTNodeKind::getFromNodeKind<TypeLoc>()
|
ASTNodeKind::getFromNodeKind<TypeLoc>()};
|
||||||
};
|
|
||||||
ArrayRef<ASTNodeKind> InitialTypesRef(InitialTypes);
|
|
||||||
|
|
||||||
// Starting with the above seed of acceptable top-level matcher types, compute
|
// Starting with the above seed of acceptable top-level matcher types, compute
|
||||||
// the acceptable type set for the argument indicated by each context element.
|
// the acceptable type set for the argument indicated by each context element.
|
||||||
std::set<ASTNodeKind> TypeSet(InitialTypesRef.begin(), InitialTypesRef.end());
|
std::set<ArgKind> TypeSet(std::begin(InitialTypes), std::end(InitialTypes));
|
||||||
for (ArrayRef<std::pair<MatcherCtor, unsigned> >::iterator
|
for (const auto &CtxEntry : Context) {
|
||||||
CtxI = Context.begin(),
|
MatcherCtor Ctor = CtxEntry.first;
|
||||||
CtxE = Context.end();
|
unsigned ArgNumber = CtxEntry.second;
|
||||||
CtxI != CtxE; ++CtxI) {
|
std::vector<ArgKind> NextTypeSet;
|
||||||
std::vector<internal::ArgKind> NextTypeSet;
|
for (const ArgKind &Kind : TypeSet) {
|
||||||
for (std::set<ASTNodeKind>::iterator I = TypeSet.begin(), E = TypeSet.end();
|
if (Kind.getArgKind() == Kind.AK_Matcher &&
|
||||||
I != E; ++I) {
|
Ctor->isConvertibleTo(Kind.getMatcherKind()) &&
|
||||||
if (CtxI->first->isConvertibleTo(*I) &&
|
(Ctor->isVariadic() || ArgNumber < Ctor->getNumArgs()))
|
||||||
(CtxI->first->isVariadic() ||
|
Ctor->getArgKinds(Kind.getMatcherKind(), ArgNumber, NextTypeSet);
|
||||||
CtxI->second < CtxI->first->getNumArgs()))
|
|
||||||
CtxI->first->getArgKinds(*I, CtxI->second, NextTypeSet);
|
|
||||||
}
|
}
|
||||||
TypeSet.clear();
|
TypeSet.clear();
|
||||||
for (std::vector<internal::ArgKind>::iterator I = NextTypeSet.begin(),
|
TypeSet.insert(NextTypeSet.begin(), NextTypeSet.end());
|
||||||
E = NextTypeSet.end();
|
|
||||||
I != E; ++I) {
|
|
||||||
if (I->getArgKind() == internal::ArgKind::AK_Matcher)
|
|
||||||
TypeSet.insert(I->getMatcherKind());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return std::vector<ArgKind>(TypeSet.begin(), TypeSet.end());
|
||||||
|
}
|
||||||
|
|
||||||
typedef std::map<std::pair<unsigned, std::string>, MatcherCompletion,
|
std::vector<MatcherCompletion>
|
||||||
ReverseSpecificityThenName> CompletionsTy;
|
Registry::getMatcherCompletions(ArrayRef<ArgKind> AcceptedTypes) {
|
||||||
CompletionsTy Completions;
|
std::vector<MatcherCompletion> Completions;
|
||||||
|
|
||||||
// TypeSet now contains the list of acceptable types for the argument we are
|
// Search the registry for acceptable matchers.
|
||||||
// completing. Search the registry for acceptable matchers.
|
|
||||||
for (ConstructorMap::const_iterator I = RegistryData->constructors().begin(),
|
for (ConstructorMap::const_iterator I = RegistryData->constructors().begin(),
|
||||||
E = RegistryData->constructors().end();
|
E = RegistryData->constructors().end();
|
||||||
I != E; ++I) {
|
I != E; ++I) {
|
||||||
std::set<ASTNodeKind> RetKinds;
|
std::set<ASTNodeKind> RetKinds;
|
||||||
unsigned NumArgs = I->second->isVariadic() ? 1 : I->second->getNumArgs();
|
unsigned NumArgs = I->second->isVariadic() ? 1 : I->second->getNumArgs();
|
||||||
bool IsPolymorphic = I->second->isPolymorphic();
|
bool IsPolymorphic = I->second->isPolymorphic();
|
||||||
std::vector<std::vector<internal::ArgKind> > ArgsKinds(NumArgs);
|
std::vector<std::vector<ArgKind>> ArgsKinds(NumArgs);
|
||||||
unsigned MaxSpecificity = 0;
|
unsigned MaxSpecificity = 0;
|
||||||
for (std::set<ASTNodeKind>::iterator TI = TypeSet.begin(),
|
for (const ArgKind& Kind : AcceptedTypes) {
|
||||||
TE = TypeSet.end();
|
if (Kind.getArgKind() != Kind.AK_Matcher)
|
||||||
TI != TE; ++TI) {
|
continue;
|
||||||
unsigned Specificity;
|
unsigned Specificity;
|
||||||
ASTNodeKind LeastDerivedKind;
|
ASTNodeKind LeastDerivedKind;
|
||||||
if (I->second->isConvertibleTo(*TI, &Specificity, &LeastDerivedKind)) {
|
if (I->second->isConvertibleTo(Kind.getMatcherKind(), &Specificity,
|
||||||
|
&LeastDerivedKind)) {
|
||||||
if (MaxSpecificity < Specificity)
|
if (MaxSpecificity < Specificity)
|
||||||
MaxSpecificity = Specificity;
|
MaxSpecificity = Specificity;
|
||||||
RetKinds.insert(LeastDerivedKind);
|
RetKinds.insert(LeastDerivedKind);
|
||||||
for (unsigned Arg = 0; Arg != NumArgs; ++Arg)
|
for (unsigned Arg = 0; Arg != NumArgs; ++Arg)
|
||||||
I->second->getArgKinds(*TI, Arg, ArgsKinds[Arg]);
|
I->second->getArgKinds(Kind.getMatcherKind(), Arg, ArgsKinds[Arg]);
|
||||||
if (IsPolymorphic)
|
if (IsPolymorphic)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -437,24 +423,20 @@ std::vector<MatcherCompletion> Registry::getCompletions(
|
||||||
OS << "Matcher<T> " << I->first() << "(Matcher<T>";
|
OS << "Matcher<T> " << I->first() << "(Matcher<T>";
|
||||||
} else {
|
} else {
|
||||||
OS << "Matcher<" << RetKinds << "> " << I->first() << "(";
|
OS << "Matcher<" << RetKinds << "> " << I->first() << "(";
|
||||||
for (std::vector<std::vector<internal::ArgKind> >::iterator
|
for (const std::vector<ArgKind> &Arg : ArgsKinds) {
|
||||||
KI = ArgsKinds.begin(),
|
if (&Arg != &ArgsKinds[0])
|
||||||
KE = ArgsKinds.end();
|
|
||||||
KI != KE; ++KI) {
|
|
||||||
if (KI != ArgsKinds.begin())
|
|
||||||
OS << ", ";
|
OS << ", ";
|
||||||
// This currently assumes that a matcher may not overload a
|
// This currently assumes that a matcher may not overload a
|
||||||
// non-matcher, and all non-matcher overloads have identical
|
// non-matcher, and all non-matcher overloads have identical
|
||||||
// arguments.
|
// arguments.
|
||||||
if ((*KI)[0].getArgKind() == internal::ArgKind::AK_Matcher) {
|
if (Arg[0].getArgKind() == ArgKind::AK_Matcher) {
|
||||||
std::set<ASTNodeKind> MatcherKinds;
|
std::set<ASTNodeKind> MatcherKinds;
|
||||||
std::transform(
|
std::transform(Arg.begin(), Arg.end(),
|
||||||
KI->begin(), KI->end(),
|
std::inserter(MatcherKinds, MatcherKinds.end()),
|
||||||
std::inserter(MatcherKinds, MatcherKinds.end()),
|
std::mem_fun_ref(&ArgKind::getMatcherKind));
|
||||||
std::mem_fun_ref(&internal::ArgKind::getMatcherKind));
|
|
||||||
OS << "Matcher<" << MatcherKinds << ">";
|
OS << "Matcher<" << MatcherKinds << ">";
|
||||||
} else {
|
} else {
|
||||||
OS << (*KI)[0].asString();
|
OS << Arg[0].asString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -466,19 +448,14 @@ std::vector<MatcherCompletion> Registry::getCompletions(
|
||||||
TypedText += "(";
|
TypedText += "(";
|
||||||
if (ArgsKinds.empty())
|
if (ArgsKinds.empty())
|
||||||
TypedText += ")";
|
TypedText += ")";
|
||||||
else if (ArgsKinds[0][0].getArgKind() == internal::ArgKind::AK_String)
|
else if (ArgsKinds[0][0].getArgKind() == ArgKind::AK_String)
|
||||||
TypedText += "\"";
|
TypedText += "\"";
|
||||||
|
|
||||||
Completions[std::make_pair(MaxSpecificity, I->first())] =
|
Completions.emplace_back(TypedText, OS.str(), MaxSpecificity);
|
||||||
MatcherCompletion(TypedText, OS.str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<MatcherCompletion> RetVal;
|
return Completions;
|
||||||
for (CompletionsTy::iterator I = Completions.begin(), E = Completions.end();
|
|
||||||
I != E; ++I)
|
|
||||||
RetVal.push_back(I->second);
|
|
||||||
return RetVal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
|
|
@ -20,6 +20,35 @@ namespace clang {
|
||||||
namespace ast_matchers {
|
namespace ast_matchers {
|
||||||
namespace dynamic {
|
namespace dynamic {
|
||||||
|
|
||||||
|
std::string ArgKind::asString() const {
|
||||||
|
switch (getArgKind()) {
|
||||||
|
case AK_Matcher:
|
||||||
|
return (Twine("Matcher<") + MatcherKind.asStringRef() + ">").str();
|
||||||
|
case AK_Unsigned:
|
||||||
|
return "unsigned";
|
||||||
|
case AK_String:
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
llvm_unreachable("unhandled ArgKind");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArgKind::isConvertibleTo(ArgKind To, unsigned *Specificity) const {
|
||||||
|
if (K != To.K)
|
||||||
|
return false;
|
||||||
|
if (K != AK_Matcher) {
|
||||||
|
if (Specificity)
|
||||||
|
*Specificity = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
unsigned Distance;
|
||||||
|
if (!MatcherKind.isBaseOf(To.MatcherKind, &Distance))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (Specificity)
|
||||||
|
*Specificity = 100 - Distance;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
VariantMatcher::MatcherOps::~MatcherOps() {}
|
VariantMatcher::MatcherOps::~MatcherOps() {}
|
||||||
VariantMatcher::Payload::~Payload() {}
|
VariantMatcher::Payload::~Payload() {}
|
||||||
|
|
||||||
|
@ -27,21 +56,27 @@ class VariantMatcher::SinglePayload : public VariantMatcher::Payload {
|
||||||
public:
|
public:
|
||||||
SinglePayload(const DynTypedMatcher &Matcher) : Matcher(Matcher) {}
|
SinglePayload(const DynTypedMatcher &Matcher) : Matcher(Matcher) {}
|
||||||
|
|
||||||
virtual llvm::Optional<DynTypedMatcher> getSingleMatcher() const {
|
llvm::Optional<DynTypedMatcher> getSingleMatcher() const override {
|
||||||
return Matcher;
|
return Matcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::string getTypeAsString() const {
|
std::string getTypeAsString() const override {
|
||||||
return (Twine("Matcher<") + Matcher.getSupportedKind().asStringRef() + ">")
|
return (Twine("Matcher<") + Matcher.getSupportedKind().asStringRef() + ">")
|
||||||
.str();
|
.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void makeTypedMatcher(MatcherOps &Ops) const {
|
void makeTypedMatcher(MatcherOps &Ops) const override {
|
||||||
bool Ignore;
|
bool Ignore;
|
||||||
if (Ops.canConstructFrom(Matcher, Ignore))
|
if (Ops.canConstructFrom(Matcher, Ignore))
|
||||||
Ops.constructFrom(Matcher);
|
Ops.constructFrom(Matcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isConvertibleTo(ast_type_traits::ASTNodeKind Kind,
|
||||||
|
unsigned *Specificity) const override {
|
||||||
|
return ArgKind(Matcher.getSupportedKind())
|
||||||
|
.isConvertibleTo(Kind, Specificity);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const DynTypedMatcher Matcher;
|
const DynTypedMatcher Matcher;
|
||||||
};
|
};
|
||||||
|
@ -51,15 +86,15 @@ public:
|
||||||
PolymorphicPayload(std::vector<DynTypedMatcher> MatchersIn)
|
PolymorphicPayload(std::vector<DynTypedMatcher> MatchersIn)
|
||||||
: Matchers(std::move(MatchersIn)) {}
|
: Matchers(std::move(MatchersIn)) {}
|
||||||
|
|
||||||
virtual ~PolymorphicPayload() {}
|
~PolymorphicPayload() override {}
|
||||||
|
|
||||||
virtual llvm::Optional<DynTypedMatcher> getSingleMatcher() const {
|
llvm::Optional<DynTypedMatcher> getSingleMatcher() const override {
|
||||||
if (Matchers.size() != 1)
|
if (Matchers.size() != 1)
|
||||||
return llvm::Optional<DynTypedMatcher>();
|
return llvm::Optional<DynTypedMatcher>();
|
||||||
return Matchers[0];
|
return Matchers[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::string getTypeAsString() const {
|
std::string getTypeAsString() const override {
|
||||||
std::string Inner;
|
std::string Inner;
|
||||||
for (size_t i = 0, e = Matchers.size(); i != e; ++i) {
|
for (size_t i = 0, e = Matchers.size(); i != e; ++i) {
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
|
@ -69,7 +104,7 @@ public:
|
||||||
return (Twine("Matcher<") + Inner + ">").str();
|
return (Twine("Matcher<") + Inner + ">").str();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void makeTypedMatcher(MatcherOps &Ops) const {
|
void makeTypedMatcher(MatcherOps &Ops) const override {
|
||||||
bool FoundIsExact = false;
|
bool FoundIsExact = false;
|
||||||
const DynTypedMatcher *Found = nullptr;
|
const DynTypedMatcher *Found = nullptr;
|
||||||
int NumFound = 0;
|
int NumFound = 0;
|
||||||
|
@ -92,6 +127,21 @@ public:
|
||||||
Ops.constructFrom(*Found);
|
Ops.constructFrom(*Found);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isConvertibleTo(ast_type_traits::ASTNodeKind Kind,
|
||||||
|
unsigned *Specificity) const override {
|
||||||
|
unsigned MaxSpecificity = 0;
|
||||||
|
for (const DynTypedMatcher &Matcher : Matchers) {
|
||||||
|
unsigned ThisSpecificity;
|
||||||
|
if (ArgKind(Matcher.getSupportedKind())
|
||||||
|
.isConvertibleTo(Kind, &ThisSpecificity)) {
|
||||||
|
MaxSpecificity = std::max(MaxSpecificity, ThisSpecificity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Specificity)
|
||||||
|
*Specificity = MaxSpecificity;
|
||||||
|
return MaxSpecificity > 0;
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<DynTypedMatcher> Matchers;
|
const std::vector<DynTypedMatcher> Matchers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -101,11 +151,11 @@ public:
|
||||||
std::vector<VariantMatcher> Args)
|
std::vector<VariantMatcher> Args)
|
||||||
: Func(Func), Args(std::move(Args)) {}
|
: Func(Func), Args(std::move(Args)) {}
|
||||||
|
|
||||||
virtual llvm::Optional<DynTypedMatcher> getSingleMatcher() const {
|
llvm::Optional<DynTypedMatcher> getSingleMatcher() const override {
|
||||||
return llvm::Optional<DynTypedMatcher>();
|
return llvm::Optional<DynTypedMatcher>();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::string getTypeAsString() const {
|
std::string getTypeAsString() const override {
|
||||||
std::string Inner;
|
std::string Inner;
|
||||||
for (size_t i = 0, e = Args.size(); i != e; ++i) {
|
for (size_t i = 0, e = Args.size(); i != e; ++i) {
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
|
@ -115,10 +165,19 @@ public:
|
||||||
return Inner;
|
return Inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void makeTypedMatcher(MatcherOps &Ops) const {
|
void makeTypedMatcher(MatcherOps &Ops) const override {
|
||||||
Ops.constructVariadicOperator(Func, Args);
|
Ops.constructVariadicOperator(Func, Args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isConvertibleTo(ast_type_traits::ASTNodeKind Kind,
|
||||||
|
unsigned *Specificity) const override {
|
||||||
|
for (const VariantMatcher &Matcher : Args) {
|
||||||
|
if (!Matcher.isConvertibleTo(Kind, Specificity))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const ast_matchers::internal::VariadicOperatorFunction Func;
|
const ast_matchers::internal::VariadicOperatorFunction Func;
|
||||||
const std::vector<VariantMatcher> Args;
|
const std::vector<VariantMatcher> Args;
|
||||||
|
@ -251,6 +310,43 @@ void VariantValue::setMatcher(const VariantMatcher &NewValue) {
|
||||||
Value.Matcher = new VariantMatcher(NewValue);
|
Value.Matcher = new VariantMatcher(NewValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VariantValue::isConvertibleTo(ArgKind Kind, unsigned *Specificity) const {
|
||||||
|
switch (Kind.getArgKind()) {
|
||||||
|
case ArgKind::AK_Unsigned:
|
||||||
|
if (!isUnsigned())
|
||||||
|
return false;
|
||||||
|
*Specificity = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ArgKind::AK_String:
|
||||||
|
if (!isString())
|
||||||
|
return false;
|
||||||
|
*Specificity = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ArgKind::AK_Matcher:
|
||||||
|
if (!isMatcher())
|
||||||
|
return false;
|
||||||
|
return getMatcher().isConvertibleTo(Kind.getMatcherKind(), Specificity);
|
||||||
|
}
|
||||||
|
llvm_unreachable("Invalid Type");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VariantValue::isConvertibleTo(ArrayRef<ArgKind> Kinds,
|
||||||
|
unsigned *Specificity) const {
|
||||||
|
unsigned MaxSpecificity = 0;
|
||||||
|
for (const ArgKind& Kind : Kinds) {
|
||||||
|
unsigned ThisSpecificity;
|
||||||
|
if (!isConvertibleTo(Kind, &ThisSpecificity))
|
||||||
|
continue;
|
||||||
|
MaxSpecificity = std::max(MaxSpecificity, ThisSpecificity);
|
||||||
|
}
|
||||||
|
if (Specificity && MaxSpecificity > 0) {
|
||||||
|
*Specificity = MaxSpecificity;
|
||||||
|
}
|
||||||
|
return MaxSpecificity > 0;
|
||||||
|
}
|
||||||
|
|
||||||
std::string VariantValue::getTypeAsString() const {
|
std::string VariantValue::getTypeAsString() const {
|
||||||
switch (Type) {
|
switch (Type) {
|
||||||
case VT_String: return "String";
|
case VT_String: return "String";
|
||||||
|
|
|
@ -152,6 +152,14 @@ TEST(ParserTest, ParseMatcher) {
|
||||||
|
|
||||||
using ast_matchers::internal::Matcher;
|
using ast_matchers::internal::Matcher;
|
||||||
|
|
||||||
|
Parser::NamedValueMap getTestNamedValues() {
|
||||||
|
Parser::NamedValueMap Values;
|
||||||
|
Values["nameX"] = std::string("x");
|
||||||
|
Values["hasParamA"] =
|
||||||
|
VariantMatcher::SingleMatcher(hasParameter(0, hasName("a")));
|
||||||
|
return Values;
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ParserTest, FullParserTest) {
|
TEST(ParserTest, FullParserTest) {
|
||||||
Diagnostics Error;
|
Diagnostics Error;
|
||||||
llvm::Optional<DynTypedMatcher> VarDecl(Parser::parseMatcherExpression(
|
llvm::Optional<DynTypedMatcher> VarDecl(Parser::parseMatcherExpression(
|
||||||
|
@ -174,21 +182,11 @@ TEST(ParserTest, FullParserTest) {
|
||||||
EXPECT_FALSE(matches("void f(int x, int a);", M));
|
EXPECT_FALSE(matches("void f(int x, int a);", M));
|
||||||
|
|
||||||
// Test named values.
|
// Test named values.
|
||||||
struct NamedSema : public Parser::RegistrySema {
|
auto NamedValues = getTestNamedValues();
|
||||||
public:
|
|
||||||
virtual VariantValue getNamedValue(StringRef Name) {
|
|
||||||
if (Name == "nameX")
|
|
||||||
return std::string("x");
|
|
||||||
if (Name == "param0")
|
|
||||||
return VariantMatcher::SingleMatcher(hasParameter(0, hasName("a")));
|
|
||||||
return VariantValue();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
NamedSema Sema;
|
|
||||||
llvm::Optional<DynTypedMatcher> HasParameterWithNamedValues(
|
llvm::Optional<DynTypedMatcher> HasParameterWithNamedValues(
|
||||||
Parser::parseMatcherExpression(
|
Parser::parseMatcherExpression(
|
||||||
"functionDecl(param0, hasParameter(1, hasName(nameX)))", &Sema,
|
"functionDecl(hasParamA, hasParameter(1, hasName(nameX)))",
|
||||||
&Error));
|
nullptr, &NamedValues, &Error));
|
||||||
EXPECT_EQ("", Error.toStringFull());
|
EXPECT_EQ("", Error.toStringFull());
|
||||||
M = HasParameterWithNamedValues->unconditionalConvertTo<Decl>();
|
M = HasParameterWithNamedValues->unconditionalConvertTo<Decl>();
|
||||||
|
|
||||||
|
@ -270,7 +268,7 @@ TEST(ParserTest, OverloadErrors) {
|
||||||
ParseWithError("callee(\"A\")"));
|
ParseWithError("callee(\"A\")"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ParserTest, Completion) {
|
TEST(ParserTest, CompletionRegistry) {
|
||||||
std::vector<MatcherCompletion> Comps =
|
std::vector<MatcherCompletion> Comps =
|
||||||
Parser::completeExpression("while", 5);
|
Parser::completeExpression("while", 5);
|
||||||
ASSERT_EQ(1u, Comps.size());
|
ASSERT_EQ(1u, Comps.size());
|
||||||
|
@ -284,6 +282,38 @@ TEST(ParserTest, Completion) {
|
||||||
EXPECT_EQ("bind", Comps[0].MatcherDecl);
|
EXPECT_EQ("bind", Comps[0].MatcherDecl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ParserTest, CompletionNamedValues) {
|
||||||
|
// Can complete non-matcher types.
|
||||||
|
auto NamedValues = getTestNamedValues();
|
||||||
|
StringRef Code = "functionDecl(hasName(";
|
||||||
|
std::vector<MatcherCompletion> Comps =
|
||||||
|
Parser::completeExpression(Code, Code.size(), nullptr, &NamedValues);
|
||||||
|
ASSERT_EQ(1u, Comps.size());
|
||||||
|
EXPECT_EQ("nameX", Comps[0].TypedText);
|
||||||
|
EXPECT_EQ("String nameX", Comps[0].MatcherDecl);
|
||||||
|
|
||||||
|
// Can complete if there are names in the expression.
|
||||||
|
Code = "methodDecl(hasName(nameX), ";
|
||||||
|
Comps = Parser::completeExpression(Code, Code.size(), nullptr, &NamedValues);
|
||||||
|
EXPECT_LT(0u, Comps.size());
|
||||||
|
|
||||||
|
// Can complete names and registry together.
|
||||||
|
Code = "methodDecl(hasP";
|
||||||
|
Comps = Parser::completeExpression(Code, Code.size(), nullptr, &NamedValues);
|
||||||
|
ASSERT_EQ(3u, Comps.size());
|
||||||
|
EXPECT_EQ("aramA", Comps[0].TypedText);
|
||||||
|
EXPECT_EQ("Matcher<FunctionDecl> hasParamA", Comps[0].MatcherDecl);
|
||||||
|
|
||||||
|
EXPECT_EQ("arameter(", Comps[1].TypedText);
|
||||||
|
EXPECT_EQ(
|
||||||
|
"Matcher<FunctionDecl> hasParameter(unsigned, Matcher<ParmVarDecl>)",
|
||||||
|
Comps[1].MatcherDecl);
|
||||||
|
|
||||||
|
EXPECT_EQ("arent(", Comps[2].TypedText);
|
||||||
|
EXPECT_EQ("Matcher<Decl> hasParent(Matcher<Decl|Stmt>)",
|
||||||
|
Comps[2].MatcherDecl);
|
||||||
|
}
|
||||||
|
|
||||||
} // end anonymous namespace
|
} // end anonymous namespace
|
||||||
} // end namespace dynamic
|
} // end namespace dynamic
|
||||||
} // end namespace ast_matchers
|
} // end namespace ast_matchers
|
||||||
|
|
|
@ -82,8 +82,9 @@ public:
|
||||||
typedef std::vector<MatcherCompletion> CompVector;
|
typedef std::vector<MatcherCompletion> CompVector;
|
||||||
|
|
||||||
CompVector getCompletions() {
|
CompVector getCompletions() {
|
||||||
return Registry::getCompletions(
|
std::vector<std::pair<MatcherCtor, unsigned> > Context;
|
||||||
ArrayRef<std::pair<MatcherCtor, unsigned> >());
|
return Registry::getMatcherCompletions(
|
||||||
|
Registry::getAcceptedCompletionTypes(Context));
|
||||||
}
|
}
|
||||||
|
|
||||||
CompVector getCompletions(StringRef MatcherName1, unsigned ArgNo1) {
|
CompVector getCompletions(StringRef MatcherName1, unsigned ArgNo1) {
|
||||||
|
@ -92,7 +93,8 @@ public:
|
||||||
if (!Ctor)
|
if (!Ctor)
|
||||||
return CompVector();
|
return CompVector();
|
||||||
Context.push_back(std::make_pair(*Ctor, ArgNo1));
|
Context.push_back(std::make_pair(*Ctor, ArgNo1));
|
||||||
return Registry::getCompletions(Context);
|
return Registry::getMatcherCompletions(
|
||||||
|
Registry::getAcceptedCompletionTypes(Context));
|
||||||
}
|
}
|
||||||
|
|
||||||
CompVector getCompletions(StringRef MatcherName1, unsigned ArgNo1,
|
CompVector getCompletions(StringRef MatcherName1, unsigned ArgNo1,
|
||||||
|
@ -106,18 +108,16 @@ public:
|
||||||
if (!Ctor)
|
if (!Ctor)
|
||||||
return CompVector();
|
return CompVector();
|
||||||
Context.push_back(std::make_pair(*Ctor, ArgNo2));
|
Context.push_back(std::make_pair(*Ctor, ArgNo2));
|
||||||
return Registry::getCompletions(Context);
|
return Registry::getMatcherCompletions(
|
||||||
|
Registry::getAcceptedCompletionTypes(Context));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasCompletion(const CompVector &Comps, StringRef TypedText,
|
bool hasCompletion(const CompVector &Comps, StringRef TypedText,
|
||||||
StringRef MatcherDecl = StringRef(),
|
StringRef MatcherDecl = StringRef()) {
|
||||||
unsigned *Index = nullptr) {
|
|
||||||
for (CompVector::const_iterator I = Comps.begin(), E = Comps.end(); I != E;
|
for (CompVector::const_iterator I = Comps.begin(), E = Comps.end(); I != E;
|
||||||
++I) {
|
++I) {
|
||||||
if (I->TypedText == TypedText &&
|
if (I->TypedText == TypedText &&
|
||||||
(MatcherDecl.empty() || I->MatcherDecl == MatcherDecl)) {
|
(MatcherDecl.empty() || I->MatcherDecl == MatcherDecl)) {
|
||||||
if (Index)
|
|
||||||
*Index = I - Comps.begin();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -445,17 +445,12 @@ TEST_F(RegistryTest, Completion) {
|
||||||
|
|
||||||
CompVector WhileComps = getCompletions("whileStmt", 0);
|
CompVector WhileComps = getCompletions("whileStmt", 0);
|
||||||
|
|
||||||
unsigned HasBodyIndex, HasParentIndex, AllOfIndex;
|
|
||||||
EXPECT_TRUE(hasCompletion(WhileComps, "hasBody(",
|
EXPECT_TRUE(hasCompletion(WhileComps, "hasBody(",
|
||||||
"Matcher<WhileStmt> hasBody(Matcher<Stmt>)",
|
"Matcher<WhileStmt> hasBody(Matcher<Stmt>)"));
|
||||||
&HasBodyIndex));
|
|
||||||
EXPECT_TRUE(hasCompletion(WhileComps, "hasParent(",
|
EXPECT_TRUE(hasCompletion(WhileComps, "hasParent(",
|
||||||
"Matcher<Stmt> hasParent(Matcher<Decl|Stmt>)",
|
"Matcher<Stmt> hasParent(Matcher<Decl|Stmt>)"));
|
||||||
&HasParentIndex));
|
EXPECT_TRUE(
|
||||||
EXPECT_TRUE(hasCompletion(WhileComps, "allOf(",
|
hasCompletion(WhileComps, "allOf(", "Matcher<T> allOf(Matcher<T>...)"));
|
||||||
"Matcher<T> allOf(Matcher<T>...)", &AllOfIndex));
|
|
||||||
EXPECT_GT(HasParentIndex, HasBodyIndex);
|
|
||||||
EXPECT_GT(AllOfIndex, HasParentIndex);
|
|
||||||
|
|
||||||
EXPECT_FALSE(hasCompletion(WhileComps, "whileStmt("));
|
EXPECT_FALSE(hasCompletion(WhileComps, "whileStmt("));
|
||||||
EXPECT_FALSE(hasCompletion(WhileComps, "ifStmt("));
|
EXPECT_FALSE(hasCompletion(WhileComps, "ifStmt("));
|
||||||
|
|
Loading…
Reference in New Issue