Skip to content

[cxx-interop] Support importing subscript operators with multiple params #83487

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 1 commit into from
Aug 6, 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
54 changes: 29 additions & 25 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2647,22 +2647,23 @@ namespace {
result->addMember(p);
}

for (auto &subscriptInfo : Impl.cxxSubscripts) {
auto declAndParameterType = subscriptInfo.first;
if (declAndParameterType.first != result)
continue;

auto getterAndSetter = subscriptInfo.second;
auto subscript = synthesizer.makeSubscript(getterAndSetter.first,
getterAndSetter.second);
// Also add subscripts directly because they won't be found from the
// clang decl.
result->addMember(subscript);

// Add the subscript as an alternative for the getter so that it gets
// carried into derived classes.
auto *subscriptImpl = getterAndSetter.first ? getterAndSetter.first : getterAndSetter.second;
Impl.addAlternateDecl(subscriptImpl, subscript);
auto it = Impl.cxxSubscripts.find(result);
if (it != Impl.cxxSubscripts.end()) {
for (auto &subscriptInfo : it->second) {
auto getterAndSetter = subscriptInfo.second;
auto subscript = synthesizer.makeSubscript(getterAndSetter.first,
getterAndSetter.second);
// Also add subscripts directly because they won't be found from the
// clang decl.
result->addMember(subscript);

// Add the subscript as an alternative for the getter so that it
// gets carried into derived classes.
auto *subscriptImpl = getterAndSetter.first
? getterAndSetter.first
: getterAndSetter.second;
Impl.addAlternateDecl(subscriptImpl, subscript);
}
}

auto getterAndSetterIt = Impl.cxxDereferenceOperators.find(result);
Expand Down Expand Up @@ -3633,14 +3634,16 @@ namespace {
return true;

if (importedName.isSubscriptAccessor()) {
assert(func->getParameters()->size() == 1);
auto parameter = func->getParameters()->get(0);
auto parameterType = parameter->getTypeInContext();
if (!typeDecl || !parameterType)
return false;
if (parameter->isInOut())
// Subscripts with inout parameters are not allowed in Swift.
return false;
SmallVector<TypeBase *> params;
for (auto parameter : *(func->getParameters())) {
auto parameterType = parameter->getTypeInContext();
if (!typeDecl || !parameterType)
return false;
if (parameter->isInOut())
// Subscripts with inout parameters are not allowed in Swift.
return false;
params.push_back(parameterType.getPointer());
}
// Subscript setter is marked as mutating in Swift even if the
// C++ `operator []` is `const`.
if (importedName.getAccessorKind() ==
Expand All @@ -3649,7 +3652,8 @@ namespace {
!typeDecl->getDeclaredType()->isForeignReferenceType())
func->setSelfAccessKind(SelfAccessKind::Mutating);

auto &getterAndSetter = Impl.cxxSubscripts[{typeDecl, parameterType}];
auto &getterAndSetterMap = Impl.cxxSubscripts[typeDecl];
auto &getterAndSetter = getterAndSetterMap[params];

switch (importedName.getAccessorKind()) {
case ImportedAccessorKind::SubscriptGetter:
Expand Down
8 changes: 6 additions & 2 deletions lib/ClangImporter/ImporterImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "swift/AST/ASTContext.h"
#include "swift/AST/Decl.h"
#include "swift/AST/ForeignErrorConvention.h"
#include "swift/AST/Identifier.h"
#include "swift/AST/LazyResolver.h"
#include "swift/AST/Module.h"
#include "swift/AST/RequirementSignature.h"
Expand Down Expand Up @@ -683,8 +684,11 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
///
/// `.first` corresponds to a getter
/// `.second` corresponds to a setter
llvm::MapVector<std::pair<NominalTypeDecl *, Type>,
std::pair<FuncDecl *, FuncDecl *>> cxxSubscripts;
llvm::DenseMap<NominalTypeDecl *,
llvm::MapVector<SmallVector<TypeBase *>,
std::pair<FuncDecl *, FuncDecl *>,
std::map<SmallVector<TypeBase *>, unsigned>>>
cxxSubscripts;

llvm::MapVector<NominalTypeDecl *, std::pair<FuncDecl *, FuncDecl *>>
cxxDereferenceOperators;
Expand Down
86 changes: 44 additions & 42 deletions lib/ClangImporter/SwiftDeclSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
//
//===----------------------------------------------------------------------===//

#include "CXXMethodBridging.h"
#include "SwiftDeclSynthesizer.h"
#include "CXXMethodBridging.h"
#include "swift/AST/ASTMangler.h"
#include "swift/AST/Attr.h"
#include "swift/AST/AttrKind.h"
Expand Down Expand Up @@ -47,7 +47,7 @@ static Argument createSelfArg(AccessorDecl *accessorDecl) {

static CallExpr *createAccessorImplCallExpr(FuncDecl *accessorImpl,
Argument selfArg,
DeclRefExpr *keyRefExpr = nullptr) {
ArrayRef<Expr *> keyRefExprs) {
ASTContext &ctx = accessorImpl->getASTContext();

auto accessorImplExpr =
Expand All @@ -60,12 +60,8 @@ static CallExpr *createAccessorImplCallExpr(FuncDecl *accessorImpl,
accessorImplDotCallExpr->setType(accessorImpl->getMethodInterfaceType());
accessorImplDotCallExpr->setThrows(nullptr);

ArgumentList *argList;
if (keyRefExpr) {
argList = ArgumentList::forImplicitUnlabeled(ctx, {keyRefExpr});
} else {
argList = ArgumentList::forImplicitUnlabeled(ctx, {});
}
ArgumentList *argList = ArgumentList::forImplicitUnlabeled(ctx, keyRefExprs);

auto *accessorImplCallExpr =
CallExpr::createImplicit(ctx, accessorImplDotCallExpr, argList);
accessorImplCallExpr->setType(accessorImpl->getResultInterfaceType());
Expand Down Expand Up @@ -1556,33 +1552,36 @@ synthesizeUnwrappingGetterOrAddressGetterBody(AbstractFunctionDecl *afd,
ASTContext &ctx = getterDecl->getASTContext();

auto selfArg = createSelfArg(getterDecl);
DeclRefExpr *keyRefExpr = getterDecl->getParameters()->size() == 0
? nullptr
: createParamRefExpr(getterDecl, 0);
SmallVector<Expr *> arguments;
for (size_t idx = 0, end = getterDecl->getParameters()->size(); idx < end;
++idx)
arguments.push_back(createParamRefExpr(getterDecl, idx));

Type elementTy = getterDecl->getResultInterfaceType();

auto *getterImplCallExpr =
createAccessorImplCallExpr(getterImpl, selfArg, keyRefExpr);
createAccessorImplCallExpr(getterImpl, selfArg, arguments);

// This default handles C++'s operator[] that returns a value type.
Expr *propertyExpr = getterImplCallExpr;
PointerTypeKind ptrKind;

// The following check returns true if the subscript operator returns a C++
// reference type. This check actually checks to see if the type is a pointer
// type, but this does not apply to C pointers because they are Optional types
// when imported. TODO: Use a more obvious check here.
// The following check returns true if the subscript operator returns a
// C++ reference type. This check actually checks to see if the type is
// a pointer type, but this does not apply to C pointers because they
// are Optional types when imported. TODO: Use a more obvious check
// here.
if (!isAddress &&
getterImpl->getResultInterfaceType()->getAnyPointerElementType(ptrKind)) {
// `getterImpl` can return either UnsafePointer or UnsafeMutablePointer.
// Retrieve the corresponding `.pointee` declaration.
// `getterImpl` can return either UnsafePointer or
// UnsafeMutablePointer. Retrieve the corresponding `.pointee`
// declaration.
VarDecl *pointeePropertyDecl = ctx.getPointerPointeePropertyDecl(ptrKind);

// Handle operator[] that returns a reference type.
SubstitutionMap subMap = SubstitutionMap::get(
ctx.getUnsafePointerDecl()->getGenericSignature(), {elementTy},
LookUpConformanceInModule());
SubstitutionMap subMap =
SubstitutionMap::get(ctx.getUnsafePointerDecl()->getGenericSignature(),
{elementTy}, LookUpConformanceInModule());
auto pointeePropertyRefExpr = new (ctx) MemberRefExpr(
getterImplCallExpr, SourceLoc(),
ConcreteDeclRef(pointeePropertyDecl, subMap), DeclNameLoc(),
Expand Down Expand Up @@ -1627,16 +1626,15 @@ synthesizeUnwrappingSetterBody(AbstractFunctionDecl *afd, void *context) {

auto selfArg = createSelfArg(setterDecl);
DeclRefExpr *valueParamRefExpr = createParamRefExpr(setterDecl, 0);
// For a subscript this decl will have two parameters, for a pointee property
// it will only have one.
DeclRefExpr *keyParamRefExpr = setterDecl->getParameters()->size() == 1
? nullptr
: createParamRefExpr(setterDecl, 1);
SmallVector<Expr *> arguments;
for (size_t idx = 1, end = setterDecl->getParameters()->size(); idx < end;
++idx)
arguments.push_back(createParamRefExpr(setterDecl, idx));

Type elementTy = valueParamRefExpr->getDecl()->getInterfaceType();

auto *setterImplCallExpr =
createAccessorImplCallExpr(setterImpl, selfArg, keyParamRefExpr);
createAccessorImplCallExpr(setterImpl, selfArg, arguments);

VarDecl *pointeePropertyDecl =
ctx.getPointerPointeePropertyDecl(PTK_UnsafeMutablePointer);
Expand Down Expand Up @@ -1673,7 +1671,7 @@ synthesizeUnwrappingAddressSetterBody(AbstractFunctionDecl *afd,

auto selfArg = createSelfArg(setterDecl);
auto *setterImplCallExpr =
createAccessorImplCallExpr(setterImpl, selfArg, nullptr);
createAccessorImplCallExpr(setterImpl, selfArg, {});

auto *returnStmt = ReturnStmt::createImplicit(ctx, setterImplCallExpr);

Expand Down Expand Up @@ -1701,15 +1699,16 @@ SubscriptDecl *SwiftDeclSynthesizer::makeSubscript(FuncDecl *getter,

auto &ctx = ImporterImpl.SwiftContext;

assert(getterImpl->getParameters()->size() == 1 &&
"subscript can only have 1 parameter");
auto bodyParam = ParamDecl::clone(ctx, getterImpl->getParameters()->get(0));
// If the subscript parameter is unnamed, give it a name to make sure SILGen
// creates a variable for it.
if (bodyParam->getName().empty())
bodyParam->setName(ctx.getIdentifier("__index"));

auto bodyParams = ParameterList::create(ctx, bodyParam);
SmallVector<ParamDecl *> paramVec;
for (auto [i, param] : llvm::enumerate(*getterImpl->getParameters())) {
auto clonedParam = ParamDecl::clone(ctx, param);
// If the subscript parameter is unnamed, give it a name to make sure SILGen
// creates a variable for it.
if (clonedParam->getName().empty())
clonedParam->setName(ctx.getIdentifier("__index" + std::to_string(i)));
paramVec.push_back(clonedParam);
}
auto bodyParams = ParameterList::create(ctx, paramVec);
DeclName name(ctx, DeclBaseName::createSubscript(), bodyParams);
auto dc = getterImpl->getDeclContext();

Expand Down Expand Up @@ -1744,8 +1743,11 @@ SubscriptDecl *SwiftDeclSynthesizer::makeSubscript(FuncDecl *getter,
paramVarDecl->setSpecifier(ParamSpecifier::Default);
paramVarDecl->setInterfaceType(elementTy);

auto setterParamList =
ParameterList::create(ctx, {paramVarDecl, bodyParams->get(0)});
SmallVector<ParamDecl *> setterParams;
setterParams.push_back(paramVarDecl);
setterParams.append(bodyParams->begin(), bodyParams->end());

auto setterParamList = ParameterList::create(ctx, setterParams);

setterDecl = AccessorDecl::create(
ctx, setterImpl->getLoc(), setterImpl->getLoc(), AccessorKind::Set,
Expand Down Expand Up @@ -1918,7 +1920,7 @@ synthesizeSuccessorFuncBody(AbstractFunctionDecl *afd, void *context) {

// Call `operator++`.
auto incrementExpr = createAccessorImplCallExpr(
incrementImpl, Argument::implicitInOut(ctx, copyRefLValueExpr));
incrementImpl, Argument::implicitInOut(ctx, copyRefLValueExpr), {});

auto copyRefRValueExpr = new (ctx) DeclRefExpr(copyDecl, DeclNameLoc(),
/*implicit*/ true);
Expand Down Expand Up @@ -2331,7 +2333,7 @@ synthesizeComputedGetterFromCXXMethod(AbstractFunctionDecl *afd,

auto selfArg = createSelfArg(accessor);

auto *getterImplCallExpr = createAccessorImplCallExpr(method, selfArg);
auto *getterImplCallExpr = createAccessorImplCallExpr(method, selfArg, {});
auto &ctx = method->getASTContext();
auto *returnStmt = ReturnStmt::createImplicit(ctx, getterImplCallExpr);
auto *body = BraceStmt::create(ctx, SourceLoc(), {returnStmt}, SourceLoc());
Expand All @@ -2349,7 +2351,7 @@ synthesizeComputedSetterFromCXXMethod(AbstractFunctionDecl *afd,
DeclRefExpr *valueParamRefExpr = createParamRefExpr(setterDecl, 0);

auto *getterImplCallExpr =
createAccessorImplCallExpr(setterImpl, selfArg, valueParamRefExpr);
createAccessorImplCallExpr(setterImpl, selfArg, {valueParamRefExpr});

auto body = BraceStmt::create(setterImpl->getASTContext(), SourceLoc(),
{getterImplCallExpr}, SourceLoc());
Expand Down
14 changes: 14 additions & 0 deletions test/Interop/Cxx/operators/Inputs/member-inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,20 @@ struct ReadWriteIntArray {
};
};

#if __cplusplus >= 202302L
struct NullarySubscript {
int operator[]() const { return 42; }
int &operator[]() { return field; }
int field;
};

struct BinarySubscript {
int operator[](int x, int y) const { return x + y; }
int &operator[](int, int) { return field; }
int field;
};
#endif

struct __attribute__((swift_attr("import_owned"))) ReadOnlyIntArray {
private:
int values[5] = { 1, 2, 3, 4, 5 };
Expand Down
25 changes: 20 additions & 5 deletions test/Interop/Cxx/operators/member-inline-module-interface.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberInline -I %S/Inputs -source-filename=x -cxx-interoperability-mode=swift-5.9 | %FileCheck %s
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberInline -I %S/Inputs -source-filename=x -cxx-interoperability-mode=swift-6 | %FileCheck %s
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberInline -I %S/Inputs -source-filename=x -cxx-interoperability-mode=upcoming-swift | %FileCheck %s
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberInline -I %S/Inputs -source-filename=x -cxx-interoperability-mode=swift-5.9 -Xcc -std=c++23 | %FileCheck %s
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberInline -I %S/Inputs -source-filename=x -cxx-interoperability-mode=swift-6 -Xcc -std=c++23 | %FileCheck %s
// RUN: %target-swift-ide-test -print-module -module-to-print=MemberInline -I %S/Inputs -source-filename=x -cxx-interoperability-mode=upcoming-swift -Xcc -std=c++23 | %FileCheck %s

// CHECK: struct LoadableIntWrapper {
// CHECK: func successor() -> LoadableIntWrapper
Expand Down Expand Up @@ -58,6 +58,21 @@
// CHECK: }
// CHECK: }

// CHECK: struct NullarySubscript {
// CHECK: subscript() -> Int32
// CHECK: @available(*, unavailable, message: "use subscript")
// CHECK: func __operatorSubscriptConst() -> Int32
// CHECK: @available(*, unavailable, message: "use subscript")
// CHECK: mutating func __operatorSubscript() -> UnsafeMutablePointer<Int32>
// CHECK: }

// CHECK: struct BinarySubscript {
// CHECK: subscript(x: Int32, y: Int32) -> Int32
// CHECK: @available(*, unavailable, message: "use subscript")
// CHECK: func __operatorSubscriptConst(_ x: Int32, _ y: Int32) -> Int32
// CHECK: @available(*, unavailable, message: "use subscript")
// CHECK: mutating func __operatorSubscript(_: Int32, _: Int32) -> UnsafeMutablePointer<Int32>
// CHECK: }

// CHECK: struct ReadOnlyIntArray {
// CHECK: subscript(x: Int32) -> Int32 { get }
Expand Down Expand Up @@ -212,10 +227,10 @@
// CHECK: }

// CHECK: struct SubscriptUnnamedParameter {
// CHECK: subscript(__index: Int32) -> Int32 { get }
// CHECK: subscript(__index0: Int32) -> Int32 { get }
// CHECK: }
// CHECK: struct SubscriptUnnamedParameterReadWrite {
// CHECK: subscript(__index: Int32) -> Int32
// CHECK: subscript(__index0: Int32) -> Int32
// CHECK: }

// CHECK: struct Iterator {
Expand Down
16 changes: 15 additions & 1 deletion test/Interop/Cxx/operators/member-inline.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -cxx-interoperability-mode=swift-5.9)
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -cxx-interoperability-mode=swift-6)
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -cxx-interoperability-mode=upcoming-swift)
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -cxx-interoperability-mode=upcoming-swift -Xcc -std=c++23 -D CPP23)
//
// REQUIRES: executable_test

Expand Down Expand Up @@ -185,6 +185,20 @@ OperatorsTestSuite.test("ReadWriteIntArray.subscript (inline)") {
expectEqual(234, resultAfter)
}

#if CPP23
OperatorsTestSuite.test("Subscript operators with parameter number != 1") {
var ns = NullarySubscript()
expectEqual(42, ns[])
ns[] = 5
expectEqual(5, ns.field)

var bs = BinarySubscript()
expectEqual(10, bs[3, 7])
bs[1, 2] = 6
expectEqual(6, bs.field)
}
#endif

OperatorsTestSuite.test("DerivedFromReadWriteIntArray.subscript (inline, base class)") {
var arr = DerivedFromReadWriteIntArray()

Expand Down