Skip to content

[cxx-interop] Import parameterized public ctors of C++ foreign ref types as Swift Initializer #80449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2513,12 +2513,13 @@ namespace {
});
});
if (!hasUserProvidedStaticFactory) {
if (auto generatedCxxMethodDecl =
synthesizer.synthesizeStaticFactoryForCXXForeignRef(
cxxRecordDecl)) {
auto generatedCxxMethodDecls =
synthesizer.synthesizeStaticFactoryForCXXForeignRef(
cxxRecordDecl);
for (auto *methodDecl : generatedCxxMethodDecls) {
if (Decl *importedInitDecl =
Impl.SwiftContext.getClangModuleLoader()
->importDeclDirectly(generatedCxxMethodDecl))
->importDeclDirectly(methodDecl))
result->addMember(importedInitDecl);
}
}
Expand Down
261 changes: 165 additions & 96 deletions lib/ClangImporter/SwiftDeclSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2534,128 +2534,197 @@ SwiftDeclSynthesizer::makeDefaultArgument(const clang::ParmVarDecl *param,

// MARK: C++ foreign reference type constructors

clang::CXXMethodDecl *
llvm::SmallVector<clang::CXXMethodDecl *, 4>
SwiftDeclSynthesizer::synthesizeStaticFactoryForCXXForeignRef(
const clang::CXXRecordDecl *cxxRecordDecl) {

clang::ASTContext &clangCtx = cxxRecordDecl->getASTContext();
clang::Sema &clangSema = ImporterImpl.getClangSema();

clang::QualType cxxRecordTy = clangCtx.getRecordType(cxxRecordDecl);
clang::SourceLocation cxxRecordDeclLoc = cxxRecordDecl->getLocation();

clang::CXXConstructorDecl *defaultCtorDecl = nullptr;
for (clang::CXXConstructorDecl *ctor : cxxRecordDecl->ctors()) {
if (ctor->parameters().empty() && !ctor->isDeleted() &&
ctor->getAccess() != clang::AS_private &&
ctor->getAccess() != clang::AS_protected) {
defaultCtorDecl = ctor;
break;
}
llvm::SmallVector<clang::CXXConstructorDecl *, 4> ctorDeclsForSynth;
for (clang::CXXConstructorDecl *ctorDecl : cxxRecordDecl->ctors()) {
if (ctorDecl->isDeleted() || ctorDecl->getAccess() == clang::AS_private ||
ctorDecl->getAccess() == clang::AS_protected ||
ctorDecl->isCopyOrMoveConstructor() || ctorDecl->isVariadic())
continue;

bool hasDefaultArg = !ctorDecl->parameters().empty() &&
ctorDecl->parameters().back()->hasDefaultArg();
// TODO: Add support for default args in ctors for C++ foreign reference
// types.
if (hasDefaultArg)
continue;
ctorDeclsForSynth.push_back(ctorDecl);
}
if (!defaultCtorDecl)
return nullptr;

if (ctorDeclsForSynth.empty())
return {};

clang::FunctionDecl *operatorNew = nullptr;
clang::FunctionDecl *operatorDelete = nullptr;
bool passAlignment = false;
clang::Sema::SFINAETrap trap(clangSema);
bool findingAllocFuncFailed = clangSema.FindAllocationFunctions(
cxxRecordDecl->getLocation(), clang::SourceRange(), clang::Sema::AFS_Both,
clang::Sema::AFS_Both, cxxRecordTy,
/*IsArray*/ false, passAlignment, clang::MultiExprArg(), operatorNew,
operatorDelete, /*Diagnose*/ false);
if (findingAllocFuncFailed || !operatorNew || operatorNew->isDeleted() ||
cxxRecordDeclLoc, clang::SourceRange(), clang::Sema::AFS_Both,
clang::Sema::AFS_Both, cxxRecordTy, /*IsArray=*/false, passAlignment,
clang::MultiExprArg(), operatorNew, operatorDelete,
/*Diagnose=*/false);
if (trap.hasErrorOccurred() || findingAllocFuncFailed || !operatorNew ||
operatorNew->isDeleted() ||
operatorNew->getAccess() == clang::AS_private ||
operatorNew->getAccess() == clang::AS_protected)
return nullptr;
return {};

clang::QualType cxxRecordPtrTy = clangCtx.getPointerType(cxxRecordTy);
// Adding `_Nonnull` to the return type of synthesized static factory
bool nullabilityCannotBeAdded =
clangSema.CheckImplicitNullabilityTypeSpecifier(
cxxRecordPtrTy, clang::NullabilityKind::NonNull,
cxxRecordDecl->getLocation(),
/*isParam=*/false,
/*OverrideExisting=*/true);
cxxRecordPtrTy, clang::NullabilityKind::NonNull, cxxRecordDeclLoc,
/*isParam=*/false, /*OverrideExisting=*/true);
assert(!nullabilityCannotBeAdded &&
"Failed to add _Nonnull specifier to synthesized "
"static factory's return type");

clang::IdentifierTable &clangIdents = clangCtx.Idents;
clang::IdentifierInfo *funcNameToSynthesize = &clangIdents.get(
("__returns_" + cxxRecordDecl->getNameAsString()).c_str());
clang::FunctionProtoType::ExtProtoInfo EPI;
clang::QualType funcTypeToSynthesize =
clangCtx.getFunctionType(cxxRecordPtrTy, {}, EPI);

clang::CXXMethodDecl *synthesizedCxxMethodDecl = clang::CXXMethodDecl::Create(
clangCtx, const_cast<clang::CXXRecordDecl *>(cxxRecordDecl),
cxxRecordDecl->getLocation(),
clang::DeclarationNameInfo(funcNameToSynthesize,
cxxRecordDecl->getLocation()),
funcTypeToSynthesize,
clangCtx.getTrivialTypeSourceInfo(funcTypeToSynthesize), clang::SC_Static,
/*UsesFPIntrin=*/false, /*isInline=*/true,
clang::ConstexprSpecKind::Unspecified, cxxRecordDecl->getLocation());
assert(synthesizedCxxMethodDecl &&
"Unable to synthesize static factory for c++ foreign reference type");
synthesizedCxxMethodDecl->setAccess(clang::AccessSpecifier::AS_public);

if (!hasImmortalAttrs(cxxRecordDecl)) {
clang::SwiftAttrAttr *returnsRetainedAttrForSynthesizedCxxMethodDecl =
clang::SwiftAttrAttr::Create(clangCtx, "returns_retained");
synthesizedCxxMethodDecl->addAttr(
returnsRetainedAttrForSynthesizedCxxMethodDecl);

llvm::SmallVector<clang::CXXMethodDecl *, 4> synthesizedFactories;
unsigned int selectedCtorDeclCounter = 0;
for (clang::CXXConstructorDecl *selectedCtorDecl : ctorDeclsForSynth) {
unsigned int ctorParamCount = selectedCtorDecl->getNumParams();
selectedCtorDeclCounter++;

std::string funcName = "__returns_" + cxxRecordDecl->getNameAsString();
if (ctorParamCount > 0)
funcName += "_" + std::to_string(ctorParamCount) + "_params";
funcName += "_" + std::to_string(selectedCtorDeclCounter);
clang::IdentifierInfo *funcNameToSynth = &clangIdents.get(funcName);

auto ctorFunctionProtoTy =
selectedCtorDecl->getType()->getAs<clang::FunctionProtoType>();
clang::ArrayRef<clang::QualType> paramTypes =
ctorFunctionProtoTy->getParamTypes();
clang::FunctionProtoType::ExtProtoInfo EPI;
clang::QualType funcTypeToSynth =
clangCtx.getFunctionType(cxxRecordPtrTy, paramTypes, EPI);

clang::CXXMethodDecl *synthCxxMethodDecl = clang::CXXMethodDecl::Create(
clangCtx, const_cast<clang::CXXRecordDecl *>(cxxRecordDecl),
cxxRecordDeclLoc,
clang::DeclarationNameInfo(funcNameToSynth, cxxRecordDeclLoc),
funcTypeToSynth, clangCtx.getTrivialTypeSourceInfo(funcTypeToSynth),
clang::SC_Static, /*UsesFPIntrin=*/false, /*isInline=*/true,
clang::ConstexprSpecKind::Unspecified, cxxRecordDeclLoc);
assert(
synthCxxMethodDecl &&
"Unable to synthesize static factory for c++ foreign reference type");
synthCxxMethodDecl->setAccess(clang::AccessSpecifier::AS_public);

llvm::SmallVector<clang::ParmVarDecl *, 4> synthParams;
for (unsigned int i = 0; i < ctorParamCount; ++i) {
auto *origParam = selectedCtorDecl->getParamDecl(i);
clang::IdentifierInfo *paramIdent = origParam->getIdentifier();
if (!paramIdent) {
std::string dummyName = "__unnamed_param_" + std::to_string(i);
paramIdent = &clangIdents.get(dummyName);
}
auto *param = clang::ParmVarDecl::Create(
clangCtx, synthCxxMethodDecl, cxxRecordDeclLoc, cxxRecordDeclLoc,
paramIdent, origParam->getType(),
clangCtx.getTrivialTypeSourceInfo(origParam->getType()),
clang::SC_None, /*DefArg=*/nullptr);
param->setIsUsed();
synthParams.push_back(param);
}
synthCxxMethodDecl->setParams(synthParams);

if (!hasImmortalAttrs(cxxRecordDecl)) {
synthCxxMethodDecl->addAttr(
clang::SwiftAttrAttr::Create(clangCtx, "returns_retained"));
}

std::string swiftInitStr = "init(";
for (unsigned i = 0; i < ctorParamCount; ++i) {
auto paramType = selectedCtorDecl->getParamDecl(i)->getType();
if (paramType->isRValueReferenceType()) {
swiftInitStr += "consuming:";
} else {
swiftInitStr += "_:";
}
}
swiftInitStr += ")";
synthCxxMethodDecl->addAttr(
clang::SwiftNameAttr::Create(clangCtx, swiftInitStr));

llvm::SmallVector<clang::Expr *, 4> ctorArgs;
for (auto *param : synthParams) {
clang::QualType paramTy = param->getType();
clang::QualType exprTy = paramTy.getNonReferenceType();
clang::Expr *argExpr = clang::DeclRefExpr::Create(
clangCtx, clang::NestedNameSpecifierLoc(), cxxRecordDeclLoc, param,
/*RefersToEnclosingVariableOrCapture=*/false, cxxRecordDeclLoc,
exprTy, clang::VK_LValue);
if (paramTy->isRValueReferenceType()) {
argExpr = clangSema
.BuildCXXNamedCast(
cxxRecordDeclLoc, clang::tok::kw_static_cast,
clangCtx.getTrivialTypeSourceInfo(paramTy), argExpr,
clang::SourceRange(), clang::SourceRange())
.get();
}
ctorArgs.push_back(argExpr);
}
llvm::SmallVector<clang::Expr *, 4> ctorArgsToAdd;

if (clangSema.CompleteConstructorCall(selectedCtorDecl, cxxRecordTy,
ctorArgs, cxxRecordDeclLoc,
ctorArgsToAdd))
continue;

clang::ExprResult synthCtorExprResult = clangSema.BuildCXXConstructExpr(
cxxRecordDeclLoc, cxxRecordTy, selectedCtorDecl,
/*Elidable=*/false, ctorArgsToAdd,
/*HadMultipleCandidates=*/false,
/*IsListInitialization=*/false,
/*IsStdInitListInitialization=*/false,
/*RequiresZeroInit=*/false, clang::CXXConstructionKind::Complete,
clang::SourceRange(cxxRecordDeclLoc, cxxRecordDeclLoc));
assert(!synthCtorExprResult.isInvalid() &&
"Unable to synthesize constructor expression for c++ foreign "
"reference type");
clang::Expr *synthCtorExpr = synthCtorExprResult.get();

clang::ExprResult synthNewExprResult = clangSema.BuildCXXNew(
clang::SourceRange(), /*UseGlobal=*/false, clang::SourceLocation(), {},
clang::SourceLocation(), clang::SourceRange(), cxxRecordTy,
clangCtx.getTrivialTypeSourceInfo(cxxRecordTy), std::nullopt,
clang::SourceRange(cxxRecordDeclLoc, cxxRecordDeclLoc), synthCtorExpr);
assert(
!synthNewExprResult.isInvalid() &&
"Unable to synthesize `new` expression for c++ foreign reference type");
auto *synthNewExpr = cast<clang::CXXNewExpr>(synthNewExprResult.get());

clang::ReturnStmt *synthRetStmt = clang::ReturnStmt::Create(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I wonder if we want to use Sema::BuildReturnStmt here, and add the SFNAETrap to all of the sema calls (could be the same trap for all the Build* calls).

clangCtx, cxxRecordDeclLoc, synthNewExpr, /*NRVOCandidate=*/nullptr);
assert(synthRetStmt && "Unable to synthesize return statement for "
"static factory of c++ foreign reference type");

clang::CompoundStmt *synthFuncBody = clang::CompoundStmt::Create(
clangCtx, {synthRetStmt}, clang::FPOptionsOverride(), cxxRecordDeclLoc,
cxxRecordDeclLoc);
assert(synthRetStmt && "Unable to synthesize function body for static "
"factory of c++ foreign reference type");

synthCxxMethodDecl->setBody(synthFuncBody);
synthCxxMethodDecl->addAttr(clang::NoDebugAttr::CreateImplicit(clangCtx));

synthCxxMethodDecl->setImplicit();
synthCxxMethodDecl->setImplicitlyInline();

synthesizedFactories.push_back(synthCxxMethodDecl);
}

clang::SwiftNameAttr *swiftNameInitAttrForSynthesizedCxxMethodDecl =
clang::SwiftNameAttr::Create(clangCtx, "init()");
synthesizedCxxMethodDecl->addAttr(
swiftNameInitAttrForSynthesizedCxxMethodDecl);

clang::ExprResult synthesizedConstructExprResult =
clangSema.BuildCXXConstructExpr(
clang::SourceLocation(), cxxRecordTy, defaultCtorDecl,
/*Elidable=*/false, clang::MultiExprArg(),
/*HadMultipleCandidates=*/false,
/*IsListInitialization=*/false,
/*IsStdInitListInitialization=*/false,
/*RequiresZeroInit=*/false, clang::CXXConstructionKind::Complete,
clang::SourceRange());
assert(!synthesizedConstructExprResult.isInvalid() &&
"Unable to synthesize constructor expression for c++ foreign "
"reference type");
clang::CXXConstructExpr *synthesizedConstructExpr =
cast<clang::CXXConstructExpr>(synthesizedConstructExprResult.get());

clang::ExprResult synthesizedNewExprResult = clangSema.BuildCXXNew(
clang::SourceRange(), /*UseGlobal=*/false, clang::SourceLocation(), {},
clang::SourceLocation(), clang::SourceRange(), cxxRecordTy,
clangCtx.getTrivialTypeSourceInfo(cxxRecordTy), std::nullopt,
clang::SourceRange(), synthesizedConstructExpr);
assert(
!synthesizedNewExprResult.isInvalid() &&
"Unable to synthesize `new` expression for c++ foreign reference type");
clang::CXXNewExpr *synthesizedNewExpr =
cast<clang::CXXNewExpr>(synthesizedNewExprResult.get());

clang::ReturnStmt *synthesizedRetStmt =
clang::ReturnStmt::Create(clangCtx, clang::SourceLocation(),
synthesizedNewExpr, /*VarDecl=*/nullptr);
assert(synthesizedRetStmt && "Unable to synthesize return statement for "
"static factory of c++ foreign reference type");

clang::CompoundStmt *synthesizedFuncBody = clang::CompoundStmt::Create(
clangCtx, {synthesizedRetStmt}, clang::FPOptionsOverride(),
clang::SourceLocation(), clang::SourceLocation());
assert(synthesizedRetStmt && "Unable to synthesize function body for static "
"factory of c++ foreign reference type");

synthesizedCxxMethodDecl->setBody(synthesizedFuncBody);
synthesizedCxxMethodDecl->addAttr(
clang::NoDebugAttr::CreateImplicit(clangCtx));

synthesizedCxxMethodDecl->setImplicit();
synthesizedCxxMethodDecl->setImplicitlyInline();

return synthesizedCxxMethodDecl;
return synthesizedFactories;
}
3 changes: 2 additions & 1 deletion lib/ClangImporter/SwiftDeclSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ class SwiftDeclSynthesizer {
/// Synthesize a static factory method for a C++ foreign reference type,
/// returning a `CXXMethodDecl*` or `nullptr` if the required constructor or
/// allocation function is not found.
clang::CXXMethodDecl *synthesizeStaticFactoryForCXXForeignRef(
llvm::SmallVector<clang::CXXMethodDecl *, 4>
synthesizeStaticFactoryForCXXForeignRef(
const clang::CXXRecordDecl *cxxRecordDecl);

private:
Expand Down
Loading