1 //===- ExternalASTSource.h - Abstract External AST Interface ----*- C++ -*-===// 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 // This file defines the ExternalASTSource interface, which enables 10 // construction of AST nodes from some external source. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #ifndef LLVM_CLANG_AST_EXTERNALASTSOURCE_H 15 #define LLVM_CLANG_AST_EXTERNALASTSOURCE_H 16 17 #include "clang/AST/CharUnits.h" 18 #include "clang/AST/DeclBase.h" 19 #include "clang/Basic/LLVM.h" 20 #include "llvm/ADT/ArrayRef.h" 21 #include "llvm/ADT/DenseMap.h" 22 #include "llvm/ADT/IntrusiveRefCntPtr.h" 23 #include "llvm/ADT/PointerUnion.h" 24 #include "llvm/ADT/STLExtras.h" 25 #include "llvm/ADT/SmallVector.h" 26 #include "llvm/ADT/iterator.h" 27 #include "llvm/Support/PointerLikeTypeTraits.h" 28 #include <algorithm> 29 #include <cassert> 30 #include <cstddef> 31 #include <cstdint> 32 #include <iterator> 33 #include <new> 34 #include <optional> 35 #include <utility> 36 37 namespace clang { 38 39 class ASTConsumer; 40 class ASTContext; 41 class ASTSourceDescriptor; 42 class CXXBaseSpecifier; 43 class CXXCtorInitializer; 44 class CXXRecordDecl; 45 class DeclarationName; 46 class FieldDecl; 47 class IdentifierInfo; 48 class NamedDecl; 49 class ObjCInterfaceDecl; 50 class RecordDecl; 51 class Selector; 52 class Stmt; 53 class TagDecl; 54 55 /// Abstract interface for external sources of AST nodes. 56 /// 57 /// External AST sources provide AST nodes constructed from some 58 /// external source, such as a precompiled header. External AST 59 /// sources can resolve types and declarations from abstract IDs into 60 /// actual type and declaration nodes, and read parts of declaration 61 /// contexts. 62 class ExternalASTSource : public RefCountedBase<ExternalASTSource> { 63 friend class ExternalSemaSource; 64 65 /// Generation number for this external AST source. Must be increased 66 /// whenever we might have added new redeclarations for existing decls. 67 uint32_t CurrentGeneration = 0; 68 69 /// LLVM-style RTTI. 70 static char ID; 71 72 public: 73 ExternalASTSource() = default; 74 virtual ~ExternalASTSource(); 75 76 /// RAII class for safely pairing a StartedDeserializing call 77 /// with FinishedDeserializing. 78 class Deserializing { 79 ExternalASTSource *Source; 80 81 public: 82 explicit Deserializing(ExternalASTSource *source) : Source(source) { 83 assert(Source); 84 Source->StartedDeserializing(); 85 } 86 87 ~Deserializing() { 88 Source->FinishedDeserializing(); 89 } 90 }; 91 92 /// Get the current generation of this AST source. This number 93 /// is incremented each time the AST source lazily extends an existing 94 /// entity. 95 uint32_t getGeneration() const { return CurrentGeneration; } 96 97 /// Resolve a declaration ID into a declaration, potentially 98 /// building a new declaration. 99 /// 100 /// This method only needs to be implemented if the AST source ever 101 /// passes back decl sets as VisibleDeclaration objects. 102 /// 103 /// The default implementation of this method is a no-op. 104 virtual Decl *GetExternalDecl(GlobalDeclID ID); 105 106 /// Resolve a selector ID into a selector. 107 /// 108 /// This operation only needs to be implemented if the AST source 109 /// returns non-zero for GetNumKnownSelectors(). 110 /// 111 /// The default implementation of this method is a no-op. 112 virtual Selector GetExternalSelector(uint32_t ID); 113 114 /// Returns the number of selectors known to the external AST 115 /// source. 116 /// 117 /// The default implementation of this method is a no-op. 118 virtual uint32_t GetNumExternalSelectors(); 119 120 /// Resolve the offset of a statement in the decl stream into 121 /// a statement. 122 /// 123 /// This operation is meant to be used via a LazyOffsetPtr. It only 124 /// needs to be implemented if the AST source uses methods like 125 /// FunctionDecl::setLazyBody when building decls. 126 /// 127 /// The default implementation of this method is a no-op. 128 virtual Stmt *GetExternalDeclStmt(uint64_t Offset); 129 130 /// Resolve the offset of a set of C++ constructor initializers in 131 /// the decl stream into an array of initializers. 132 /// 133 /// The default implementation of this method is a no-op. 134 virtual CXXCtorInitializer **GetExternalCXXCtorInitializers(uint64_t Offset); 135 136 /// Resolve the offset of a set of C++ base specifiers in the decl 137 /// stream into an array of specifiers. 138 /// 139 /// The default implementation of this method is a no-op. 140 virtual CXXBaseSpecifier *GetExternalCXXBaseSpecifiers(uint64_t Offset); 141 142 /// Update an out-of-date identifier. 143 virtual void updateOutOfDateIdentifier(const IdentifierInfo &II) {} 144 145 /// Find all declarations with the given name in the given context, 146 /// and add them to the context by calling SetExternalVisibleDeclsForName 147 /// or SetNoExternalVisibleDeclsForName. 148 /// \param DC The context for lookup in. \c DC should be a primary context. 149 /// \param Name The name to look for. 150 /// \param OriginalDC The original context for lookup. \c OriginalDC can 151 /// provide more information than \c DC. e.g., The same namespace can appear 152 /// in multiple module units. So we need the \c OriginalDC to tell us what 153 /// the module the lookup come from. 154 /// 155 /// \return \c true if any declarations might have been found, \c false if 156 /// we definitely have no declarations with tbis name. 157 /// 158 /// The default implementation of this method is a no-op returning \c false. 159 virtual bool FindExternalVisibleDeclsByName(const DeclContext *DC, 160 DeclarationName Name, 161 const DeclContext *OriginalDC); 162 163 /// Load all the external specializations for the Decl \param D if \param 164 /// OnlyPartial is false. Otherwise, load all the external **partial** 165 /// specializations for the \param D. 166 /// 167 /// Return true if any new specializations get loaded. Return false otherwise. 168 virtual bool LoadExternalSpecializations(const Decl *D, bool OnlyPartial); 169 170 /// Load all the specializations for the Decl \param D with the same template 171 /// args specified by \param TemplateArgs. 172 /// 173 /// Return true if any new specializations get loaded. Return false otherwise. 174 virtual bool 175 LoadExternalSpecializations(const Decl *D, 176 ArrayRef<TemplateArgument> TemplateArgs); 177 178 /// Ensures that the table of all visible declarations inside this 179 /// context is up to date. 180 /// 181 /// The default implementation of this function is a no-op. 182 virtual void completeVisibleDeclsMap(const DeclContext *DC); 183 184 /// Retrieve the module that corresponds to the given module ID. 185 virtual Module *getModule(unsigned ID) { return nullptr; } 186 187 /// Return a descriptor for the corresponding module, if one exists. 188 virtual std::optional<ASTSourceDescriptor> getSourceDescriptor(unsigned ID); 189 190 enum ExtKind { EK_Always, EK_Never, EK_ReplyHazy }; 191 192 virtual ExtKind hasExternalDefinitions(const Decl *D); 193 194 /// Finds all declarations lexically contained within the given 195 /// DeclContext, after applying an optional filter predicate. 196 /// 197 /// \param IsKindWeWant a predicate function that returns true if the passed 198 /// declaration kind is one we are looking for. 199 /// 200 /// The default implementation of this method is a no-op. 201 virtual void 202 FindExternalLexicalDecls(const DeclContext *DC, 203 llvm::function_ref<bool(Decl::Kind)> IsKindWeWant, 204 SmallVectorImpl<Decl *> &Result); 205 206 /// Finds all declarations lexically contained within the given 207 /// DeclContext. 208 void FindExternalLexicalDecls(const DeclContext *DC, 209 SmallVectorImpl<Decl *> &Result) { 210 FindExternalLexicalDecls(DC, [](Decl::Kind) { return true; }, Result); 211 } 212 213 /// Get the decls that are contained in a file in the Offset/Length 214 /// range. \p Length can be 0 to indicate a point at \p Offset instead of 215 /// a range. 216 virtual void FindFileRegionDecls(FileID File, unsigned Offset, 217 unsigned Length, 218 SmallVectorImpl<Decl *> &Decls); 219 220 /// Gives the external AST source an opportunity to complete 221 /// the redeclaration chain for a declaration. Called each time we 222 /// need the most recent declaration of a declaration after the 223 /// generation count is incremented. 224 virtual void CompleteRedeclChain(const Decl *D); 225 226 /// Gives the external AST source an opportunity to complete 227 /// an incomplete type. 228 virtual void CompleteType(TagDecl *Tag); 229 230 /// Gives the external AST source an opportunity to complete an 231 /// incomplete Objective-C class. 232 /// 233 /// This routine will only be invoked if the "externally completed" bit is 234 /// set on the ObjCInterfaceDecl via the function 235 /// \c ObjCInterfaceDecl::setExternallyCompleted(). 236 virtual void CompleteType(ObjCInterfaceDecl *Class); 237 238 /// Loads comment ranges. 239 virtual void ReadComments(); 240 241 /// Notify ExternalASTSource that we started deserialization of 242 /// a decl or type so until FinishedDeserializing is called there may be 243 /// decls that are initializing. Must be paired with FinishedDeserializing. 244 /// 245 /// The default implementation of this method is a no-op. 246 virtual void StartedDeserializing(); 247 248 /// Notify ExternalASTSource that we finished the deserialization of 249 /// a decl or type. Must be paired with StartedDeserializing. 250 /// 251 /// The default implementation of this method is a no-op. 252 virtual void FinishedDeserializing(); 253 254 /// Function that will be invoked when we begin parsing a new 255 /// translation unit involving this external AST source. 256 /// 257 /// The default implementation of this method is a no-op. 258 virtual void StartTranslationUnit(ASTConsumer *Consumer); 259 260 /// Print any statistics that have been gathered regarding 261 /// the external AST source. 262 /// 263 /// The default implementation of this method is a no-op. 264 virtual void PrintStats(); 265 266 /// Perform layout on the given record. 267 /// 268 /// This routine allows the external AST source to provide an specific 269 /// layout for a record, overriding the layout that would normally be 270 /// constructed. It is intended for clients who receive specific layout 271 /// details rather than source code (such as LLDB). The client is expected 272 /// to fill in the field offsets, base offsets, virtual base offsets, and 273 /// complete object size. 274 /// 275 /// \param Record The record whose layout is being requested. 276 /// 277 /// \param Size The final size of the record, in bits. 278 /// 279 /// \param Alignment The final alignment of the record, in bits. 280 /// 281 /// \param FieldOffsets The offset of each of the fields within the record, 282 /// expressed in bits. All of the fields must be provided with offsets. 283 /// 284 /// \param BaseOffsets The offset of each of the direct, non-virtual base 285 /// classes. If any bases are not given offsets, the bases will be laid 286 /// out according to the ABI. 287 /// 288 /// \param VirtualBaseOffsets The offset of each of the virtual base classes 289 /// (either direct or not). If any bases are not given offsets, the bases will be laid 290 /// out according to the ABI. 291 /// 292 /// \returns true if the record layout was provided, false otherwise. 293 virtual bool layoutRecordType( 294 const RecordDecl *Record, uint64_t &Size, uint64_t &Alignment, 295 llvm::DenseMap<const FieldDecl *, uint64_t> &FieldOffsets, 296 llvm::DenseMap<const CXXRecordDecl *, CharUnits> &BaseOffsets, 297 llvm::DenseMap<const CXXRecordDecl *, CharUnits> &VirtualBaseOffsets); 298 299 //===--------------------------------------------------------------------===// 300 // Queries for performance analysis. 301 //===--------------------------------------------------------------------===// 302 303 struct MemoryBufferSizes { 304 size_t malloc_bytes; 305 size_t mmap_bytes; 306 307 MemoryBufferSizes(size_t malloc_bytes, size_t mmap_bytes) 308 : malloc_bytes(malloc_bytes), mmap_bytes(mmap_bytes) {} 309 }; 310 311 /// Return the amount of memory used by memory buffers, breaking down 312 /// by heap-backed versus mmap'ed memory. 313 MemoryBufferSizes getMemoryBufferSizes() const { 314 MemoryBufferSizes sizes(0, 0); 315 getMemoryBufferSizes(sizes); 316 return sizes; 317 } 318 319 virtual void getMemoryBufferSizes(MemoryBufferSizes &sizes) const; 320 321 /// LLVM-style RTTI. 322 /// \{ 323 virtual bool isA(const void *ClassID) const { return ClassID == &ID; } 324 static bool classof(const ExternalASTSource *S) { return S->isA(&ID); } 325 /// \} 326 327 protected: 328 static DeclContextLookupResult 329 SetExternalVisibleDeclsForName(const DeclContext *DC, 330 DeclarationName Name, 331 ArrayRef<NamedDecl*> Decls); 332 333 static DeclContextLookupResult 334 SetNoExternalVisibleDeclsForName(const DeclContext *DC, 335 DeclarationName Name); 336 337 /// Increment the current generation. 338 uint32_t incrementGeneration(ASTContext &C); 339 }; 340 341 /// A lazy pointer to an AST node (of base type T) that resides 342 /// within an external AST source. 343 /// 344 /// The AST node is identified within the external AST source by a 345 /// 63-bit offset, and can be retrieved via an operation on the 346 /// external AST source itself. 347 template<typename T, typename OffsT, T* (ExternalASTSource::*Get)(OffsT Offset)> 348 struct LazyOffsetPtr { 349 /// Either a pointer to an AST node or the offset within the 350 /// external AST source where the AST node can be found. 351 /// 352 /// If the low bit is clear, a pointer to the AST node. If the low 353 /// bit is set, the upper 63 bits are the offset. 354 static constexpr size_t DataSize = std::max(sizeof(uint64_t), sizeof(T *)); 355 alignas(uint64_t) alignas(T *) mutable unsigned char Data[DataSize] = {}; 356 357 unsigned char GetLSB() const { 358 return Data[llvm::sys::IsBigEndianHost ? DataSize - 1 : 0]; 359 } 360 361 template <typename U> U &As(bool New) const { 362 unsigned char *Obj = 363 Data + (llvm::sys::IsBigEndianHost ? DataSize - sizeof(U) : 0); 364 if (New) 365 return *new (Obj) U; 366 return *std::launder(reinterpret_cast<U *>(Obj)); 367 } 368 369 T *&GetPtr() const { return As<T *>(false); } 370 uint64_t &GetU64() const { return As<uint64_t>(false); } 371 void SetPtr(T *Ptr) const { As<T *>(true) = Ptr; } 372 void SetU64(uint64_t U64) const { As<uint64_t>(true) = U64; } 373 374 public: 375 LazyOffsetPtr() = default; 376 explicit LazyOffsetPtr(T *Ptr) : Data() { SetPtr(Ptr); } 377 378 explicit LazyOffsetPtr(uint64_t Offset) : Data() { 379 assert((Offset << 1 >> 1) == Offset && "Offsets must require < 63 bits"); 380 if (Offset == 0) 381 SetPtr(nullptr); 382 else 383 SetU64((Offset << 1) | 0x01); 384 } 385 386 LazyOffsetPtr &operator=(T *Ptr) { 387 SetPtr(Ptr); 388 return *this; 389 } 390 391 LazyOffsetPtr &operator=(uint64_t Offset) { 392 assert((Offset << 1 >> 1) == Offset && "Offsets must require < 63 bits"); 393 if (Offset == 0) 394 SetPtr(nullptr); 395 else 396 SetU64((Offset << 1) | 0x01); 397 398 return *this; 399 } 400 401 /// Whether this pointer is non-NULL. 402 /// 403 /// This operation does not require the AST node to be deserialized. 404 explicit operator bool() const { return isOffset() || GetPtr() != nullptr; } 405 406 /// Whether this pointer is non-NULL. 407 /// 408 /// This operation does not require the AST node to be deserialized. 409 bool isValid() const { return isOffset() || GetPtr() != nullptr; } 410 411 /// Whether this pointer is currently stored as an offset. 412 bool isOffset() const { return GetLSB() & 0x01; } 413 414 /// Retrieve the pointer to the AST node that this lazy pointer points to. 415 /// 416 /// \param Source the external AST source. 417 /// 418 /// \returns a pointer to the AST node. 419 T *get(ExternalASTSource *Source) const { 420 if (isOffset()) { 421 assert(Source && 422 "Cannot deserialize a lazy pointer without an AST source"); 423 SetPtr((Source->*Get)(OffsT(GetU64() >> 1))); 424 } 425 return GetPtr(); 426 } 427 428 /// Retrieve the address of the AST node pointer. Deserializes the pointee if 429 /// necessary. 430 T **getAddressOfPointer(ExternalASTSource *Source) const { 431 // Ensure the integer is in pointer form. 432 (void)get(Source); 433 return &GetPtr(); 434 } 435 }; 436 437 /// A lazy value (of type T) that is within an AST node of type Owner, 438 /// where the value might change in later generations of the external AST 439 /// source. 440 template<typename Owner, typename T, void (ExternalASTSource::*Update)(Owner)> 441 struct LazyGenerationalUpdatePtr { 442 /// A cache of the value of this pointer, in the most recent generation in 443 /// which we queried it. 444 struct LazyData { 445 ExternalASTSource *ExternalSource; 446 uint32_t LastGeneration = 0; 447 T LastValue; 448 449 LazyData(ExternalASTSource *Source, T Value) 450 : ExternalSource(Source), LastValue(Value) {} 451 }; 452 453 // Our value is represented as simply T if there is no external AST source. 454 using ValueType = llvm::PointerUnion<T, LazyData*>; 455 ValueType Value; 456 457 LazyGenerationalUpdatePtr(ValueType V) : Value(V) {} 458 459 // Defined in ASTContext.h 460 static ValueType makeValue(const ASTContext &Ctx, T Value); 461 462 public: 463 explicit LazyGenerationalUpdatePtr(const ASTContext &Ctx, T Value = T()) 464 : Value(makeValue(Ctx, Value)) {} 465 466 /// Create a pointer that is not potentially updated by later generations of 467 /// the external AST source. 468 enum NotUpdatedTag { NotUpdated }; 469 LazyGenerationalUpdatePtr(NotUpdatedTag, T Value = T()) 470 : Value(Value) {} 471 472 /// Forcibly set this pointer (which must be lazy) as needing updates. 473 void markIncomplete() { cast<LazyData *>(Value)->LastGeneration = 0; } 474 475 /// Set the value of this pointer, in the current generation. 476 void set(T NewValue) { 477 if (auto *LazyVal = Value.template dyn_cast<LazyData *>()) { 478 LazyVal->LastValue = NewValue; 479 return; 480 } 481 Value = NewValue; 482 } 483 484 /// Set the value of this pointer, for this and all future generations. 485 void setNotUpdated(T NewValue) { Value = NewValue; } 486 487 /// Get the value of this pointer, updating its owner if necessary. 488 T get(Owner O) { 489 if (auto *LazyVal = Value.template dyn_cast<LazyData *>()) { 490 if (LazyVal->LastGeneration != LazyVal->ExternalSource->getGeneration()) { 491 LazyVal->LastGeneration = LazyVal->ExternalSource->getGeneration(); 492 (LazyVal->ExternalSource->*Update)(O); 493 } 494 return LazyVal->LastValue; 495 } 496 return cast<T>(Value); 497 } 498 499 /// Get the most recently computed value of this pointer without updating it. 500 T getNotUpdated() const { 501 if (auto *LazyVal = Value.template dyn_cast<LazyData *>()) 502 return LazyVal->LastValue; 503 return cast<T>(Value); 504 } 505 506 void *getOpaqueValue() { return Value.getOpaqueValue(); } 507 static LazyGenerationalUpdatePtr getFromOpaqueValue(void *Ptr) { 508 return LazyGenerationalUpdatePtr(ValueType::getFromOpaqueValue(Ptr)); 509 } 510 }; 511 512 } // namespace clang 513 514 namespace llvm { 515 516 /// Specialize PointerLikeTypeTraits to allow LazyGenerationalUpdatePtr to be 517 /// placed into a PointerUnion. 518 template<typename Owner, typename T, 519 void (clang::ExternalASTSource::*Update)(Owner)> 520 struct PointerLikeTypeTraits< 521 clang::LazyGenerationalUpdatePtr<Owner, T, Update>> { 522 using Ptr = clang::LazyGenerationalUpdatePtr<Owner, T, Update>; 523 524 static void *getAsVoidPointer(Ptr P) { return P.getOpaqueValue(); } 525 static Ptr getFromVoidPointer(void *P) { return Ptr::getFromOpaqueValue(P); } 526 527 static constexpr int NumLowBitsAvailable = 528 PointerLikeTypeTraits<T>::NumLowBitsAvailable - 1; 529 }; 530 531 } // namespace llvm 532 533 namespace clang { 534 535 /// Represents a lazily-loaded vector of data. 536 /// 537 /// The lazily-loaded vector of data contains data that is partially loaded 538 /// from an external source and partially added by local translation. The 539 /// items loaded from the external source are loaded lazily, when needed for 540 /// iteration over the complete vector. 541 template<typename T, typename Source, 542 void (Source::*Loader)(SmallVectorImpl<T>&), 543 unsigned LoadedStorage = 2, unsigned LocalStorage = 4> 544 class LazyVector { 545 SmallVector<T, LoadedStorage> Loaded; 546 SmallVector<T, LocalStorage> Local; 547 548 public: 549 /// Iteration over the elements in the vector. 550 /// 551 /// In a complete iteration, the iterator walks the range [-M, N), 552 /// where negative values are used to indicate elements 553 /// loaded from the external source while non-negative values are used to 554 /// indicate elements added via \c push_back(). 555 /// However, to provide iteration in source order (for, e.g., chained 556 /// precompiled headers), dereferencing the iterator flips the negative 557 /// values (corresponding to loaded entities), so that position -M 558 /// corresponds to element 0 in the loaded entities vector, position -M+1 559 /// corresponds to element 1 in the loaded entities vector, etc. This 560 /// gives us a reasonably efficient, source-order walk. 561 /// 562 /// We define this as a wrapping iterator around an int. The 563 /// iterator_adaptor_base class forwards the iterator methods to basic integer 564 /// arithmetic. 565 class iterator 566 : public llvm::iterator_adaptor_base< 567 iterator, int, std::random_access_iterator_tag, T, int, T *, T &> { 568 friend class LazyVector; 569 570 LazyVector *Self; 571 572 iterator(LazyVector *Self, int Position) 573 : iterator::iterator_adaptor_base(Position), Self(Self) {} 574 575 bool isLoaded() const { return this->I < 0; } 576 577 public: 578 iterator() : iterator(nullptr, 0) {} 579 580 typename iterator::reference operator*() const { 581 if (isLoaded()) 582 return Self->Loaded.end()[this->I]; 583 return Self->Local.begin()[this->I]; 584 } 585 }; 586 587 iterator begin(Source *source, bool LocalOnly = false) { 588 if (LocalOnly) 589 return iterator(this, 0); 590 591 if (source) 592 (source->*Loader)(Loaded); 593 return iterator(this, -(int)Loaded.size()); 594 } 595 596 iterator end() { 597 return iterator(this, Local.size()); 598 } 599 600 void push_back(const T& LocalValue) { 601 Local.push_back(LocalValue); 602 } 603 604 void erase(iterator From, iterator To) { 605 if (From.isLoaded() && To.isLoaded()) { 606 Loaded.erase(&*From, &*To); 607 return; 608 } 609 610 if (From.isLoaded()) { 611 Loaded.erase(&*From, Loaded.end()); 612 From = begin(nullptr, true); 613 } 614 615 Local.erase(&*From, &*To); 616 } 617 }; 618 619 /// A lazy pointer to a statement. 620 using LazyDeclStmtPtr = 621 LazyOffsetPtr<Stmt, uint64_t, &ExternalASTSource::GetExternalDeclStmt>; 622 623 /// A lazy pointer to a declaration. 624 using LazyDeclPtr = 625 LazyOffsetPtr<Decl, GlobalDeclID, &ExternalASTSource::GetExternalDecl>; 626 627 /// A lazy pointer to a set of CXXCtorInitializers. 628 using LazyCXXCtorInitializersPtr = 629 LazyOffsetPtr<CXXCtorInitializer *, uint64_t, 630 &ExternalASTSource::GetExternalCXXCtorInitializers>; 631 632 /// A lazy pointer to a set of CXXBaseSpecifiers. 633 using LazyCXXBaseSpecifiersPtr = 634 LazyOffsetPtr<CXXBaseSpecifier, uint64_t, 635 &ExternalASTSource::GetExternalCXXBaseSpecifiers>; 636 637 } // namespace clang 638 639 #endif // LLVM_CLANG_AST_EXTERNALASTSOURCE_H 640