Skip to content
Open
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
27 changes: 25 additions & 2 deletions lib/IDE/SourceEntityWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,31 @@ ASTWalker::PreWalkResult<Expr *> 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<std::optional<AccessKind>>
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))
Expand Down
36 changes: 36 additions & 0 deletions test/Index/index_subscript_key.swift
Original file line number Diff line number Diff line change
@@ -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<KeyPathContainer, Int>: 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