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 f1f326ce8bc939fb93184b0aa468edc0164d30d8 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, 89 insertions(+), 43 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index b37422b7e551f..a5c248e5c2f24 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -1153,69 +1153,115 @@ 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) + auto *key_type = lookupCxxTypeMember(clangSema, clangDecl, "key_type", true); + auto *mapped_type = + lookupCxxTypeMember(clangSema, clangDecl, "mapped_type", true); + auto *value_type = + lookupCxxTypeMember(clangSema, clangDecl, "value_type", true); + auto *size_type = + lookupCxxTypeMember(clangSema, clangDecl, "size_type", true); + auto *iterator = lookupCxxTypeMember(clangSema, clangDecl, "iterator", true); + auto *const_iterator = + lookupCxxTypeMember(clangSema, clangDecl, "const_iterator", true); + if (!key_type || !mapped_type || !value_type || !size_type || !iterator || + !const_iterator) return; - auto insert = getInsertFunc(decl, keyValuePairType); + 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; + } + + 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) + auto *Size = dyn_cast_or_null( + impl.importDecl(size_type, impl.CurrentVersion)); + auto *Key = dyn_cast_or_null( + impl.importDecl(key_type, impl.CurrentVersion)); + auto *Value = dyn_cast_or_null( + impl.importDecl(mapped_type, impl.CurrentVersion)); + auto *Element = dyn_cast_or_null( + impl.importDecl(value_type, impl.CurrentVersion)); + auto *RawIterator = dyn_cast_or_null( + impl.importDecl(const_iterator, impl.CurrentVersion)); + auto *RawMutableIterator = dyn_cast_or_null( + impl.importDecl(iterator, impl.CurrentVersion)); + if (!Size || !Key || !Value || !Element || !RawIterator || + !RawMutableIterator) return; - auto rawIteratorTy = iterType->getUnderlyingType(); - auto rawMutableIteratorTy = mutableIterType->getUnderlyingType(); - - // 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 4dd2889aec2caa439d3ed3322de1cc4d0a62f306 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 a5c248e5c2f24..b38d0318a0136 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 8d6a489a9c1cdc98c4e10a5add1377006af1afc7 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 b38d0318a0136..2cd443f57fc09 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -1284,22 +1284,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, @@ -1311,15 +1325,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, @@ -1352,17 +1365,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 673aa4923c86c3e874cbec8ff1d2512951c2028f 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 2cd443f57fc09..6a67d755bdd3b 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 58d578c91b3b895443db3ac437de85f87cc17602 Mon Sep 17 00:00:00 2001 From: John Hui Date: Tue, 16 Dec 2025 19:45:03 -0800 Subject: [PATCH 10/11] [cxx-interop] [NFC] Elide unnecessary static_cast --- lib/ClangImporter/ClangDerivedConformances.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 6a67d755bdd3b..498d2981a7574 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -72,8 +72,7 @@ lookupCxxTypeMember(clang::Sema &Sema, const clang::CXXRecordDecl *Rec, clang::SourceLocation(), clang::Sema::LookupMemberName); R.suppressDiagnostics(); - auto *Ctx = static_cast(Rec); - Sema.LookupQualifiedName(R, const_cast(Ctx)); + Sema.LookupQualifiedName(R, const_cast(Rec)); if (auto *td = R.getAsSingle()) { if (auto *paths = R.getBasePaths(); @@ -1014,8 +1013,8 @@ static void conformToCxxSet(ClangImporter::Implementation &impl, clangSema, &clangSema.PP.getIdentifierTable().get("insert"), clang::SourceLocation(), clang::Sema::LookupMemberName); R.suppressDiagnostics(); - auto *Ctx = static_cast(clangDecl); - clangSema.LookupQualifiedName(R, const_cast(Ctx)); + clangSema.LookupQualifiedName( + R, const_cast(clangDecl)); switch (R.getResultKind()) { case clang::LookupResultKind::Found: case clang::LookupResultKind::FoundOverloaded: @@ -1152,8 +1151,8 @@ static void conformToCxxDictionary(ClangImporter::Implementation &impl, clangSema, &clangSema.PP.getIdentifierTable().get("insert"), clang::SourceLocation(), clang::Sema::LookupMemberName); R.suppressDiagnostics(); - auto *Ctx = static_cast(clangDecl); - clangSema.LookupQualifiedName(R, const_cast(Ctx)); + clangSema.LookupQualifiedName( + R, const_cast(clangDecl)); switch (R.getResultKind()) { case clang::LookupResultKind::Found: case clang::LookupResultKind::FoundOverloaded: From d927ade6677873284af704ea3ac1124cbb3a60ac Mon Sep 17 00:00:00 2001 From: John Hui Date: Wed, 17 Dec 2025 08:41:11 -0800 Subject: [PATCH 11/11] [cxx-interop] Check access of non-inherited type member for derived conformances --- lib/ClangImporter/ClangDerivedConformances.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index 498d2981a7574..7210d53f32b07 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -75,9 +75,16 @@ lookupCxxTypeMember(clang::Sema &Sema, const clang::CXXRecordDecl *Rec, Sema.LookupQualifiedName(R, const_cast(Rec)); if (auto *td = R.getAsSingle()) { - if (auto *paths = R.getBasePaths(); - paths && paths->front().Access != clang::AS_public) - return nullptr; + if (auto *paths = R.getBasePaths()) { + // For inherited type member, check access of the single CXXBasePath + if (paths->front().Access != clang::AS_public) + return nullptr; + } else { + // For direct (non-inherited) type member, check its access directly + if (td->getAccess() != clang::AS_public) + return nullptr; + } + if (mustBeComplete && !Sema.isCompleteType({}, td->getASTContext().getTypeDeclType(td))) return nullptr;