Skip to content

Commit b6d70db

Browse files
committed
[Concurrency] Warn about "old" Executor.enqueue impl and prefer new
1 parent 054c6af commit b6d70db

20 files changed

+180
-26
lines changed

include/swift/AST/Decl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3816,6 +3816,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
38163816
/// Find the 'RemoteCallArgument(label:name:value:)' initializer function.
38173817
ConstructorDecl *getDistributedRemoteCallArgumentInitFunction() const;
38183818

3819+
/// Get the move-only `enqueue(Job)` protocol requirement function on the `Executor` protocol.
3820+
AbstractFunctionDecl *getExecutorOwnedEnqueueFunction() const;
3821+
38193822
/// Collect the set of protocols to which this type should implicitly
38203823
/// conform, such as AnyObject (for classes).
38213824
void getImplicitProtocols(SmallVectorImpl<ProtocolDecl *> &protocols);

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6324,6 +6324,11 @@ WARNING(hashvalue_implementation,Deprecation,
63246324
"conform type %0 to 'Hashable' by implementing 'hash(into:)' instead",
63256325
(Type))
63266326

6327+
WARNING(executor_enqueue_unowned_implementation,Deprecation,
6328+
"'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; "
6329+
"conform type %0 to 'Executor' by implementing 'enqueueOwned(Job)' instead",
6330+
(Type))
6331+
63276332
//------------------------------------------------------------------------------
63286333
// MARK: property wrapper diagnostics
63296334
//------------------------------------------------------------------------------

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ IDENTIFIER(encodeIfPresent)
8484
IDENTIFIER(Encoder)
8585
IDENTIFIER(encoder)
8686
IDENTIFIER(enqueue)
87+
IDENTIFIER(enqueueOwned)
8788
IDENTIFIER(erasing)
8889
IDENTIFIER(error)
8990
IDENTIFIER(errorDomain)

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ PROTOCOL(SIMD)
7575
PROTOCOL(SIMDScalar)
7676
PROTOCOL(BinaryInteger)
7777
PROTOCOL(RangeReplaceableCollection)
78+
PROTOCOL(Executor)
7879
PROTOCOL(SerialExecutor)
7980
PROTOCOL(GlobalActor)
8081

include/swift/AST/KnownSDKTypes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ KNOWN_SDK_TYPE_DECL(ObjectiveC, ObjCBool, StructDecl, 0)
3939
// standardized
4040
KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeContinuation, NominalTypeDecl, 2)
4141
KNOWN_SDK_TYPE_DECL(Concurrency, MainActor, NominalTypeDecl, 0)
42+
KNOWN_SDK_TYPE_DECL(Concurrency, Executor, NominalTypeDecl, 0)
43+
KNOWN_SDK_TYPE_DECL(Concurrency, SerialExecutor, NominalTypeDecl, 0)
4244
KNOWN_SDK_TYPE_DECL(Concurrency, UnownedSerialExecutor, NominalTypeDecl, 0)
4345

4446
KNOWN_SDK_TYPE_DECL(Concurrency, TaskLocal, ClassDecl, 1)

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
11141114
case KnownProtocolKind::GlobalActor:
11151115
case KnownProtocolKind::AsyncSequence:
11161116
case KnownProtocolKind::AsyncIteratorProtocol:
1117+
case KnownProtocolKind::Executor:
11171118
case KnownProtocolKind::SerialExecutor:
11181119
M = getLoadedModule(Id_Concurrency);
11191120
break;

lib/AST/Decl.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5085,6 +5085,39 @@ VarDecl *NominalTypeDecl::getGlobalActorInstance() const {
50855085
nullptr);
50865086
}
50875087

