Skip to content

Commit 5c2ae73

Browse files
authored
Merge pull request swiftlang#71994 from apple/egorzhdan/virtual-methods
[cxx-interop] Overhaul virtual method support
2 parents 0250603 + 8ead722 commit 5c2ae73

13 files changed

+423
-240
lines changed

include/swift/ClangImporter/ClangImporter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ typedef llvm::PointerUnion<const clang::Decl *, const clang::MacroInfo *,
131131
/// from Clang ASTs over to Swift ASTs.
132132
class ClangImporter final : public ClangModuleLoader {
133133
friend class ClangModuleUnit;
134+
friend class SwiftDeclSynthesizer;
134135

135136
// Make requests in the ClangImporter zone friends so they can access `Impl`.
136137
#define SWIFT_REQUEST(Zone, Name, Sig, Caching, LocOptions) \

lib/ClangImporter/ClangImporter.cpp

Lines changed: 22 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -4891,187 +4891,6 @@ MemberRefExpr *getSelfInteropStaticCast(FuncDecl *funcDecl,
48914891
return pointeePropertyRefExpr;
48924892
}
48934893

4894-
enum class ReferenceReturnTypeBehaviorForBaseMethodSynthesis {
4895-
KeepReference,
4896-
RemoveReference,
4897-
RemoveReferenceIfPointer,
4898-
};
4899-
4900-
// Synthesize a C++ method that invokes the method from the base
4901-
// class. This lets Clang take care of the cast from the derived class
4902-
// to the base class during the invocation of the method.
4903-
static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
4904-
ClangImporter &impl, const clang::CXXRecordDecl *derivedClass,
4905-
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method,
4906-
ReferenceReturnTypeBehaviorForBaseMethodSynthesis
4907-
referenceReturnTypeBehavior =
4908-
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference,
4909-
bool forceConstQualifier = false,
4910-
bool isVirtualCall = false) {
4911-
auto &clangCtx = impl.getClangASTContext();
4912-
auto &clangSema = impl.getClangSema();
4913-
// When emitting symbolic decls, the method might not have a concrete
4914-
// record type as this type.
4915-
if (impl.isSymbolicImportEnabled()
4916-
&& !method->getThisType()->getPointeeCXXRecordDecl()) {
4917-
return nullptr;
4918-
}
4919-
4920-
// Create a new method in the derived class that calls the base method.
4921-
clang::DeclarationName name = method->getNameInfo().getName();
4922-
if (name.isIdentifier()) {
4923-
std::string newName;
4924-
llvm::raw_string_ostream os(newName);
4925-
os << (isVirtualCall ? "__synthesizedVirtualCall_" :
4926-
"__synthesizedBaseCall_")
4927-
<< name.getAsIdentifierInfo()->getName();
4928-
name = clang::DeclarationName(
4929-
&impl.getClangPreprocessor().getIdentifierTable().get(os.str()));
4930-
} else if (name.getCXXOverloadedOperator() == clang::OO_Subscript) {
4931-
name = clang::DeclarationName(
4932-
&impl.getClangPreprocessor().getIdentifierTable().get(
4933-
(isVirtualCall ? "__synthesizedVirtualCall_operatorSubscript" :
4934-
"__synthesizedBaseCall_operatorSubscript")));
4935-
} else if (name.getCXXOverloadedOperator() == clang::OO_Star) {
4936-
name = clang::DeclarationName(
4937-
&impl.getClangPreprocessor().getIdentifierTable().get(
4938-
(isVirtualCall ? "__synthesizedVirtualCall_operatorStar" :
4939-
"__synthesizedBaseCall_operatorStar")));
4940-
}
4941-
auto methodType = method->getType();
4942-
// Check if we need to drop the reference from the return type
4943-
// of the new method. This is needed when a synthesized `operator []`
4944-
// derived-to-base call is invoked from Swift's subscript getter.
4945-
if (referenceReturnTypeBehavior !=
4946-
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference) {
4947-
if (const auto *fpt = methodType->getAs<clang::FunctionProtoType>()) {
4948-
auto retType = fpt->getReturnType();
4949-
if (retType->isReferenceType() &&
4950-
(referenceReturnTypeBehavior ==
4951-
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
4952-
RemoveReference ||
4953-
(referenceReturnTypeBehavior ==
4954-
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
4955-
RemoveReferenceIfPointer &&
4956-
retType->getPointeeType()->isPointerType()))) {
4957-
methodType = clangCtx.getFunctionType(retType->getPointeeType(),
4958-
fpt->getParamTypes(),
4959-
fpt->getExtProtoInfo());
4960-
}
4961-
}
4962-
}
4963-
// Check if this method requires an additional `const` qualifier.
4964-
// This might needed when a non-const synthesized `operator []`
4965-
// derived-to-base call is invoked from Swift's subscript getter.
4966-
bool castThisToNonConstThis = false;
4967-
if (forceConstQualifier) {
4968-
if (const auto *fpt = methodType->getAs<clang::FunctionProtoType>()) {
4969-
auto info = fpt->getExtProtoInfo();
4970-
if (!info.TypeQuals.hasConst()) {
4971-
info.TypeQuals.addConst();
4972-
castThisToNonConstThis = true;
4973-
methodType = clangCtx.getFunctionType(fpt->getReturnType(),
4974-
fpt->getParamTypes(), info);
4975-
}
4976-
}
4977-
}
4978-
auto newMethod = clang::CXXMethodDecl::Create(
4979-
clangCtx, const_cast<clang::CXXRecordDecl *>(derivedClass),
4980-
method->getSourceRange().getBegin(),
4981-
clang::DeclarationNameInfo(name, clang::SourceLocation()), methodType,
4982-
method->getTypeSourceInfo(), method->getStorageClass(),
4983-
method->UsesFPIntrin(), /*isInline=*/true, method->getConstexprKind(),
4984-
method->getSourceRange().getEnd());
4985-
newMethod->setImplicit();
4986-
newMethod->setImplicitlyInline();
4987-
newMethod->setAccess(clang::AccessSpecifier::AS_public);
4988-
if (method->hasAttr<clang::CFReturnsRetainedAttr>()) {
4989-
// Return an FRT field at +1 if the base method also follows this
4990-
// convention.
4991-
newMethod->addAttr(clang::CFReturnsRetainedAttr::CreateImplicit(clangCtx));
4992-
}
4993-
4994-
llvm::SmallVector<clang::ParmVarDecl *, 4> params;
4995-
for (size_t i = 0; i < method->getNumParams(); ++i) {
4996-
const auto &param = *method->getParamDecl(i);
4997-
params.push_back(clang::ParmVarDecl::Create(
4998-
clangCtx, newMethod, param.getSourceRange().getBegin(),
4999-
param.getLocation(), param.getIdentifier(), param.getType(),
5000-
param.getTypeSourceInfo(), param.getStorageClass(),
5001-
/*DefExpr=*/nullptr));
5002-
}
5003-
newMethod->setParams(params);
5004-
5005-
// Create a new Clang diagnostic pool to capture any diagnostics
5006-
// emitted during the construction of the method.
5007-
clang::sema::DelayedDiagnosticPool diagPool{
5008-
clangSema.DelayedDiagnostics.getCurrentPool()};
5009-
auto diagState = clangSema.DelayedDiagnostics.push(diagPool);
5010-
5011-
// Construct the method's body.
5012-
clang::Expr *thisExpr = new (clangCtx) clang::CXXThisExpr(
5013-
clang::SourceLocation(), newMethod->getThisType(), /*IsImplicit=*/false);
5014-
if (castThisToNonConstThis) {
5015-
auto baseClassPtr =
5016-
clangCtx.getPointerType(clangCtx.getRecordType(derivedClass));
5017-
clang::CastKind Kind;
5018-
clang::CXXCastPath Path;
5019-
clangSema.CheckPointerConversion(thisExpr, baseClassPtr, Kind, Path,
5020-
/*IgnoreBaseAccess=*/false,
5021-
/*Diagnose=*/true);
5022-
auto conv = clangSema.ImpCastExprToType(thisExpr, baseClassPtr, Kind,
5023-
clang::VK_PRValue, &Path);
5024-
if (!conv.isUsable())
5025-
return nullptr;
5026-
thisExpr = conv.get();
5027-
}
5028-
5029-
auto memberExpr = clangSema.BuildMemberExpr(
5030-
thisExpr, /*isArrow=*/true, clang::SourceLocation(),
5031-
clang::NestedNameSpecifierLoc(), clang::SourceLocation(),
5032-
const_cast<clang::CXXMethodDecl *>(method),
5033-
clang::DeclAccessPair::make(const_cast<clang::CXXMethodDecl *>(method),
5034-
clang::AS_public),
5035-
/*HadMultipleCandidates=*/false, method->getNameInfo(),
5036-
clangCtx.BoundMemberTy, clang::VK_PRValue, clang::OK_Ordinary);
5037-
llvm::SmallVector<clang::Expr *, 4> args;
5038-
for (size_t i = 0; i < newMethod->getNumParams(); ++i) {
5039-
auto *param = newMethod->getParamDecl(i);
5040-
auto type = param->getType();
5041-
if (type->isReferenceType())
5042-
type = type->getPointeeType();
5043-
args.push_back(new (clangCtx) clang::DeclRefExpr(
5044-
clangCtx, param, false, type, clang::ExprValueKind::VK_LValue,
5045-
clang::SourceLocation()));
5046-
}
5047-
auto memberCall = clangSema.BuildCallToMemberFunction(
5048-
nullptr, memberExpr, clang::SourceLocation(), args,
5049-
clang::SourceLocation());
5050-
if (!memberCall.isUsable())
5051-
return nullptr;
5052-
auto returnStmt = clang::ReturnStmt::Create(clangCtx, clang::SourceLocation(),
5053-
memberCall.get(), nullptr);
5054-
5055-
// Check if there were any Clang errors during the construction
5056-
// of the method body.
5057-
clangSema.DelayedDiagnostics.popWithoutEmitting(diagState);
5058-
if (!diagPool.empty())
5059-
return nullptr;
5060-
5061-
newMethod->setBody(returnStmt);
5062-
return newMethod;
5063-
}
5064-
5065-
// Synthesize a C++ virtual method
5066-
clang::CXXMethodDecl *synthesizeCxxVirtualMethod(
5067-
swift::ClangImporter &Impl, const clang::CXXRecordDecl *derivedClass,
5068-
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method) {
5069-
return synthesizeCxxBaseMethod(
5070-
Impl, derivedClass, baseClass, method,
5071-
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference,
5072-
false /* forceConstQualifier */, true /* isVirtualCall */);
5073-
}
5074-
50754894
// Find the base C++ method called by the base function we want to synthesize
50764895
// the derived thunk for.
50774896
// The base C++ method is either the original C++ method that corresponds
@@ -5127,9 +4946,11 @@ FuncDecl *synthesizeBaseFunctionDeclCall(ClangImporter &impl, ASTContext &ctx,
51274946
auto *cxxMethod = getCalledBaseCxxMethod(baseMember);
51284947
if (!cxxMethod)
51294948
return nullptr;
5130-
auto *newClangMethod = synthesizeCxxBaseMethod(
5131-
impl, cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
5132-
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), cxxMethod);
4949+
auto *newClangMethod =
4950+
SwiftDeclSynthesizer(&impl).synthesizeCXXForwardingMethod(
4951+
cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
4952+
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), cxxMethod,
4953+
ForwardingMethodKind::Base);
51334954
if (!newClangMethod)
51344955
return nullptr;
51354956
return cast_or_null<FuncDecl>(
@@ -5388,19 +5209,22 @@ synthesizeBaseClassFieldGetterOrAddressGetterBody(AbstractFunctionDecl *afd,
53885209
if (auto *md = dyn_cast_or_null<clang::CXXMethodDecl>(baseClangDecl)) {
53895210
// Subscript operator, or `.pointee` wrapper is represented through a
53905211
// generated C++ method call that calls the base operator.
5391-
baseGetterCxxMethod = synthesizeCxxBaseMethod(
5392-
*static_cast<ClangImporter *>(ctx.getClangModuleLoader()),
5393-
cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
5394-
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), md,
5395-
getterDecl->getResultInterfaceType()->isForeignReferenceType()
5396-
? ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
5397-
RemoveReferenceIfPointer
5398-
: (kind != AccessorKind::Get
5399-
? ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
5400-
KeepReference
5401-
: ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
5402-
RemoveReference),
5403-
/*forceConstQualifier=*/kind != AccessorKind::MutableAddress);
5212+
baseGetterCxxMethod =
5213+
SwiftDeclSynthesizer(
5214+
static_cast<ClangImporter *>(ctx.getClangModuleLoader()))
5215+
.synthesizeCXXForwardingMethod(
5216+
cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
5217+
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), md,
5218+
ForwardingMethodKind::Base,
5219+
getterDecl->getResultInterfaceType()->isForeignReferenceType()
5220+
? ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
5221+
RemoveReferenceIfPointer
5222+
: (kind != AccessorKind::Get
5223+
? ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
5224+
KeepReference
5225+
: ReferenceReturnTypeBehaviorForBaseMethodSynthesis::
5226+
RemoveReference),
5227+
/*forceConstQualifier=*/kind != AccessorKind::MutableAddress);
54045228
} else if (auto *fd = dyn_cast_or_null<clang::FieldDecl>(baseClangDecl)) {
54055229
ValueDecl *retainOperationFn = nullptr;
54065230
// Check if this field getter is returning a retainable FRT.
@@ -6881,7 +6705,7 @@ static ValueDecl *addThunkForDependentTypes(FuncDecl *oldDecl,
68816705
// are not used in the function signature. We supply the type params as explicit
68826706
// metatype arguments to aid in typechecking, but they shouldn't be forwarded to
68836707
// the corresponding C++ function.
6884-
std::pair<BraceStmt *, bool>
6708+
static std::pair<BraceStmt *, bool>
68856709
synthesizeForwardingThunkBody(AbstractFunctionDecl *afd, void *context) {
68866710
ASTContext &ctx = afd->getASTContext();
68876711

lib/ClangImporter/ImportDecl.cpp

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3753,38 +3753,42 @@ namespace {
37533753
}
37543754
}
37553755

3756-
if (decl->isVirtual() && isa_and_nonnull<ValueDecl>(method)) {
3757-
if (Impl.isCxxInteropCompatVersionAtLeast(6)) {
3758-
if (auto dc = method->getDeclContext();
3759-
!decl->isPure() &&
3760-
isa_and_nonnull<NominalTypeDecl>(dc->getAsDecl())) {
3761-
3762-
// generates the __synthesizedVirtualCall_ C++ thunk
3763-
clang::CXXMethodDecl *cxxThunk = synthesizeCxxVirtualMethod(
3764-
*static_cast<ClangImporter *>(
3765-
dc->getASTContext().getClangModuleLoader()),
3766-
decl->getParent(), decl->getParent(), decl);
3767-
3768-
// call the __synthesizedVirtualCall_ C++ thunk from a Swift thunk
3769-
if (Decl *swiftThunk =
3770-
cxxThunk ? VisitCXXMethodDecl(cxxThunk) : nullptr;
3771-
isa_and_nonnull<FuncDecl>(swiftThunk)) {
3772-
// synthesize the body of the Swift method to call the swiftThunk
3773-
synthesizeForwardingThunkBody(cast<FuncDecl>(method),
3774-
cast<FuncDecl>(swiftThunk));
3775-
return method;
3756+
if (decl->isVirtual()) {
3757+
if (auto funcDecl = dyn_cast_or_null<FuncDecl>(method)) {
3758+
if (Impl.isCxxInteropCompatVersionAtLeast(6)) {
3759+
if (auto structDecl =
3760+
dyn_cast_or_null<StructDecl>(method->getDeclContext())) {
3761+
// If this is a method of a Swift struct, any possible override of
3762+
// this method would get sliced away, and an invocation would get
3763+
// dispatched statically. This is fine because it matches the C++
3764+
// behavior.
3765+
if (decl->isPure()) {
3766+
// If this is a pure virtual method, we won't have any
3767+
// implementation of it to invoke.
3768+
Impl.markUnavailable(
3769+
funcDecl, "virtual function is not available in Swift "
3770+
"because it is pure");
3771+
}
3772+
} else if (auto classDecl = dyn_cast_or_null<ClassDecl>(
3773+
funcDecl->getDeclContext())) {
3774+
// This is a foreign reference type. Since `class T` on the Swift
3775+
// side is mapped from `T*` on the C++ side, an invocation of a
3776+
// virtual method `t->method()` should get dispatched dynamically.
3777+
// Create a thunk that will perform dynamic dispatch.
3778+
// TODO: we don't have to import the actual `method` in this case,
3779+
// we can just synthesize a thunk and import that instead.
3780+
auto result = synthesizer.makeVirtualMethod(decl);
3781+
if (result) {
3782+
return result;
3783+
} else {
3784+
Impl.markUnavailable(
3785+
funcDecl, "virtual function is not available in Swift");
3786+
}
37763787
}
3788+
} else {
3789+
Impl.markUnavailable(
3790+
funcDecl, "virtual functions are not yet available in Swift");
37773791
}
3778-
3779-
Impl.markUnavailable(
3780-
cast<ValueDecl>(method),
3781-
decl->isPure() ? "virtual function is not available in Swift "
3782-
"because it is pure"
3783-
: "virtual function is not available in Swift");
3784-
} else {
3785-
Impl.markUnavailable(
3786-
cast<ValueDecl>(method),
3787-
"virtual functions are not yet available in Swift");
37883792
}
37893793
}
37903794

lib/ClangImporter/ImportName.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,14 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
22582258
baseName = newName;
22592259
}
22602260
}
2261+
if (method->isImplicit() &&
2262+
baseName.starts_with("__synthesizedVirtualCall_")) {
2263+
// If this is a thunk for a virtual method of a C++ reference type, we
2264+
// strip away the underscored prefix. This method should be visible and
2265+
// callable from Swift.
2266+
newName = baseName.substr(StringRef("__synthesizedVirtualCall_").size());
2267+
baseName = newName;
2268+
}
22612269
}
22622270

22632271
// swift_newtype-ed declarations may have common words with the type name

lib/ClangImporter/ImporterImpl.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,14 +2024,4 @@ bool isViewType(const clang::CXXRecordDecl *decl);
20242024
}
20252025
}
20262026

2027-
// Forwards to synthesizeCxxBasicMethod(), producing a thunk that calls a
2028-
// virtual function.
2029-
clang::CXXMethodDecl *synthesizeCxxVirtualMethod(
2030-
swift::ClangImporter &Impl, const clang::CXXRecordDecl *derivedClass,
2031-
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method);
2032-
2033-
// Exposed to produce a Swift method body for calling a Swift thunk.
2034-
std::pair<swift::BraceStmt *, bool>
2035-
synthesizeForwardingThunkBody(swift::AbstractFunctionDecl *afd, void *context);
2036-
20372027
#endif

0 commit comments

Comments
 (0)