Coverage Report

Created: 2022-07-16 07:03

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/AST/QualTypeNames.cpp
Line
Count
Source (jump to first uncovered line)
1
//===------- QualTypeNames.cpp - Generate Complete QualType Names ---------===//
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
#include "clang/AST/DeclTemplate.h"
10
#include "clang/AST/DeclarationName.h"
11
#include "clang/AST/GlobalDecl.h"
12
#include "clang/AST/Mangle.h"
13
#include "clang/AST/QualTypeNames.h"
14
15
#include <stdio.h>
16
#include <memory>
17
18
namespace clang {
19
20
namespace TypeName {
21
22
/// Create a NestedNameSpecifier for Namesp and its enclosing
23
/// scopes.
24
///
25
/// \param[in] Ctx - the AST Context to be used.
26
/// \param[in] Namesp - the NamespaceDecl for which a NestedNameSpecifier
27
/// is requested.
28
/// \param[in] WithGlobalNsPrefix - Indicate whether the global namespace
29
/// specifier "::" should be prepended or not.
30
static NestedNameSpecifier *createNestedNameSpecifier(
31
    const ASTContext &Ctx,
32
    const NamespaceDecl *Namesp,
33
    bool WithGlobalNsPrefix);
34
35
/// Create a NestedNameSpecifier for TagDecl and its enclosing
36
/// scopes.
37
///
38
/// \param[in] Ctx - the AST Context to be used.
39
/// \param[in] TD - the TagDecl for which a NestedNameSpecifier is
40
/// requested.
41
/// \param[in] FullyQualify - Convert all template arguments into fully
42
/// qualified names.
43
/// \param[in] WithGlobalNsPrefix - Indicate whether the global namespace
44
/// specifier "::" should be prepended or not.
45
static NestedNameSpecifier *createNestedNameSpecifier(
46
    const ASTContext &Ctx, const TypeDecl *TD,
47
    bool FullyQualify, bool WithGlobalNsPrefix);
48
49
static NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
50
    const ASTContext &Ctx, const Decl *decl,
51
    bool FullyQualified, bool WithGlobalNsPrefix);
52
53
static NestedNameSpecifier *getFullyQualifiedNestedNameSpecifier(
54
    const ASTContext &Ctx, NestedNameSpecifier *scope, bool WithGlobalNsPrefix);
55
56
static bool getFullyQualifiedTemplateName(const ASTContext &Ctx,
57
                                          TemplateName &TName,
58
0
                                          bool WithGlobalNsPrefix) {
59
0
  bool Changed = false;
60
0
  NestedNameSpecifier *NNS = nullptr;
61
62
0
  TemplateDecl *ArgTDecl = TName.getAsTemplateDecl();
63
  // ArgTDecl won't be NULL because we asserted that this isn't a
64
  // dependent context very early in the call chain.
65
0
  assert(ArgTDecl != nullptr);
66
0
  QualifiedTemplateName *QTName = TName.getAsQualifiedTemplateName();
67
68
0
  if (QTName && !QTName->hasTemplateKeyword()) {
69
0
    NNS = QTName->getQualifier();
70
0
    NestedNameSpecifier *QNNS = getFullyQualifiedNestedNameSpecifier(
71
0
        Ctx, NNS, WithGlobalNsPrefix);
72
0
    if (QNNS != NNS) {
73
0
      Changed = true;
74
0
      NNS = QNNS;
75
0
    } else {
76
0
      NNS = nullptr;
77
0
    }
78
0
  } else {
79
0
    NNS = createNestedNameSpecifierForScopeOf(
80
0
        Ctx, ArgTDecl, true, WithGlobalNsPrefix);
81
0
  }
82
0
  if (NNS) {
83
0
    TemplateName UnderlyingTN(ArgTDecl);
84
0
    if (UsingShadowDecl *USD = TName.getAsUsingShadowDecl())
85
0
      UnderlyingTN = TemplateName(USD);
86
0
    TName =
87
0
        Ctx.getQualifiedTemplateName(NNS,
88
0
                                     /*TemplateKeyword=*/false, UnderlyingTN);
89
0
    Changed = true;
90
0
  }
91
0
  return Changed;
92
0
}
93
94
static bool getFullyQualifiedTemplateArgument(const ASTContext &Ctx,
95
                                              TemplateArgument &Arg,
96
32
                                              bool WithGlobalNsPrefix) {
97
32
  bool Changed = false;
98
99
  // Note: we do not handle TemplateArgument::Expression, to replace it
100
  // we need the information for the template instance decl.
101
102
32
  if (Arg.getKind() == TemplateArgument::Template) {
103
0
    TemplateName TName = Arg.getAsTemplate();
104
0
    Changed = getFullyQualifiedTemplateName(Ctx, TName, WithGlobalNsPrefix);
105
0
    if (Changed) {
106
0
      Arg = TemplateArgument(TName);
107
0
    }
108
32
  } else if (Arg.getKind() == TemplateArgument::Type) {
109
31
    QualType SubTy = Arg.getAsType();
110
    // Check if the type needs more desugaring and recurse.
111
31
    QualType QTFQ = getFullyQualifiedType(SubTy, Ctx, WithGlobalNsPrefix);
112
31
    if (QTFQ != SubTy) {
113
11
      Arg = TemplateArgument(QTFQ);
114
11
      Changed = true;
115
11
    }
116
31
  }
117
32
  return Changed;
118
32
}
119
120
static const Type *getFullyQualifiedTemplateType(const ASTContext &Ctx,
121
                                                 const Type *TypePtr,
122
56
                                                 bool WithGlobalNsPrefix) {
123
  // DependentTemplateTypes exist within template declarations and
124
  // definitions. Therefore we shouldn't encounter them at the end of
125
  // a translation unit. If we do, the caller has made an error.
126
56
  assert(!isa<DependentTemplateSpecializationType>(TypePtr));
127
  // In case of template specializations, iterate over the arguments
128
  // and fully qualify them as well.
129
56
  if (const auto *TST = dyn_cast<const TemplateSpecializationType>(TypePtr)) {
130
11
    bool MightHaveChanged = false;
131
11
    SmallVector<TemplateArgument, 4> FQArgs;
132
11
    for (TemplateSpecializationType::iterator I = TST->begin(), E = TST->end();
133
31
         I != E; 
++I20
) {
134
      // Cheap to copy and potentially modified by
135
      // getFullyQualifedTemplateArgument.
136
20
      TemplateArgument Arg(*I);
137
20
      MightHaveChanged |= getFullyQualifiedTemplateArgument(
138
20
          Ctx, Arg, WithGlobalNsPrefix);
139
20
      FQArgs.push_back(Arg);
140
20
    }
141
142
    // If a fully qualified arg is different from the unqualified arg,
143
    // allocate new type in the AST.
144
11
    if (MightHaveChanged) {
145
5
      QualType QT = Ctx.getTemplateSpecializationType(
146
5
          TST->getTemplateName(), FQArgs,
147
5
          TST->getCanonicalTypeInternal());
148
      // getTemplateSpecializationType returns a fully qualified
149
      // version of the specialization itself, so no need to qualify
150
      // it.
151
5
      return QT.getTypePtr();
152
5
    }
153
45
  } else if (const auto *TSTRecord = dyn_cast<const RecordType>(TypePtr)) {
154
    // We are asked to fully qualify and we have a Record Type,
155
    // which can point to a template instantiation with no sugar in any of
156
    // its template argument, however we still need to fully qualify them.
157
158
45
    if (const auto *TSTDecl =
159
45
        dyn_cast<ClassTemplateSpecializationDecl>(TSTRecord->getDecl())) {
160
9
      const TemplateArgumentList &TemplateArgs = TSTDecl->getTemplateArgs();
161
162
9
      bool MightHaveChanged = false;
163
9
      SmallVector<TemplateArgument, 4> FQArgs;
164
21
      for (unsigned int I = 0, E = TemplateArgs.size(); I != E; 
++I12
) {
165
        // cheap to copy and potentially modified by
166
        // getFullyQualifedTemplateArgument
167
12
        TemplateArgument Arg(TemplateArgs[I]);
168
12
        MightHaveChanged |= getFullyQualifiedTemplateArgument(
169
12
            Ctx, Arg, WithGlobalNsPrefix);
170
12
        FQArgs.push_back(Arg);
171
12
      }
172
173
      // If a fully qualified arg is different from the unqualified arg,
174
      // allocate new type in the AST.
175
9
      if (MightHaveChanged) {
176
1
        TemplateName TN(TSTDecl->getSpecializedTemplate());
177
1
        QualType QT = Ctx.getTemplateSpecializationType(
178
1
            TN, FQArgs,
179
1
            TSTRecord->getCanonicalTypeInternal());
180
        // getTemplateSpecializationType returns a fully qualified
181
        // version of the specialization itself, so no need to qualify
182
        // it.
183
1
        return QT.getTypePtr();
184
1
      }
185
9
    }
186
45
  }
187
50
  return TypePtr;
188
56
}
189
190
static NestedNameSpecifier *createOuterNNS(const ASTContext &Ctx, const Decl *D,
191
                                           bool FullyQualify,
192
113
                                           bool WithGlobalNsPrefix) {
193
113
  const DeclContext *DC = D->getDeclContext();
194
113
  if (const auto *NS = dyn_cast<NamespaceDecl>(DC)) {
195
47
    while (NS && 
NS->isInline()46
) {
196
      // Ignore inline namespace;
197
1
      NS = dyn_cast<NamespaceDecl>(NS->getDeclContext());
198
1
    }
199
46
    if (NS && 
NS->getDeclName()45
) {
200
45
      return createNestedNameSpecifier(Ctx, NS, WithGlobalNsPrefix);
201
45
    }
202
1
    return nullptr;  // no starting '::', no anonymous
203
67
  } else if (const auto *TD = dyn_cast<TagDecl>(DC)) {
204
0
    return createNestedNameSpecifier(Ctx, TD, FullyQualify, WithGlobalNsPrefix);
205
67
  } else if (const auto *TDD = dyn_cast<TypedefNameDecl>(DC)) {
206
0
    return createNestedNameSpecifier(
207
0
        Ctx, TDD, FullyQualify, WithGlobalNsPrefix);
208
67
  } else if (WithGlobalNsPrefix && 
DC->isTranslationUnit()5
) {
209
5
    return NestedNameSpecifier::GlobalSpecifier(Ctx);
210
5
  }
211
62
  return nullptr;  // no starting '::' if |WithGlobalNsPrefix| is false
212
113
}
213
214
/// Return a fully qualified version of this name specifier.
215
static NestedNameSpecifier *getFullyQualifiedNestedNameSpecifier(
216
    const ASTContext &Ctx, NestedNameSpecifier *Scope,
217
0
    bool WithGlobalNsPrefix) {
218
0
  switch (Scope->getKind()) {
219
0
    case NestedNameSpecifier::Global:
220
      // Already fully qualified
221
0
      return Scope;
222
0
    case NestedNameSpecifier::Namespace:
223
0
      return TypeName::createNestedNameSpecifier(
224
0
          Ctx, Scope->getAsNamespace(), WithGlobalNsPrefix);
225
0
    case NestedNameSpecifier::NamespaceAlias:
226
      // Namespace aliases are only valid for the duration of the
227
      // scope where they were introduced, and therefore are often
228
      // invalid at the end of the TU.  So use the namespace name more
229
      // likely to be valid at the end of the TU.
230
0
      return TypeName::createNestedNameSpecifier(
231
0
          Ctx,
232
0
          Scope->getAsNamespaceAlias()->getNamespace()->getCanonicalDecl(),
233
0
          WithGlobalNsPrefix);
234
0
    case NestedNameSpecifier::Identifier:
235
      // A function or some other construct that makes it un-namable
236
      // at the end of the TU. Skip the current component of the name,
237
      // but use the name of it's prefix.
238
0
      return getFullyQualifiedNestedNameSpecifier(
239
0
          Ctx, Scope->getPrefix(), WithGlobalNsPrefix);
240
0
    case NestedNameSpecifier::Super:
241
0
    case NestedNameSpecifier::TypeSpec:
242
0
    case NestedNameSpecifier::TypeSpecWithTemplate: {
243
0
      const Type *Type = Scope->getAsType();
244
      // Find decl context.
245
0
      const TagDecl *TD = nullptr;
246
0
      if (const TagType *TagDeclType = Type->getAs<TagType>()) {
247
0
        TD = TagDeclType->getDecl();
248
0
      } else {
249
0
        TD = Type->getAsCXXRecordDecl();
250
0
      }
251
0
      if (TD) {
252
0
        return TypeName::createNestedNameSpecifier(Ctx, TD,
253
0
                                                   true /*FullyQualified*/,
254
0
                                                   WithGlobalNsPrefix);
255
0
      } else if (const auto *TDD = dyn_cast<TypedefType>(Type)) {
256
0
        return TypeName::createNestedNameSpecifier(Ctx, TDD->getDecl(),
257
0
                                                   true /*FullyQualified*/,
258
0
                                                   WithGlobalNsPrefix);
259
0
      }
260
0
      return Scope;
261
0
    }
262
0
  }
263
0
  llvm_unreachable("bad NNS kind");
264
0
}
265
266
/// Create a nested name specifier for the declaring context of
267
/// the type.
268
static NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
269
    const ASTContext &Ctx, const Decl *Decl,
270
162
    bool FullyQualified, bool WithGlobalNsPrefix) {
271
162
  assert(Decl);
272
273
0
  const DeclContext *DC = Decl->getDeclContext()->getRedeclContext();
274
162
  const auto *Outer = dyn_cast_or_null<NamedDecl>(DC);
275
162
  const auto *OuterNS = dyn_cast_or_null<NamespaceDecl>(DC);
276
162
  if (Outer && 
!(72
OuterNS72
&&
OuterNS->isAnonymousNamespace()48
)) {
277
69
    if (const auto *CxxDecl = dyn_cast<CXXRecordDecl>(DC)) {
278
23
      if (ClassTemplateDecl *ClassTempl =
279
23
              CxxDecl->getDescribedClassTemplate()) {
280
        // We are in the case of a type(def) that was declared in a
281
        // class template but is *not* type dependent.  In clang, it
282
        // gets attached to the class template declaration rather than
283
        // any specific class template instantiation.  This result in
284
        // 'odd' fully qualified typename:
285
        //
286
        //    vector<_Tp,_Alloc>::size_type
287
        //
288
        // Make the situation is 'useable' but looking a bit odd by
289
        // picking a random instance as the declaring context.
290
2
        if (ClassTempl->spec_begin() != ClassTempl->spec_end()) {
291
2
          Decl = *(ClassTempl->spec_begin());
292
2
          Outer = dyn_cast<NamedDecl>(Decl);
293
2
          OuterNS = dyn_cast<NamespaceDecl>(Decl);
294
2
        }
295
2
      }
296
23
    }
297
298
69
    if (OuterNS) {
299
45
      return createNestedNameSpecifier(Ctx, OuterNS, WithGlobalNsPrefix);
300
45
    } else 
if (const auto *24
TD24
= dyn_cast<TagDecl>(Outer)) {
301
23
      return createNestedNameSpecifier(
302
23
          Ctx, TD, FullyQualified, WithGlobalNsPrefix);
303
23
    } else 
if (1
isa<TranslationUnitDecl>(Outer)1
) {
304
      // Context is the TU. Nothing needs to be done.
305
0
      return nullptr;
306
1
    } else {
307
      // Decl's context was neither the TU, a namespace, nor a
308
      // TagDecl, which means it is a type local to a scope, and not
309
      // accessible at the end of the TU.
310
1
      return nullptr;
311
1
    }
312
93
  } else if (WithGlobalNsPrefix && 
DC->isTranslationUnit()2
) {
313
1
    return NestedNameSpecifier::GlobalSpecifier(Ctx);
314
1
  }
315
92
  return nullptr;
316
162
}
317
318
/// Create a nested name specifier for the declaring context of
319
/// the type.
320
static NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
321
    const ASTContext &Ctx, const Type *TypePtr,
