Skip to content

Commit

Permalink
[cxx-interop] Support importing swift::String
Browse files Browse the repository at this point in the history
Whenever the String Swift type is exported to C++, we create
swift::String on the C++ side. However, we did not support importing
swift::String back into Swift. This PR addresses this problem.
Unfortunately, we cannot just set swift::String as the ClangNode of
Swift's String type. Types with ClangNodes are using the foreign
representation, but in this case we want to use the native type whenever
possible (e.g., for codegen). To work this around, this PR updates
ClangTypeConverter to make sure we have the correct Clang types when we
emit function calls to C++ APIs that use swift::String in their
interface.
  • Loading branch information
Gabor Horvath committed Sep 30, 2024
1 parent bf6bb65 commit fb3b479
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 7 deletions.
2 changes: 2 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,8 @@ class ASTContext final {
/// if applicable.
const Decl *getSwiftDeclForExportedClangDecl(const clang::Decl *decl);

void registerExportedClangDecl(Decl *decl, const clang::Decl *clangDecl);

/// General conversion method from Swift types -> Clang types.
///
/// HACK: This method is only intended to be called from a specific place in
Expand Down
8 changes: 8 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6239,6 +6239,14 @@ ASTContext::getSwiftDeclForExportedClangDecl(const clang::Decl *decl) {
return impl.Converter->getSwiftDeclForExportedClangDecl(decl);
}

void ASTContext::registerExportedClangDecl(Decl *decl,
const clang::Decl *clangDecl) {
// Make sure we have a converter.
getClangTypeConverter();
auto &impl = getImpl();
impl.Converter->registerExportedClangDecl(decl, clangDecl);
}

const clang::Type *
ASTContext::getClangTypeForIRGen(Type ty) {
return getClangTypeConverter().convert(ty).getTypePtrOrNull();
Expand Down
15 changes: 15 additions & 0 deletions lib/AST/ClangTypeConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#include "swift/Basic/LLVM.h"

#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Type.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Sema/Sema.h"

Expand Down Expand Up @@ -850,6 +852,18 @@ clang::QualType ClangTypeConverter::convert(Type type) {
1);
return ctx.getObjCObjectPointerType(clangType);
}
} else {
auto it = ReversedExportMapBackwards.find(decl);
// This can happen, when the String type is exported to C++ (as
// swift::String) which is later imported back to Swift as String.
if (it != ReversedExportMapBackwards.end()) {
const auto *clangDecl = it->getSecond();
const auto *dc = clangDecl->getDeclContext();
if (dc->isNamespace() &&
cast<clang::NamespaceDecl>(dc)->getName() == "swift")
return clang::QualType(
cast<clang::TypeDecl>(clangDecl)->getTypeForDecl(), 0);
}
}
}

Expand All @@ -865,6 +879,7 @@ void ClangTypeConverter::registerExportedClangDecl(Decl *swiftDecl,
"generated Clang declaration for Swift declaration should not "
"have multiple declarations");
ReversedExportMap.insert({clangDecl, swiftDecl});
ReversedExportMapBackwards.insert({swiftDecl, clangDecl});
}

Decl *ClangTypeConverter::getSwiftDeclForExportedClangDecl(
Expand Down
1 change: 1 addition & 0 deletions lib/AST/ClangTypeConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class ClangTypeConverter :

llvm::DenseMap<Type, clang::QualType> Cache;
llvm::DenseMap<const clang::Decl *, swift::Decl *> ReversedExportMap;
llvm::DenseMap<swift::Decl *, const clang::Decl *> ReversedExportMapBackwards;

bool StdlibTypesAreCached = false;

Expand Down
23 changes: 16 additions & 7 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2746,15 +2746,25 @@ namespace {
return nullptr;
}

auto dc = decl->getDeclContext();
// Bail if this is `std::chrono::tzdb`. This type causes issues in copy
// constructor instantiation.
// FIXME: https://github.com/apple/swift/issues/73037
if (decl->getDeclContext()->isNamespace() &&
decl->getDeclContext()->getParent()->isStdNamespace() &&
if (dc->isNamespace() && dc->getParent()->isStdNamespace() &&
decl->getIdentifier() &&
(decl->getName() == "tzdb" || decl->getName() == "time_zone_link"))
return nullptr;

// Import the C++ swift::String as the swift String type.
if (dc->isNamespace() &&
cast<clang::NamespaceDecl>(dc)->getName() == "swift" &&
decl->getIdentifier() && decl->getName() == "String") {
auto swiftDecl = Impl.SwiftContext.getStringDecl();
Impl.SwiftContext.registerExportedClangDecl(swiftDecl,
decl->getCanonicalDecl());
return swiftDecl;
}

auto &clangSema = Impl.getClangSema();
// Make Clang define any implicit constructors it may need (copy,
// default). Make sure we only do this if the class has been fully defined
Expand Down Expand Up @@ -3021,15 +3031,14 @@ namespace {

Decl *VisitClassTemplateSpecializationDecl(
const clang::ClassTemplateSpecializationDecl *decl) {
auto *classTemplate = decl->getSpecializedTemplate();
// Treat a specific specialization like the unspecialized class template
// when importing it in symbolic mode.
if (Impl.importSymbolicCXXDecls)
return Impl.importDecl(decl->getSpecializedTemplate(),
Impl.CurrentVersion);

bool isPair = decl->getSpecializedTemplate()->isInStdNamespace() &&
decl->getSpecializedTemplate()->getName() == "pair";
return Impl.importDecl(classTemplate,Impl.CurrentVersion);

bool isPair = classTemplate->isInStdNamespace() &&
classTemplate->getName() == "pair";
// Before we go any further, check if we've already got tens of thousands
// of specializations. If so, it means we're likely instantiating a very
// deep/complex template, or we've run into an infinite loop. In either
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// RUN: %empty-directory(%t)
// RUN: split-file %s %t

// RUN: %target-swift-frontend -typecheck %t/swiftMod.swift -typecheck -module-name SwiftMod -emit-clang-header-path %t/swiftMod.h -I %t -enable-experimental-cxx-interop -Xcc -DFIRSTPASS

// RUN: %target-interop-build-swift %t/swiftMod.swift -o %t/swift-execution -module-name SwiftMod -I %t -g -DSECOND_PASS -Xcc -DSWIFT_CXX_INTEROP_HIDE_SWIFT_ERROR

// RUN: %target-codesign %t/swift-execution
// RUN: %target-run %t/swift-execution | %FileCheck %s

// REQUIRES: executable_test

//--- header.h
#ifndef FIRSTPASS

#include "swiftMod.h"

inline swift::String createString() {
return swift::String("Foobar");
}

#endif

//--- module.modulemap
module SwiftToCxxTest {
header "header.h"
requires cplusplus
}

//--- swiftMod.swift
import SwiftToCxxTest

public func f() -> String? { "" }

#if SECOND_PASS

let str = createString()
print(str)

#endif

// CHECK: Foobar

0 comments on commit fb3b479

Please sign in to comment.