5088+
AbstractFunctionDecl *
5089+
NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
5090+
auto &C = getASTContext();
5091+
5092+
auto proto = dyn_cast<ProtocolDecl>(this);
5093+
if (!proto)
5094+
return nullptr;
5095+
5096+
llvm::SmallVector<ValueDecl *, 2> results;
5097+
lookupQualified(getSelfNominalTypeDecl(),
5098+
DeclNameRef(C.Id_enqueueOwned), // FIXME: make it enqueue
5099+
NL_ProtocolMembers,
5100+
results);
5101+
5102+
for (auto candidate: results) {
5103+
// we're specifically looking for the Executor protocol requirement
5104+
if (!isa<ProtocolDecl>(candidate->getDeclContext()))
5105+
continue;
5106+
5107+
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
5108+
if (funcDecl->getParameters()->size() != 1)
5109+
continue;
5110+
5111+
auto params = funcDecl->getParameters();
5112+
if (params->get(0)->getSpecifier() == ParamSpecifier::Owned) {
5113+
return funcDecl;
5114+
}
5115+
}
5116+
}
5117+
5118+
return nullptr;
5119+
}
5120+
50885121
ClassDecl::ClassDecl(SourceLoc ClassLoc, Identifier Name, SourceLoc NameLoc,
50895122
ArrayRef<InheritedEntry> Inherited,
50905123
GenericParamList *GenericParams, DeclContext *Parent,

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5880,6 +5880,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
58805880
case KnownProtocolKind::CxxSequence:
58815881
case KnownProtocolKind::UnsafeCxxInputIterator:
58825882
case KnownProtocolKind::UnsafeCxxRandomAccessIterator:
5883+
case KnownProtocolKind::Executor:
58835884
case KnownProtocolKind::SerialExecutor:
58845885
case KnownProtocolKind::Sendable:
58855886
case KnownProtocolKind::UnsafeSendable:

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,45 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) {
11911191
}
11921192
}
11931193

