Skip to content

Commit 7530105

Browse files
committed
implement actor key path typechecking
The rule is that you cannot form a key path to any actor-isolated members. This avoids issues with having to track whether a keypath does something 'async' in the KeyPath type.
1 parent 4c7059f commit 7530105

File tree

4 files changed

+121
-14
lines changed

4 files changed

+121
-14
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4331,6 +4331,9 @@ ERROR(actor_isolated_from_async_let,none,
43314331
ERROR(actor_isolated_from_escaping_closure,none,
43324332
"actor-isolated %0 %1 cannot be %select{referenced|mutated|used 'inout'}2 from an '@escaping' closure",
43334333
(DescriptiveDeclKind, DeclName, unsigned))
4334+
ERROR(actor_isolated_keypath_component,none,
4335+
"cannot form key path to actor-isolated %0 %1",
4336+
(DescriptiveDeclKind, DeclName))
43344337
ERROR(local_function_executed_concurrently,none,
43354338
"concurrently-executed %0 %1 must be marked as '@concurrent'",
43364339
(DescriptiveDeclKind, DeclName))
@@ -4377,6 +4380,9 @@ WARNING(non_concurrent_property_type,none,
43774380
WARNING(non_concurrent_keypath_capture,none,
43784381
"cannot form key path that captures non-concurrent-value type %0",
43794382
(Type))
4383+
WARNING(non_concurrent_keypath_access,none,
4384+
"cannot form key path that accesses non-concurrent-value type %0",
4385+
(Type))
43804386
ERROR(non_concurrent_type_member,none,
43814387
"%select{stored property %1|associated value %1}0 of "
43824388
"'ConcurrentValue'-conforming %2 %3 has non-concurrent-value type %4",

include/swift/AST/Expr.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5654,6 +5654,26 @@ class KeyPathExpr : public Expr {
56545654
llvm_unreachable("unhandled kind");
56555655
}
56565656

5657+
bool hasDeclRef() const {
5658+
switch (getKind()) {
5659+
case Kind::Property:
5660+
case Kind::Subscript:
5661+
return true;
5662+
5663+
case Kind::Invalid:
5664+
case Kind::UnresolvedProperty:
5665+
case Kind::UnresolvedSubscript:
5666+
case Kind::OptionalChain:
5667+
case Kind::OptionalWrap:
5668+
case Kind::OptionalForce:
5669+
case Kind::Identity:
5670+
case Kind::TupleElement:
5671+
case Kind::DictionaryKey:
5672+
return false;
5673+
}
5674+
llvm_unreachable("unhandled kind");
5675+
}
5676+
56575677
ConcreteDeclRef getDeclRef() const {
56585678
switch (getKind()) {
56595679
case Kind::Property:

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 85 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,21 +1266,9 @@ namespace {
12661266
}
12671267
}
12681268

1269-
// Key paths require any captured values to be ConcurrentValue-conforming.
1270-
if (auto keyPath = dyn_cast<KeyPathExpr>(expr)) {
1271-
for (const auto &component : keyPath->getComponents()) {
1272-
auto indexExpr = component.getIndexExpr();
1273-
if (!indexExpr || !indexExpr->getType())
1274-
continue;
12751269

1276-
if (shouldDiagnoseNonConcurrentValueViolations(ctx.LangOpts) &&
1277-
!isConcurrentValueType(getDeclContext(), indexExpr->getType())) {
1278-
ctx.Diags.diagnose(
1279-
component.getLoc(), diag::non_concurrent_keypath_capture,
1280-
indexExpr->getType());
1281-
}
1282-
}
1283-
}
1270+
if (auto keyPath = dyn_cast<KeyPathExpr>(expr))
1271+
checkKeyPathExpr(keyPath);
12841272

12851273
// The children of #selector expressions are not evaluated, so we do not
12861274
// need to do isolation checking there. This is convenient because such
@@ -1868,6 +1856,89 @@ namespace {
18681856
return true;
18691857
}
18701858

1859+
///
1860+
/// \return true iff a diagnostic was emitted
1861+
bool checkKeyPathExpr(KeyPathExpr *keyPath) {
1862+
bool diagnosed = false;
1863+
1864+
// returns None if it is not a 'let'-bound var decl. Otherwise,
1865+
// the bool indicates whether a diagnostic was emitted.
1866+
auto checkLetBoundVarDecl = [&](KeyPathExpr::Component const& component)
1867+
-> Optional<bool> {
1868+
auto decl = component.getDeclRef().getDecl();
1869+
if (auto varDecl = dyn_cast<VarDecl>(decl)) {
1870+
if (varDecl->isLet()) {
1871+
auto type = component.getComponentType();
1872+
if (shouldDiagnoseNonConcurrentValueViolations(ctx.LangOpts)
1873+
&& !isConcurrentValueType(getDeclContext(), type)) {
1874+
ctx.Diags.diagnose(
1875+
component.getLoc(), diag::non_concurrent_keypath_access,
1876+
type);
1877+
return true;
1878+
}
1879+
return false;
1880+
}
1881+
}
1882+
return None;
1883+
};
1884+
1885+
// check the components of the keypath.
1886+
for (const auto &component : keyPath->getComponents()) {
1887+
// The decl referred to by the path component cannot be within an actor.
1888+
if (component.hasDeclRef()) {
1889+
auto concDecl = component.getDeclRef();
1890+
auto isolation = ActorIsolationRestriction::forDeclaration(concDecl);
1891+
1892+
switch (isolation.getKind()) {
1893+
case ActorIsolationRestriction::Unsafe:
1894+
case ActorIsolationRestriction::Unrestricted:
1895+
break; // OK. Does not refer to an actor-isolated member.
1896+
1897+
case ActorIsolationRestriction::GlobalActorUnsafe:
1898+
// Only check if we're in code that's adopted concurrency features.
1899+
if (!shouldDiagnoseExistingDataRaces(getDeclContext()))
1900+
break; // do not check
1901+
1902+
LLVM_FALLTHROUGH; // otherwise, perform checking
1903+
1904+
case ActorIsolationRestriction::GlobalActor:
1905+
case ActorIsolationRestriction::CrossActorSelf:
1906+
// 'let'-bound decls with this isolation are OK, just check them.
1907+
if (auto wasLetBound = checkLetBoundVarDecl(component)) {
1908+
diagnosed = wasLetBound.getValue();
1909+
break;
1910+
}
1911+
LLVM_FALLTHROUGH; // otherwise, it's invalid so diagnose it.
1912+
1913+
case ActorIsolationRestriction::ActorSelf: {
1914+
auto decl = concDecl.getDecl();
1915+
ctx.Diags.diagnose(component.getLoc(),
1916+
diag::actor_isolated_keypath_component,
1917+
decl->getDescriptiveKind(), decl->getName());
1918+
diagnosed = true;
1919+
break;
1920+
}
1921+
}; // end switch
1922+
}
1923+
1924+
// Captured values in a path component must conform to ConcurrentValue.
1925+
// These captured values appear in Subscript, aka "index" components,
1926+
// such as \Type.dict[k] where k is a captured dictionary key.
1927+
if (auto indexExpr = component.getIndexExpr()) {
1928+
auto type = indexExpr->getType();
1929+
if (type && shouldDiagnoseNonConcurrentValueViolations(ctx.LangOpts)
1930+
&& !isConcurrentValueType(getDeclContext(), type)) {
1931+
ctx.Diags.diagnose(
1932+
component.getLoc(), diag::non_concurrent_keypath_capture,
1933+
indexExpr->getType());
1934+
diagnosed = true;
1935+
}
1936+
}
1937+
}
1938+
1939+
return diagnosed;
1940+
}
1941+
18711942
/// Check a reference to a local or global.
18721943
bool checkNonMemberReference(
18731944
ConcreteDeclRef valueRef, SourceLoc loc, DeclRefExpr *declRefExpr) {

test/Concurrency/actor_isolation_objc.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@ actor A {
1414
}
1515

1616
func keypaths() {
17+
_ = #keyPath(A.w) // expected-error {{'#keyPath' refers to non-'@objc' property 'w'}}
18+
19+
// expected-error@+1 {{cannot form key path to actor-isolated property 'x'}}
1720
_ = #keyPath(A.x) // expected-error{{argument of '#keyPath' refers to non-'@objc' property 'x'}}
21+
22+
// expected-error@+1 {{cannot form key path to actor-isolated property 'y'}}
1823
_ = #keyPath(A.y) // expected-error{{argument of '#keyPath' refers to non-'@objc' property 'y'}}
24+
25+
// expected-error@+1 {{cannot form key path to actor-isolated property 'computed'}}
1926
_ = #keyPath(A.computed) // expected-error{{argument of '#keyPath' refers to non-'@objc' property 'computed'}}
27+
2028
_ = #keyPath(A.z)
2129
}
2230

31+
let w: Int = 0 // expected-note{{add '@objc' to expose this property to Objective-C}}
32+
2333
var x: Int = 0 // expected-note{{add '@objc' to expose this property to Objective-C}}
2434

2535
@objc var y: Int = 0 // expected-note{{add '@objc' to expose this property to Objective-C}}

0 commit comments

Comments
 (0)