Skip to content
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

[cxx-interop] import static operator call from C++23 as member callAs… #76523

Merged
merged 3 commits into from
Sep 20, 2024
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
12 changes: 12 additions & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3735,6 +3735,18 @@ namespace {
}

Decl *VisitCXXMethodDecl(const clang::CXXMethodDecl *decl) {
// The static `operator ()` introduced in C++ 23 is still callable as an
// instance operator in C++, and we want to preserve the ability to call
// it as an instance method in Swift as well for source compatibility.
// Therefore, we synthesize a C++ instance member that invokes the
// operator and import it instead.
if (decl->getOverloadedOperator() ==
clang::OverloadedOperatorKind::OO_Call &&
decl->isStatic()) {
auto result = synthesizer.makeInstanceToStaticOperatorCallMethod(decl);
if (result)
return result;
}
auto method = VisitFunctionDecl(decl);

// Do not expose constructors of abstract C++ classes.
Expand Down
43 changes: 39 additions & 4 deletions lib/ClangImporter/SwiftDeclSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2021,9 +2021,12 @@ clang::CXXMethodDecl *SwiftDeclSynthesizer::synthesizeCXXForwardingMethod(

auto &clangCtx = ImporterImpl.getClangASTContext();
auto &clangSema = ImporterImpl.getClangSema();
assert(!method->isStatic() ||
method->getNameInfo().getName().getCXXOverloadedOperator() ==
clang::OO_Call);
// When emitting symbolic decls, the method might not have a concrete
// record type as this type.
if (ImporterImpl.importSymbolicCXXDecls &&
if (ImporterImpl.importSymbolicCXXDecls && !method->isStatic() &&
!method->getThisType()->getPointeeCXXRecordDecl())
return nullptr;

Expand Down Expand Up @@ -2051,6 +2054,11 @@ clang::CXXMethodDecl *SwiftDeclSynthesizer::synthesizeCXXForwardingMethod(
(forwardingMethodKind == ForwardingMethodKind::Virtual
? "__synthesizedVirtualCall_operatorStar"
: "__synthesizedBaseCall_operatorStar")));
} else if (name.getCXXOverloadedOperator() == clang::OO_Call) {
assert(forwardingMethodKind != ForwardingMethodKind::Virtual);
name = clang::DeclarationName(
&ImporterImpl.getClangPreprocessor().getIdentifierTable().get(
"__synthesizedBaseCall_operatorCall"));
}
auto methodType = method->getType();
// Check if we need to drop the reference from the return type
Expand Down Expand Up @@ -2093,7 +2101,8 @@ clang::CXXMethodDecl *SwiftDeclSynthesizer::synthesizeCXXForwardingMethod(
clangCtx, const_cast<clang::CXXRecordDecl *>(derivedClass),
method->getSourceRange().getBegin(),
clang::DeclarationNameInfo(name, clang::SourceLocation()), methodType,
method->getTypeSourceInfo(), method->getStorageClass(),
method->getTypeSourceInfo(),
method->isStatic() ? clang::SC_None : method->getStorageClass(),
method->UsesFPIntrin(), /*isInline=*/true, method->getConstexprKind(),
method->getSourceRange().getEnd());
newMethod->setImplicit();
Expand Down Expand Up @@ -2140,14 +2149,19 @@ clang::CXXMethodDecl *SwiftDeclSynthesizer::synthesizeCXXForwardingMethod(
thisExpr = conv.get();
}

auto memberExprTy =
(method->isStatic() && method->getOverloadedOperator() ==
clang::OverloadedOperatorKind::OO_Call)
? method->getType()
: clangCtx.BoundMemberTy;
auto memberExpr = clangSema.BuildMemberExpr(
thisExpr, /*isArrow=*/true, clang::SourceLocation(),
clang::NestedNameSpecifierLoc(), clang::SourceLocation(),
const_cast<clang::CXXMethodDecl *>(method),
clang::DeclAccessPair::make(const_cast<clang::CXXMethodDecl *>(method),
clang::AS_public),
/*HadMultipleCandidates=*/false, method->getNameInfo(),
clangCtx.BoundMemberTy, clang::VK_PRValue, clang::OK_Ordinary);
memberExprTy, clang::VK_PRValue, clang::OK_Ordinary);
llvm::SmallVector<clang::Expr *, 4> args;
for (size_t i = 0; i < newMethod->getNumParams(); ++i) {
auto *param = newMethod->getParamDecl(i);
Expand All @@ -2158,7 +2172,7 @@ clang::CXXMethodDecl *SwiftDeclSynthesizer::synthesizeCXXForwardingMethod(
clangCtx, param, false, type, clang::ExprValueKind::VK_LValue,
clang::SourceLocation()));
}
auto memberCall = clangSema.BuildCallToMemberFunction(
auto memberCall = clangSema.BuildCallExpr(
nullptr, memberExpr, clang::SourceLocation(), args,
clang::SourceLocation());
if (!memberCall.isUsable())
Expand Down Expand Up @@ -2264,6 +2278,27 @@ FuncDecl *SwiftDeclSynthesizer::makeVirtualMethod(
return result;
}

// MARK: C++ operators

FuncDecl *SwiftDeclSynthesizer::makeInstanceToStaticOperatorCallMethod(
const clang::CXXMethodDecl *clangMethodDecl) {
auto clangDC = clangMethodDecl->getParent();
auto &ctx = ImporterImpl.SwiftContext;

assert(clangMethodDecl->isStatic() && "Expected a static operator");

auto newMethod = synthesizeCXXForwardingMethod(
clangDC, clangDC, clangMethodDecl, ForwardingMethodKind::Base,
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference,
/*forceConstQualifier*/ true);
newMethod->addAttr(clang::SwiftNameAttr::CreateImplicit(
clangMethodDecl->getASTContext(), "callAsFunction"));

auto result = dyn_cast_or_null<FuncDecl>(
ctx.getClangModuleLoader()->importDeclDirectly(newMethod));
return result;
}

// MARK: C++ properties

static std::pair<BraceStmt *, bool>
Expand Down
3 changes: 3 additions & 0 deletions lib/ClangImporter/SwiftDeclSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ class SwiftDeclSynthesizer {
/// method that dispatches the call dynamically.
FuncDecl *makeVirtualMethod(const clang::CXXMethodDecl *clangMethodDecl);

FuncDecl *makeInstanceToStaticOperatorCallMethod(
const clang::CXXMethodDecl *clangMethodDecl);

VarDecl *makeComputedPropertyFromCXXMethods(FuncDecl *getter,
FuncDecl *setter);

Expand Down
4 changes: 4 additions & 0 deletions test/Interop/Cxx/class/Inputs/protocol-conformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ struct HasVirtualMethod {
virtual int return42() { return 42; }
};

struct HasStaticOperatorCall {
static int operator()(int x) { return x * 2; }
};

#endif // TEST_INTEROP_CXX_CLASS_INPUTS_PROTOCOL_CONFORMANCE_H
6 changes: 6 additions & 0 deletions test/Interop/Cxx/class/protocol-conformance-typechecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ protocol HasOperatorPlusEqualProtocol {
}

extension HasOperatorPlusEqualInt : HasOperatorPlusEqualProtocol {}

protocol HasOperatorCall {
func callAsFunction(_ x: Int32) -> Int32
}

extension HasStaticOperatorCall : HasOperatorCall {}
24 changes: 24 additions & 0 deletions test/Interop/Cxx/operators/Inputs/member-inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,4 +477,28 @@ struct HasOperatorCallWithDefaultArg {
int operator()(int x = 0) const { return value + x; }
};

class HasStaticOperatorCallBase {
public:
static int operator()(int x) { return x + 42; }
};

class HasStaticOperatorCallBaseNonTrivial: public NonTrivial {
public:
HasStaticOperatorCallBaseNonTrivial() {}
HasStaticOperatorCallBaseNonTrivial(const HasStaticOperatorCallBaseNonTrivial &self) : NonTrivial(self) {}
HasStaticOperatorCallBaseNonTrivial(HasStaticOperatorCallBaseNonTrivial &&self) : NonTrivial(self) {}

static int operator()(const NonTrivial &arg) { return arg.f + 42; }
};

class HasStaticOperatorCallDerived : public HasStaticOperatorCallBase {};

class HasStaticOperatorCallWithConstOperator {
public:
inline int operator()(int x, int y) const { return x + y; }
static int operator()(int x) {
return x - 1;
}
};

#endif
13 changes: 13 additions & 0 deletions test/Interop/Cxx/operators/member-inline-module-interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,16 @@
// CHECK: struct HasOperatorCallWithDefaultArg {
// CHECK: func callAsFunction(_ x: Int32 = cxxDefaultArg) -> Int32
// CHECK: }

// CHECK: struct HasStaticOperatorCallBase {
// CHECK: func callAsFunction(_ x: Int32) -> Int32
// CHECK: }

// CHECK: struct HasStaticOperatorCallDerived {
// CHECK: func callAsFunction(_ x: Int32) -> Int32
// CHECK: }

// CHECK: struct HasStaticOperatorCallWithConstOperator {
// CHECK: func callAsFunction(_ x: Int32, _ y: Int32) -> Int32
// CHECK: func callAsFunction(_ x: Int32) -> Int32
// CHECK: }
27 changes: 27 additions & 0 deletions test/Interop/Cxx/operators/member-inline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,31 @@ OperatorsTestSuite.test("HasOperatorCallWithDefaultArg.call") {
expectEqual(444, res)
}

OperatorsTestSuite.test("HasStaticOperatorCallBase.call") {
let h = HasStaticOperatorCallBase()
let res = h(1)
expectEqual(43, res)
}

OperatorsTestSuite.test("HasStaticOperatorCallBase2.call") {
let m = NonTrivial()
let h = HasStaticOperatorCallBaseNonTrivial()
let res = h(m)
expectEqual(48, res)
}

OperatorsTestSuite.test("HasStaticOperatorCallDerived.call") {
let h = HasStaticOperatorCallDerived()
let res = h(0)
expectEqual(42, res)
}

OperatorsTestSuite.test("HasStaticOperatorCallWithConstOperator.call") {
let h = HasStaticOperatorCallWithConstOperator()
let res = h(10)
expectEqual(9, res)
let res2 = h(3, 5)
expectEqual(8, res2)
}

runAllTests()