1194+
void swift::tryDiagnoseExecutorConformance(ASTContext &C,
1195+
const NominalTypeDecl *nominal,
1196+
ProtocolDecl *proto) {
1197+
assert(proto->isSpecificProtocol(KnownProtocolKind::Executor) ||
1198+
proto->isSpecificProtocol(KnownProtocolKind::SerialExecutor));
1199+
1200+
auto &diags = C.Diags;
1201+
auto module = nominal->getParentModule();
1202+
Type nominalTy = nominal->getDeclaredInterfaceType();
1203+
1204+
// enqueue(_: UnownedJob)
1205+
auto unownedEnqueueDeclName = DeclName(C, DeclBaseName(C.Id_enqueue), { Identifier() });
1206+
// enqueueOwned(_: Job)
1207+
auto moveOnlyEnqueueDeclName = DeclName(C, DeclBaseName(C.Id_enqueueOwned), { Identifier() });
1208+
1209+
auto conformance = module->lookupConformance(nominalTy, proto);
1210+
auto unownedEnqueueWitness = conformance.getWitnessByName(nominalTy, unownedEnqueueDeclName);
1211+
auto moveOnlyEnqueueWitness = conformance.getWitnessByName(nominalTy, moveOnlyEnqueueDeclName);
1212+
1213+
if (auto enqueueUnownedDecl = unownedEnqueueWitness.getDecl()) {
1214+
// Old UnownedJob based impl is present, warn about it suggesting the new protocol requirement.
1215+
if (enqueueUnownedDecl->getLoc().isValid())
1216+
diags.diagnose(enqueueUnownedDecl->getLoc(), diag::executor_enqueue_unowned_implementation, nominalTy);
1217+
}
1218+
1219+
if (unownedEnqueueWitness.getDecl()->getLoc().isInvalid() &&
1220+
moveOnlyEnqueueWitness.getDecl()->getLoc().isInvalid()) {
1221+
// Neither old or new implementation have been found, but we provide default impls for them
1222+
// that are mutually recursive, so we must error and suggest implementing the right requirement.
1223+
auto ownedRequirement = C.getExecutorDecl()->getExecutorOwnedEnqueueFunction();
1224+
nominal->diagnose(diag::type_does_not_conform, nominalTy, proto->getDeclaredInterfaceType());
1225+
ownedRequirement->diagnose(diag::no_witnesses,
1226+
getProtocolRequirementKind(ownedRequirement),
1227+
ownedRequirement->getName(),
1228+
proto->getDeclaredInterfaceType(),
1229+
/*AddFixIt=*/true);
1230+
}
1231+
}
1232+
11941233
/// Determine whether this is the main actor type.
11951234
/// FIXME: the diagnostics engine has a copy of this.
11961235
static bool isMainActor(Type type) {

lib/Sema/TypeCheckConcurrency.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ void diagnoseMissingSendableConformance(
280280
/// state whether it conforms to Sendable, provide a diagnostic.
281281
void diagnoseMissingExplicitSendable(NominalTypeDecl *nominal);
282282

283+
/// Warn about deprecated `Executor.enqueue` implementations.
284+
void tryDiagnoseExecutorConformance(ASTContext &C, const NominalTypeDecl *nominal, ProtocolDecl *proto);
285+
283286
/// How the Sendable check should be performed.
284287
enum class SendableCheck {
285288
/// Sendable conformance was explicitly stated and should be

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6478,6 +6478,8 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
64786478
} else if (proto->isSpecificProtocol(
64796479
KnownProtocolKind::UnsafeSendable)) {
64806480
sendableConformanceIsUnchecked = true;
6481+
} else if (proto->isSpecificProtocol(KnownProtocolKind::Executor)) {
6482+
tryDiagnoseExecutorConformance(Context, nominal, proto);
64816483
}
64826484
}
64836485

stdlib/public/BackDeployConcurrency/PartialAsyncTask.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public struct UnownedJob: Sendable {
3030

3131
@_alwaysEmitIntoClient
3232
@inlinable
33+
@available(*, deprecated, message: "Use SerialExecutor.runJobSynchronously(_:) instead.")
3334
public func _runSynchronously(on executor: UnownedSerialExecutor) {
3435
_swiftJobRun(self, executor)
3536
}

stdlib/public/Concurrency/Executor.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,31 @@ import Swift
1515
/// A service that can execute jobs.
1616
@available(SwiftStdlib 5.1, *)
1717
public protocol Executor: AnyObject, Sendable {
18-
// This requirement is repeated here as a non-override so that we
19-
// get a redundant witness-table entry for it. This allows us to
20-
// avoid drilling down to the base conformance just for the basic
21-
// work-scheduling operation.
2218
@available(*, deprecated, message: "Implement enqueueJob instead")
2319
func enqueue(_ job: UnownedJob)
2420

2521
@available(SwiftStdlib 5.9, *)
26-
func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
22+
func enqueueOwned(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
2723
}
2824

2925
/// A service that executes jobs.
3026
@available(SwiftStdlib 5.1, *)
3127
public protocol SerialExecutor: Executor {
32-
28+
// This requirement is repeated here as a non-override so that we
29+
// get a redundant witness-table entry for it. This allows us to
30+
// avoid drilling down to the base conformance just for the basic
31+
// work-scheduling operation.
3332
@_nonoverride
3433
@available(*, deprecated, message: "Implement enqueueJob instead")
3534
func enqueue(_ job: UnownedJob)
3635

36+
// This requirement is repeated here as a non-override so that we
37+
// get a redundant witness-table entry for it. This allows us to
38+
// avoid drilling down to the base conformance just for the basic
39+
// work-scheduling operation.
3740
@_nonoverride
3841
@available(SwiftStdlib 5.9, *)
39-
func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
42+
func enqueueOwned(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
4043

4144
/// Convert this executor value to the optimized form of borrowed
4245
/// executor references.
@@ -48,7 +51,8 @@ extension Executor {
4851
public func enqueue(_ job: UnownedJob) { // FIXME: this is bad; how could we deploy this nicely
4952
fatalError("Please implement \(Self.self).enqueueJob(_:)")
5053
}
51-
public func enqueueJob(_ job: __owned Job) {
54+
55+
public func enqueueOwned(_ job: __owned Job) {
5256
self.enqueue(UnownedJob(job))
5357
}
5458
}
@@ -117,7 +121,7 @@ internal func _getJobTaskId(_ job: UnownedJob) -> UInt64
117121
internal func _enqueueOnExecutor<E>(job unownedJob: UnownedJob, executor: E)
118122
where E: SerialExecutor {
119123
if #available(SwiftStdlib 5.9, *) {
120-
executor.enqueueJob(Job(context: unownedJob.context))
124+
executor.enqueueOwned(Job(context: unownedJob.context))
121125
} else {
122126
executor.enqueue(unownedJob)
123127
}

stdlib/public/Concurrency/PartialAsyncTask.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,13 @@ extension UnownedSerialExecutor {
102102
@_alwaysEmitIntoClient
103103
@inlinable
104104
public func runJobSynchronously(_ job: __owned Job) {
105-
UnownedJob(job)._runSynchronously(on: self)
105+
_swiftJobRun(UnownedJob(job), self)
106+
}
107+
108+
@_alwaysEmitIntoClient
109+
@inlinable
110+
public func runUnownedJobSynchronously(_ job: UnownedJob) {
111+
_swiftJobRun(job, self)
106112
}
107113
}
108114

@@ -114,7 +120,13 @@ extension SerialExecutor {
114120
@_alwaysEmitIntoClient
115121
@inlinable
116122
public func runJobSynchronously(_ job: __owned Job) {
117-
UnownedJob(job)._runSynchronously(on: self.asUnownedSerialExecutor())
123+
_swiftJobRun(UnownedJob(job), self.asUnownedSerialExecutor())
124+
}
125+
126+
@_alwaysEmitIntoClient
127+
@inlinable
128+
public func runUnownedJobSynchronously(_ job: UnownedJob) {
129+
_swiftJobRun(job, self.asUnownedSerialExecutor())
118130
}
119131
}
120132

stdlib/public/Concurrency/Task.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ public struct UnsafeCurrentTask {
843843
/// The current task's base priority.
844844
///
845845
/// - SeeAlso: `TaskPriority`
846-
/// - SeeAlso: `Task.currentBasePriority`
846+
/// - SeeAlso: `Task.basePriority`
847847
@available(SwiftStdlib 5.9, *)
848848
public var basePriority: TaskPriority {
849849
TaskPriority(rawValue: _taskBasePriority(_task))

test/Concurrency/Runtime/custom_executors_default.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s
1+
// RUN: %target-run-simple-swift( -Xfrontend enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s
22

33
// REQUIRES: concurrency
44
// REQUIRES: executable_test

test/Concurrency/Runtime/custom_executors_moveOnly_job.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99

1010
final class InlineExecutor: SerialExecutor, CustomStringConvertible {
1111

12-
public func enqueueJob(_ job: __owned Job) {
13-
print("\(self): enqueue (job: \(job.description))")
12+
public func enqueueOwned(_ job: __owned Job) {
1413
runJobSynchronously(job)
15-
print("\(self): after run")
1614
}
1715

1816
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {

test/Concurrency/Runtime/custom_executors_priority.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ actor Custom {
4747
}.value
4848
await Task(priority: .low) {
4949
print("P: \(Task.currentPriority)")
50-
print("B: \(Task.currentBasePriority)")
50+
print("B: \(Task.basePriority)")
5151
await actor.report()
5252
}.value
5353
print("end")

test/Concurrency/Runtime/custom_executors_protocol.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
import Dispatch
1212

13-
func checkIfMainQueue(expectedAnswer expected: Bool) {
14-
dispatchPrecondition(condition: expected ? .onQueue(DispatchQueue.main)
15-
: .notOnQueue(DispatchQueue.main))
13+
func checkIfMainQueue(expectedAnswer expected: Bool, expectedQueue: DispatchQueue) {
14+
dispatchPrecondition(condition: expected ? .onQueue(expectedQueue)
15+
: .notOnQueue(expectedQueue))
1616
}
1717

1818
protocol WithSpecifiedExecutor: Actor {
@@ -29,7 +29,7 @@ extension WithSpecifiedExecutor {
2929
}
3030
}
3131

32-
final class InlineExecutor: SpecifiedExecutor, Swift.CustomStringConvertible {
32+
final class InlineExecutor: SpecifiedExecutor, CustomStringConvertible {
3333
let name: String
3434

3535
init(_ name: String) {
@@ -42,7 +42,7 @@ final class InlineExecutor: SpecifiedExecutor, Swift.CustomStringConvertible {
4242
print("\(self): after run")
4343
}
4444

45-
public func enqueueJob(_ job: __owned Job) {
45+
public func enqueueOwned(_ job: __owned Job) {
4646
print("\(self): enqueue")
4747
runJobSynchronously(job)
4848
print("\(self): after run")
@@ -78,11 +78,12 @@ actor MyActor: WithSpecifiedExecutor {
7878
@main struct Main {
7979
static func main() async {
8080
print("begin")
81-
let one = InlineExecutor("one")
81+
let queue = DispatchQueue(label: "CustomQueue")
82+
let one = NaiveQueueExecutor(queue)
8283
let actor = MyActor(executor: one)
83-
await actor.test(expectedExecutor: one)
84-
await actor.test(expectedExecutor: one)
85-
await actor.test(expectedExecutor: one)
84+
await actor.test(expectedExecutor: one, expectedQueue: queue)
85+
await actor.test(expectedExecutor: one, expectedQueue: queue)
86+
await actor.test(expectedExecutor: one, expectedQueue: queue)
8687
print("end")
8788
}
8889
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-move-only -disable-availability-checking
2+
// REQUIRES: concurrency
3+
4+
// Such type may be encountered since Swift 5.5 (5.1 backdeployed) if someone implemented the
5+
// not documented, but public Executor types back then already.
6+
//
7+
// We keep support for them, but also log a deprecation warning that they should move to the new signature.
8+
final class OldExecutor: SerialExecutor {
9+
func enqueue(_ job: UnownedJob) {} // expected-warning{{'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; conform type 'OldExecutor' to 'Executor' by implementing 'enqueueOwned(Job)' instead}}
10+
11+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
12+
UnownedSerialExecutor(ordinary: self)
13+
}
14+
}
15+
16+
/// Implementing both enqueue methods is legal, but somewhat useless --
17+
/// we call into the "old one"; so the Owned version is not used in such impl.
18+
///
19+
/// That's why we do log the deprecation warning, people should use the move-only version.
20+
final class BothExecutor: SerialExecutor {
21+
func enqueue(_ job: UnownedJob) {} // expected-warning{{'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; conform type 'BothExecutor' to 'Executor' by implementing 'enqueueOwned(Job)' instead}}
22+
func enqueueOwned(_ job: __owned Job) {}
23+
24+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
25+
UnownedSerialExecutor(ordinary: self)
26+
}
27+
}
28+
29+
/// Even though we do provide default impls for both enqueue requirements,
30+
/// we manually detect and emit an error if neither of them is implemented.
31+
///
32+
/// We do so because we implement them recursively, so one of them must be implemented basically.
33+
final class NoneExecutor: SerialExecutor { // expected-error{{type 'NoneExecutor' does not conform to protocol 'Executor'}}
34+
35+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
36+
UnownedSerialExecutor(ordinary: self)
37+
}
38+
}
39+
40+
/// Just implementing the new signature causes no warnings, good.
41+
final class NewExecutor: SerialExecutor {
42+
func enqueueOwned(_ job: __owned Job) {} // no warnings
43+
44+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
45+
UnownedSerialExecutor(ordinary: self)
46+
}
47+
}

0 commit comments

Comments
 (0)