diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index a18e3d9f2991..9cadd28e0ac5 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -13,17 +13,80 @@ #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" using namespace swift; using namespace swift::importer; +/// Known C++ stdlib types, for which we can assume conformance to the standard +/// (e.g., std::map has template parameters for key and value types, and has +/// members like key_type, size_type, and operator[]). +enum class CxxStdType { + uncategorized, + optional, + set, + unordered_set, + multiset, + pair, + map, + unordered_map, + multimap, + vector, + span, +}; + +static CxxStdType identifyCxxStdTypeByName(StringRef name) { +#define CaseStd(name) Case(#name, CxxStdType::name) + return llvm::StringSwitch(name) + .CaseStd(optional) + .CaseStd(set) + .CaseStd(unordered_set) + .CaseStd(multiset) + .CaseStd(pair) + .CaseStd(map) + .CaseStd(unordered_map) + .CaseStd(multimap) + .CaseStd(vector) + .CaseStd(span) + .Default(CxxStdType::uncategorized); +#undef CxxStdCase +} + +static const clang::TypeDecl * +lookupCxxTypeMember(clang::Sema &Sema, const clang::CXXRecordDecl *Rec, + 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 (auto *td = R.getAsSingle()) { + if (auto *paths = R.getBasePaths(); + paths && paths->front().Access != clang::AS_public) + return nullptr; + if (mustBeComplete && + !Sema.isCompleteType({}, td->getASTContext().getTypeDeclType(td))) + return nullptr; + return td; + } + return nullptr; +} + /// Alternative to `NominalTypeDecl::lookupDirect`. /// This function does not attempt to load extensions of the nominal decl. static TinyPtrVector @@ -72,65 +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 bool isStdDecl(const clang::CXXRecordDecl *clangDecl, - llvm::ArrayRef names) { - if (!clangDecl->isInStdNamespace()) - return false; - if (!clangDecl->getIdentifier()) - return false; - StringRef name = clangDecl->getName(); - return llvm::is_contained(names, name); -} - -static clang::TypeDecl * -lookupNestedClangTypeDecl(const clang::CXXRecordDecl *clangDecl, - StringRef name) { - 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. @@ -396,8 +400,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 * @@ -422,15 +436,14 @@ swift::importer::getImportedMemberOperator(const DeclBaseName &name, return nullptr; } -void swift::conformToCxxIteratorIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { - PrettyStackTraceDecl trace("conforming to UnsafeCxxInputIterator", decl); - - assert(decl); - assert(clangDecl); +static void +conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { + PrettyStackTraceDecl trace("trying to conform to UnsafeCxxInputIterator", decl); ASTContext &ctx = decl->getASTContext(); clang::ASTContext &clangCtx = clangDecl->getASTContext(); + clang::Sema &clangSema = impl.getClangSema(); if (!ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator)) return; @@ -438,13 +451,19 @@ void swift::conformToCxxIteratorIfNeeded( // 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(); @@ -503,7 +522,8 @@ void swift::conformToCxxIteratorIfNeeded( // `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); @@ -641,13 +661,11 @@ void swift::conformToCxxIteratorIfNeeded( } } -void swift::conformToCxxConvertibleToBoolIfNeeded( - ClangImporter::Implementation &impl, swift::NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { - PrettyStackTraceDecl trace("conforming to CxxConvertibleToBool", decl); - - assert(decl); - assert(clangDecl); +static void +conformToCxxConvertibleToBoolIfNeeded(ClangImporter::Implementation &impl, + swift::NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { + PrettyStackTraceDecl trace("trying to conform to CxxConvertibleToBool", decl); ASTContext &ctx = decl->getASTContext(); auto conversionId = ctx.getIdentifier("__convertToBool"); @@ -674,37 +692,26 @@ void swift::conformToCxxConvertibleToBoolIfNeeded( {KnownProtocolKind::CxxConvertibleToBool}); } -void swift::conformToCxxOptionalIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxOptional(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxOptional", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); clang::ASTContext &clangCtx = impl.getClangASTContext(); clang::Sema &clangSema = impl.getClangSema(); - if (!isStdDecl(clangDecl, {"optional"})) + auto *value_type = lookupCxxTypeMember(clangSema, clangDecl, "value_type", + /*mustBeComplete=*/true); + if (!value_type) return; - 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 *Wrapped = dyn_cast_or_null( + impl.importDecl(value_type, impl.CurrentVersion)); + if (!Wrapped) return; - auto pointeeId = ctx.getIdentifier("pointee"); - auto pointees = lookupDirectWithoutExtensions(decl, pointeeId); - if (pointees.size() != 1) - return; - auto pointee = dyn_cast(pointees.front()); - if (!pointee) - 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 @@ -712,11 +719,7 @@ void swift::conformToCxxOptionalIfNeeded( // 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"); - 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()); @@ -766,13 +769,11 @@ void swift::conformToCxxOptionalIfNeeded( decl->addMember(importedConstructor); } -void swift::conformToCxxSequenceIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { - PrettyStackTraceDecl trace("conforming to CxxSequence", decl); - - assert(decl); - assert(clangDecl); +static void +conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { + PrettyStackTraceDecl trace("trying to conform to CxxSequence", decl); ASTContext &ctx = decl->getASTContext(); ProtocolDecl *cxxIteratorProto = @@ -940,278 +941,368 @@ void swift::conformToCxxSequenceIfNeeded( } } -static bool isStdSetType(const clang::CXXRecordDecl *clangDecl) { - return isStdDecl(clangDecl, {"set", "unordered_set", "multiset"}); -} - -static bool isStdMapType(const clang::CXXRecordDecl *clangDecl) { - return isStdDecl(clangDecl, {"map", "unordered_map", "multimap"}); -} - bool swift::isUnsafeStdMethod(const clang::CXXMethodDecl *methodDecl) { - auto parentDecl = + auto *parentDecl = dyn_cast(methodDecl->getDeclContext()); - if (!parentDecl) - return false; - if (!isStdSetType(parentDecl) && !isStdMapType(parentDecl)) + if (!parentDecl || !parentDecl->isInStdNamespace() || + !parentDecl->getIdentifier()) return false; - if (methodDecl->getDeclName().isIdentifier() && - methodDecl->getName() == "insert") - return true; + + if (methodDecl->getIdentifier() && methodDecl->getName() == "insert") { + // Types for which the insert method is considered unsafe, + // due to potential iterator invalidation. + return llvm::StringSwitch(parentDecl->getName()) + .Cases({"set", "unordered_set", "multiset"}, true) + .Cases({"map", "unordered_map", "multimap"}, true) + .Default(false); + } return false; } -void swift::conformToCxxSetIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxSet(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl, + bool isUniqueSet) { PrettyStackTraceDecl trace("conforming to CxxSet", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); + auto &clangCtx = impl.getClangASTContext(); + auto &clangSema = impl.getClangSema(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdSetType(clangDecl)) + // Look up the type members we need from Clang + // + // N.B. we don't actually need const_iterator for multiset, but it should be + // there. If it's not there for any reason, we should probably bail out. + + auto *size_type = lookupCxxTypeMember(clangSema, clangDecl, "size_type", + /*mustBeComplete=*/true); + auto *value_type = lookupCxxTypeMember(clangSema, clangDecl, "value_type", + /*mustBeComplete=*/true); + auto *iterator = lookupCxxTypeMember(clangSema, clangDecl, "iterator", + /*mustBeComplete=*/true); + auto *const_iterator = + lookupCxxTypeMember(clangSema, clangDecl, "const_iterator", + /*mustBeComplete=*/true); + if (!size_type || !value_type || !iterator || !const_iterator) return; - auto valueType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("value_type")); - auto sizeType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("size_type")); - if (!valueType || !sizeType) - return; + 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; + } - auto insert = getInsertFunc(decl, valueType); + 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; - 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}); - - ProtocolDecl *cxxInputIteratorProto = - ctx.getProtocol(KnownProtocolKind::UnsafeCxxInputIterator); - if (!cxxInputIteratorProto) - return; - - auto rawIteratorType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("const_iterator")); - auto rawMutableIteratorType = - lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("iterator")); - if (!rawIteratorType || !rawMutableIteratorType) + // 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); - - // If this isn't a std::multiset, try to also synthesize the conformance to - // CxxUniqueSet. - if (!isStdDecl(clangDecl, {"set", "unordered_set"})) - 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}); } -void swift::conformToCxxPairIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxPair(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxPair", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); + clang::Sema &clangSema = impl.getClangSema(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdDecl(clangDecl, {"pair"})) + 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}); } -void swift::conformToCxxDictionaryIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxDictionary(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxDictionary", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); + clang::ASTContext &clangCtx = impl.getClangASTContext(); + clang::Sema &clangSema = impl.getClangSema(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdMapType(clangDecl)) - return; - - 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; - - auto rawIteratorTy = iterType->getUnderlyingType(); - auto rawMutableIteratorTy = mutableIterType->getUnderlyingType(); - - // Check if RawIterator conforms to UnsafeCxxInputIterator. - if (!checkConformance(rawIteratorTy, cxxInputIteratorProto)) +#define importTypeAlias(to, from) \ + auto *to = dyn_cast_or_null( \ + impl.importDecl(from, impl.CurrentVersion)); \ + if (!to) \ + return + + 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 + + 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}); } -void swift::conformToCxxVectorIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxVector(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxVector", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); + clang::Sema &clangSema = impl.getClangSema(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdDecl(clangDecl, {"vector"})) - return; - - 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(); - - // Check if RawIterator conforms to UnsafeCxxRandomAccessIterator. - if (!checkConformance(rawIteratorTy, cxxRandomAccessIteratorProto)) + 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); + + 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}); } -void swift::conformToCxxSpanIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl) { +static void conformToCxxSpan(ClangImporter::Implementation &impl, + NominalTypeDecl *decl, + const clang::CXXRecordDecl *clangDecl) { PrettyStackTraceDecl trace("conforming to CxxSpan", decl); - - assert(decl); - assert(clangDecl); ASTContext &ctx = decl->getASTContext(); clang::ASTContext &clangCtx = impl.getClangASTContext(); clang::Sema &clangSema = impl.getClangSema(); - // Only auto-conform types from the C++ standard library. Custom user types - // might have a similar interface but different semantics. - if (!isStdDecl(clangDecl, {"span"})) + 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 elementType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("element_type")); - auto sizeType = lookupDirectSingleWithoutExtensions( - decl, ctx.getIdentifier("size_type")); + auto pointerType = clangCtx.getTypeDeclType(pointer); + auto sizeType = clangCtx.getTypeDeclType(size_type); - if (!elementType || !sizeType) + 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; - auto pointerTypeDecl = lookupNestedClangTypeDecl(clangDecl, "pointer"); - auto countTypeDecl = lookupNestedClangTypeDecl(clangDecl, "size_type"); + impl.addSynthesizedTypealias(decl, ctx.Id_Element, + Element->getUnderlyingType()); + impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Size"), + Size->getUnderlyingType()); - if (!pointerTypeDecl || !countTypeDecl) - return; + 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, @@ -1223,15 +1314,14 @@ void swift::conformToCxxSpanIfNeeded(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, @@ -1264,15 +1354,62 @@ void swift::conformToCxxSpanIfNeeded(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()); +void swift::deriveAutomaticCxxConformances( + ClangImporter::Implementation &Impl, NominalTypeDecl *result, + const clang::CXXRecordDecl *clangDecl) { - if (pointerType->getPointeeType().isConstQualified()) { - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxSpan}); - } else { - impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxMutableSpan}); + ASSERT(result && clangDecl && "this should not be called with nullptrs"); + + // Skip synthesizing conformances if the associated Clang node is from + // a module that doesn't require cplusplus, to prevent us from accidentally + // pulling in Cxx/CxxStdlib modules when a client is importing a C library. + // + // We will still attempt to synthesize to account for scenarios where the + // module specification is missing altogether. + if (auto *clangModule = Impl.getClangOwningModule(result->getClangNode()); + clangModule && !requiresCPlusPlus(clangModule)) + return; + + // Automatic conformances: these may be applied to any type that fits the + // requirements. + conformToCxxIteratorIfNeeded(Impl, result, clangDecl); + conformToCxxSequenceIfNeeded(Impl, result, clangDecl); + conformToCxxConvertibleToBoolIfNeeded(Impl, result, clangDecl); + + // CxxStdlib conformances: these should only apply to known C++ stdlib types, + // which we determine by name and membership in the std namespace. + if (!clangDecl->getIdentifier() || !clangDecl->isInStdNamespace()) + return; + + auto ty = identifyCxxStdTypeByName(clangDecl->getName()); + switch (ty) { + case CxxStdType::uncategorized: + return; + case CxxStdType::optional: + conformToCxxOptional(Impl, result, clangDecl); + return; + case CxxStdType::set: + case CxxStdType::unordered_set: + conformToCxxSet(Impl, result, clangDecl, /*isUniqueSet=*/true); + return; + case CxxStdType::multiset: + conformToCxxSet(Impl, result, clangDecl, /*isUniqueSet=*/false); + return; + case CxxStdType::pair: + conformToCxxPair(Impl, result, clangDecl); + return; + case CxxStdType::map: + case CxxStdType::unordered_map: + case CxxStdType::multimap: + conformToCxxDictionary(Impl, result, clangDecl); + return; + case CxxStdType::vector: + conformToCxxVector(Impl, result, clangDecl); + return; + case CxxStdType::span: + conformToCxxSpan(Impl, result, clangDecl); + return; } } diff --git a/lib/ClangImporter/ClangDerivedConformances.h b/lib/ClangImporter/ClangDerivedConformances.h index c3c267ed70ad..a3fedb64a15b 100644 --- a/lib/ClangImporter/ClangDerivedConformances.h +++ b/lib/ClangImporter/ClangDerivedConformances.h @@ -18,65 +18,20 @@ 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); -/// If the decl is a C++ input iterator, synthesize a conformance to the -/// UnsafeCxxInputIterator protocol, which is defined in the Cxx module. -void conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl defines `operator bool()`, synthesize a conformance to the -/// CxxConvertibleToBool protocol, which is defined in the Cxx module. -void conformToCxxConvertibleToBoolIfNeeded( - ClangImporter::Implementation &impl, NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::optional`, synthesize a -/// conformance to CxxOptional protocol, which is defined in the Cxx module. -void conformToCxxOptionalIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is a C++ sequence, synthesize a conformance to the CxxSequence -/// protocol, which is defined in the Cxx module. -void conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::set`, `std::unordered_set` or -/// `std::multiset`, synthesize a conformance to CxxSet, which is defined in the -/// Cxx module. -void conformToCxxSetIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::pair`, synthesize a conformance -/// to CxxPair, which is defined in the Cxx module. -void conformToCxxPairIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::map` or `std::unordered_map`, -/// synthesize a conformance to CxxDictionary, which is defined in the Cxx module. -void conformToCxxDictionaryIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, +void deriveAutomaticCxxConformances(ClangImporter::Implementation &Impl, + NominalTypeDecl *result, const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::vector`, synthesize a -/// conformance to CxxVector, which is defined in the Cxx module. -void conformToCxxVectorIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - -/// If the decl is an instantiation of C++ `std::span`, synthesize a -/// conformance to CxxSpan, which is defined in the Cxx module. -void conformToCxxSpanIfNeeded(ClangImporter::Implementation &impl, - NominalTypeDecl *decl, - const clang::CXXRecordDecl *clangDecl); - } // namespace swift #endif // SWIFT_CLANG_DERIVED_CONFORMANCES_H diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index b6125283435f..5999abeb0910 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -8376,7 +8376,7 @@ static bool hasPointerInSubobjects(const clang::CXXRecordDecl *decl) { hasUnsafeAPIAttr(cxxRecord)) return false; - if (hasIteratorAPIAttr(cxxRecord) || isIterator(cxxRecord)) + if (hasIteratorAPIAttr(cxxRecord) || hasIteratorCategory(cxxRecord)) return true; if (hasPointerInSubobjects(cxxRecord)) @@ -8497,7 +8497,7 @@ CxxRecordSemantics::evaluate(Evaluator &evaluator, if (isSwiftClassType(cxxDecl)) return CxxRecordSemanticsKind::SwiftClassType; - if (hasIteratorAPIAttr(cxxDecl) || isIterator(cxxDecl)) { + if (hasIteratorAPIAttr(cxxDecl) || hasIteratorCategory(cxxDecl)) { return CxxRecordSemanticsKind::Iterator; } @@ -8760,7 +8760,7 @@ bool IsSafeUseOfCxxDecl::evaluate(Evaluator &evaluator, return true; if (hasIteratorAPIAttr(cxxRecordReturnType) || - isIterator(cxxRecordReturnType)) + hasIteratorCategory(cxxRecordReturnType)) return false; // Mark this as safe to help our diganostics down the road. diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 0bbcc4782082..2245408fb696 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -3293,29 +3293,14 @@ namespace { validatePrivateFileIDAttributes(decl); - // If this module is declared as a C++ module, try to synthesize - // conformances to Swift protocols from the Cxx module. - auto clangModule = Impl.getClangOwningModule(result->getClangNode()); - if (!clangModule || requiresCPlusPlus(clangModule)) { - if (auto nominalDecl = dyn_cast(result)) { - conformToCxxIteratorIfNeeded(Impl, nominalDecl, decl); - conformToCxxSequenceIfNeeded(Impl, nominalDecl, decl); - conformToCxxConvertibleToBoolIfNeeded(Impl, nominalDecl, decl); - conformToCxxSetIfNeeded(Impl, nominalDecl, decl); - conformToCxxDictionaryIfNeeded(Impl, nominalDecl, decl); - conformToCxxPairIfNeeded(Impl, nominalDecl, decl); - conformToCxxOptionalIfNeeded(Impl, nominalDecl, decl); - conformToCxxVectorIfNeeded(Impl, nominalDecl, decl); - conformToCxxSpanIfNeeded(Impl, nominalDecl, decl); - - if (Impl.needsClosureConstructor(decl)) { - if (auto closureCtor = - synthesizer.makeClosureConstructor(nominalDecl)) - nominalDecl->addMember(closureCtor); - } + if (auto *nominalResult = dyn_cast(result)) { + deriveAutomaticCxxConformances(Impl, nominalResult, decl); + + if (Impl.needsClosureConstructor(decl)) { + if (auto *ctor = synthesizer.makeClosureConstructor(nominalResult)) + nominalResult->addMember(ctor); } } - return result; } diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index fcd339f81f31..881efeff2563 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -970,11 +970,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation ClangModuleUnit *getClangModuleForDecl(const clang::Decl *D, bool allowForwardDeclaration = false); - /// Returns the module \p MI comes from, or \c None if \p MI does not have - /// a valid associated module. - /// - /// The returned module may be null (but not \c None) if \p MI comes from - /// an imported header. + /// Returns the module \p Node comes from, or \c nullptr if \p Node does not + /// have a valid owning module. const clang::Module *getClangOwningModule(ClangNode Node) const; /// Whether NSUInteger can be imported as Int in certain contexts. If false,