diff --git a/include/clang/Serialization/ASTWriter.h b/include/clang/Serialization/ASTWriter.h index d7c49462dc4c3e467908225fe4c8db99667e1094..5eeaee9a15904fc042dce9dfaede43ebe40d395c 100644 --- a/include/clang/Serialization/ASTWriter.h +++ b/include/clang/Serialization/ASTWriter.h @@ -405,6 +405,10 @@ private: /// \brief The set of declarations that may have redeclaration chains that /// need to be serialized. llvm::SmallVector<const Decl *, 16> Redeclarations; + + /// \brief A cache of the first local declaration for "interesting" + /// redeclaration chains. + llvm::DenseMap<const Decl *, const Decl *> FirstLocalDeclCache; /// \brief Statements that we've encountered while serializing a /// declaration or type. @@ -676,6 +680,10 @@ public: const ASTTemplateArgumentListInfo *ASTTemplArgList, RecordDataImpl &Record); + /// \brief Find the first local declaration of a given local redeclarable + /// decl. + const Decl *getFirstLocalDecl(const Decl *D); + /// \brief Emit a reference to a declaration. void AddDeclRef(const Decl *D, RecordDataImpl &Record); @@ -857,12 +865,6 @@ public: void CompletedTagDefinition(const TagDecl *D) override; void AddedVisibleDecl(const DeclContext *DC, const Decl *D) override; void AddedCXXImplicitMember(const CXXRecordDecl *RD, const Decl *D) override; - void AddedCXXTemplateSpecialization(const ClassTemplateDecl *TD, - const ClassTemplateSpecializationDecl *D) override; - void AddedCXXTemplateSpecialization(const VarTemplateDecl *TD, - const VarTemplateSpecializationDecl *D) override; - void AddedCXXTemplateSpecialization(const FunctionTemplateDecl *TD, - const FunctionDecl *D) override; void ResolvedExceptionSpec(const FunctionDecl *FD) override; void DeducedReturnType(const FunctionDecl *FD, QualType ReturnType) override; void ResolvedOperatorDelete(const CXXDestructorDecl *DD, diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp index 6fa1765bc4389b41a84e1ffe4910fe26aa2a0a86..b763f022b4333798757d15aba336dbe0fbc3fe73 100644 --- a/lib/Serialization/ASTReader.cpp +++ b/lib/Serialization/ASTReader.cpp @@ -8123,11 +8123,8 @@ void ASTReader::finishPendingActions() { PendingIncompleteDeclChains.clear(); // Load pending declaration chains. - for (unsigned I = 0; I != PendingDeclChains.size(); ++I) { - PendingDeclChainsKnown.erase(PendingDeclChains[I]); + for (unsigned I = 0; I != PendingDeclChains.size(); ++I) loadPendingDeclChain(PendingDeclChains[I]); - } - assert(PendingDeclChainsKnown.empty()); PendingDeclChains.clear(); assert(RedeclsDeserialized.empty() && "some redecls not wired up"); diff --git a/lib/Serialization/ASTReaderDecl.cpp b/lib/Serialization/ASTReaderDecl.cpp index 972e2b829d17d04d1e5e3f35cea0e7923af617ed..f86e835f0f7a797249d42dd300b7190236d7f099 100644 --- a/lib/Serialization/ASTReaderDecl.cpp +++ b/lib/Serialization/ASTReaderDecl.cpp @@ -147,12 +147,6 @@ namespace clang { } ~RedeclarableResult() { - if (FirstID && Owning && - isRedeclarableDeclKind(LoadedDecl->getKind())) { - auto Canon = Reader.GetDecl(FirstID)->getCanonicalDecl(); - if (Reader.PendingDeclChainsKnown.insert(Canon).second) - Reader.PendingDeclChains.push_back(Canon); - } } /// \brief Note that a RedeclarableDecl is not actually redeclarable. @@ -2186,23 +2180,33 @@ ASTDeclReader::RedeclarableResult ASTDeclReader::VisitRedeclarable(Redeclarable<T> *D) { DeclID FirstDeclID = ReadDeclID(Record, Idx); Decl *MergeWith = nullptr; + bool IsKeyDecl = ThisDeclID == FirstDeclID; + bool IsFirstLocalDecl = false; // 0 indicates that this declaration was the only declaration of its entity, // and is used for space optimization. if (FirstDeclID == 0) { FirstDeclID = ThisDeclID; IsKeyDecl = true; + IsFirstLocalDecl = true; } else if (unsigned N = Record[Idx++]) { - IsKeyDecl = false; + // This declaration was the first local declaration, but may have imported + // other declarations. + IsKeyDecl = N == 1; + IsFirstLocalDecl = true; // We have some declarations that must be before us in our redeclaration // chain. Read them now, and remember that we ought to merge with one of // them. // FIXME: Provide a known merge target to the second and subsequent such // declaration. - for (unsigned I = 0; I != N; ++I) + for (unsigned I = 0; I != N - 1; ++I) MergeWith = ReadDecl(Record, Idx/*, MergeWith*/); + } else { + // This declaration was not the first local declaration. Read the first + // local declaration now, to trigger the import of other redeclarations. + (void)ReadDecl(Record, Idx); } T *FirstDecl = cast_or_null<T>(Reader.GetDecl(FirstDeclID)); @@ -2214,13 +2218,21 @@ ASTDeclReader::VisitRedeclarable(Redeclarable<T> *D) { D->RedeclLink = Redeclarable<T>::PreviousDeclLink(FirstDecl); D->First = FirstDecl->getCanonicalDecl(); } - + // Note that this declaration has been deserialized. - Reader.RedeclsDeserialized.insert(static_cast<T *>(D)); - + T *DAsT = static_cast<T*>(D); + Reader.RedeclsDeserialized.insert(DAsT); + + // Note that we need to load local redeclarations of this decl and build a + // decl chain for them. This must happen *after* we perform the preloading + // above; this ensures that the redeclaration chain is built in the correct + // order. + if (IsFirstLocalDecl) + Reader.PendingDeclChains.push_back(DAsT); + // The result structure takes care to note that we need to load the // other declaration chains for this ID. - return RedeclarableResult(Reader, FirstDeclID, static_cast<T *>(D), MergeWith, + return RedeclarableResult(Reader, FirstDeclID, DAsT, MergeWith, IsKeyDecl); } @@ -2330,11 +2342,8 @@ void ASTDeclReader::mergeRedeclarable(Redeclarable<T> *DBase, T *Existing, TemplatePatternID, Redecl.isKeyDecl()); // If this declaration is a key declaration, make a note of that. - if (Redecl.isKeyDecl()) { + if (Redecl.isKeyDecl()) Reader.KeyDecls[ExistingCanon].push_back(Redecl.getFirstID()); - if (Reader.PendingDeclChainsKnown.insert(ExistingCanon).second) - Reader.PendingDeclChains.push_back(ExistingCanon); - } } } @@ -3424,15 +3433,12 @@ namespace { ASTReader &Reader; SmallVectorImpl<DeclID> &SearchDecls; llvm::SmallPtrSetImpl<Decl *> &Deserialized; - GlobalDeclID CanonID; SmallVector<Decl *, 4> Chain; public: RedeclChainVisitor(ASTReader &Reader, SmallVectorImpl<DeclID> &SearchDecls, - llvm::SmallPtrSetImpl<Decl *> &Deserialized, - GlobalDeclID CanonID) - : Reader(Reader), SearchDecls(SearchDecls), Deserialized(Deserialized), - CanonID(CanonID) { + llvm::SmallPtrSetImpl<Decl *> &Deserialized) + : Reader(Reader), SearchDecls(SearchDecls), Deserialized(Deserialized) { assert(std::is_sorted(SearchDecls.begin(), SearchDecls.end())); } @@ -3518,10 +3524,10 @@ namespace { break; } - if (LocalSearchDeclID && LocalSearchDeclID != CanonID) { + assert(LocalSearchDeclID); + if (LocalSearchDeclID) { // If the search decl was from this module, add it to the chain. // Note, the chain is sorted from newest to oldest, so this goes last. - // We exclude the canonical declaration; it implicitly goes at the end. addToChain(Reader.GetDecl(LocalSearchDeclID)); } @@ -3531,26 +3537,14 @@ namespace { }; } -void ASTReader::loadPendingDeclChain(Decl *CanonDecl) { - // The decl might have been merged into something else after being added to - // our list. If it was, just skip it. - if (!CanonDecl->isCanonicalDecl()) - return; - +void ASTReader::loadPendingDeclChain(Decl *FirstLocal) { // Determine the set of declaration IDs we'll be searching for. - SmallVector<DeclID, 16> SearchDecls; - GlobalDeclID CanonID = CanonDecl->getGlobalID(); - if (CanonID) - SearchDecls.push_back(CanonDecl->getGlobalID()); // Always first. - KeyDeclsMap::iterator KeyPos = KeyDecls.find(CanonDecl); - if (KeyPos != KeyDecls.end()) - SearchDecls.append(KeyPos->second.begin(), KeyPos->second.end()); - llvm::array_pod_sort(SearchDecls.begin(), SearchDecls.end()); + SmallVector<DeclID, 1> SearchDecls; + SearchDecls.push_back(FirstLocal->getGlobalID()); // Build up the list of redeclarations. - RedeclChainVisitor Visitor(*this, SearchDecls, RedeclsDeserialized, CanonID); - ModuleMgr.visit(Visitor); - RedeclsDeserialized.erase(CanonDecl); + RedeclChainVisitor Visitor(*this, SearchDecls, RedeclsDeserialized); + Visitor(*getOwningModuleFile(FirstLocal)); // Retrieve the chains. ArrayRef<Decl *> Chain = Visitor.getChain(); @@ -3561,11 +3555,14 @@ void ASTReader::loadPendingDeclChain(Decl *CanonDecl) { // // FIXME: We have three different dispatches on decl kind here; maybe // we should instead generate one loop per kind and dispatch up-front? + Decl *CanonDecl = FirstLocal->getCanonicalDecl(); Decl *MostRecent = ASTDeclReader::getMostRecentDecl(CanonDecl); if (!MostRecent) MostRecent = CanonDecl; for (unsigned I = 0, N = Chain.size(); I != N; ++I) { auto *D = Chain[N - I - 1]; + if (D == CanonDecl) + continue; ASTDeclReader::attachPreviousDecl(*this, D, MostRecent, CanonDecl); MostRecent = D; } diff --git a/lib/Serialization/ASTWriter.cpp b/lib/Serialization/ASTWriter.cpp index b554f5f1ccd084d64ad61dd48adcef0f704186b3..ae326b2068b7b75829e7f171e214e76cf5eaa5c7 100644 --- a/lib/Serialization/ASTWriter.cpp +++ b/lib/Serialization/ASTWriter.cpp @@ -3799,41 +3799,39 @@ void ASTWriter::WriteRedeclarations() { SmallVector<serialization::LocalRedeclarationsInfo, 2> LocalRedeclsMap; for (unsigned I = 0, N = Redeclarations.size(); I != N; ++I) { - const Decl *Key = Redeclarations[I]; - assert((Chain ? Chain->getKeyDeclaration(Key) == Key - : Key->isFirstDecl()) && - "not the key declaration"); + const Decl *FirstLocal = Redeclarations[I]; + assert(!FirstLocal->isFromASTFile() && + (!FirstLocal->getPreviousDecl() || + FirstLocal->getPreviousDecl()->isFromASTFile() || + getDeclID(FirstLocal->getPreviousDecl()) < NUM_PREDEF_DECL_IDS) && + "not the first local declaration"); + assert(getDeclID(FirstLocal) >= NUM_PREDEF_DECL_IDS && + "should not have predefined decl as first decl"); - const Decl *First = Key->getCanonicalDecl(); - const Decl *MostRecent = First->getMostRecentDecl(); - - assert((getDeclID(First) >= NUM_PREDEF_DECL_IDS || First == Key) && - "should not have imported key decls for predefined decl"); - - // If we only have a single declaration, there is no point in storing - // a redeclaration chain. - if (First == MostRecent) - continue; - unsigned Offset = LocalRedeclChains.size(); unsigned Size = 0; LocalRedeclChains.push_back(0); // Placeholder for the size. // Collect the set of local redeclarations of this declaration, from newest // to oldest. - for (const Decl *Prev = MostRecent; Prev; - Prev = Prev->getPreviousDecl()) { - if (!Prev->isFromASTFile() && Prev != Key) { + for (const Decl *Prev = FirstLocal->getMostRecentDecl(); Prev != FirstLocal; + Prev = Prev->getPreviousDecl()) { + if (!Prev->isFromASTFile()) { AddDeclRef(Prev, LocalRedeclChains); ++Size; } } + // If we only have a single local declaration, there is no point in storing + // a redeclaration chain. + if (LocalRedeclChains.size() == 1) + continue; + LocalRedeclChains[Offset] = Size; // Add the mapping from the first ID from the AST to the set of local // declarations. - LocalRedeclarationsInfo Info = { getDeclID(Key), Offset }; + LocalRedeclarationsInfo Info = { getDeclID(FirstLocal), Offset }; LocalRedeclsMap.push_back(Info); assert(N == Redeclarations.size() && @@ -4145,8 +4143,6 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, if (D) { assert(D->isCanonicalDecl() && "predefined decl is not canonical"); DeclIDs[D] = ID; - if (D->getMostRecentDecl() != D) - Redeclarations.push_back(D); } }; RegisterPredefDecl(Context.getTranslationUnitDecl(), @@ -5730,42 +5726,6 @@ void ASTWriter::AddedCXXImplicitMember(const CXXRecordDecl *RD, const Decl *D) { DeclUpdates[RD].push_back(DeclUpdate(UPD_CXX_ADDED_IMPLICIT_MEMBER, D)); } -void ASTWriter::AddedCXXTemplateSpecialization(const ClassTemplateDecl *TD, - const ClassTemplateSpecializationDecl *D) { - // The specializations set is kept in the canonical template. - TD = TD->getCanonicalDecl(); - if (!(!D->isFromASTFile() && TD->isFromASTFile())) - return; // Not a source specialization added to a template from PCH. - - assert(!WritingAST && "Already writing the AST!"); - DeclUpdates[TD].push_back(DeclUpdate(UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION, - D)); -} - -void ASTWriter::AddedCXXTemplateSpecialization( - const VarTemplateDecl *TD, const VarTemplateSpecializationDecl *D) { - // The specializations set is kept in the canonical template. - TD = TD->getCanonicalDecl(); - if (!(!D->isFromASTFile() && TD->isFromASTFile())) - return; // Not a source specialization added to a template from PCH. - - assert(!WritingAST && "Already writing the AST!"); - DeclUpdates[TD].push_back(DeclUpdate(UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION, - D)); -} - -void ASTWriter::AddedCXXTemplateSpecialization(const FunctionTemplateDecl *TD, - const FunctionDecl *D) { - // The specializations set is kept in the canonical template. - TD = TD->getCanonicalDecl(); - if (!(!D->isFromASTFile() && TD->isFromASTFile())) - return; // Not a source specialization added to a template from PCH. - - assert(!WritingAST && "Already writing the AST!"); - DeclUpdates[TD].push_back(DeclUpdate(UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION, - D)); -} - void ASTWriter::ResolvedExceptionSpec(const FunctionDecl *FD) { assert(!DoneWritingDeclsAndTypes && "Already done writing updates!"); if (!Chain) return; diff --git a/lib/Serialization/ASTWriterDecl.cpp b/lib/Serialization/ASTWriterDecl.cpp index 02e1a4b0b9f38b8b684e5bc27b8f65c2e268483c..712eae186ca614d6f33c37545471dd6067990933 100644 --- a/lib/Serialization/ASTWriterDecl.cpp +++ b/lib/Serialization/ASTWriterDecl.cpp @@ -159,6 +159,19 @@ namespace clang { Writer.AddStmt(FD->getBody()); } + /// Add to the record the first declaration from each module file that + /// provides a declaration of D. The intent is to provide a sufficient + /// set such that reloading this set will load all current redeclarations. + void AddFirstDeclFromEachModule(const Decl *D, bool IncludeLocal) { + llvm::MapVector<ModuleFile*, const Decl*> Firsts; + // FIXME: We can skip entries that we know are implied by others. + for (const Decl *R = D->getMostRecentDecl(); R; R = R->getPreviousDecl()) + if (IncludeLocal || R->isFromASTFile()) + Firsts[Writer.Chain->getOwningModuleFile(R)] = R; + for (const auto &F : Firsts) + Writer.AddDeclRef(F.second, Record); + } + /// Get the specialization decl from an entry in the specialization list. template <typename EntryType> typename RedeclarableTemplateDecl::SpecEntryTraits<EntryType>::DeclType * @@ -194,20 +207,46 @@ namespace clang { if (auto *LS = Common->LazySpecializations) LazySpecializations = ArrayRef<DeclID>(LS + 1, LS + 1 + LS[0]); - Record.push_back(Specializations.size() + - PartialSpecializations.size() + - LazySpecializations.size()); + // Add a slot to the record for the number of specializations. + unsigned I = Record.size(); + Record.push_back(0); + for (auto &Entry : Specializations) { auto *D = getSpecializationDecl(Entry); assert(D->isCanonicalDecl() && "non-canonical decl in set"); - Writer.AddDeclRef(D, Record); + AddFirstDeclFromEachModule(D, /*IncludeLocal*/true); } for (auto &Entry : PartialSpecializations) { auto *D = getSpecializationDecl(Entry); assert(D->isCanonicalDecl() && "non-canonical decl in set"); - Writer.AddDeclRef(D, Record); + AddFirstDeclFromEachModule(D, /*IncludeLocal*/true); } Record.append(LazySpecializations.begin(), LazySpecializations.end()); + + // Update the size entry we added earlier. + Record[I] = Record.size() - I - 1; + } + + /// Ensure that this template specialization is associated with the specified + /// template on reload. + void RegisterTemplateSpecialization(const Decl *Template, + const Decl *Specialization) { + Template = Template->getCanonicalDecl(); + + // If the canonical template is local, we'll write out this specialization + // when we emit it. + // FIXME: We can do the same thing if there is any local declaration of + // the template, to avoid emitting an update record. + if (!Template->isFromASTFile()) + return; + + // We only need to associate the first local declaration of the + // specialization. The other declarations will get pulled in by it. + if (Writer.getFirstLocalDecl(Specialization) != Specialization) + return; + + Writer.DeclUpdates[Template].push_back(ASTWriter::DeclUpdate( + UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION, Specialization)); } }; } @@ -479,6 +518,9 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) { case FunctionDecl::TK_FunctionTemplateSpecialization: { FunctionTemplateSpecializationInfo * FTSInfo = D->getTemplateSpecializationInfo(); + + RegisterTemplateSpecialization(FTSInfo->getTemplate(), D); + Writer.AddDeclRef(FTSInfo->getTemplate(), Record); Record.push_back(FTSInfo->getTemplateSpecializationKind()); @@ -1249,6 +1291,8 @@ void ASTDeclWriter::VisitClassTemplateDecl(ClassTemplateDecl *D) { void ASTDeclWriter::VisitClassTemplateSpecializationDecl( ClassTemplateSpecializationDecl *D) { + RegisterTemplateSpecialization(D->getSpecializedTemplate(), D); + VisitCXXRecordDecl(D); llvm::PointerUnion<ClassTemplateDecl *, @@ -1308,6 +1352,8 @@ void ASTDeclWriter::VisitVarTemplateDecl(VarTemplateDecl *D) { void ASTDeclWriter::VisitVarTemplateSpecializationDecl( VarTemplateSpecializationDecl *D) { + RegisterTemplateSpecialization(D->getSpecializedTemplate(), D); + VisitVarDecl(D); llvm::PointerUnion<VarTemplateDecl *, VarTemplatePartialSpecializationDecl *> @@ -1478,48 +1524,61 @@ void ASTDeclWriter::VisitDeclContext(DeclContext *DC, uint64_t LexicalOffset, Record.push_back(VisibleOffset); } -/// Determine whether D is the first declaration in its redeclaration chain that -/// is not from an AST file. -template <typename T> -static bool isFirstLocalDecl(Redeclarable<T> *D) { - assert(D && !static_cast<T*>(D)->isFromASTFile()); - do - D = D->getPreviousDecl(); - while (D && static_cast<T*>(D)->isFromASTFile()); - return !D; +/// \brief Is this a local declaration (that is, one that will be written to +/// our AST file)? This is the case for declarations that are neither imported +/// from another AST file nor predefined. +static bool isLocalDecl(ASTWriter &W, const Decl *D) { + if (D->isFromASTFile()) + return false; + return W.getDeclID(D) >= NUM_PREDEF_DECL_IDS; +} + +const Decl *ASTWriter::getFirstLocalDecl(const Decl *D) { + assert(isLocalDecl(*this, D) && "expected a local declaration"); + + const Decl *Canon = D->getCanonicalDecl(); + if (isLocalDecl(*this, Canon)) + return Canon; + + const Decl *&CacheEntry = FirstLocalDeclCache[Canon]; + if (CacheEntry) + return CacheEntry; + + for (const Decl *Redecl = D; Redecl; Redecl = Redecl->getPreviousDecl()) + if (isLocalDecl(*this, Redecl)) + D = Redecl; + return CacheEntry = D; } template <typename T> void ASTDeclWriter::VisitRedeclarable(Redeclarable<T> *D) { T *First = D->getFirstDecl(); T *MostRecent = First->getMostRecentDecl(); + T *DAsT = static_cast<T *>(D); if (MostRecent != First) { - assert(isRedeclarableDeclKind(static_cast<T *>(D)->getKind()) && + assert(isRedeclarableDeclKind(DAsT->getKind()) && "Not considered redeclarable?"); Writer.AddDeclRef(First, Record); - // In a modules build, emit a list of all imported key declarations - // (excluding First, if it was imported), so that we can be sure that all - // redeclarations visible to this module are before D in the redecl chain. - unsigned I = Record.size(); - Record.push_back(0); - if (Context.getLangOpts().Modules && Writer.Chain) { - if (isFirstLocalDecl(D)) { - Writer.Chain->forEachImportedKeyDecl(First, [&](const Decl *D) { - if (D != First) - Writer.AddDeclRef(D, Record); - }); - Record[I] = Record.size() - I - 1; - - // Write a redeclaration chain, attached to the first key decl. - Writer.Redeclarations.push_back(Writer.Chain->getKeyDeclaration(First)); - } - } else if (D == First || D->getPreviousDecl()->isFromASTFile()) { - assert(isFirstLocalDecl(D) && "imported decl after local decl"); - - // Write a redeclaration chain attached to the first decl. - Writer.Redeclarations.push_back(First); + // Write out a list of local redeclarations of this declaration if it's the + // first local declaration in the chain. + const Decl *FirstLocal = Writer.getFirstLocalDecl(DAsT); + if (DAsT == FirstLocal) { + Writer.Redeclarations.push_back(DAsT); + + // Emit a list of all imported first declarations so that we can be sure + // that all redeclarations visible to this module are before D in the + // redecl chain. + unsigned I = Record.size(); + Record.push_back(0); + if (Writer.Chain) + AddFirstDeclFromEachModule(DAsT, /*IncludeLocal*/false); + // This is the number of imported first declarations + 1. + Record[I] = Record.size() - I; + } else { + Record.push_back(0); + Writer.AddDeclRef(FirstLocal, Record); } // Make sure that we serialize both the previous and the most-recent diff --git a/test/Modules/cxx-templates.cpp b/test/Modules/cxx-templates.cpp index d0f5a145a18a6d4f154936af8c20f43c35ed839c..8e91b8247f2101a8e521fd9a83c298747710a18a 100644 --- a/test/Modules/cxx-templates.cpp +++ b/test/Modules/cxx-templates.cpp @@ -187,10 +187,10 @@ namespace Std { // CHECK-DUMP: ClassTemplateDecl {{.*}} <{{.*[/\\]}}cxx-templates-common.h:1:1, {{.*}}> col:{{.*}} in cxx_templates_common SomeTemplate // CHECK-DUMP: ClassTemplateSpecializationDecl {{.*}} prev {{.*}} SomeTemplate -// CHECK-DUMP-NEXT: TemplateArgument type 'char [2]' -// CHECK-DUMP: ClassTemplateSpecializationDecl {{.*}} SomeTemplate definition -// CHECK-DUMP-NEXT: TemplateArgument type 'char [2]' -// CHECK-DUMP: ClassTemplateSpecializationDecl {{.*}} prev {{.*}} SomeTemplate // CHECK-DUMP-NEXT: TemplateArgument type 'char [1]' // CHECK-DUMP: ClassTemplateSpecializationDecl {{.*}} SomeTemplate definition // CHECK-DUMP-NEXT: TemplateArgument type 'char [1]' +// CHECK-DUMP: ClassTemplateSpecializationDecl {{.*}} prev {{.*}} SomeTemplate +// CHECK-DUMP-NEXT: TemplateArgument type 'char [2]' +// CHECK-DUMP: ClassTemplateSpecializationDecl {{.*}} SomeTemplate definition +// CHECK-DUMP-NEXT: TemplateArgument type 'char [2]'