Skip to content

Commit 71a980d

Browse files
committed
Add @_predatesConcurrency attribute for declarations.
Introduce the `@_predatesConcurrency` attribute, which specifies that a given declaration existed prior to the introduction of Swift Concurrency, has been updated to use concurrency features (global actors, Sendable, etc.), but should retain its pre-concurrency behavior for clients that have not yet opted into concurrency. Implement type and actor-isolation adjustments to `@_predatesConcurrency` declarations to subsume the `@_unsafeMainActor`, `@_unsafeSendable`, and `@MainActor(unsafe)` use cases. This is the bulk of the semantic transformations needed for this new attribute, but is not yet complete. Part of rdar://84448438.
1 parent 0ae1203 commit 71a980d

File tree

7 files changed

+143
-7
lines changed

7 files changed

+143
-7
lines changed

include/swift/AST/Attr.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,12 @@ SIMPLE_DECL_ATTR(_noAllocation, NoAllocation,
695695
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
696696
124)
697697

698+
SIMPLE_DECL_ATTR(_predatesConcurrency, PredatesConcurrency,
699+
OnFunc | OnConstructor | OnProtocol | OnGenericType | OnVar | OnSubscript |
700+
OnEnumElement | UserInaccessible |
701+
ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
702+
125)
703+
698704
// If you're adding a new underscored attribute here, please document it in
699705
// docs/ReferenceGuides/UnderscoredAttributes.md.
700706

include/swift/AST/Decl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,9 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {
883883
/// but should behave like a top-level declaration. This is used by lldb.
884884
void setHoisted(bool hoisted = true) { Bits.Decl.Hoisted = hoisted; }
885885

886+
/// Whether this declaration predates the introduction of concurrency.
887+
bool predatesConcurrency() const;
888+
886889
public:
887890
bool escapedFromIfConfig() const {
888891
return Bits.Decl.EscapedFromIfConfig;

lib/AST/Decl.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,11 @@ Optional<CustomAttrNominalPair> Decl::getGlobalActorAttr() const {
718718
None);
719719
}
720720

721+
bool Decl::predatesConcurrency() const {
722+
return getAttrs().hasAttribute<PredatesConcurrencyAttr>();
723+
}
724+
725+
721726
Expr *AbstractFunctionDecl::getSingleExpressionBody() const {
722727
assert(hasSingleExpressionBody() && "Not a single-expression body");
723728
auto braceStmt = getBody();

lib/Sema/TypeCheckAttr.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
119119
IGNORED_ATTR(ImplicitSelfCapture)
120120
IGNORED_ATTR(InheritActorContext)
121121
IGNORED_ATTR(Isolated)
122+
IGNORED_ATTR(PredatesConcurrency)
122123
#undef IGNORED_ATTR
123124

124125
void visitAlignmentAttr(AlignmentAttr *attr) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2743,6 +2743,10 @@ static Optional<ActorIsolation> getIsolationFromAttributes(
27432743
diag::global_actor_non_unsafe_init, globalActorType);
27442744
}
27452745

2746+
// If the declaration predates concurrency, it has unsafe actor isolation.
2747+
if (decl->predatesConcurrency())
2748+
isUnsafe = true;
2749+
27462750
return ActorIsolation::forGlobalActor(
27472751
globalActorType->mapTypeOutOfContext(), isUnsafe);
27482752
}
@@ -4028,6 +4032,35 @@ static Type applyUnsafeConcurrencyToParameterType(
40284032
.withGlobalActor(globalActor));
40294033
}
40304034

