Parser.cpp revision 353358
1//===- Parser.cpp - Matcher expression parser -----------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10/// Recursive parser implementation for the matcher expression grammar.
11///
12//===----------------------------------------------------------------------===//
13
14#include "clang/ASTMatchers/Dynamic/Parser.h"
15#include "clang/ASTMatchers/ASTMatchersInternal.h"
16#include "clang/ASTMatchers/Dynamic/Diagnostics.h"
17#include "clang/ASTMatchers/Dynamic/Registry.h"
18#include "clang/Basic/CharInfo.h"
19#include "llvm/ADT/Optional.h"
20#include "llvm/ADT/StringRef.h"
21#include "llvm/Support/ErrorHandling.h"
22#include "llvm/Support/ManagedStatic.h"
23#include <algorithm>
24#include <cassert>
25#include <cerrno>
26#include <cstddef>
27#include <cstdlib>
28#include <string>
29#include <utility>
30#include <vector>
31
32namespace clang {
33namespace ast_matchers {
34namespace dynamic {
35
36/// Simple structure to hold information for one token from the parser.
37struct Parser::TokenInfo {
38  /// Different possible tokens.
39  enum TokenKind {
40    TK_Eof,
41    TK_OpenParen,
42    TK_CloseParen,
43    TK_Comma,
44    TK_Period,
45    TK_Literal,
46    TK_Ident,
47    TK_InvalidChar,
48    TK_Error,
49    TK_CodeCompletion
50  };
51
52  /// Some known identifiers.
53  static const char* const ID_Bind;
54
55  TokenInfo() = default;
56
57  StringRef Text;
58  TokenKind Kind = TK_Eof;
59  SourceRange Range;
60  VariantValue Value;
61};
62
63const char* const Parser::TokenInfo::ID_Bind = "bind";
64
65/// Simple tokenizer for the parser.
66class Parser::CodeTokenizer {
67public:
68  explicit CodeTokenizer(StringRef MatcherCode, Diagnostics *Error)
69      : Code(MatcherCode), StartOfLine(MatcherCode), Error(Error) {
70    NextToken = getNextToken();
71  }
72
73  CodeTokenizer(StringRef MatcherCode, Diagnostics *Error,
74                unsigned CodeCompletionOffset)
75      : Code(MatcherCode), StartOfLine(MatcherCode), Error(Error),
76        CodeCompletionLocation(MatcherCode.data() + CodeCompletionOffset) {
77    NextToken = getNextToken();
78  }
79
80  /// Returns but doesn't consume the next token.
81  const TokenInfo &peekNextToken() const { return NextToken; }
82
83  /// Consumes and returns the next token.
84  TokenInfo consumeNextToken() {
85    TokenInfo ThisToken = NextToken;
86    NextToken = getNextToken();
87    return ThisToken;
88  }
89
90  TokenInfo::TokenKind nextTokenKind() const { return NextToken.Kind; }
91
92private:
93  TokenInfo getNextToken() {
94    consumeWhitespace();
95    TokenInfo Result;
96    Result.Range.Start = currentLocation();
97
98    if (CodeCompletionLocation && CodeCompletionLocation <= Code.data()) {
99      Result.Kind = TokenInfo::TK_CodeCompletion;
100      Result.Text = StringRef(CodeCompletionLocation, 0);
101      CodeCompletionLocation = nullptr;
102      return Result;
103    }
104
105    if (Code.empty()) {
106      Result.Kind = TokenInfo::TK_Eof;
107      Result.Text = "";
108      return Result;
109    }
110
111    switch (Code[0]) {
112    case '#':
113      Result.Kind = TokenInfo::TK_Eof;
114      Result.Text = "";
115      return Result;
116    case ',':
117      Result.Kind = TokenInfo::TK_Comma;
118      Result.Text = Code.substr(0, 1);
119      Code = Code.drop_front();
120      break;
121    case '.':
122      Result.Kind = TokenInfo::TK_Period;
123      Result.Text = Code.substr(0, 1);
124      Code = Code.drop_front();
125      break;
126    case '(':
127      Result.Kind = TokenInfo::TK_OpenParen;
128      Result.Text = Code.substr(0, 1);
129      Code = Code.drop_front();
130      break;
131    case ')':
132      Result.Kind = TokenInfo::TK_CloseParen;
133      Result.Text = Code.substr(0, 1);
134      Code = Code.drop_front();
135      break;
136
137    case '"':
138    case '\'':
139      // Parse a string literal.
140      consumeStringLiteral(&Result);
141      break;
142
143    case '0': case '1': case '2': case '3': case '4':
144    case '5': case '6': case '7': case '8': case '9':
145      // Parse an unsigned and float literal.
146      consumeNumberLiteral(&Result);
147      break;
148
149    default:
150      if (isAlphanumeric(Code[0])) {
151        // Parse an identifier
152        size_t TokenLength = 1;
153        while (true) {
154          // A code completion location in/immediately after an identifier will
155          // cause the portion of the identifier before the code completion
156          // location to become a code completion token.
157          if (CodeCompletionLocation == Code.data() + TokenLength) {
158            CodeCompletionLocation = nullptr;
159            Result.Kind = TokenInfo::TK_CodeCompletion;
160            Result.Text = Code.substr(0, TokenLength);
161            Code = Code.drop_front(TokenLength);
162            return Result;
163          }
164          if (TokenLength == Code.size() || !isAlphanumeric(Code[TokenLength]))
165            break;
166          ++TokenLength;
167        }
168        if (TokenLength == 4 && Code.startswith("true")) {
169          Result.Kind = TokenInfo::TK_Literal;
170          Result.Value = true;
171        } else if (TokenLength == 5 && Code.startswith("false")) {
172          Result.Kind = TokenInfo::TK_Literal;
173          Result.Value = false;
174        } else {
175          Result.Kind = TokenInfo::TK_Ident;
176          Result.Text = Code.substr(0, TokenLength);
177        }
178        Code = Code.drop_front(TokenLength);
179      } else {
180        Result.Kind = TokenInfo::TK_InvalidChar;
181        Result.Text = Code.substr(0, 1);
182        Code = Code.drop_front(1);
183      }
184      break;
185    }
186
187    Result.Range.End = currentLocation();
188    return Result;
189  }
190
191  /// Consume an unsigned and float literal.
192  void consumeNumberLiteral(TokenInfo *Result) {
193    bool isFloatingLiteral = false;
194    unsigned Length = 1;
195    if (Code.size() > 1) {
196      // Consume the 'x' or 'b' radix modifier, if present.
197      switch (toLowercase(Code[1])) {
198      case 'x': case 'b': Length = 2;
199      }
200    }
201    while (Length < Code.size() && isHexDigit(Code[Length]))
202      ++Length;
203
204    // Try to recognize a floating point literal.
205    while (Length < Code.size()) {
206      char c = Code[Length];
207      if (c == '-' || c == '+' || c == '.' || isHexDigit(c)) {
208        isFloatingLiteral = true;
209        Length++;
210      } else {
211        break;
212      }
213    }
214
215    Result->Text = Code.substr(0, Length);
216    Code = Code.drop_front(Length);
217
218    if (isFloatingLiteral) {
219      char *end;
220      errno = 0;
221      std::string Text = Result->Text.str();
222      double doubleValue = strtod(Text.c_str(), &end);
223      if (*end == 0 && errno == 0) {
224        Result->Kind = TokenInfo::TK_Literal;
225        Result->Value = doubleValue;
226        return;
227      }
228    } else {
229      unsigned Value;
230      if (!Result->Text.getAsInteger(0, Value)) {
231        Result->Kind = TokenInfo::TK_Literal;
232        Result->Value = Value;
233        return;
234      }
235    }
236
237    SourceRange Range;
238    Range.Start = Result->Range.Start;
239    Range.End = currentLocation();
240    Error->addError(Range, Error->ET_ParserNumberError) << Result->Text;
241    Result->Kind = TokenInfo::TK_Error;
242  }
243
244  /// Consume a string literal.
245  ///
246  /// \c Code must be positioned at the start of the literal (the opening
247  /// quote). Consumed until it finds the same closing quote character.
248  void consumeStringLiteral(TokenInfo *Result) {
249    bool InEscape = false;
250    const char Marker = Code[0];
251    for (size_t Length = 1, Size = Code.size(); Length != Size; ++Length) {
252      if (InEscape) {
253        InEscape = false;
254        continue;
255      }
256      if (Code[Length] == '\\') {
257        InEscape = true;
258        continue;
259      }
260      if (Code[Length] == Marker) {
261        Result->Kind = TokenInfo::TK_Literal;
262        Result->Text = Code.substr(0, Length + 1);
263        Result->Value = Code.substr(1, Length - 1);
264        Code = Code.drop_front(Length + 1);
265        return;
266      }
267    }
268
269    StringRef ErrorText = Code;
270    Code = Code.drop_front(Code.size());
271    SourceRange Range;
272    Range.Start = Result->Range.Start;
273    Range.End = currentLocation();
274    Error->addError(Range, Error->ET_ParserStringError) << ErrorText;
275    Result->Kind = TokenInfo::TK_Error;
276  }
277
278  /// Consume all leading whitespace from \c Code.
279  void consumeWhitespace() {
280    while (!Code.empty() && isWhitespace(Code[0])) {
281      if (Code[0] == '\n') {
282        ++Line;
283        StartOfLine = Code.drop_front();
284      }
285      Code = Code.drop_front();
286    }
287  }
288
289  SourceLocation currentLocation() {
290    SourceLocation Location;
291    Location.Line = Line;
292    Location.Column = Code.data() - StartOfLine.data() + 1;
293    return Location;
294  }
295
296  StringRef Code;
297  StringRef StartOfLine;
298  unsigned Line = 1;
299  Diagnostics *Error;
300  TokenInfo NextToken;
301  const char *CodeCompletionLocation = nullptr;
302};
303
304Parser::Sema::~Sema() = default;
305
306std::vector<ArgKind> Parser::Sema::getAcceptedCompletionTypes(
307    llvm::ArrayRef<std::pair<MatcherCtor, unsigned>> Context) {
308  return {};
309}
310
311std::vector<MatcherCompletion>
312Parser::Sema::getMatcherCompletions(llvm::ArrayRef<ArgKind> AcceptedTypes) {
313  return {};
314}
315
316struct Parser::ScopedContextEntry {
317  Parser *P;
318
319  ScopedContextEntry(Parser *P, MatcherCtor C) : P(P) {
320    P->ContextStack.push_back(std::make_pair(C, 0u));
321  }
322
323  ~ScopedContextEntry() {
324    P->ContextStack.pop_back();
325  }
326
327  void nextArg() {
328    ++P->ContextStack.back().second;
329  }
330};
331
332/// Parse expressions that start with an identifier.
333///
334/// This function can parse named values and matchers.
335/// In case of failure it will try to determine the user's intent to give
336/// an appropriate error message.
337bool Parser::parseIdentifierPrefixImpl(VariantValue *Value) {
338  const TokenInfo NameToken = Tokenizer->consumeNextToken();
339
340  if (Tokenizer->nextTokenKind() != TokenInfo::TK_OpenParen) {
341    // Parse as a named value.
342    if (const VariantValue NamedValue =
343            NamedValues ? NamedValues->lookup(NameToken.Text)
344                        : VariantValue()) {
345
346      if (Tokenizer->nextTokenKind() != TokenInfo::TK_Period) {
347        *Value = NamedValue;
348        return true;
349      }
350
351      std::string BindID;
352      if (!parseBindID(BindID))
353        return false;
354
355      assert(NamedValue.isMatcher());
356      llvm::Optional<DynTypedMatcher> Result =
357          NamedValue.getMatcher().getSingleMatcher();
358      if (Result.hasValue()) {
359        llvm::Optional<DynTypedMatcher> Bound = Result->tryBind(BindID);
360        if (Bound.hasValue()) {
361          *Value = VariantMatcher::SingleMatcher(*Bound);
362          return true;
363        }
364      }
365      return false;
366    }
367    // If the syntax is correct and the name is not a matcher either, report
368    // unknown named value.
369    if ((Tokenizer->nextTokenKind() == TokenInfo::TK_Comma ||
370         Tokenizer->nextTokenKind() == TokenInfo::TK_CloseParen ||
371         Tokenizer->nextTokenKind() == TokenInfo::TK_Eof) &&
372        !S->lookupMatcherCtor(NameToken.Text)) {
373      Error->addError(NameToken.Range, Error->ET_RegistryValueNotFound)
374          << NameToken.Text;
375      return false;
376    }
377    // Otherwise, fallback to the matcher parser.
378  }
379
380  // Parse as a matcher expression.
381  return parseMatcherExpressionImpl(NameToken, Value);
382}
383
384bool Parser::parseBindID(std::string &BindID) {
385  // Parse .bind("foo")
386  assert(Tokenizer->peekNextToken().Kind == TokenInfo::TK_Period);
387  Tokenizer->consumeNextToken(); // consume the period.
388  const TokenInfo BindToken = Tokenizer->consumeNextToken();
389  if (BindToken.Kind == TokenInfo::TK_CodeCompletion) {
390    addCompletion(BindToken, MatcherCompletion("bind(\"", "bind", 1));
391    return false;
392  }
393
394  const TokenInfo OpenToken = Tokenizer->consumeNextToken();
395  const TokenInfo IDToken = Tokenizer->consumeNextToken();
396  const TokenInfo CloseToken = Tokenizer->consumeNextToken();
397
398  // TODO: We could use different error codes for each/some to be more
399  //       explicit about the syntax error.
400  if (BindToken.Kind != TokenInfo::TK_Ident ||
401      BindToken.Text != TokenInfo::ID_Bind) {
402    Error->addError(BindToken.Range, Error->ET_ParserMalformedBindExpr);
403    return false;
404  }
405  if (OpenToken.Kind != TokenInfo::TK_OpenParen) {
406    Error->addError(OpenToken.Range, Error->ET_ParserMalformedBindExpr);
407    return false;
408  }
409  if (IDToken.Kind != TokenInfo::TK_Literal || !IDToken.Value.isString()) {
410    Error->addError(IDToken.Range, Error->ET_ParserMalformedBindExpr);
411    return false;
412  }
413  if (CloseToken.Kind != TokenInfo::TK_CloseParen) {
414    Error->addError(CloseToken.Range, Error->ET_ParserMalformedBindExpr);
415    return false;
416  }
417  BindID = IDToken.Value.getString();
418  return true;
419}
420
421/// Parse and validate a matcher expression.
422/// \return \c true on success, in which case \c Value has the matcher parsed.
423///   If the input is malformed, or some argument has an error, it
424///   returns \c false.
425bool Parser::parseMatcherExpressionImpl(const TokenInfo &NameToken,
426                                        VariantValue *Value) {
427  assert(NameToken.Kind == TokenInfo::TK_Ident);
428  const TokenInfo OpenToken = Tokenizer->consumeNextToken();
429  if (OpenToken.Kind != TokenInfo::TK_OpenParen) {
430    Error->addError(OpenToken.Range, Error->ET_ParserNoOpenParen)
431        << OpenToken.Text;
432    return false;
433  }
434
435  llvm::Optional<MatcherCtor> Ctor = S->lookupMatcherCtor(NameToken.Text);
436
437  if (!Ctor) {
438    Error->addError(NameToken.Range, Error->ET_RegistryMatcherNotFound)
439        << NameToken.Text;
440    // Do not return here. We need to continue to give completion suggestions.
441  }
442
443  std::vector<ParserValue> Args;
444  TokenInfo EndToken;
445
446  {
447    ScopedContextEntry SCE(this, Ctor ? *Ctor : nullptr);
448
449    while (Tokenizer->nextTokenKind() != TokenInfo::TK_Eof) {
450      if (Tokenizer->nextTokenKind() == TokenInfo::TK_CloseParen) {
451        // End of args.
452        EndToken = Tokenizer->consumeNextToken();
453        break;
454      }
455      if (!Args.empty()) {
456        // We must find a , token to continue.
457        const TokenInfo CommaToken = Tokenizer->consumeNextToken();
458        if (CommaToken.Kind != TokenInfo::TK_Comma) {
459          Error->addError(CommaToken.Range, Error->ET_ParserNoComma)
460              << CommaToken.Text;
461          return false;
462        }
463      }
464
465      Diagnostics::Context Ctx(Diagnostics::Context::MatcherArg, Error,
466                               NameToken.Text, NameToken.Range,
467                               Args.size() + 1);
468      ParserValue ArgValue;
469      ArgValue.Text = Tokenizer->peekNextToken().Text;
470      ArgValue.Range = Tokenizer->peekNextToken().Range;
471      if (!parseExpressionImpl(&ArgValue.Value)) {
472        return false;
473      }
474
475      Args.push_back(ArgValue);
476      SCE.nextArg();
477    }
478  }
479
480  if (EndToken.Kind == TokenInfo::TK_Eof) {
481    Error->addError(OpenToken.Range, Error->ET_ParserNoCloseParen);
482    return false;
483  }
484
485  std::string BindID;
486  if (Tokenizer->peekNextToken().Kind == TokenInfo::TK_Period) {
487    if (!parseBindID(BindID))
488      return false;
489  }
490
491  if (!Ctor)
492    return false;
493
494  // Merge the start and end infos.
495  Diagnostics::Context Ctx(Diagnostics::Context::ConstructMatcher, Error,
496                           NameToken.Text, NameToken.Range);
497  SourceRange MatcherRange = NameToken.Range;
498  MatcherRange.End = EndToken.Range.End;
499  VariantMatcher Result = S->actOnMatcherExpression(
500      *Ctor, MatcherRange, BindID, Args, Error);
501  if (Result.isNull()) return false;
502
503  *Value = Result;
504  return true;
505}
506
507// If the prefix of this completion matches the completion token, add it to
508// Completions minus the prefix.
509void Parser::addCompletion(const TokenInfo &CompToken,
510                           const MatcherCompletion& Completion) {
511  if (StringRef(Completion.TypedText).startswith(CompToken.Text) &&
512      Completion.Specificity > 0) {
513    Completions.emplace_back(Completion.TypedText.substr(CompToken.Text.size()),
514                             Completion.MatcherDecl, Completion.Specificity);
515  }
516}
517
518std::vector<MatcherCompletion> Parser::getNamedValueCompletions(
519    ArrayRef<ArgKind> AcceptedTypes) {
520  if (!NamedValues) return std::vector<MatcherCompletion>();
521  std::vector<MatcherCompletion> Result;
522  for (const auto &Entry : *NamedValues) {
523    unsigned Specificity;
524    if (Entry.getValue().isConvertibleTo(AcceptedTypes, &Specificity)) {
525      std::string Decl =
526          (Entry.getValue().getTypeAsString() + " " + Entry.getKey()).str();
527      Result.emplace_back(Entry.getKey(), Decl, Specificity);
528    }
529  }
530  return Result;
531}
532
533void Parser::addExpressionCompletions() {
534  const TokenInfo CompToken = Tokenizer->consumeNextToken();
535  assert(CompToken.Kind == TokenInfo::TK_CodeCompletion);
536
537  // We cannot complete code if there is an invalid element on the context
538  // stack.
539  for (ContextStackTy::iterator I = ContextStack.begin(),
540                                E = ContextStack.end();
541       I != E; ++I) {
542    if (!I->first)
543      return;
544  }
545
546  auto AcceptedTypes = S->getAcceptedCompletionTypes(ContextStack);
547  for (const auto &Completion : S->getMatcherCompletions(AcceptedTypes)) {
548    addCompletion(CompToken, Completion);
549  }
550
551  for (const auto &Completion : getNamedValueCompletions(AcceptedTypes)) {
552    addCompletion(CompToken, Completion);
553  }
554}
555
556/// Parse an <Expression>
557bool Parser::parseExpressionImpl(VariantValue *Value) {
558  switch (Tokenizer->nextTokenKind()) {
559  case TokenInfo::TK_Literal:
560    *Value = Tokenizer->consumeNextToken().Value;
561    return true;
562
563  case TokenInfo::TK_Ident:
564    return parseIdentifierPrefixImpl(Value);
565
566  case TokenInfo::TK_CodeCompletion:
567    addExpressionCompletions();
568    return false;
569
570  case TokenInfo::TK_Eof:
571    Error->addError(Tokenizer->consumeNextToken().Range,
572                    Error->ET_ParserNoCode);
573    return false;
574
575  case TokenInfo::TK_Error:
576    // This error was already reported by the tokenizer.
577    return false;
578
579  case TokenInfo::TK_OpenParen:
580  case TokenInfo::TK_CloseParen:
581  case TokenInfo::TK_Comma:
582  case TokenInfo::TK_Period:
583  case TokenInfo::TK_InvalidChar:
584    const TokenInfo Token = Tokenizer->consumeNextToken();
585    Error->addError(Token.Range, Error->ET_ParserInvalidToken) << Token.Text;
586    return false;
587  }
588
589  llvm_unreachable("Unknown token kind.");
590}
591
592static llvm::ManagedStatic<Parser::RegistrySema> DefaultRegistrySema;
593
594Parser::Parser(CodeTokenizer *Tokenizer, Sema *S,
595               const NamedValueMap *NamedValues, Diagnostics *Error)
596    : Tokenizer(Tokenizer), S(S ? S : &*DefaultRegistrySema),
597      NamedValues(NamedValues), Error(Error) {}
598
599Parser::RegistrySema::~RegistrySema() = default;
600
601llvm::Optional<MatcherCtor>
602Parser::RegistrySema::lookupMatcherCtor(StringRef MatcherName) {
603  return Registry::lookupMatcherCtor(MatcherName);
604}
605
606VariantMatcher Parser::RegistrySema::actOnMatcherExpression(
607    MatcherCtor Ctor, SourceRange NameRange, StringRef BindID,
608    ArrayRef<ParserValue> Args, Diagnostics *Error) {
609  if (BindID.empty()) {
610    return Registry::constructMatcher(Ctor, NameRange, Args, Error);
611  } else {
612    return Registry::constructBoundMatcher(Ctor, NameRange, BindID, Args,
613                                           Error);
614  }
615}
616
617std::vector<ArgKind> Parser::RegistrySema::getAcceptedCompletionTypes(
618    ArrayRef<std::pair<MatcherCtor, unsigned>> Context) {
619  return Registry::getAcceptedCompletionTypes(Context);
620}
621
622std::vector<MatcherCompletion> Parser::RegistrySema::getMatcherCompletions(
623    ArrayRef<ArgKind> AcceptedTypes) {
624  return Registry::getMatcherCompletions(AcceptedTypes);
625}
626
627bool Parser::parseExpression(StringRef Code, Sema *S,
628                             const NamedValueMap *NamedValues,
629                             VariantValue *Value, Diagnostics *Error) {
630  CodeTokenizer Tokenizer(Code, Error);
631  if (!Parser(&Tokenizer, S, NamedValues, Error).parseExpressionImpl(Value))
632    return false;
633  if (Tokenizer.peekNextToken().Kind != TokenInfo::TK_Eof) {
634    Error->addError(Tokenizer.peekNextToken().Range,
635                    Error->ET_ParserTrailingCode);
636    return false;
637  }
638  return true;
639}
640
641std::vector<MatcherCompletion>
642Parser::completeExpression(StringRef Code, unsigned CompletionOffset, Sema *S,
643                           const NamedValueMap *NamedValues) {
644  Diagnostics Error;
645  CodeTokenizer Tokenizer(Code, &Error, CompletionOffset);
646  Parser P(&Tokenizer, S, NamedValues, &Error);
647  VariantValue Dummy;
648  P.parseExpressionImpl(&Dummy);
649
650  // Sort by specificity, then by name.
651  llvm::sort(P.Completions,
652             [](const MatcherCompletion &A, const MatcherCompletion &B) {
653               if (A.Specificity != B.Specificity)
654                 return A.Specificity > B.Specificity;
655               return A.TypedText < B.TypedText;
656             });
657
658  return P.Completions;
659}
660
661llvm::Optional<DynTypedMatcher>
662Parser::parseMatcherExpression(StringRef Code, Sema *S,
663                               const NamedValueMap *NamedValues,
664                               Diagnostics *Error) {
665  VariantValue Value;
666  if (!parseExpression(Code, S, NamedValues, &Value, Error))
667    return llvm::Optional<DynTypedMatcher>();
668  if (!Value.isMatcher()) {
669    Error->addError(SourceRange(), Error->ET_ParserNotAMatcher);
670    return llvm::Optional<DynTypedMatcher>();
671  }
672  llvm::Optional<DynTypedMatcher> Result =
673      Value.getMatcher().getSingleMatcher();
674  if (!Result.hasValue()) {
675    Error->addError(SourceRange(), Error->ET_ParserOverloadedType)
676        << Value.getTypeAsString();
677  }
678  return Result;
679}
680
681} // namespace dynamic
682} // namespace ast_matchers
683} // namespace clang
684