diff --git a/devel/qt6-tools/files/patch-QTBUG-144620 b/devel/qt6-tools/files/patch-QTBUG-144620 new file mode 100644 index 000000000000..698fc7a90aa5 --- /dev/null +++ b/devel/qt6-tools/files/patch-QTBUG-144620 @@ -0,0 +1,542 @@ +From f75f4090ed8e5c9dc898a313a5dd9b6878c2f034 Mon Sep 17 00:00:00 2001 +From: Paul Wicking +Date: Tue, 17 Mar 2026 14:36:28 +0100 +Subject: QDoc: Add LLVM 22 implementation to QualTypeNames fork + +LLVM 22 introduced sweeping changes to the Clang type system that break +the existing QualTypeNames fork. NestedNameSpecifier changed from a +pointer type to a value type, RecordType merged into TagType, and +ElaboratedType merged into TypedefType and TagType through a new +TypeWithKeyword base. These changes touch nearly every function in the +fork, making incremental adaptation impractical. + +The fork now carries both the existing pre-22 implementation and a +complete LLVM 22 implementation behind version guards. The LLVM 22 block +is adapted from upstream clang/lib/AST/QualTypeNames.cpp (release/22.x) +with three marked divergences: the random-specialization block in scope +resolution is disabled to avoid unstable output, TypedefType qualifier +resolution prefers the existing qualifier to preserve member type alias +context, and the unhandled-type assertion is relaxed to a silent drop +for types QDoc encounters that upstream doesn't anticipate. + +Both implementations are completely self-contained within their version +guard to avoid subtle behavioral differences from shared code paths. + +Task-number: QTBUG-144620 +Change-Id: I36c4115b1c20f165d87a244b9aa852cb66352c99 +Reviewed-by: Topi Reinio +--- + src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h | 490 +++++++++++++++++++++++ + 1 file changed, 490 insertions(+) + +diff --git a/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h b/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h +index 977859911..f7440449e 100644 +--- ./src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h ++++ ./src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h +@@ -36,6 +36,494 @@ namespace clang { + + namespace TypeName { + ++#if CLANG_VERSION_MAJOR >= 22 ++ ++// ========================================================================= ++// LLVM 22+ implementation ++// ++// Adapted from upstream clang/lib/AST/QualTypeNames.cpp (release/22.x). ++// LLVM 22 changed NestedNameSpecifier from pointer to value type, ++// merged RecordType into TagType, and merged ElaboratedType into ++// TypedefType/TagType via TypeWithKeyword. ++// ++// QDoc divergences from upstream are marked with "QDoc divergence" comments. ++// ========================================================================= ++ ++inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx, ++ bool WithGlobalNsPrefix); ++ ++static inline NestedNameSpecifier ++createNestedNameSpecifier(const ASTContext &Ctx, const NamespaceDecl *Namesp, ++ bool WithGlobalNsPrefix); ++ ++static inline NestedNameSpecifier ++createNestedNameSpecifier(const ASTContext &Ctx, const TypeDecl *TD, ++ bool FullyQualify, bool WithGlobalNsPrefix); ++ ++static inline NestedNameSpecifier ++createNestedNameSpecifierForScopeOf(const ASTContext &Ctx, const Decl *decl, ++ bool FullyQualified, ++ bool WithGlobalNsPrefix); ++ ++static inline NestedNameSpecifier ++getFullyQualifiedNestedNameSpecifier(const ASTContext &Ctx, ++ NestedNameSpecifier NNS, ++ bool WithGlobalNsPrefix); ++ ++static inline bool getFullyQualifiedTemplateName(const ASTContext &Ctx, ++ TemplateName &TName, ++ bool WithGlobalNsPrefix) { ++ bool Changed = false; ++ NestedNameSpecifier NNS = std::nullopt; ++ ++ TemplateDecl *ArgTDecl = TName.getAsTemplateDecl(); ++ if (!ArgTDecl) // ArgTDecl can be null in dependent contexts. ++ return false; ++ ++ QualifiedTemplateName *QTName = TName.getAsQualifiedTemplateName(); ++ ++ if (QTName && ++ !QTName->hasTemplateKeyword() && ++ (NNS = QTName->getQualifier())) { ++ NestedNameSpecifier QNNS = ++ getFullyQualifiedNestedNameSpecifier(Ctx, NNS, WithGlobalNsPrefix); ++ if (QNNS != NNS) { ++ Changed = true; ++ NNS = QNNS; ++ } else { ++ NNS = std::nullopt; ++ } ++ } else { ++ NNS = createNestedNameSpecifierForScopeOf( ++ Ctx, ArgTDecl, true, WithGlobalNsPrefix); ++ } ++ if (NNS) { ++ TemplateName UnderlyingTN(ArgTDecl); ++ if (UsingShadowDecl *USD = TName.getAsUsingShadowDecl()) ++ UnderlyingTN = TemplateName(USD); ++ TName = ++ Ctx.getQualifiedTemplateName(NNS, ++ /*TemplateKeyword=*/false, UnderlyingTN); ++ Changed = true; ++ } ++ return Changed; ++} ++ ++static inline bool getFullyQualifiedTemplateArgument(const ASTContext &Ctx, ++ TemplateArgument &Arg, ++ bool WithGlobalNsPrefix) { ++ bool Changed = false; ++ ++ // Note: we do not handle TemplateArgument::Expression, to replace it ++ // we need the information for the template instance decl. ++ ++ if (Arg.getKind() == TemplateArgument::Template) { ++ TemplateName TName = Arg.getAsTemplate(); ++ Changed = getFullyQualifiedTemplateName(Ctx, TName, WithGlobalNsPrefix); ++ if (Changed) { ++ Arg = TemplateArgument(TName); ++ } ++ } else if (Arg.getKind() == TemplateArgument::Type) { ++ QualType SubTy = Arg.getAsType(); ++ // Check if the type needs more desugaring and recurse. ++ QualType QTFQ = getFullyQualifiedType(SubTy, Ctx, WithGlobalNsPrefix); ++ if (QTFQ != SubTy) { ++ Arg = TemplateArgument(QTFQ); ++ Changed = true; ++ } ++ } ++ return Changed; ++} ++ ++static inline const Type *getFullyQualifiedTemplateType( ++ const ASTContext &Ctx, ++ const TagType *TSTRecord, ++ ElaboratedTypeKeyword Keyword, ++ NestedNameSpecifier Qualifier, ++ bool WithGlobalNsPrefix) { ++ // We are asked to fully qualify and we have a Record Type, ++ // which can point to a template instantiation with no sugar in any of ++ // its template argument, however we still need to fully qualify them. ++ ++ const auto *TD = TSTRecord->getDecl(); ++ const auto *TSTDecl = dyn_cast(TD); ++ if (!TSTDecl) ++ return Ctx.getTagType(Keyword, Qualifier, TD, /*OwnsTag=*/false) ++ .getTypePtr(); ++ ++ const TemplateArgumentList &TemplateArgs = TSTDecl->getTemplateArgs(); ++ ++ bool MightHaveChanged = false; ++ SmallVector FQArgs; ++ for (unsigned int I = 0, E = TemplateArgs.size(); I != E; ++I) { ++ // cheap to copy and potentially modified by ++ // getFullyQualifedTemplateArgument ++ TemplateArgument Arg(TemplateArgs[I]); ++ MightHaveChanged |= ++ getFullyQualifiedTemplateArgument(Ctx, Arg, WithGlobalNsPrefix); ++ FQArgs.push_back(Arg); ++ } ++ ++ if (!MightHaveChanged) ++ return Ctx.getTagType(Keyword, Qualifier, TD, /*OwnsTag=*/false) ++ .getTypePtr(); ++ // If a fully qualified arg is different from the unqualified arg, ++ // allocate new type in the AST. ++ TemplateName TN = Ctx.getQualifiedTemplateName( ++ Qualifier, /*TemplateKeyword=*/false, ++ TemplateName(TSTDecl->getSpecializedTemplate())); ++ QualType QT = Ctx.getTemplateSpecializationType( ++ Keyword, TN, FQArgs, ++ /*CanonicalArgs=*/{}, TSTRecord->getCanonicalTypeInternal()); ++ // getTemplateSpecializationType returns a fully qualified ++ // version of the specialization itself, so no need to qualify ++ // it. ++ return QT.getTypePtr(); ++} ++ ++static inline const Type * ++getFullyQualifiedTemplateType(const ASTContext &Ctx, ++ const TemplateSpecializationType *TST, ++ bool WithGlobalNsPrefix) { ++ TemplateName TName = TST->getTemplateName(); ++ bool MightHaveChanged = ++ getFullyQualifiedTemplateName(Ctx, TName, WithGlobalNsPrefix); ++ SmallVector FQArgs; ++ // Cheap to copy and potentially modified by ++ // getFullyQualifedTemplateArgument. ++ for (TemplateArgument Arg : TST->template_arguments()) { ++ MightHaveChanged |= ++ getFullyQualifiedTemplateArgument(Ctx, Arg, WithGlobalNsPrefix); ++ FQArgs.push_back(Arg); ++ } ++ ++ if (!MightHaveChanged) ++ return TST; ++ ++ QualType NewQT = ++ Ctx.getTemplateSpecializationType(TST->getKeyword(), TName, FQArgs, ++ /*CanonicalArgs=*/{}, TST->desugar()); ++ // getTemplateSpecializationType returns a fully qualified ++ // version of the specialization itself, so no need to qualify ++ // it. ++ return NewQT.getTypePtr(); ++} ++ ++static inline NestedNameSpecifier createOuterNNS(const ASTContext &Ctx, ++ const Decl *D, ++ bool FullyQualify, ++ bool WithGlobalNsPrefix) { ++ const DeclContext *DC = D->getDeclContext(); ++ if (const auto *NS = dyn_cast(DC)) { ++ while (NS && NS->isInline()) { ++ // Ignore inline namespace; ++ NS = dyn_cast(NS->getDeclContext()); ++ } ++ if (NS && NS->getDeclName()) { ++ return createNestedNameSpecifier(Ctx, NS, WithGlobalNsPrefix); ++ } ++ return std::nullopt; // no starting '::', no anonymous ++ } ++ if (const auto *TD = dyn_cast(DC)) ++ return createNestedNameSpecifier(Ctx, TD, FullyQualify, WithGlobalNsPrefix); ++ if (const auto *TDD = dyn_cast(DC)) ++ return createNestedNameSpecifier(Ctx, TDD, FullyQualify, ++ WithGlobalNsPrefix); ++ if (WithGlobalNsPrefix && DC->isTranslationUnit()) ++ return NestedNameSpecifier::getGlobal(); ++ return std::nullopt; // no starting '::' if |WithGlobalNsPrefix| is false ++} ++ ++/// Return a fully qualified version of this name specifier. ++static inline NestedNameSpecifier getFullyQualifiedNestedNameSpecifier( ++ const ASTContext &Ctx, NestedNameSpecifier Scope, ++ bool WithGlobalNsPrefix) { ++ switch (Scope.getKind()) { ++ case NestedNameSpecifier::Kind::Null: ++ llvm_unreachable("can't fully qualify the empty nested name specifier"); ++ case NestedNameSpecifier::Kind::Global: ++ case NestedNameSpecifier::Kind::MicrosoftSuper: ++ // Already fully qualified ++ return Scope; ++ case NestedNameSpecifier::Kind::Namespace: ++ return TypeName::createNestedNameSpecifier( ++ Ctx, Scope.getAsNamespaceAndPrefix().Namespace->getNamespace(), ++ WithGlobalNsPrefix); ++ case NestedNameSpecifier::Kind::Type: { ++ const Type *Type = Scope.getAsType(); ++ // Find decl context. ++ const TypeDecl *TD; ++ if (const TagType *TagDeclType = Type->getAs()) ++ TD = TagDeclType->getDecl(); ++ else if (const auto *D = dyn_cast(Type)) ++ TD = D->getDecl(); ++ else ++ return Scope; ++ return TypeName::createNestedNameSpecifier(Ctx, TD, /*FullyQualify=*/true, ++ WithGlobalNsPrefix); ++ } ++ } ++ llvm_unreachable("bad NNS kind"); ++} ++ ++/// Create a nested name specifier for the declaring context of ++/// the type. ++static inline NestedNameSpecifier ++createNestedNameSpecifierForScopeOf(const ASTContext &Ctx, const Decl *Decl, ++ bool FullyQualified, ++ bool WithGlobalNsPrefix) { ++ assert(Decl); ++ ++ // Some declaration cannot be qualified. ++ if (Decl->isTemplateParameter()) ++ return std::nullopt; ++ const DeclContext *DC = Decl->getDeclContext()->getRedeclContext(); ++ const auto *Outer = dyn_cast(DC); ++ const auto *OuterNS = dyn_cast(DC); ++ if (OuterNS && OuterNS->isAnonymousNamespace()) ++ OuterNS = dyn_cast(OuterNS->getParent()); ++ if (Outer) { ++#if 0 ++ // QDoc divergence: upstream picks an arbitrary template specialization ++ // as the declaring context when a type is declared inside a class ++ // template but is not type-dependent. This produces unstable output ++ // (depends on specialization order) and is incorrect for QDoc's use ++ // case where we want the unspecialized template name. ++ // See QTBUG-144620. ++ if (const auto *CxxDecl = dyn_cast(DC)) { ++ if (ClassTemplateDecl *ClassTempl = ++ CxxDecl->getDescribedClassTemplate()) { ++ if (!ClassTempl->specializations().empty()) { ++ Decl = *(ClassTempl->spec_begin()); ++ Outer = dyn_cast(Decl); ++ OuterNS = dyn_cast(Decl); ++ } ++ } ++ } ++#endif ++ ++ if (OuterNS) { ++ return createNestedNameSpecifier(Ctx, OuterNS, WithGlobalNsPrefix); ++ } else if (const auto *TD = dyn_cast(Outer)) { ++ return createNestedNameSpecifier( ++ Ctx, TD, FullyQualified, WithGlobalNsPrefix); ++ } else if (isa(Outer)) { ++ // Context is the TU. Nothing needs to be done. ++ return std::nullopt; ++ } else { ++ // Decl's context was neither the TU, a namespace, nor a ++ // TagDecl, which means it is a type local to a scope, and not ++ // accessible at the end of the TU. ++ return std::nullopt; ++ } ++ } else if (WithGlobalNsPrefix && DC->isTranslationUnit()) { ++ return NestedNameSpecifier::getGlobal(); ++ } ++ return std::nullopt; ++} ++ ++/// Create a nested name specifier for the declaring context of ++/// the type. ++static inline NestedNameSpecifier ++createNestedNameSpecifierForScopeOf(const ASTContext &Ctx, const Type *TypePtr, ++ bool FullyQualified, ++ bool WithGlobalNsPrefix) { ++ if (!TypePtr) ++ return std::nullopt; ++ ++ Decl *Decl = nullptr; ++ // There are probably other cases ... ++ if (const auto *TDT = dyn_cast(TypePtr)) { ++ Decl = TDT->getDecl(); ++ } else if (const auto *TagDeclType = dyn_cast(TypePtr)) { ++ Decl = TagDeclType->getDecl(); ++ } else if (const auto *TST = dyn_cast(TypePtr)) { ++ Decl = TST->getTemplateName().getAsTemplateDecl(); ++ } else { ++ Decl = TypePtr->getAsCXXRecordDecl(); ++ } ++ ++ if (!Decl) ++ return std::nullopt; ++ ++ return createNestedNameSpecifierForScopeOf( ++ Ctx, Decl, FullyQualified, WithGlobalNsPrefix); ++} ++ ++inline NestedNameSpecifier ++createNestedNameSpecifier(const ASTContext &Ctx, const NamespaceDecl *Namespace, ++ bool WithGlobalNsPrefix) { ++ while (Namespace && Namespace->isInline()) { ++ // Ignore inline namespace; ++ Namespace = dyn_cast(Namespace->getDeclContext()); ++ } ++ if (!Namespace) ++ return std::nullopt; ++ ++ bool FullyQualify = true; // doesn't matter, DeclContexts are namespaces ++ return NestedNameSpecifier( ++ Ctx, Namespace, ++ createOuterNNS(Ctx, Namespace, FullyQualify, WithGlobalNsPrefix)); ++} ++ ++inline NestedNameSpecifier ++createNestedNameSpecifier(const ASTContext &Ctx, const TypeDecl *TD, ++ bool FullyQualify, bool WithGlobalNsPrefix) { ++ const Type *TypePtr = Ctx.getTypeDeclType(TD).getTypePtr(); ++ if (auto *RD = dyn_cast(TypePtr)) { ++ // We are asked to fully qualify and we have a Record Type (which ++ // may point to a template specialization) or Template ++ // Specialization Type. We need to fully qualify their arguments. ++ TypePtr = getFullyQualifiedTemplateType( ++ Ctx, RD, ElaboratedTypeKeyword::None, ++ createOuterNNS(Ctx, TD, FullyQualify, WithGlobalNsPrefix), ++ WithGlobalNsPrefix); ++ } else if (auto *TST = dyn_cast(TypePtr)) { ++ TypePtr = getFullyQualifiedTemplateType(Ctx, TST, WithGlobalNsPrefix); ++ } ++ return NestedNameSpecifier(TypePtr); ++} ++ ++/// Return the fully qualified type, including fully-qualified ++/// versions of any template parameters. ++inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx, ++ bool WithGlobalNsPrefix = false) { ++ // In case of myType* we need to strip the pointer first, fully ++ // qualify and attach the pointer once again. ++ if (isa(QT.getTypePtr())) { ++ // Get the qualifiers. ++ Qualifiers Quals = QT.getQualifiers(); ++ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix); ++ QT = Ctx.getPointerType(QT); ++ // Add back the qualifiers. ++ QT = Ctx.getQualifiedType(QT, Quals); ++ return QT; ++ } ++ ++ if (auto *MPT = dyn_cast(QT.getTypePtr())) { ++ // Get the qualifiers. ++ Qualifiers Quals = QT.getQualifiers(); ++ // Fully qualify the pointee and class types. ++ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix); ++ NestedNameSpecifier Qualifier = getFullyQualifiedNestedNameSpecifier( ++ Ctx, MPT->getQualifier(), WithGlobalNsPrefix); ++ QT = Ctx.getMemberPointerType(QT, Qualifier, ++ MPT->getMostRecentCXXRecordDecl()); ++ // Add back the qualifiers. ++ QT = Ctx.getQualifiedType(QT, Quals); ++ return QT; ++ } ++ ++ // In case of myType& we need to strip the reference first, fully ++ // qualify and attach the reference once again. ++ if (isa(QT.getTypePtr())) { ++ // Get the qualifiers. ++ bool IsLValueRefTy = isa(QT.getTypePtr()); ++ Qualifiers Quals = QT.getQualifiers(); ++ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix); ++ // Add the r- or l-value reference type back to the fully ++ // qualified one. ++ if (IsLValueRefTy) ++ QT = Ctx.getLValueReferenceType(QT); ++ else ++ QT = Ctx.getRValueReferenceType(QT); ++ // Add back the qualifiers. ++ QT = Ctx.getQualifiedType(QT, Quals); ++ return QT; ++ } ++ ++ // Handle types with attributes such as `unique_ptr _Nonnull`. ++ if (auto *AT = dyn_cast(QT.getTypePtr())) { ++ QualType NewModified = ++ getFullyQualifiedType(AT->getModifiedType(), Ctx, WithGlobalNsPrefix); ++ QualType NewEquivalent = ++ getFullyQualifiedType(AT->getEquivalentType(), Ctx, WithGlobalNsPrefix); ++ Qualifiers Qualifiers = QT.getLocalQualifiers(); ++ return Ctx.getQualifiedType( ++ Ctx.getAttributedType(AT->getAttrKind(), NewModified, NewEquivalent), ++ Qualifiers); ++ } ++ ++ // Remove the part of the type related to the type being a template ++ // parameter (we won't report it as part of the 'type name' and it ++ // is actually make the code below to be more complex (to handle ++ // those) ++ while (isa(QT.getTypePtr())) { ++ // Get the qualifiers. ++ Qualifiers Quals = QT.getQualifiers(); ++ ++ QT = cast(QT.getTypePtr())->desugar(); ++ ++ // Add back the qualifiers. ++ QT = Ctx.getQualifiedType(QT, Quals); ++ } ++ ++ if (const auto *TST = ++ dyn_cast(QT.getTypePtr())) { ++ ++ const Type *T = getFullyQualifiedTemplateType(Ctx, TST, WithGlobalNsPrefix); ++ if (T == TST) ++ return QT; ++ return Ctx.getQualifiedType(T, QT.getQualifiers()); ++ } ++ ++ // Local qualifiers are attached to the QualType outside of the ++ // elaborated type. Retrieve them before descending into the ++ // elaborated type. ++ Qualifiers PrefixQualifiers = QT.getLocalQualifiers(); ++ QT = QualType(QT.getTypePtr(), 0); ++ ++ // We don't consider the alias introduced by `using a::X` as a new type. ++ // The qualified name is still a::X. ++ if (const auto *UT = QT->getAs()) { ++ QT = Ctx.getQualifiedType(UT->desugar(), PrefixQualifiers); ++ return getFullyQualifiedType(QT, Ctx, WithGlobalNsPrefix); ++ } ++ ++ // Create a nested name specifier if needed. ++ NestedNameSpecifier Prefix = createNestedNameSpecifierForScopeOf( ++ Ctx, QT.getTypePtr(), true /*FullyQualified*/, WithGlobalNsPrefix); ++ ++ // In case of template specializations iterate over the arguments and ++ // fully qualify them as well. ++ if (const auto *TT = dyn_cast(QT.getTypePtr())) { ++ // We are asked to fully qualify and we have a Record Type (which ++ // may point to a template specialization) or Template ++ // Specialization Type. We need to fully qualify their arguments. ++ ++ const Type *TypePtr = getFullyQualifiedTemplateType( ++ Ctx, TT, TT->getKeyword(), Prefix, WithGlobalNsPrefix); ++ QT = QualType(TypePtr, 0); ++ } else if (const auto *TT = dyn_cast(QT.getTypePtr())) { ++ // QDoc divergence: prefer the existing qualifier from the TypedefType ++ // when available, falling back to the computed Prefix. This preserves ++ // member type alias qualifiers (e.g., QList::parameter_type) ++ // that would otherwise be lost when the Prefix is recomputed from the ++ // declaring context. See QTBUG-144620. ++ NestedNameSpecifier TypedefPrefix = TT->getQualifier(); ++ QT = Ctx.getTypedefType( ++ TT->getKeyword(), TypedefPrefix ? TypedefPrefix : Prefix, ++ TT->getDecl(), ++ getFullyQualifiedType(TT->desugar(), Ctx, WithGlobalNsPrefix)); ++ } else { ++ // QDoc divergence: upstream asserts here (!Prefix && "Unhandled type node"). ++ // QDoc encounters types (such as AutoType and BuiltinType) that may have ++ // a non-null Prefix but are not TagType or TypedefType. Silently dropping ++ // the prefix is safe — it only affects qualification of the printed name. ++ } ++ QT = Ctx.getQualifiedType(QT, PrefixQualifiers); ++ return QT; ++} ++ ++#else // CLANG_VERSION_MAJOR < 22 ++ ++// ========================================================================= ++// Pre-LLVM 22 implementation ++// ++// This block is the existing fork, unchanged. It supports LLVM 17–21 ++// with version-specific guards for API differences between those releases. ++// ========================================================================= ++ + inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx, + bool WithGlobalNsPrefix); + +@@ -503,6 +991,8 @@ inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx, + return QT; + } + ++#endif // CLANG_VERSION_MAJOR >= 22 ++ + inline std::string getFullyQualifiedName(QualType QT, + const ASTContext &Ctx, + const PrintingPolicy &Policy, +-- +cgit v1.2.3 +