From 6ed9efbaa053500fec87e4306fb686d86d4c1025 Mon Sep 17 00:00:00 2001 From: John Hui Date: Thu, 11 Dec 2025 17:15:24 -0800 Subject: [PATCH 01/11] [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 4608437ec89e3..8219885b151b5 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 0b7d17bfcf1d4..a3fedb64a15b7 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 b6125283435ff..5999abeb09101 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 c5927a56bfe0fda9d0da038d911bdd720939b031 Mon Sep 17 00:00:00 2001 From: John Hui Date: Sat, 13 Dec 2025 17:09:45 -0800 Subject: [PATCH 02/11] [cxx-interop] [NFC] Break apart calls to conformToCxxSet() This is slightly easier to read. --- lib/ClangImporter/ClangDerivedConformances.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 8219885b151b5..74ce9568cf428 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -1300,9 +1300,10 @@ void swift::deriveAutomaticCxxConformances( return; case CxxStdType::set: case CxxStdType::unordered_set: + conformToCxxSet(Impl, result, clangDecl, /*isUniqueSet=*/true); + return; case CxxStdType::multiset: - conformToCxxSet(Impl, result, clangDecl, - /*isUniqueSet=*/ty != CxxStdType::multiset); + conformToCxxSet(Impl, result, clangDecl, /*isUniqueSet=*/false); return; case CxxStdType::pair: conformToCxxPair(Impl, result, clangDecl); From 55bd1ce343ffab08b9d349d35f144e576a541478 Mon Sep 17 00:00:00 2001 From: John Hui Date: Fri, 12 Dec 2025 17:19:28 -0800 Subject: [PATCH 03/11] [cxx-interop] Use clang lookups for std::set conformance Instead of looking up the *already imported* Swift members of std::set, this patch shifts the conformance synthesis logic to look up the equivalent members from Clang, and *then* imports them to Swift types to satisfy the CxxSet conformance. Doing so removes the logic's dependency on eagerly importing such members. --- .../ClangDerivedConformances.cpp | 170 +++++++++++++----- 1 file changed, 121 insertions(+), 49 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 74ce9568cf428..3d6d786e01ec7 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -13,12 +13,17 @@ #include "ClangDerivedConformances.h" #include "ImporterImpl.h" #include "swift/AST/ConformanceLookup.h" +#include "swift/AST/LayoutConstraint.h" #include "swift/AST/ParameterList.h" #include "swift/AST/PrettyStackTrace.h" #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/AST/Type.h" #include "clang/Sema/DelayedDiagnostic.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" @@ -62,23 +67,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 +997,127 @@ static void conformToCxxSet(ClangImporter::Implementation &impl, bool isUniqueSet) { PrettyStackTraceDecl trace("conforming to CxxSet", decl); ASTContext &ctx = decl->getASTContext(); + auto &clangCtx = impl.getClangASTContext(); + auto &clangSema = impl.getClangSema(); - auto valueType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("value_type")); - auto sizeType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("size_type")); - if (!valueType || !sizeType) - return; - - auto insert = getInsertFunc(decl, valueType); - if (!insert) + // 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; - impl.addSynthesizedTypealias(decl, ctx.Id_Element, - valueType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.Id_ArrayLiteralElement, - valueType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), - sizeType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("InsertionResult"), - insert->getResultInterfaceType()); - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSet}); + const clang::CXXMethodDecl *insert = nullptr; + { + // CxxSet requires the InsertionResult associated type, which is the return + // type of std::set (and co.)'s insert function. But there is no equivalent + // typedef in C++ we can use directly, so we need get it by converting the + // return type of the insert function. + // + // A wrinkle here is that std::set actually has multiple insert overloads. + // There are two overloads that could work for us: + // + // insert_return_type insert(const value_type &value); + // insert_return_type insert(value_type &&value); + // + // where insert_return_type is std::pair for std::set and + // std::unordered_set and just iterator for std::multiset. + // + // Look for the version with the single const-lref value_type parameter, + // since that's the one that maps to Swift's semantics most closely. + // + // NOTE: this code is a bit lengthy, and could be abstracted into a helper + // function, but at this time of writing, the only two times we need to look + // for a member is for std::set and std::map's insert methods. We keep this + // lookup routine inlined for now until the interface for a reasonably + // encapsulated helper function emerges. + + auto R = clang::LookupResult( + clangSema, &clangSema.PP.getIdentifierTable().get("insert"), + clang::SourceLocation(), clang::Sema::LookupMemberName); + R.suppressDiagnostics(); + auto *Ctx = static_cast(clangDecl); + clangSema.LookupQualifiedName(R, const_cast(Ctx)); + switch (R.getResultKind()) { + case clang::LookupResultKind::Found: + case clang::LookupResultKind::FoundOverloaded: + break; + default: + return; + } - ProtocolDecl *cxxInputIteratorProto = - ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator); - if (!cxxInputIteratorProto) + for (auto *nd : R) { + if (auto *insertOverload = dyn_cast(nd)) { + if (insertOverload->param_size() != 1) + continue; + auto *paramTy = (*insertOverload->param_begin()) + ->getType() + ->getAs(); + if (!paramTy) + continue; + if (paramTy->getPointeeType()->getCanonicalTypeUnqualified() != + clangCtx.getTypeDeclType(value_type)->getCanonicalTypeUnqualified()) + continue; + if (!paramTy->getPointeeType().isConstQualified()) + continue; + insert = insertOverload; // Found the insert() we're looking for + break; + } + } + } + if (!insert) return; - auto rawIteratorType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("const_iterator")); - auto rawMutableIteratorType = - lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("iterator")); - if (!rawIteratorType || !rawMutableIteratorType) + // We've looked up everything we need from Clang for the conformance. + // Now, use ClangImporter to convert import those types to Swift. + // + // NOTE: we're actually importing the typedefs and function members here, + // but not *adding* them as members to the Swift StructDecl---that is done + // elsewhere (and could be lazy too, though not at this time of writing). + // We are just using these imported Swift members for their type fields, + // because importDecl() needs fewer arguments than importTypeIgnoreIUO(). + + auto *Size = dyn_cast_or_null( + impl.importDecl(size_type, impl.CurrentVersion)); + auto *Value = dyn_cast_or_null( + impl.importDecl(value_type, impl.CurrentVersion)); + auto *RawMutableIterator = dyn_cast_or_null( + impl.importDecl(iterator, impl.CurrentVersion)); + auto *RawIterator = dyn_cast_or_null( + impl.importDecl(const_iterator, impl.CurrentVersion)); + auto *Insert = + dyn_cast_or_null(impl.importDecl(insert, impl.CurrentVersion)); + if (!Size || !Value || !RawMutableIterator || !RawIterator || !Insert) return; - auto rawIteratorTy = rawIteratorType->getUnderlyingType(); - auto rawMutableIteratorTy = rawMutableIteratorType->getUnderlyingType(); - - if (!checkConformance(rawIteratorTy, cxxInputIteratorProto) || - !checkConformance(rawMutableIteratorTy, cxxInputIteratorProto)) - return; + // We have our Swift types, synthesize type aliases and conformances + impl.addSynthesizedTypealias(decl, ctx.Id_Element, + Value->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.Id_ArrayLiteralElement, + Value->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), + Size->getUnderlyingType()); impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawIterator"), - rawIteratorTy); + RawIterator->getUnderlyingType()); 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; + RawMutableIterator->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("InsertionResult"), + Insert->getResultInterfaceType()); - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxUniqueSet}); + impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSet}); + if (isUniqueSet) + impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxUniqueSet}); } static void conformToCxxPair(ClangImporter::Implementation &impl, From e7f493ed18f7c5fc200414f711b198e5e8cbaaf5 Mon Sep 17 00:00:00 2001 From: John Hui Date: Sat, 13 Dec 2025 17:10:35 -0800 Subject: [PATCH 04/11] [cxx-interop] Use clang lookups for std::pair conformance Instead of looking up the *imported* first_type and second_type Swift type aliases, this patch shifts the conformance to look up these typedefs from Clang, and *then* imports them to Swift types to satisfy the CxxPair conformance. Doing so removes the conformance's dependency on eagerly importing such typedefs. --- .../ClangDerivedConformances.cpp | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 3d6d786e01ec7..0c9113639ee8d 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -1125,18 +1125,26 @@ static void conformToCxxPair(ClangImporter::Implementation &impl, const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxPair", decl); ASTContext &ctx = decl->getASTContext(); + clang::Sema &clangSema = impl.getClangSema(); + + auto *first_type = lookupCxxTypeMember(clangSema, clangDecl, "first_type", + /*mustBeComplete=*/true); + auto *second_type = lookupCxxTypeMember(clangSema, clangDecl, "second_type", + /*mustBeComplete=*/true); + if (!first_type || !second_type) + return; - auto firstType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("first_type")); - auto secondType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("second_type")); - if (!firstType || !secondType) + auto *First = dyn_cast_or_null( + impl.importDecl(first_type, impl.CurrentVersion)); + auto *Second = dyn_cast_or_null( + impl.importDecl(second_type, impl.CurrentVersion)); + if (!First || !Second) return; impl.addSynthesizedTypealias(decl, ctx.getIdentifier("First"), - firstType->getUnderlyingType()); + First->getUnderlyingType()); impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Second"), - secondType->getUnderlyingType()); + Second->getUnderlyingType()); impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxPair}); } From b570fcea33d323464e4ad62a8c0e9de8685b6854 Mon Sep 17 00:00:00 2001 From: John Hui Date: Sat, 13 Dec 2025 20:08:22 -0800 Subject: [PATCH 05/11] [cxx-interop] Use clang lookups for std::vector conformance Instead of looking up the *imported* Swift type aliases, this patch shifts the conformance to look up these typedefs from Clang, and *then* imports them to Swift types to satisfy CxxVector conformance. Doing so removes the conformance's dependency on eagerly importing such typedefs. This patch also drops a conformance check that RawIterator conforms to UnsafeCxxRandomAccessIterator. It shouldn't be necessary, because we are looking at std::vector, which we assume comes from a conforming stdlib. Even if it were necessary, it's not clear that this is the right place and strategy for doing conformance checking. Besides we are fairly inconsistent about checking other associated types. Let's be optimistic about conformance for now (-: --- .../ClangDerivedConformances.cpp | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 0c9113639ee8d..b37422b7e551f 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -1223,35 +1223,33 @@ static void conformToCxxVector(ClangImporter::Implementation &impl, const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxVector", decl); ASTContext &ctx = decl->getASTContext(); + clang::Sema &clangSema = impl.getClangSema(); - auto valueType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("value_type")); - auto iterType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("const_iterator")); - auto sizeType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("size_type")); - if (!valueType || !iterType || !sizeType) - return; - - ProtocolDecl *cxxRandomAccessIteratorProto = - ctx.getProtocol(KnownProtocolKind::UnsafeCxxRandomAccessIterator); - if (!cxxRandomAccessIteratorProto) - return; - - auto rawIteratorTy = iterType->getUnderlyingType(); + auto *value_type = lookupCxxTypeMember(clangSema, clangDecl, "value_type", + /*mustBeComplete=*/true); + auto *size_type = lookupCxxTypeMember(clangSema, clangDecl, "size_type", + /*mustBeComplete=*/true); + auto *const_iterator = + lookupCxxTypeMember(clangSema, clangDecl, "const_iterator", + /*mustBeComplete=*/true); - // Check if RawIterator conforms to UnsafeCxxRandomAccessIterator. - if (!checkConformance(rawIteratorTy, cxxRandomAccessIteratorProto)) + auto *Element = dyn_cast_or_null( + impl.importDecl(value_type, impl.CurrentVersion)); + auto *Size = dyn_cast_or_null( + impl.importDecl(size_type, impl.CurrentVersion)); + auto *RawIterator = dyn_cast_or_null( + impl.importDecl(const_iterator, impl.CurrentVersion)); + if (!Element || !Size || !RawIterator) return; impl.addSynthesizedTypealias(decl, ctx.Id_Element, - valueType->getUnderlyingType()); + Element->getUnderlyingType()); impl.addSynthesizedTypealias(decl, ctx.Id_ArrayLiteralElement, - valueType->getUnderlyingType()); + Element->getUnderlyingType()); impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), - sizeType->getUnderlyingType()); + Size->getUnderlyingType()); impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawIterator"), - rawIteratorTy); + RawIterator->getUnderlyingType()); impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxVector}); } From b6363362fb03292a938683b37b13a7a96913117f Mon Sep 17 00:00:00 2001 From: John Hui Date: Sat, 13 Dec 2025 20:41:00 -0800 Subject: [PATCH 06/11] [cxx-interop] Use clang lookups for std::map conformance Instead of looking up the *imported* Swift type aliases, this patch shifts the conformance to look up these typedefs from Clang, and *then* imports them to Swift types to satisfy CxxVector conformance. Doing so removes the conformance's dependency on eagerly importing such typedefs. There is one remaining dependency on eager imports, which is the unavailability patching we do for the subscript operator. This patch also drops a conformance check that the iterators conform to the our Cxx iterator protocols. It shouldn't be necessary, because we are looking at std::map, which we assume comes from a conforming stdlib. --- .../ClangDerivedConformances.cpp | 132 ++++++++++++------ 1 file changed, 88 insertions(+), 44 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index b37422b7e551f..2de826aae3b30 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -1153,69 +1153,113 @@ static void conformToCxxDictionary(ClangImporter::Implementation &impl, const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxDictionary", decl); ASTContext &ctx = decl->getASTContext(); + clang::ASTContext &clangCtx = impl.getClangASTContext(); + clang::Sema &clangSema = impl.getClangSema(); - auto keyType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("key_type")); - auto valueType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("mapped_type")); - auto iterType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("const_iterator")); - auto mutableIterType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("iterator")); - auto sizeType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("size_type")); - auto keyValuePairType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("value_type")); - if (!keyType || !valueType || !iterType || !mutableIterType || !sizeType || - !keyValuePairType) - return; +#define lookup_type(member) \ + auto *member = lookupCxxTypeMember(clangSema, clangDecl, #member, \ + /*mustBeComplete=*/true); \ + if (!member) \ + return + + lookup_type(key_type); + lookup_type(mapped_type); + lookup_type(value_type); + lookup_type(size_type); + lookup_type(iterator); + lookup_type(const_iterator); +#undef lookup_type + + const clang::CXXMethodDecl *insert = nullptr; + { + // CxxDictionary requires the InsertionResult associated type, which is the + // return type of std::map (and co.)'s insert function. But there is no + // equivalent typedef in C++ we can use directly, so we need get it by + // converting the return type of this overload of the insert function: + // + // insert_return_type insert(const value_type &value); + // + // See also: extended monologuing in conformToCxxSet(). + auto R = clang::LookupResult( + clangSema, &clangSema.PP.getIdentifierTable().get("insert"), + clang::SourceLocation(), clang::Sema::LookupMemberName); + R.suppressDiagnostics(); + auto *Ctx = static_cast(clangDecl); + clangSema.LookupQualifiedName(R, const_cast(Ctx)); + switch (R.getResultKind()) { + case clang::LookupResultKind::Found: + case clang::LookupResultKind::FoundOverloaded: + break; + default: + return; + } - auto insert = getInsertFunc(decl, keyValuePairType); + for (auto *nd : R) { + if (auto *insertOverload = dyn_cast(nd)) { + if (insertOverload->param_size() != 1) + continue; + auto *paramTy = (*insertOverload->param_begin()) + ->getType() + ->getAs(); + if (!paramTy) + continue; + if (paramTy->getPointeeType()->getCanonicalTypeUnqualified() != + clangCtx.getTypeDeclType(value_type)->getCanonicalTypeUnqualified()) + continue; + if (!paramTy->getPointeeType().isConstQualified()) + continue; + insert = insertOverload; // Found the insert() we're looking for + break; + } + } + } if (!insert) return; - ProtocolDecl *cxxInputIteratorProto = - ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator); - ProtocolDecl *cxxMutableInputIteratorProto = - ctx.getProtocol(KnownProtocolKind::UnsafeCxxMutableInputIterator); - if (!cxxInputIteratorProto || !cxxMutableInputIteratorProto) - return; +#define importTypeAlias(to, from) \ + auto *to = dyn_cast_or_null( \ + impl.importDecl(from, impl.CurrentVersion)); \ + if (!to) \ + return - auto rawIteratorTy = iterType->getUnderlyingType(); - auto rawMutableIteratorTy = mutableIterType->getUnderlyingType(); + importTypeAlias(Size, size_type); + importTypeAlias(Key, key_type); + importTypeAlias(Value, mapped_type); + importTypeAlias(Element, value_type); + importTypeAlias(RawIterator, const_iterator); + importTypeAlias(RawMutableIterator, iterator); +#undef importTypeAlias - // Check if RawIterator conforms to UnsafeCxxInputIterator. - if (!checkConformance(rawIteratorTy, cxxInputIteratorProto)) + auto *Insert = + dyn_cast_or_null(impl.importDecl(insert, impl.CurrentVersion)); + if (!Insert) return; - // Check if RawMutableIterator conforms to UnsafeCxxMutableInputIterator. - if (!checkConformance(rawMutableIteratorTy, cxxMutableInputIteratorProto)) - return; + impl.addSynthesizedTypealias(decl, ctx.Id_Key, Key->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.Id_Value, Value->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.Id_Element, + Element->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawIterator"), + RawIterator->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawMutableIterator"), + RawMutableIterator->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), + Size->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("InsertionResult"), + Insert->getResultInterfaceType()); + impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxDictionary}); // Make the original subscript that returns a non-optional value unavailable. // CxxDictionary adds another subscript that returns an optional value, // similarly to Swift.Dictionary. + // + // NOTE: this relies on the SubscriptDecl member being imported eagerly. for (auto member : decl->getCurrentMembersWithoutLoading()) { if (auto subscript = dyn_cast(member)) { impl.markUnavailable(subscript, "use subscript with optional return value"); } } - - impl.addSynthesizedTypealias(decl, ctx.Id_Key, keyType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.Id_Value, - valueType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.Id_Element, - keyValuePairType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawIterator"), - rawIteratorTy); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("RawMutableIterator"), - rawMutableIteratorTy); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), - sizeType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("InsertionResult"), - insert->getResultInterfaceType()); - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxDictionary}); } static void conformToCxxVector(ClangImporter::Implementation &impl, From 4564aa68e609a26ca52e9c320ba68d1ff19b28b2 Mon Sep 17 00:00:00 2001 From: John Hui Date: Mon, 15 Dec 2025 02:46:10 -0800 Subject: [PATCH 07/11] [cxx-interop] [NFC] Remove unused getInsertFunc() helper method This is no longer needed now that we are looking up the insert function via clang::Sema:::LookupQualifiedName --- .../ClangDerivedConformances.cpp | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 2de826aae3b30..a43713018bddd 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -135,29 +135,6 @@ static Decl *lookupDirectSingleWithoutExtensions(NominalTypeDecl *decl, return dyn_cast(results.front()); } -static FuncDecl *getInsertFunc(NominalTypeDecl *decl, - TypeAliasDecl *valueType) { - ASTContext &ctx = decl->getASTContext(); - - auto insertId = ctx.getIdentifier("__insertUnsafe"); - auto inserts = lookupDirectWithoutExtensions(decl, insertId); - FuncDecl *insert = nullptr; - for (auto candidate : inserts) { - if (auto candidateMethod = dyn_cast(candidate)) { - auto params = candidateMethod->getParameters(); - if (params->size() != 1) - continue; - auto param = params->front(); - if (param->getTypeInContext()->getCanonicalType() != - valueType->getUnderlyingType()->getCanonicalType()) - continue; - insert = candidateMethod; - break; - } - } - return insert; -} - static ValueDecl *lookupOperator(NominalTypeDecl *decl, Identifier id, function_ref isValid) { // First look for operator declared as a member. From e11829cc532157480af069a4f34c2c571e9d4767 Mon Sep 17 00:00:00 2001 From: John Hui Date: Mon, 15 Dec 2025 02:47:30 -0800 Subject: [PATCH 08/11] [cxx-interop] Use clang lookups for std::span conformance Instead of looking up the *imported* Swift type aliases, this patch shifts the conformance to look up these typedefs from Clang, and *then* imports them to Swift types to satisfy CxxSpan conformance. Doing so removes the conformance's dependency on eagerly importing such typedefs. --- .../ClangDerivedConformances.cpp | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index a43713018bddd..f138c3984286b 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -1282,22 +1282,36 @@ static void conformToCxxSpan(ClangImporter::Implementation &impl, clang::ASTContext &clangCtx = impl.getClangASTContext(); clang::Sema &clangSema = impl.getClangSema(); - auto elementType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("element_type")); - auto sizeType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("size_type")); - - if (!elementType || !sizeType) + auto *element_type = lookupCxxTypeMember(clangSema, clangDecl, "element_type", + /*mustBeComplete=*/true); + auto *size_type = lookupCxxTypeMember(clangSema, clangDecl, "size_type", + /*mustBeComplete=*/true); + auto *pointer = lookupCxxTypeMember(clangSema, clangDecl, "pointer", + /*mustBeComplete=*/true); + if (!element_type || !size_type || !pointer) return; - auto pointerTypeDecl = lookupCxxTypeMember(clangSema, clangDecl, "pointer"); - auto countTypeDecl = lookupCxxTypeMember(clangSema, clangDecl, "size_type"); + auto pointerType = clangCtx.getTypeDeclType(pointer); + auto sizeType = clangCtx.getTypeDeclType(size_type); - if (!pointerTypeDecl || !countTypeDecl) + auto *Element = dyn_cast_or_null( + impl.importDecl(element_type, impl.CurrentVersion)); + auto *Size = dyn_cast_or_null( + impl.importDecl(size_type, impl.CurrentVersion)); + if (!Element || !Size) return; + impl.addSynthesizedTypealias(decl, ctx.Id_Element, + Element->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), + Size->getUnderlyingType()); + + if (pointerType->getPointeeType().isConstQualified()) + impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSpan}); + else + impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxMutableSpan}); + // create fake variable for pointer (constructor arg 1) - clang::QualType pointerType = clangCtx.getTypeDeclType(pointerTypeDecl); auto fakePointerVarDecl = clang::VarDecl::Create( clangCtx, /*DC*/ clangCtx.getTranslationUnitDecl(), clang::SourceLocation(), clang::SourceLocation(), /*Id*/ nullptr, @@ -1309,15 +1323,14 @@ static void conformToCxxSpan(ClangImporter::Implementation &impl, clang::ExprValueKind::VK_LValue, clang::SourceLocation()); // create fake variable for count (constructor arg 2) - auto countType = clangCtx.getTypeDeclType(countTypeDecl); auto fakeCountVarDecl = clang::VarDecl::Create( clangCtx, /*DC*/ clangCtx.getTranslationUnitDecl(), clang::SourceLocation(), clang::SourceLocation(), /*Id*/ nullptr, - countType, clangCtx.getTrivialTypeSourceInfo(countType), + sizeType, clangCtx.getTrivialTypeSourceInfo(sizeType), clang::StorageClass::SC_None); auto fakeCount = new (clangCtx) clang::DeclRefExpr( - clangCtx, fakeCountVarDecl, false, countType, + clangCtx, fakeCountVarDecl, false, sizeType, clang::ExprValueKind::VK_LValue, clang::SourceLocation()); // Use clangSema.BuildCxxTypeConstructExpr to create a CXXTypeConstructExpr, @@ -1350,17 +1363,6 @@ static void conformToCxxSpan(ClangImporter::Implementation &impl, importedConstructor->addAttribute(attr); decl->addMember(importedConstructor); - - impl.addSynthesizedTypealias(decl, ctx.Id_Element, - elementType->getUnderlyingType()); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), - sizeType->getUnderlyingType()); - - if (pointerType->getPointeeType().isConstQualified()) { - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSpan}); - } else { - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxMutableSpan}); - } } void swift::deriveAutomaticCxxConformances( From 7bf20c95b335e9d99108b690aac176ebe2b479a7 Mon Sep 17 00:00:00 2001 From: John Hui Date: Mon, 15 Dec 2025 02:59:16 -0800 Subject: [PATCH 09/11] [cxx-interop] Use clang lookups for std::optional conformance Instead of looking up the Wrapped type from the type of the computed variable .pointee, this patch shifts the conformance logic to look up the value_type typedef from Clang, and uses the imported version of that to to satsify the CxxOptional conformance. Doing so removes the conformance's dependency on eagerly importing the .pointee method, and makes the conformance logic consistent with the rest of the CxxStdlib. --- .../ClangDerivedConformances.cpp | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index f138c3984286b..9cadd28e0ac5a 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -700,23 +700,18 @@ static void conformToCxxOptional(ClangImporter::Implementation &impl, clang::ASTContext &clangCtx = impl.getClangASTContext(); clang::Sema &clangSema = impl.getClangSema(); - ProtocolDecl *cxxOptionalProto = - ctx.getProtocol(KnownProtocolKind::CxxOptional); - // If the Cxx module is missing, or does not include one of the necessary - // protocol, bail. - if (!cxxOptionalProto) + auto *value_type = lookupCxxTypeMember(clangSema, clangDecl, "value_type", + /*mustBeComplete=*/true); + if (!value_type) return; - auto pointeeId = ctx.getIdentifier("pointee"); - auto pointees = lookupDirectWithoutExtensions(decl, pointeeId); - if (pointees.size() != 1) - return; - auto pointee = dyn_cast(pointees.front()); - if (!pointee) + auto *Wrapped = dyn_cast_or_null( + impl.importDecl(value_type, impl.CurrentVersion)); + if (!Wrapped) return; - auto pointeeTy = pointee->getInterfaceType(); - impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Wrapped"), pointeeTy); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Wrapped"), + Wrapped->getUnderlyingType()); impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxOptional}); // `std::optional` has a C++ constructor that takes the wrapped value as a @@ -724,11 +719,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 = lookupCxxTypeMember(clangSema, clangDecl, "value_type"); - if (!valueTypeDecl) - // `std::optional` without a value_type?! - return; - auto valueType = clangCtx.getTypeDeclType(valueTypeDecl); + auto valueType = clangCtx.getTypeDeclType(value_type); auto constRefValueType = clangCtx.getLValueReferenceType(valueType.withConst()); From eec64fe69c3ec4c33554e00181fc5068c77cc49a Mon Sep 17 00:00:00 2001 From: John Hui Date: Mon, 15 Dec 2025 04:06:15 -0800 Subject: [PATCH 10/11] [cxx-interop] Do not eagerly import non-field Clang members Skipping these members makes importing structs much lazier, and greatly reduces the chance we run into spurious template instantiation errors. rdar://145238539&152542137 --- lib/ClangImporter/ImportDecl.cpp | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 2245408fb6962..a5010b5f744b6 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2449,6 +2449,64 @@ namespace { } } + // Determine whether we should import this member; we should only import + // members that are necessary even when they are never referred to. + // + // Not only is this better for performance, it also avoids unnecessarily + // instantiating templates, which can be a bit of a minefield in light + // of SFINAE and template meta-programming. + bool shouldImport = false; + + // underlyingMember is what we will use to determine whether to import, + // looking through any using decls. + auto *underlyingMember = nd; + while (auto *usd = dyn_cast(underlyingMember)) + underlyingMember = usd->getTargetDecl(); + + if (isa(underlyingMember)) { + // Always import fields; these affect layout and thus codegen. + shouldImport = true; + } else if (isa(underlyingMember)) { + // Import function template decls for now. The way these these are + // handled right now, these should be safe from spurious + // instantiation, and seem to be necessary for lookups. + // FIXME: these shouldn't be necessary to import. + shouldImport = true; + } else if (auto *fn = dyn_cast(underlyingMember)) { + switch (fn->getDeclName().getNameKind()) { + case clang::DeclarationName::Identifier: + // Always import begin() and end() eagerly, since those are + // handled specially, but skip importing other functions. + if (fn->getMinRequiredArguments() == 0 && + (fn->getName() == "begin" || fn->getName() == "end")) { + shouldImport = true; + } else if (auto *md = dyn_cast(fn)) { + // Lookup doesn't know about these + if (CXXMethodBridging(md).classify() != + CXXMethodBridging::Kind::unknown) + shouldImport = true; + } + break; + + case clang::DeclarationName::CXXOperatorName: + case clang::DeclarationName::CXXConversionFunctionName: + case clang::DeclarationName::CXXConstructorName: + // Always import operators, conversions, and constructors for now. + // Some of these are handled in special ways that require them to + // be eagerly available, e.g., in VisitCXXRecordDecl() and + // conformToCxxConvertibleToBoolIfNeeded(). + shouldImport = true; + break; + default: + break; + } + } + + // Members we skip here can be imported on-demand if and when they are + // referred to in Swift + if (!shouldImport) + continue; + Decl *member = Impl.importDecl(nd, getActiveSwiftVersion()); if (!member) { From 63ae3560578f4ce2e752098a40ce49a06648fee8 Mon Sep 17 00:00:00 2001 From: John Hui Date: Mon, 15 Dec 2025 04:10:27 -0800 Subject: [PATCH 11/11] [cxx-interop] Always import function return type There isn't a very good reason _not_ to import the return type when we already import the argument types, and can occasionally lead to the return type being misinterpreted as void when our heuristics are wrong. Even if we're not binding the return value in Swift, we still need to, say, call its destructor. rdar://139225705 --- lib/ClangImporter/ImportType.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 6988c78a462e2..3a93c154a9377 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -2436,13 +2436,7 @@ ImportedType ClangImporter::Implementation::importFunctionParamsAndReturnType( if (!genericPointerType) addDiag(Diagnostic(diag::bridged_pointer_type_not_found, pointerKind)); importedType = {genericPointerType, false}; - } else if (!(isa(returnType) || - isa(returnType)) || - // TODO: we currently don't lazily load operator return types, but - // this should be trivial to add. - clangDecl->isOverloadedOperator() || - // Dependant types are trivially mapped as Any. - returnType->isDependentType()) { + } else { // If importedType is already initialized, it means we found the enum that // was supposed to be used (instead of the typedef type). if (!importedType) {