From 42101e0c2fec21b8551f3c8785a58cf13f49054d Mon Sep 17 00:00:00 2001 From: Ian Leitch Date: Thu, 18 Dec 2025 11:20:12 +0100 Subject: [PATCH] [Index] Fix incorrect setter references for subscript keys Properties used as subscript keys (e.g., `dictionary[key] = 42`) were incorrectly indexed as writes, referencing setters instead of getters. Fix by forcing subscript arguments to be treated as reads, except for dynamic member lookup subscripts where the argument represents the member being accessed. Resolves #56541 --- lib/IDE/SourceEntityWalker.cpp | 27 +++++++++++++++++++-- test/Index/index_subscript_key.swift | 36 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 test/Index/index_subscript_key.swift diff --git a/lib/IDE/SourceEntityWalker.cpp b/lib/IDE/SourceEntityWalker.cpp index 40dacd93193bc..c0500fb5bd95e 100644 --- a/lib/IDE/SourceEntityWalker.cpp +++ b/lib/IDE/SourceEntityWalker.cpp @@ -451,8 +451,31 @@ ASTWalker::PreWalkResult SemaAnnotator::walkToExprPre(Expr *E) { return Action::Stop(); } - if (!SE->getArgs()->walk(*this)) - return Action::Stop(); + // For regular subscripts (not dynamic member lookups), the index expressions + // are always read, regardless of how the subscript is being used (read or write). + // Do not propagate a write access kind into the subscript arguments. + // However, for dynamic member lookup subscripts, the argument represents + // a member being accessed, so it should reflect the access kind. + bool isDynamicMemberLookup = false; + if (SubscrD) { + if (auto *args = SE->getArgs()) { + auto &ctx = SubscrD->getASTContext(); + if (args->isUnary() && + args->getLabel(0) == ctx.Id_dynamicMember) { + isDynamicMemberLookup = true; + } + } + } + + if (!isDynamicMemberLookup) { + llvm::SaveAndRestore> + C(this->OpAccess, AccessKind::Read); + if (!SE->getArgs()->walk(*this)) + return Action::Stop(); + } else { + if (!SE->getArgs()->walk(*this)) + return Action::Stop(); + } if (SubscrD) { if (!passSubscriptReference(SubscrD, E->getEndLoc(), data, false)) diff --git a/test/Index/index_subscript_key.swift b/test/Index/index_subscript_key.swift new file mode 100644 index 0000000000000..2f2dea0ab102e --- /dev/null +++ b/test/Index/index_subscript_key.swift @@ -0,0 +1,36 @@ +// RUN: %target-swift-ide-test -print-indexed-symbols -source-filename %s | %FileCheck %s + +// Test that properties used as subscript keys are treated as reads (getter), not writes (setter). +let key = "key" +var dictionary: [String: Any] = [:] +// CHECK: [[@LINE+2]]:12 | variable/Swift | key | {{.*}} | Ref,Read | rel: 0 +// CHECK: [[@LINE+1]]:12 | function/acc-get/Swift | getter:key | {{.*}} | Ref,Call,Impl | rel: 0 +dictionary[key] = 42 + +// Test that chained property accesses used as subscript keys are treated as reads. +struct Config { + var settings: Settings +} +struct Settings { + var apiKey: String +} +let config = Config(settings: Settings(apiKey: "secret")) +var apiKeyDict: [String: String] = [:] +// CHECK: [[@LINE+6]]:12 | variable/Swift | config | {{.*}} | Ref,Read | rel: 0 +// CHECK: [[@LINE+5]]:12 | function/acc-get/Swift | getter:config | {{.*}} | Ref,Call,Impl | rel: 0 +// CHECK: [[@LINE+4]]:19 | instance-property/Swift | settings | {{.*}} | Ref,Read | rel: 0 +// CHECK: [[@LINE+3]]:19 | instance-method/acc-get/Swift | getter:settings | {{.*}} | Ref,Call,Impl | rel: 0 +// CHECK: [[@LINE+2]]:28 | instance-property/Swift | apiKey | {{.*}} | Ref,Read | rel: 0 +// CHECK: [[@LINE+1]]:28 | instance-method/acc-get/Swift | getter:apiKey | {{.*}} | Ref,Call,Impl | rel: 0 +apiKeyDict[config.settings.apiKey] = "new-secret" + +// Test that keypaths used as subscript keys are also treated as reads. +struct KeyPathContainer { + var value: Int = 0 +} +let container = KeyPathContainer() +var keyPathDict: [KeyPath: Int] = [:] +// CHECK: [[@LINE+3]]:31 | instance-property/Swift | value | {{.*}} | Ref,Read | rel: 0 +// CHECK: [[@LINE+2]]:31 | instance-method/acc-get/Swift | getter:value | {{.*}} | Ref,Call,Impl | rel: 0 +// CHECK: [[@LINE+1]]:14 | struct/Swift | KeyPathContainer | {{.*}} | Ref | rel: 0 +keyPathDict[\KeyPathContainer.value] = 42