4035+
/// Strip concurrency from the type of the given parameter.
4036+
static Type stripConcurrencyFromParameterType(
4037+
Type paramType, bool dropGlobalActor) {
4038+
// Look through optionals.
4039+
if (Type optionalObject = paramType->getOptionalObjectType()) {
4040+
Type newOptionalObject =
4041+
stripConcurrencyFromParameterType(optionalObject, dropGlobalActor);
4042+
if (optionalObject->isEqual(newOptionalObject))
4043+
return paramType;
4044+
4045+
return OptionalType::get(newOptionalObject);
4046+
}
4047+
4048+
// For function types, strip off Sendable and possibly the global actor.
4049+
if (auto fnType = paramType->getAs<FunctionType>()) {
4050+
auto extInfo = fnType->getExtInfo().withConcurrent(false);
4051+
if (dropGlobalActor)
4052+
extInfo = extInfo.withGlobalActor(Type());
4053+
auto newFnType = FunctionType::get(
4054+
fnType->getParams(), fnType->getResult(), extInfo);
4055+
if (newFnType->isEqual(paramType))
4056+
return paramType;
4057+
4058+
return newFnType;
4059+
}
4060+
4061+
return paramType;
4062+
}
4063+
40314064
/// Determine whether the given name is that of a DispatchQueue operation that
40324065
/// takes a closure to be executed on the queue.
40334066
bool swift::isDispatchQueueOperationName(StringRef name) {
@@ -4061,8 +4094,8 @@ static bool hasKnownUnsafeSendableFunctionParams(AbstractFunctionDecl *func) {
40614094
return false;
40624095
}
40634096

4064-
/// Apply @_unsafeSendable and @_unsafeMainActor to the parameters of the
4065-
/// given function.
4097+
/// Adjust a function type for @_unsafeSendable, @_unsafeMainActor, and
4098+
/// @_predatesConcurrency.
40664099
static AnyFunctionType *applyUnsafeConcurrencyToFunctionType(
40674100
AnyFunctionType *fnType, ValueDecl *funcOrEnum,
40684101
bool inConcurrencyContext, unsigned numApplies, bool isMainDispatchQueue) {
@@ -4085,6 +4118,9 @@ static AnyFunctionType *applyUnsafeConcurrencyToFunctionType(
40854118
auto paramDecls = func->getParameters();
40864119
assert(typeParams.size() == paramDecls->size());
40874120
bool knownUnsafeParams = hasKnownUnsafeSendableFunctionParams(func);
4121+
bool stripConcurrency =
4122+
funcOrEnum->predatesConcurrency() &&
4123+
!inConcurrencyContext;
40884124
for (unsigned index : indices(typeParams)) {
40894125
auto param = typeParams[index];
40904126
auto paramDecl = (*paramDecls)[index];
@@ -4093,19 +4129,28 @@ static AnyFunctionType *applyUnsafeConcurrencyToFunctionType(
40934129
// @MainActor. @Sendable occurs only in concurrency contents, while
40944130
// @MainActor occurs in concurrency contexts or those where we have an
40954131
// application.
4096-
bool isSendable =
4132+
bool addSendable =
40974133
(paramDecl->getAttrs().hasAttribute<UnsafeSendableAttr>() ||
40984134
knownUnsafeParams) &&
40994135
inConcurrencyContext;
4100-
bool isMainActor =
4136+
bool addMainActor =
41014137
(paramDecl->getAttrs().hasAttribute<UnsafeMainActorAttr>() ||
41024138
(isMainDispatchQueue && knownUnsafeParams)) &&
41034139
(inConcurrencyContext || numApplies >= 1);
4140+
Type newParamType = param.getPlainType();
4141+
if (addSendable || addMainActor) {
4142+
newParamType = applyUnsafeConcurrencyToParameterType(
4143+
param.getPlainType(), addSendable, addMainActor);
4144+
} else if (stripConcurrency) {
4145+
newParamType = stripConcurrencyFromParameterType(
4146+
param.getPlainType(), numApplies == 0);
4147+
}
41044148

4105-
if (!isSendable && !isMainActor) {
4149+
if (!newParamType || newParamType->isEqual(param.getPlainType())) {
41064150
// If any prior parameter has changed, record this one.
41074151
if (!newTypeParams.empty())
41084152
newTypeParams.push_back(param);
4153+
41094154
continue;
41104155
}
41114156

@@ -4116,8 +4161,6 @@ static AnyFunctionType *applyUnsafeConcurrencyToFunctionType(
41164161
}
41174162

41184163
// Transform the parameter type.
4119-
Type newParamType = applyUnsafeConcurrencyToParameterType(
4120-
param.getPlainType(), isSendable, isMainActor);
41214164
newTypeParams.push_back(param.withType(newParamType));
41224165
}
41234166

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,7 @@ namespace {
15051505
UNINTERESTING_ATTR(DynamicReplacement)
15061506
UNINTERESTING_ATTR(PrivateImport)
15071507
UNINTERESTING_ATTR(MainType)
1508+
UNINTERESTING_ATTR(PredatesConcurrency)
15081509

15091510
// Differentiation-related attributes.
15101511
UNINTERESTING_ATTR(Differentiable)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking
2+
// REQUIRES: concurrency
3+
4+
@_predatesConcurrency func unsafelySendableClosure(_ closure: @Sendable () -> Void) { }
5+
6+
@_predatesConcurrency func unsafelyMainActorClosure(_ closure: @MainActor () -> Void) { }
7+
8+
@_predatesConcurrency func unsafelyDoEverythingClosure(_ closure: @MainActor @Sendable () -> Void) { }
9+
10+
struct X {
11+
@_predatesConcurrency func unsafelyDoEverythingClosure(_ closure: @MainActor @Sendable () -> Void) { }
12+
}
13+
14+
15+
func testInAsync(x: X) async {
16+
let _: Int = unsafelySendableClosure // expected-error{{type '(@Sendable () -> Void) -> ()'}}
17+
let _: Int = unsafelyMainActorClosure // expected-error{{type '(@MainActor () -> Void) -> ()'}}
18+
let _: Int = unsafelyDoEverythingClosure // expected-error{{type '(@MainActor @Sendable () -> Void) -> ()'}}
19+
let _: Int = x.unsafelyDoEverythingClosure // expected-error{{type '(@MainActor @Sendable () -> Void) -> ()'}}
20+
let _: Int = X.unsafelyDoEverythingClosure // expected-error{{type '(X) -> (@MainActor @Sendable () -> Void) -> ()'}}
21+
let _: Int = (X.unsafelyDoEverythingClosure)(x) // expected-error{{type '(@MainActor @Sendable () -> Void) -> ()'}}
22+
}
23+
24+
func testElsewhere(x: X) {
25+
let _: Int = unsafelySendableClosure // expected-error{{type '(() -> Void) -> ()'}}
26+
let _: Int = unsafelyMainActorClosure // expected-error{{type '(() -> Void) -> ()'}}
27+
let _: Int = unsafelyDoEverythingClosure // expected-error{{type '(() -> Void) -> ()'}}
28+
let _: Int = x.unsafelyDoEverythingClosure // expected-error{{type '(() -> Void) -> ()'}}
29+
let _: Int = X.unsafelyDoEverythingClosure // expected-error{{type '(X) -> (() -> Void) -> ()'}}
30+
let _: Int = (X.unsafelyDoEverythingClosure)(x) // expected-error{{type '(() -> Void) -> ()'}}
31+
}
32+
33+
@MainActor func onMainActor() { }
34+
35+
@MainActor @_predatesConcurrency func onMainActorAlways() { }
36+
37+
@_predatesConcurrency @MainActor class MyModelClass {
38+
func f() { }
39+
}
40+
41+
func testCalls(x: X) {
42+
unsafelyMainActorClosure {
43+
onMainActor()
44+
}
45+
46+
unsafelyDoEverythingClosure {
47+
onMainActor()
48+
}
49+
50+
x.unsafelyDoEverythingClosure {
51+
onMainActor()
52+
onMainActorAlways()
53+
}
54+
(X.unsafelyDoEverythingClosure)(x)( {
55+
onMainActor()
56+
})
57+
58+
onMainActorAlways() // okay, haven't adopted concurrency
59+
60+
let _: () -> Void = onMainActorAlways
61+
62+
// both okay
63+
let c = MyModelClass()
64+
c.f()
65+
}
66+
67+
func testCallsWithAsync() async {
68+
onMainActorAlways() // expected-error{{expression is 'async' but is not marked with 'await'}}
69+
// expected-note@-1{{calls to global function 'onMainActorAlways()' from outside of its actor context are implicitly asynchronous}}
70+
71+
let _: () -> Void = onMainActorAlways // expected-error{{converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor 'MainActor'}}
72+
73+
let c = MyModelClass() // expected-error{{expression is 'async' but is not marked with 'await'}}
74+
// expected-note@-1{{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
75+
c.f() // expected-error{{expression is 'async' but is not marked with 'await'}}
76+
// expected-note@-1{{calls to instance method 'f()' from outside of its actor context are implicitly asynchronous}}
77+
}

0 commit comments

Comments
 (0)