322
185
    bool FullyQualified, bool WithGlobalNsPrefix) {
323
185
  if (!TypePtr) 
return nullptr0
;
324
325
185
  Decl *Decl = nullptr;
326
  // There are probably other cases ...
327
185
  if (const auto *TDT = dyn_cast<TypedefType>(TypePtr)) {
328
13
    Decl = TDT->getDecl();
329
172
  } else if (const auto *TagDeclType = dyn_cast<TagType>(TypePtr)) {
330
138
    Decl = TagDeclType->getDecl();
331
138
  } else 
if (const auto *34
TST34
= dyn_cast<TemplateSpecializationType>(TypePtr)) {
332
11
    Decl = TST->getTemplateName().getAsTemplateDecl();
333
23
  } else {
334
23
    Decl = TypePtr->getAsCXXRecordDecl();
335
23
  }
336
337
185
  if (!Decl) 
return nullptr23
;
338
339
162
  return createNestedNameSpecifierForScopeOf(
340
162
      Ctx, Decl, FullyQualified, WithGlobalNsPrefix);
341
185
}
342
343
NestedNameSpecifier *createNestedNameSpecifier(const ASTContext &Ctx,
344
                                               const NamespaceDecl *Namespace,
345
90
                                               bool WithGlobalNsPrefix) {
346
90
  while (Namespace && Namespace->isInline()) {
347
    // Ignore inline namespace;
348
0
    Namespace = dyn_cast<NamespaceDecl>(Namespace->getDeclContext());
349
0
  }
350
90
  if (!Namespace) 
return nullptr0
;
351
352
90
  bool FullyQualified = true;  // doesn't matter, DeclContexts are namespaces
353
90
  return NestedNameSpecifier::Create(
354
90
      Ctx,
355
90
      createOuterNNS(Ctx, Namespace, FullyQualified, WithGlobalNsPrefix),
356
90
      Namespace);
357
90
}
358
359
NestedNameSpecifier *createNestedNameSpecifier(const ASTContext &Ctx,
360
                                               const TypeDecl *TD,
361
                                               bool FullyQualify,
362
23
                                               bool WithGlobalNsPrefix) {
363
23
  const Type *TypePtr = TD->getTypeForDecl();
364
23
  if (isa<const TemplateSpecializationType>(TypePtr) ||
365
23
      isa<const RecordType>(TypePtr)) {
366
    // We are asked to fully qualify and we have a Record Type (which
367
    // may point to a template specialization) or Template
368
    // Specialization Type. We need to fully qualify their arguments.
369
370
23
    TypePtr = getFullyQualifiedTemplateType(Ctx, TypePtr, WithGlobalNsPrefix);
371
23
  }
372
373
23
  return NestedNameSpecifier::Create(
374
23
      Ctx, createOuterNNS(Ctx, TD, FullyQualify, WithGlobalNsPrefix),
375
23
      false /*No TemplateKeyword*/, TypePtr);
376
23
}
377
378
/// Return the fully qualified type, including fully-qualified
379
/// versions of any template parameters.
380
QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx,
381
191
                               bool WithGlobalNsPrefix) {
382
  // In case of myType* we need to strip the pointer first, fully
383
  // qualify and attach the pointer once again.
384
191
  if (isa<PointerType>(QT.getTypePtr())) {
385
    // Get the qualifiers.
386
2
    Qualifiers Quals = QT.getQualifiers();
387
2
    QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
388
2
    QT = Ctx.getPointerType(QT);
389
    // Add back the qualifiers.
390
2
    QT = Ctx.getQualifiedType(QT, Quals);
391
2
    return QT;
392
2
  }
393
394
189
  if (auto *MPT = dyn_cast<MemberPointerType>(QT.getTypePtr())) {
395
    // Get the qualifiers.
396
1
    Qualifiers Quals = QT.getQualifiers();
397
    // Fully qualify the pointee and class types.
398
1
    QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
399
1
    QualType Class = getFullyQualifiedType(QualType(MPT->getClass(), 0), Ctx,
400
1
                                           WithGlobalNsPrefix);
401
1
    QT = Ctx.getMemberPointerType(QT, Class.getTypePtr());
402
    // Add back the qualifiers.
403
1
    QT = Ctx.getQualifiedType(QT, Quals);
404
1
    return QT;
405
1
  }
406
407
  // In case of myType& we need to strip the reference first, fully
408
  // qualify and attach the reference once again.
409
188
  if (isa<ReferenceType>(QT.getTypePtr())) {
410
    // Get the qualifiers.
411
0
    bool IsLValueRefTy = isa<LValueReferenceType>(QT.getTypePtr());
412
0
    Qualifiers Quals = QT.getQualifiers();
413
0
    QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
414
    // Add the r- or l-value reference type back to the fully
415
    // qualified one.
416
0
    if (IsLValueRefTy)
417
0
      QT = Ctx.getLValueReferenceType(QT);
418
0
    else
419
0
      QT = Ctx.getRValueReferenceType(QT);
420
    // Add back the qualifiers.
421
0
    QT = Ctx.getQualifiedType(QT, Quals);
422
0
    return QT;
423
0
  }
424
425
  // We don't consider the alias introduced by `using a::X` as a new type.
426
  // The qualified name is still a::X.
427
188
  if (isa<UsingType>(QT.getTypePtr())) {
428
3
    return getFullyQualifiedType(QT.getSingleStepDesugaredType(Ctx), Ctx,
429
3
                                 WithGlobalNsPrefix);
430
3
  }
431
432
  // Remove the part of the type related to the type being a template
433
  // parameter (we won't report it as part of the 'type name' and it
434
  // is actually make the code below to be more complex (to handle
435
  // those)
436
185
  while (isa<SubstTemplateTypeParmType>(QT.getTypePtr())) {
437
    // Get the qualifiers.
438
0
    Qualifiers Quals = QT.getQualifiers();
439
440
0
    QT = cast<SubstTemplateTypeParmType>(QT.getTypePtr())->desugar();
441
442
    // Add back the qualifiers.
443
0
    QT = Ctx.getQualifiedType(QT, Quals);
444
0
  }
445
446
185
  NestedNameSpecifier *Prefix = nullptr;
447
  // Local qualifiers are attached to the QualType outside of the
448
  // elaborated type.  Retrieve them before descending into the
449
  // elaborated type.
450
185
  Qualifiers PrefixQualifiers = QT.getLocalQualifiers();
451
185
  QT = QualType(QT.getTypePtr(), 0);
452
185
  ElaboratedTypeKeyword Keyword = ETK_None;
453
185
  if (const auto *ETypeInput = dyn_cast<ElaboratedType>(QT.getTypePtr())) {
454
16
    QT = ETypeInput->getNamedType();
455
16
    assert(!QT.hasLocalQualifiers());
456
0
    Keyword = ETypeInput->getKeyword();
457
16
  }
458
  // Create a nested name specifier if needed.
459
0
  Prefix = createNestedNameSpecifierForScopeOf(Ctx, QT.getTypePtr(),
460
185
                                               true /*FullyQualified*/,
461
185
                                               WithGlobalNsPrefix);
462
463
  // In case of template specializations iterate over the arguments and
464
  // fully qualify them as well.
465
185
  if (isa<const TemplateSpecializationType>(QT.getTypePtr()) ||
466
185
      
isa<const RecordType>(QT.getTypePtr())174
) {
467
    // We are asked to fully qualify and we have a Record Type (which
468
    // may point to a template specialization) or Template
469
    // Specialization Type. We need to fully qualify their arguments.
470
471
33
    const Type *TypePtr = getFullyQualifiedTemplateType(
472
33
        Ctx, QT.getTypePtr(), WithGlobalNsPrefix);
473
33
    QT = QualType(TypePtr, 0);
474
33
  }
475
185
  if (Prefix || 
Keyword != ETK_None116
) {
476
71
    QT = Ctx.getElaboratedType(Keyword, Prefix, QT);
477
71
  }
478
185
  QT = Ctx.getQualifiedType(QT, PrefixQualifiers);
479
185
  return QT;
480
188
}
481
482
std::string getFullyQualifiedName(QualType QT,
483
                                  const ASTContext &Ctx,
484
                                  const PrintingPolicy &Policy,
485
38
                                  bool WithGlobalNsPrefix) {
486
38
  QualType FQQT = getFullyQualifiedType(QT, Ctx, WithGlobalNsPrefix);
487
38
  return FQQT.getAsString(Policy);
488
38
}
489
490
}  // end namespace TypeName
491
}  // end namespace clang