From 0ab6a716ddaeb00c8991f338747f4dacc2841cdf Mon Sep 17 00:00:00 2001 From: John Hui Date: Tue, 9 Dec 2025 13:11:35 -0800 Subject: [PATCH 1/4] [ClangImporter] Fix dangling documentation getClangOwningModule() no longer takes a parameter named `MI` nor does it return an optional (it just returns a poitner). --- lib/ClangImporter/ImporterImpl.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index fcd339f81f31..881efeff2563 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -970,11 +970,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation ClangModuleUnit *getClangModuleForDecl(const clang::Decl *D, bool allowForwardDeclaration = false); - /// Returns the module \p MI comes from, or \c None if \p MI does not have - /// a valid associated module. - /// - /// The returned module may be null (but not \c None) if \p MI comes from - /// an imported header. + /// Returns the module \p Node comes from, or \c nullptr if \p Node does not + /// have a valid owning module. const clang::Module *getClangOwningModule(ClangNode Node) const; /// Whether NSUInteger can be imported as Int in certain contexts. If false, From c8ecb1f87cb9ab6d5dda5a624bee451acb74368d Mon Sep 17 00:00:00 2001 From: John Hui Date: Tue, 9 Dec 2025 14:11:51 -0800 Subject: [PATCH 2/4] [cxx-interop] [NFC] Refactor ClangDerivedConformances This refactoring restructures the code in a couple of ways: - move all the "conformToXXXIfNeeded()" functions into a single function and remove them from the ClangDerivedConformances.h interface - unify the check for isInStdNamespace() for the C++ stdlib types - unify the name comparisons into a single llvm::StringSwitch - unify redundant nullptr assert()s, and upgrade them to ASSERT() - move the known stdlib type name-checking logic out of the "conformToCxxSTDLIBTYPEIfNeeded()" functions, and rename them to drop the "IfNeeded" suffix. This patch also introduces a local CxxStdType enum class that enumerates (and thus documents) all of the known (and thus supported) types from the C++ stdlib types. The ultimate goal is to organize the code such that by the time we call "conformToCxxSTDLIBTYPE()" we are already sure that the type is (or at least claims to be, based on its name and DeclContext) the C++ stdlib type we think it is, rather than interleaving such checks with the conformance synthesis logic. The main observable difference should be the logging: now, we will no longer have PrettyStackTraceDecls logging for C++ stdlib types like "conforming to CxxVector" unless we are already certain we are looking at a relevant type, e.g., std::vector. Otherwise, the functional behavior of this code should be the same as before. --- .../ClangDerivedConformances.cpp | 253 ++++++++++-------- lib/ClangImporter/ClangDerivedConformances.h | 56 +--- lib/ClangImporter/ImportDecl.cpp | 27 +- 3 files changed, 148 insertions(+), 188 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index a18e3d9f2991..4608437ec89e 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -24,6 +24,40 @@ using namespace swift; using namespace swift::importer; +/// Known C++ stdlib types, for which we can assume conformance to the standard +/// (e.g., std::map has template parameters for key and value types, and has +/// members like key_type, size_type, and operator[]). +enum class CxxStdType { + uncategorized, + optional, + set, + unordered_set, + multiset, + pair, + map, + unordered_map, + multimap, + vector, + span, +}; + +static CxxStdType identifyCxxStdTypeByName(StringRef name) { +#define CaseStd(name) Case(#name, CxxStdType::name) + return llvm::StringSwitch(name) + .CaseStd(optional) + .CaseStd(set) + .CaseStd(unordered_set) + .CaseStd(multiset) + .CaseStd(pair) + .CaseStd(map) + .CaseStd(unordered_map) + .CaseStd(multimap) + .CaseStd(vector) + .CaseStd(span) + .Default(CxxStdType::uncategorized); +#undef CxxStdCase +} + /// Alternative to `NominalTypeDecl::lookupDirect`. /// This function does not attempt to load extensions of the nominal decl. static TinyPtrVector @@ -95,16 +129,6 @@ static FuncDecl *getInsertFunc(NominalTypeDecl *decl, return insert; } -static bool isStdDecl(const clang::CXXRecordDecl *clangDecl, - llvm::ArrayRef names) { - if (!clangDecl->isInStdNamespace()) - return false; - if (!clangDecl->getIdentifier()) - return false; - StringRef name = clangDecl->getName(); - return llvm::is_contained(names, name); -} - static clang::TypeDecl * lookupNestedClangTypeDecl(const clang::CXXRecordDecl *clangDecl, StringRef name) { @@ -422,13 +446,11 @@ swift::importer::getImportedMemberOperator(const DeclBaseName &name, return nullptr; } -void swift::conformToCxxIteratorIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { - PrettyStackTraceDecl trace("conforming to UnsafeCxxInputIterator", decl); - - assert(decl); - assert(clangDecl); +static void +conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { + PrettyStackTraceDecl trace("trying to conform to UnsafeCxxInputIterator", decl); ASTContext &ctx = decl->getASTContext(); clang::ASTContext &clangCtx = clangDecl->getASTContext(); @@ -641,13 +663,11 @@ void swift::conformToCxxIteratorIfNeeded( } } -void swift::conformToCxxConvertibleToBoolIfNeeded( - ClangImporter::Implementation &impl, swift::NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { - PrettyStackTraceDecl trace("conforming to CxxConvertibleToBool", decl); - - assert(decl); - assert(clangDecl); +static void +conformToCxxConvertibleToBoolIfNeeded(ClangImporter::Implementation &impl, + swift::NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { + PrettyStackTraceDecl trace("trying to conform to CxxConvertibleToBool", decl); ASTContext &ctx = decl->getASTContext(); auto conversionId = ctx.getIdentifier("__convertToBool"); @@ -674,20 +694,14 @@ void swift::conformToCxxConvertibleToBoolIfNeeded( {KnownProtocolKind::CxxConvertibleToBool}); } -void swift::conformToCxxOptionalIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxOptional(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxOptional", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); clang::ASTContext &clangCtx = impl.getClangASTContext(); clang::Sema &clangSema = impl.getClangSema(); - if (!isStdDecl(clangDecl, {"optional"})) - return; - ProtocolDecl *cxxOptionalProto = ctx.getProtocol(KnownProtocolKind::CxxOptional); // If the Cxx module is missing, or does not include one of the necessary @@ -766,13 +780,11 @@ void swift::conformToCxxOptionalIfNeeded( decl->addMember(importedConstructor); } -void swift::conformToCxxSequenceIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { - PrettyStackTraceDecl trace("conforming to CxxSequence", decl); - - assert(decl); - assert(clangDecl); +static void +conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { + PrettyStackTraceDecl trace("trying to conform to CxxSequence", decl); ASTContext &ctx = decl->getASTContext(); ProtocolDecl *cxxIteratorProto = @@ -940,41 +952,31 @@ void swift::conformToCxxSequenceIfNeeded( } } -static bool isStdSetType(const clang::CXXRecordDecl *clangDecl) { - return isStdDecl(clangDecl, {"set", "unordered_set", "multiset"}); -} - -static bool isStdMapType(const clang::CXXRecordDecl *clangDecl) { - return isStdDecl(clangDecl, {"map", "unordered_map", "multimap"}); -} - bool swift::isUnsafeStdMethod(const clang::CXXMethodDecl *methodDecl) { - auto parentDecl = + auto *parentDecl = dyn_cast(methodDecl->getDeclContext()); - if (!parentDecl) - return false; - if (!isStdSetType(parentDecl) && !isStdMapType(parentDecl)) + if (!parentDecl || !parentDecl->isInStdNamespace() || + !parentDecl->getIdentifier()) return false; - if (methodDecl->getDeclName().isIdentifier() && - methodDecl->getName() == "insert") - return true; + + if (methodDecl->getIdentifier() && methodDecl->getName() == "insert") { + // Types for which the insert method is considered unsafe, + // due to potential iterator invalidation. + return llvm::StringSwitch(parentDecl->getName()) + .Cases({"set", "unordered_set", "multiset"}, true) + .Cases({"map", "unordered_map", "multimap"}, true) + .Default(false); + } return false; } -void swift::conformToCxxSetIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxSet(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl, + bool isUniqueSet) { PrettyStackTraceDecl trace("conforming to CxxSet", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdSetType(clangDecl)) - return; - auto valueType = lookupDirectSingleWithoutExtensions( decl, ctx.getIdentifier("value_type")); auto sizeType = lookupDirectSingleWithoutExtensions( @@ -1021,28 +1023,20 @@ void swift::conformToCxxSetIfNeeded(ClangImporter::Implementation &impl, impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawMutableIterator"), rawMutableIteratorTy); - // If this isn't a std::multiset, try to also synthesize the conformance to - // CxxUniqueSet. - if (!isStdDecl(clangDecl, {"set", "unordered_set"})) + // Synthesize conformance to CxxUniqueSet if the caller asked for it + // (if decl is std::set or std::unordered_set, but not std::multiset) + if (!isUniqueSet) return; impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxUniqueSet}); } -void swift::conformToCxxPairIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxPair(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxPair", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdDecl(clangDecl, {"pair"})) - return; - auto firstType = lookupDirectSingleWithoutExtensions( decl, ctx.getIdentifier("first_type")); auto secondType = lookupDirectSingleWithoutExtensions( @@ -1057,20 +1051,12 @@ void swift::conformToCxxPairIfNeeded(ClangImporter::Implementation &impl, impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxPair}); } -void swift::conformToCxxDictionaryIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxDictionary(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxDictionary", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdMapType(clangDecl)) - return; - auto keyType = lookupDirectSingleWithoutExtensions( decl, ctx.getIdentifier("key_type")); auto valueType = lookupDirectSingleWithoutExtensions( @@ -1135,20 +1121,12 @@ void swift::conformToCxxDictionaryIfNeeded( impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxDictionary}); } -void swift::conformToCxxVectorIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxVector(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxVector", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdDecl(clangDecl, {"vector"})) - return; - auto valueType = lookupDirectSingleWithoutExtensions( decl, ctx.getIdentifier("value_type")); auto iterType = lookupDirectSingleWithoutExtensions( @@ -1180,22 +1158,14 @@ void swift::conformToCxxVectorIfNeeded(ClangImporter::Implementation &impl, impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxVector}); } -void swift::conformToCxxSpanIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxSpan(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxSpan", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); clang::ASTContext &clangCtx = impl.getClangASTContext(); clang::Sema &clangSema = impl.getClangSema(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdDecl(clangDecl, {"span"})) - return; - auto elementType = lookupDirectSingleWithoutExtensions( decl, ctx.getIdentifier("element_type")); auto sizeType = lookupDirectSingleWithoutExtensions( @@ -1276,3 +1246,60 @@ void swift::conformToCxxSpanIfNeeded(ClangImporter::Implementation &impl, impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxMutableSpan}); } } + +void swift::deriveAutomaticCxxConformances( + ClangImporter::Implementation &Impl, NominalTypeDecl *result, + const clang::CXXRecordDecl *clangDecl) { + + ASSERT(result && clangDecl && "this should not be called with nullptrs"); + + // Skip synthesizing conformances if the associated Clang node is from + // a module that doesn't require cplusplus, to prevent us from accidentally + // pulling in Cxx/CxxStdlib modules when a client is importing a C library. + // + // We will still attempt to synthesize to account for scenarios where the + // module specification is missing altogether. + if (auto *clangModule = Impl.getClangOwningModule(result->getClangNode()); + clangModule && !requiresCPlusPlus(clangModule)) + return; + + // Automatic conformances: these may be applied to any type that fits the + // requirements. + conformToCxxIteratorIfNeeded(Impl, result, clangDecl); + conformToCxxSequenceIfNeeded(Impl, result, clangDecl); + conformToCxxConvertibleToBoolIfNeeded(Impl, result, clangDecl); + + // CxxStdlib conformances: these should only apply to known C++ stdlib types, + // which we determine by name and membership in the std namespace. + if (!clangDecl->getIdentifier() || !clangDecl->isInStdNamespace()) + return; + + auto ty = identifyCxxStdTypeByName(clangDecl->getName()); + switch (ty) { + case CxxStdType::uncategorized: + return; + case CxxStdType::optional: + conformToCxxOptional(Impl, result, clangDecl); + return; + case CxxStdType::set: + case CxxStdType::unordered_set: + case CxxStdType::multiset: + conformToCxxSet(Impl, result, clangDecl, + /*isUniqueSet=*/ty != CxxStdType::multiset); + return; + case CxxStdType::pair: + conformToCxxPair(Impl, result, clangDecl); + return; + case CxxStdType::map: + case CxxStdType::unordered_map: + case CxxStdType::multimap: + conformToCxxDictionary(Impl, result, clangDecl); + return; + case CxxStdType::vector: + conformToCxxVector(Impl, result, clangDecl); + return; + case CxxStdType::span: + conformToCxxSpan(Impl, result, clangDecl); + return; + } +} diff --git a/lib/ClangImporter/ClangDerivedConformances.h b/lib/ClangImporter/ClangDerivedConformances.h index c3c267ed70ad..0b7d17bfcf1d 100644 --- a/lib/ClangImporter/ClangDerivedConformances.h +++ b/lib/ClangImporter/ClangDerivedConformances.h @@ -22,61 +22,9 @@ bool isIterator(const clang::CXXRecordDecl *clangDecl); bool isUnsafeStdMethod(const clang::CXXMethodDecl *methodDecl); -/// If the decl is a C++ input iterator, synthesize a conformance to the -/// UnsafeCxxInputIterator protocol, which is defined in the Cxx module. -void conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl defines `operator bool()`, synthesize a conformance to the -/// CxxConvertibleToBool protocol, which is defined in the Cxx module. -void conformToCxxConvertibleToBoolIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::optional`, synthesize a -/// conformance to CxxOptional protocol, which is defined in the Cxx module. -void conformToCxxOptionalIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is a C++ sequence, synthesize a conformance to the CxxSequence -/// protocol, which is defined in the Cxx module. -void conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::set`, `std::unordered_set` or -/// `std::multiset`, synthesize a conformance to CxxSet, which is defined in the -/// Cxx module. -void conformToCxxSetIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::pair`, synthesize a conformance -/// to CxxPair, which is defined in the Cxx module. -void conformToCxxPairIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::map` or `std::unordered_map`, -/// synthesize a conformance to CxxDictionary, which is defined in the Cxx module. -void conformToCxxDictionaryIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, +void deriveAutomaticCxxConformances(ClangImporter::Implementation &Impl, + NominalTypeDecl *result, const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::vector`, synthesize a -/// conformance to CxxVector, which is defined in the Cxx module. -void conformToCxxVectorIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::span`, synthesize a -/// conformance to CxxSpan, which is defined in the Cxx module. -void conformToCxxSpanIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - } // namespace swift #endif // SWIFT_CLANG_DERIVED_CONFORMANCES_H diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 0bbcc4782082..2245408fb696 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -3293,29 +3293,14 @@ namespace { validatePrivateFileIDAttributes(decl); - // If this module is declared as a C++ module, try to synthesize - // conformances to Swift protocols from the Cxx module. - auto clangModule = Impl.getClangOwningModule(result->getClangNode()); - if (!clangModule || requiresCPlusPlus(clangModule)) { - if (auto nominalDecl = dyn_cast(result)) { - conformToCxxIteratorIfNeeded(Impl, nominalDecl, decl); - conformToCxxSequenceIfNeeded(Impl, nominalDecl, decl); - conformToCxxConvertibleToBoolIfNeeded(Impl, nominalDecl, decl); - conformToCxxSetIfNeeded(Impl, nominalDecl, decl); - conformToCxxDictionaryIfNeeded(Impl, nominalDecl, decl); - conformToCxxPairIfNeeded(Impl, nominalDecl, decl); - conformToCxxOptionalIfNeeded(Impl, nominalDecl, decl); - conformToCxxVectorIfNeeded(Impl, nominalDecl, decl); - conformToCxxSpanIfNeeded(Impl, nominalDecl, decl); - - if (Impl.needsClosureConstructor(decl)) { - if (auto closureCtor = - synthesizer.makeClosureConstructor(nominalDecl)) - nominalDecl->addMember(closureCtor); - } + if (auto *nominalResult = dyn_cast(result)) { + deriveAutomaticCxxConformances(Impl, nominalResult, decl); + + if (Impl.needsClosureConstructor(decl)) { + if (auto *ctor = synthesizer.makeClosureConstructor(nominalResult)) + nominalResult->addMember(ctor); } } - return result; } From 6ed9efbaa053500fec87e4306fb686d86d4c1025 Mon Sep 17 00:00:00 2001 From: John Hui Date: Thu, 11 Dec 2025 17:15:24 -0800 Subject: [PATCH 3/4] [cxx-interop] Use LookupQualifiedName in derived conformances This is the first patch of a series of patches intended to shift lookup toward using Clang lookup, to avoid Swift lookup's dependency on eager Clang member importation. The lookupCxxTypeMember() helper function replaces the previous helper function, lookupNestedClangTypeDecl(). Functionally, the main difference is that lookupCxxTypeMember() uses clang::Sema::LookupQualifiedName(), which takes cares of issues like inheritance, ambiguity, etc. Meanwhile, lookupNestedClangTypeDecl() only used clang::DeclContext::lookup(), which is limited to the lexical context, and does not work with inheritance. The isIterator() function is renamed to hasIteratorConcept() to better reflect what it is checking, and still uses clang::DeclContext::lookup() to preserve the original behavior and to avoid requiring its callers to supply a clang::Sema instance. --- .../ClangDerivedConformances.cpp | 89 +++++++++++-------- lib/ClangImporter/ClangDerivedConformances.h | 9 +- lib/ClangImporter/ClangImporter.cpp | 6 +- 3 files changed, 64 insertions(+), 40 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 4608437ec89e..8219885b151b 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -18,7 +18,9 @@ #include "swift/AST/ProtocolConformance.h" #include "swift/Basic/Assertions.h" #include "swift/ClangImporter/ClangImporterRequests.h" +#include "clang/AST/CXXInheritance.h" #include "clang/Sema/DelayedDiagnostic.h" +#include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" using namespace swift; @@ -58,6 +60,29 @@ static CxxStdType identifyCxxStdTypeByName(StringRef name) { #undef CxxStdCase } +static const clang::TypeDecl * +lookupCxxTypeMember(clang::Sema &Sema, const clang::CXXRecordDecl *Rec, + StringRef name) { + auto R = clang::LookupResult(Sema, &Sema.PP.getIdentifierTable().get(name), + clang::SourceLocation(), + clang::Sema::LookupMemberName); + R.suppressDiagnostics(); + + auto *Ctx = static_cast(Rec); + Sema.LookupQualifiedName(R, const_cast(Ctx)); + + if (R.isSingleResult()) { + if (auto *paths = R.getBasePaths(); + paths && R.getBasePaths()->front().Access != clang::AS_public) + return nullptr; + + for (auto *nd : R) + if (auto *td = dyn_cast(nd)) + return td; + } + return nullptr; +} + /// Alternative to `NominalTypeDecl::lookupDirect`. /// This function does not attempt to load extensions of the nominal decl. static TinyPtrVector @@ -129,32 +154,6 @@ static FuncDecl *getInsertFunc(NominalTypeDecl *decl, return insert; } -static clang::TypeDecl * -lookupNestedClangTypeDecl(const clang::CXXRecordDecl *clangDecl, - StringRef name) { - clang::IdentifierInfo *nestedDeclName = - &clangDecl->getASTContext().Idents.get(name); - auto nestedDecls = clangDecl->lookup(nestedDeclName); - // If this is a templated typedef, Clang might have instantiated several - // equivalent typedef decls. If they aren't equivalent, Clang has already - // complained about this. Let's assume that they are equivalent. (see - // filterNonConflictingPreviousTypedefDecls in clang/Sema/SemaDecl.cpp) - if (nestedDecls.empty()) - return nullptr; - auto nestedDecl = nestedDecls.front(); - return dyn_cast_or_null(nestedDecl); -} - -static clang::TypeDecl * -getIteratorCategoryDecl(const clang::CXXRecordDecl *clangDecl) { - return lookupNestedClangTypeDecl(clangDecl, "iterator_category"); -} - -static clang::TypeDecl * -getIteratorConceptDecl(const clang::CXXRecordDecl *clangDecl) { - return lookupNestedClangTypeDecl(clangDecl, "iterator_concept"); -} - static ValueDecl *lookupOperator(NominalTypeDecl *decl, Identifier id, function_ref isValid) { // First look for operator declared as a member. @@ -420,8 +419,18 @@ static bool synthesizeCXXOperator(ClangImporter::Implementation &impl, return true; } -bool swift::isIterator(const clang::CXXRecordDecl *clangDecl) { - return getIteratorCategoryDecl(clangDecl); +bool swift::hasIteratorCategory(const clang::CXXRecordDecl *clangDecl) { + clang::IdentifierInfo *name = + &clangDecl->getASTContext().Idents.get("iterator_category"); + auto members = clangDecl->lookup(name); + if (members.empty()) + return false; + // NOTE: If this is a templated typedef, Clang might have instantiated + // several equivalent typedef decls, so members.isSingleResult() may + // return false here. But if they aren't equivalent, Clang should have + // already complained about this. Let's assume that they are equivalent. + // (see filterNonConflictingPreviousTypedefDecls in clang/Sema/SemaDecl.cpp) + return isa(members.front()); } ValueDecl * @@ -453,6 +462,7 @@ conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl, PrettyStackTraceDecl trace("trying to conform to UnsafeCxxInputIterator", decl); ASTContext &ctx = decl->getASTContext(); clang::ASTContext &clangCtx = clangDecl->getASTContext(); + clang::Sema &clangSema = impl.getClangSema(); if (!ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator)) return; @@ -460,13 +470,19 @@ conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl, // We consider a type to be an input iterator if it defines an // `iterator_category` that inherits from `std::input_iterator_tag`, e.g. // `using iterator_category = std::input_iterator_tag`. - auto iteratorCategory = getIteratorCategoryDecl(clangDecl); - if (!iteratorCategory) + // + // FIXME: The second hasIteratorCategory() is more conservative than it should + // be because it doesn't consider things like inheritance, but checking this + // here maintains existing behavior and ensures consistency across + // ClangImporter, where clang::Sema isn't always readily available. + const auto *iteratorCategory = + lookupCxxTypeMember(clangSema, clangDecl, "iterator_category"); + if (!iteratorCategory || !hasIteratorCategory(clangDecl)) return; auto unwrapUnderlyingTypeDecl = - [](clang::TypeDecl *typeDecl) -> clang::CXXRecordDecl * { - clang::CXXRecordDecl *underlyingDecl = nullptr; + [](const clang::TypeDecl *typeDecl) -> const clang::CXXRecordDecl * { + const clang::CXXRecordDecl *underlyingDecl = nullptr; if (auto typedefDecl = dyn_cast(typeDecl)) { auto type = typedefDecl->getUnderlyingType(); underlyingDecl = type->getAsCXXRecordDecl(); @@ -525,7 +541,8 @@ conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl, // `iterator_concept`. It is not possible to detect a contiguous iterator // based on its `iterator_category`. The type might not have an // `iterator_concept` defined. - if (auto iteratorConcept = getIteratorConceptDecl(clangDecl)) { + if (const auto *iteratorConcept = + lookupCxxTypeMember(clangSema, clangDecl, "iterator_concept")) { if (auto underlyingConceptDecl = unwrapUnderlyingTypeDecl(iteratorConcept)) { isContiguousIterator = isContiguousIteratorDecl(underlyingConceptDecl); @@ -726,7 +743,7 @@ static void conformToCxxOptional(ClangImporter::Implementation &impl, // it isn't directly usable from Swift. Let's explicitly instantiate a // constructor with the wrapped value type, and then import it into Swift. - auto valueTypeDecl = lookupNestedClangTypeDecl(clangDecl, "value_type"); + auto valueTypeDecl = lookupCxxTypeMember(clangSema, clangDecl, "value_type"); if (!valueTypeDecl) // `std::optional` without a value_type?! return; @@ -1174,8 +1191,8 @@ static void conformToCxxSpan(ClangImporter::Implementation &impl, if (!elementType || !sizeType) return; - auto pointerTypeDecl = lookupNestedClangTypeDecl(clangDecl, "pointer"); - auto countTypeDecl = lookupNestedClangTypeDecl(clangDecl, "size_type"); + auto pointerTypeDecl = lookupCxxTypeMember(clangSema, clangDecl, "pointer"); + auto countTypeDecl = lookupCxxTypeMember(clangSema, clangDecl, "size_type"); if (!pointerTypeDecl || !countTypeDecl) return; diff --git a/lib/ClangImporter/ClangDerivedConformances.h b/lib/ClangImporter/ClangDerivedConformances.h index 0b7d17bfcf1d..a3fedb64a15b 100644 --- a/lib/ClangImporter/ClangDerivedConformances.h +++ b/lib/ClangImporter/ClangDerivedConformances.h @@ -18,7 +18,14 @@ namespace swift { -bool isIterator(const clang::CXXRecordDecl *clangDecl); +/// Whether a C++ record decl contains a type member named "iterator_category", +/// a heuristic we use to determine whether that record type is an iterator. +/// +/// This function returns true if there is exactly one public type member named +/// "iterator_category", but does not look for inherited members. Note that, as +/// a result of these limitations, it may return false even if that record type +/// is usable as an iterator. +bool hasIteratorCategory(const clang::CXXRecordDecl *clangDecl); bool isUnsafeStdMethod(const clang::CXXMethodDecl *methodDecl); diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index b6125283435f..5999abeb0910 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -8376,7 +8376,7 @@ static bool hasPointerInSubobjects(const clang::CXXRecordDecl *decl) { hasUnsafeAPIAttr(cxxRecord)) return false; - if (hasIteratorAPIAttr(cxxRecord) || isIterator(cxxRecord)) + if (hasIteratorAPIAttr(cxxRecord) || hasIteratorCategory(cxxRecord)) return true; if (hasPointerInSubobjects(cxxRecord)) @@ -8497,7 +8497,7 @@ CxxRecordSemantics::evaluate(Evaluator &evaluator, if (isSwiftClassType(cxxDecl)) return CxxRecordSemanticsKind::SwiftClassType; - if (hasIteratorAPIAttr(cxxDecl) || isIterator(cxxDecl)) { + if (hasIteratorAPIAttr(cxxDecl) || hasIteratorCategory(cxxDecl)) { return CxxRecordSemanticsKind::Iterator; } @@ -8760,7 +8760,7 @@ bool IsSafeUseOfCxxDecl::evaluate(Evaluator &evaluator, return true; if (hasIteratorAPIAttr(cxxRecordReturnType) || - isIterator(cxxRecordReturnType)) + hasIteratorCategory(cxxRecordReturnType)) return false; // Mark this as safe to help our diganostics down the road. From c4d15357ed9149faeccc5ccaa48883b3b84633a1 Mon Sep 17 00:00:00 2001 From: John Hui Date: Fri, 12 Dec 2025 17:19:28 -0800 Subject: [PATCH 4/4] [cxx-interop] Use clang lookups for std::set conformance --- .../ClangDerivedConformances.cpp | 111 ++++++++++-------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 8219885b151b..aac3d7e0e8c9 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -18,7 +18,10 @@ #include "swift/AST/ProtocolConformance.h" #include "swift/Basic/Assertions.h" #include "swift/ClangImporter/ClangImporterRequests.h" +#include "clang/AST/ASTContext.h" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" #include "clang/Sema/DelayedDiagnostic.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" @@ -62,23 +65,22 @@ static CxxStdType identifyCxxStdTypeByName(StringRef name) { static const clang::TypeDecl * lookupCxxTypeMember(clang::Sema &Sema, const clang::CXXRecordDecl *Rec, - StringRef name) { + StringRef name, bool mustBeComplete = false) { auto R = clang::LookupResult(Sema, &Sema.PP.getIdentifierTable().get(name), clang::SourceLocation(), clang::Sema::LookupMemberName); R.suppressDiagnostics(); - auto *Ctx = static_cast(Rec); Sema.LookupQualifiedName(R, const_cast(Ctx)); - if (R.isSingleResult()) { + if (auto *td = R.getAsSingle()) { if (auto *paths = R.getBasePaths(); - paths && R.getBasePaths()->front().Access != clang::AS_public) + paths && paths->front().Access != clang::AS_public) return nullptr; - - for (auto *nd : R) - if (auto *td = dyn_cast(nd)) - return td; + if (mustBeComplete && + !Sema.isCompleteType({}, td->getASTContext().getTypeDeclType(td))) + return nullptr; + return td; } return nullptr; } @@ -993,59 +995,66 @@ static void conformToCxxSet(ClangImporter::Implementation &impl, bool isUniqueSet) { PrettyStackTraceDecl trace("conforming to CxxSet", decl); ASTContext &ctx = decl->getASTContext(); + clang::Sema &clangSema = impl.getClangSema(); - auto valueType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("value_type")); - auto sizeType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("size_type")); - if (!valueType || !sizeType) + // Look up the type members we need from Clang + // + // N.B. we don't actually need const_iterator for multiset, but it should be + // there. If it's not there for any reason, we should probably bail out. + + auto *size_type = lookupCxxTypeMember(clangSema, clangDecl, "size_type", + /*mustBeComplete=*/true); + auto *value_type = lookupCxxTypeMember(clangSema, clangDecl, "value_type", + /*mustBeComplete=*/true); + auto *iterator = lookupCxxTypeMember(clangSema, clangDecl, "iterator", + /*mustBeComplete=*/true); + auto *const_iterator = + lookupCxxTypeMember(clangSema, clangDecl, "const_iterator", + /*mustBeComplete=*/true); + if (!size_type || !value_type || !iterator || !const_iterator) return; - auto insert = getInsertFunc(decl, valueType); - if (!insert) + // We've looked up everything we need from Clang for the conformance. + // Now, use ClangImporter to convert those to types in Swift. + + auto *Size = dyn_cast_or_null( + impl.importDecl(size_type, impl.CurrentVersion)); + auto *Value = dyn_cast_or_null( + impl.importDecl(value_type, impl.CurrentVersion)); + if (!Size || !Value) return; + // These are not needed for multiset, i.e., when isUniqueSet = false + TypeAliasDecl *RawMutableIterator, *RawIterator; + if (isUniqueSet) { + RawMutableIterator = dyn_cast_or_null( + impl.importDecl(iterator, impl.CurrentVersion)); + RawIterator = dyn_cast_or_null( + impl.importDecl(const_iterator, impl.CurrentVersion)); + if (!RawMutableIterator || !RawIterator) + return; + } + + // We have our Swift types, synthesize type aliases and conformances + impl.addSynthesizedTypealias(decl, ctx.Id_Element, - valueType->getUnderlyingType()); + Value->getUnderlyingType()); impl.addSynthesizedTypealias(decl, ctx.Id_ArrayLiteralElement, - valueType->getUnderlyingType()); + Value->getUnderlyingType()); impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), - sizeType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("InsertionResult"), - insert->getResultInterfaceType()); + Size->getUnderlyingType()); + // The type checker can infer this from the return type of insert() + // impl.addSynthesizedTypealias(decl, ctx.getIdentifier("InsertionResult"), + // InsertionResult); impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSet}); - ProtocolDecl *cxxInputIteratorProto = - ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator); - if (!cxxInputIteratorProto) - return; - - auto rawIteratorType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("const_iterator")); - auto rawMutableIteratorType = - lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("iterator")); - if (!rawIteratorType || !rawMutableIteratorType) - return; - - auto rawIteratorTy = rawIteratorType->getUnderlyingType(); - auto rawMutableIteratorTy = rawMutableIteratorType->getUnderlyingType(); - - if (!checkConformance(rawIteratorTy, cxxInputIteratorProto) || - !checkConformance(rawMutableIteratorTy, cxxInputIteratorProto)) - return; - - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawIterator"), - rawIteratorTy); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawMutableIterator"), - rawMutableIteratorTy); - - // Synthesize conformance to CxxUniqueSet if the caller asked for it - // (if decl is std::set or std::unordered_set, but not std::multiset) - if (!isUniqueSet) - return; - - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxUniqueSet}); + if (isUniqueSet) { + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawIterator"), + RawIterator->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawMutableIterator"), + RawMutableIterator->getUnderlyingType()); + impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxUniqueSet}); + } } static void conformToCxxPair(ClangImporter::Implementation &impl,