diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 8951e4895b9c3..9736a72367384 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -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. diff --git a/lib/ClangImporter/SwiftDeclSynthesizer.cpp b/lib/ClangImporter/SwiftDeclSynthesizer.cpp index 3bd00a0dddbb8..0027daae45bfa 100644 --- a/lib/ClangImporter/SwiftDeclSynthesizer.cpp +++ b/lib/ClangImporter/SwiftDeclSynthesizer.cpp @@ -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; @@ -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 @@ -2093,7 +2101,8 @@ clang::CXXMethodDecl *SwiftDeclSynthesizer::synthesizeCXXForwardingMethod( clangCtx, const_cast(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(); @@ -2140,6 +2149,11 @@ 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(), @@ -2147,7 +2161,7 @@ clang::CXXMethodDecl *SwiftDeclSynthesizer::synthesizeCXXForwardingMethod( clang::DeclAccessPair::make(const_cast(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 args; for (size_t i = 0; i < newMethod->getNumParams(); ++i) { auto *param = newMethod->getParamDecl(i); @@ -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()) @@ -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( + ctx.getClangModuleLoader()->importDeclDirectly(newMethod)); + return result; +} + // MARK: C++ properties static std::pair diff --git a/lib/ClangImporter/SwiftDeclSynthesizer.h b/lib/ClangImporter/SwiftDeclSynthesizer.h index ae54c8406ee53..e7ab62a46e7ab 100644 --- a/lib/ClangImporter/SwiftDeclSynthesizer.h +++ b/lib/ClangImporter/SwiftDeclSynthesizer.h @@ -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); diff --git a/test/Interop/Cxx/class/Inputs/protocol-conformance.h b/test/Interop/Cxx/class/Inputs/protocol-conformance.h index aa504d1d9e36f..bc95f2a058bf8 100644 --- a/test/Interop/Cxx/class/Inputs/protocol-conformance.h +++ b/test/Interop/Cxx/class/Inputs/protocol-conformance.h @@ -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 diff --git a/test/Interop/Cxx/class/protocol-conformance-typechecker.swift b/test/Interop/Cxx/class/protocol-conformance-typechecker.swift index 4b9754b6be0c6..3fb6c0bab719b 100644 --- a/test/Interop/Cxx/class/protocol-conformance-typechecker.swift +++ b/test/Interop/Cxx/class/protocol-conformance-typechecker.swift @@ -49,3 +49,9 @@ protocol HasOperatorPlusEqualProtocol { } extension HasOperatorPlusEqualInt : HasOperatorPlusEqualProtocol {} + +protocol HasOperatorCall { + func callAsFunction(_ x: Int32) -> Int32 +} + +extension HasStaticOperatorCall : HasOperatorCall {} \ No newline at end of file diff --git a/test/Interop/Cxx/operators/Inputs/member-inline.h b/test/Interop/Cxx/operators/Inputs/member-inline.h index c168de6a5bd89..873dca8c79df3 100644 --- a/test/Interop/Cxx/operators/Inputs/member-inline.h +++ b/test/Interop/Cxx/operators/Inputs/member-inline.h @@ -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 diff --git a/test/Interop/Cxx/operators/member-inline-module-interface.swift b/test/Interop/Cxx/operators/member-inline-module-interface.swift index e9a40f365a691..d5d454def2ffc 100644 --- a/test/Interop/Cxx/operators/member-inline-module-interface.swift +++ b/test/Interop/Cxx/operators/member-inline-module-interface.swift @@ -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: } diff --git a/test/Interop/Cxx/operators/member-inline.swift b/test/Interop/Cxx/operators/member-inline.swift index 8354e7cc04035..adf5bff3b5bad 100644 --- a/test/Interop/Cxx/operators/member-inline.swift +++ b/test/Interop/Cxx/operators/member-inline.swift @@ -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()