Skip to content

Commit 134eefe

Browse files
authored
Merge pull request #65459 from ktoso/pick-wip-bincompat-job-rename
🍒[5.9][Executors] Make move to ExecutorJob binary compatible; deprecate Job
2 parents 65e6402 + 4fc21de commit 134eefe

File tree

12 files changed

+331
-48
lines changed

12 files changed

+331
-48
lines changed

include/swift/AST/Decl.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3879,8 +3879,13 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
38793879
/// Find the 'RemoteCallArgument(label:name:value:)' initializer function.
38803880
ConstructorDecl *getDistributedRemoteCallArgumentInitFunction() const;
38813881

3882-
/// Get the move-only `enqueue(Job)` protocol requirement function on the `Executor` protocol.
3882+
/// Get the move-only `enqueue(ExecutorJob)` protocol requirement function on the `Executor` protocol.
38833883
AbstractFunctionDecl *getExecutorOwnedEnqueueFunction() const;
3884+
/// This method should be deprecated and removed
3885+
/// Get the move-only `enqueue(Job)` protocol requirement function on the `Executor` protocol.
3886+
AbstractFunctionDecl *getExecutorLegacyOwnedEnqueueFunction() const;
3887+
/// Get the move-only `enqueue(UnownedJob)` protocol requirement function on the `Executor` protocol.
3888+
AbstractFunctionDecl *getExecutorLegacyUnownedEnqueueFunction() const;
38843889

38853890
/// Collect the set of protocols to which this type should implicitly
38863891
/// conform, such as AnyObject (for classes).

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6521,7 +6521,11 @@ WARNING(hashvalue_implementation,Deprecation,
65216521

65226522
WARNING(executor_enqueue_unowned_implementation,Deprecation,
65236523
"'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; "
6524-
"conform type %0 to 'Executor' by implementing 'func enqueue(Job)' instead",
6524+
"conform type %0 to 'Executor' by implementing 'func enqueue(ExecutorJob)' instead",
6525+
(Type))
6526+
WARNING(executor_enqueue_deprecated_owned_job_implementation,Deprecation,
6527+
"'Executor.enqueue(Job)' is deprecated as a protocol requirement; "
6528+
"conform type %0 to 'Executor' by implementing 'func enqueue(ExecutorJob)' instead",
65256529
(Type))
65266530

65276531
//------------------------------------------------------------------------------

lib/AST/Decl.cpp

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5305,6 +5305,48 @@ VarDecl *NominalTypeDecl::getGlobalActorInstance() const {
53055305
AbstractFunctionDecl *
53065306
NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
53075307
auto &C = getASTContext();
5308+
StructDecl *executorJobDecl = C.getExecutorJobDecl();
5309+
if (!executorJobDecl)
5310+
return nullptr;
5311+
5312+
auto proto = dyn_cast<ProtocolDecl>(this);
5313+
if (!proto)
5314+
return nullptr;
5315+
5316+
llvm::SmallVector<ValueDecl *, 2> results;
5317+
lookupQualified(getSelfNominalTypeDecl(),
5318+
DeclNameRef(C.Id_enqueue),
5319+
NL_ProtocolMembers,
5320+
results);
5321+
5322+
for (auto candidate: results) {
5323+
// we're specifically looking for the Executor protocol requirement
5324+
if (!isa<ProtocolDecl>(candidate->getDeclContext()))
5325+
continue;
5326+
5327+
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
5328+
auto params = funcDecl->getParameters();
5329+
5330+
if (params->size() != 1)
5331+
continue;
5332+
5333+
if ((params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned ||
5334+
params->get(0)->getSpecifier() == ParamSpecifier::Consuming) &&
5335+
params->get(0)->getInterfaceType()->isEqual(executorJobDecl->getDeclaredInterfaceType())) {
5336+
return funcDecl;
5337+
}
5338+
}
5339+
}
5340+
5341+
return nullptr;
5342+
}
5343+
5344+
AbstractFunctionDecl *
5345+
NominalTypeDecl::getExecutorLegacyOwnedEnqueueFunction() const {
5346+
auto &C = getASTContext();
5347+
StructDecl *legacyJobDecl = C.getJobDecl();
5348+
if (!legacyJobDecl)
5349+
return nullptr;
53085350

53095351
auto proto = dyn_cast<ProtocolDecl>(this);
53105352
if (!proto)
@@ -5322,11 +5364,51 @@ NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
53225364
continue;
53235365

53245366
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
5325-
if (funcDecl->getParameters()->size() != 1)
5367+
auto params = funcDecl->getParameters();
5368+
5369+
if (params->size() != 1)
53265370
continue;
53275371

5372+
if ((params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned ||
5373+
params->get(0)->getSpecifier() == ParamSpecifier::Consuming) &&
5374+
params->get(0)->getType()->isEqual(legacyJobDecl->getDeclaredInterfaceType())) {
5375+
return funcDecl;
5376+
}
5377+
}
5378+
}
5379+
5380+
return nullptr;
5381+
}
5382+
5383+
AbstractFunctionDecl *
5384+
NominalTypeDecl::getExecutorLegacyUnownedEnqueueFunction() const {
5385+
auto &C = getASTContext();
5386+
StructDecl *unownedJobDecl = C.getUnownedJobDecl();
5387+
if (!unownedJobDecl)
5388+
return nullptr;
5389+
5390+
auto proto = dyn_cast<ProtocolDecl>(this);
5391+
if (!proto)
5392+
return nullptr;
5393+
5394+
llvm::SmallVector<ValueDecl *, 2> results;
5395+
lookupQualified(getSelfNominalTypeDecl(),
5396+
DeclNameRef(C.Id_enqueue),
5397+
NL_ProtocolMembers,
5398+
results);
5399+
5400+
for (auto candidate: results) {
5401+
// we're specifically looking for the Executor protocol requirement
5402+
if (!isa<ProtocolDecl>(candidate->getDeclContext()))
5403+
continue;
5404+
5405+
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
53285406
auto params = funcDecl->getParameters();
5329-
if (params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned) { // TODO: make this Consuming
5407+
5408+
if (params->size() != 1)
5409+
continue;
5410+
5411+
if (params->get(0)->getType()->isEqual(unownedJobDecl->getDeclaredInterfaceType())) {
53305412
return funcDecl;
53315413
}
53325414
}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,8 +1269,9 @@ void swift::tryDiagnoseExecutorConformance(ASTContext &C,
12691269
// enqueue(_:)
12701270
auto enqueueDeclName = DeclName(C, DeclBaseName(C.Id_enqueue), { Identifier() });
12711271

1272-
FuncDecl *unownedEnqueueRequirement = nullptr;
12731272
FuncDecl *moveOnlyEnqueueRequirement = nullptr;
1273+
FuncDecl *legacyMoveOnlyEnqueueRequirement = nullptr; // TODO: preferably we'd want to remove handling of `enqueue(Job)` when able to
1274+
FuncDecl *unownedEnqueueRequirement = nullptr;
12741275
for (auto req: proto->getProtocolRequirements()) {
12751276
auto *funcDecl = dyn_cast<FuncDecl>(req);
12761277
if (!funcDecl)
@@ -1282,61 +1283,83 @@ void swift::tryDiagnoseExecutorConformance(ASTContext &C,
12821283
// look for the first parameter being a Job or UnownedJob
12831284
if (funcDecl->getParameters()->size() != 1)
12841285
continue;
1286+
12851287
if (auto param = funcDecl->getParameters()->front()) {
1288+
StructDecl *executorJobDecl = C.getExecutorJobDecl();
1289+
StructDecl *legacyJobDecl = C.getJobDecl();
12861290
StructDecl *unownedJobDecl = C.getUnownedJobDecl();
1287-
StructDecl *jobDecl = nullptr;
1288-
if (auto executorJobDecl = C.getExecutorJobDecl()) {
1289-
jobDecl = executorJobDecl;
1290-
} else if (auto plainJobDecl = C.getJobDecl()) {
1291-
// old standard library, before we introduced the `typealias Job = ExecutorJob`
1292-
jobDecl = plainJobDecl;
1293-
}
12941291

1295-
if (jobDecl &&
1296-
param->getType()->isEqual(jobDecl->getDeclaredInterfaceType())) {
1292+
if (executorJobDecl && param->getType()->isEqual(executorJobDecl->getDeclaredInterfaceType())) {
12971293
assert(moveOnlyEnqueueRequirement == nullptr);
12981294
moveOnlyEnqueueRequirement = funcDecl;
1299-
} else if (unownedJobDecl &&
1300-
param->getType()->isEqual(unownedJobDecl->getDeclaredInterfaceType())) {
1295+
} else if (legacyJobDecl && param->getType()->isEqual(legacyJobDecl->getDeclaredInterfaceType())) {
1296+
assert(legacyMoveOnlyEnqueueRequirement == nullptr);
1297+
legacyMoveOnlyEnqueueRequirement = funcDecl;
1298+
} else if (unownedJobDecl && param->getType()->isEqual(unownedJobDecl->getDeclaredInterfaceType())) {
13011299
assert(unownedEnqueueRequirement == nullptr);
13021300
unownedEnqueueRequirement = funcDecl;
13031301
}
13041302
}
13051303

1306-
// if we found both, we're done here and break out of the loop
1307-
if (unownedEnqueueRequirement && moveOnlyEnqueueRequirement)
1304+
// if we found all potential requirements, we're done here and break out of the loop
1305+
if (unownedEnqueueRequirement &&
1306+
moveOnlyEnqueueRequirement &&
1307+
legacyMoveOnlyEnqueueRequirement)
13081308
break; // we're done looking for the requirements
13091309
}
13101310

13111311
auto conformance = module->lookupConformance(nominalTy, proto);
13121312
auto concreteConformance = conformance.getConcrete();
13131313
assert(unownedEnqueueRequirement && "could not find the enqueue(UnownedJob) requirement, which should be always there");
1314+
1315+
// try to find at least a single implementations of enqueue(_:)
13141316
ConcreteDeclRef unownedEnqueueWitness = concreteConformance->getWitnessDeclRef(unownedEnqueueRequirement);
1317+
ValueDecl *unownedEnqueueWitnessDecl = unownedEnqueueWitness.getDecl();
1318+
ValueDecl *moveOnlyEnqueueWitnessDecl = nullptr;
1319+
ValueDecl *legacyMoveOnlyEnqueueWitnessDecl = nullptr;
13151320

1316-
if (auto enqueueUnownedDecl = unownedEnqueueWitness.getDecl()) {
1317-
// Old UnownedJob based impl is present, warn about it suggesting the new protocol requirement.
1318-
if (enqueueUnownedDecl->getLoc().isValid()) {
1319-
diags.diagnose(enqueueUnownedDecl->getLoc(), diag::executor_enqueue_unowned_implementation, nominalTy);
1320-
}
1321+
if (moveOnlyEnqueueRequirement) {
1322+
moveOnlyEnqueueWitnessDecl = concreteConformance->getWitnessDeclRef(
1323+
moveOnlyEnqueueRequirement).getDecl();
1324+
}
1325+
if (legacyMoveOnlyEnqueueRequirement) {
1326+
legacyMoveOnlyEnqueueWitnessDecl = concreteConformance->getWitnessDeclRef(
1327+
legacyMoveOnlyEnqueueRequirement).getDecl();
13211328
}
13221329

1323-
if (auto unownedEnqueueDecl = unownedEnqueueWitness.getDecl()) {
1324-
if (moveOnlyEnqueueRequirement) {
1325-
ConcreteDeclRef moveOnlyEnqueueWitness = concreteConformance->getWitnessDeclRef(moveOnlyEnqueueRequirement);
1326-
if (auto moveOnlyEnqueueDecl = moveOnlyEnqueueWitness.getDecl()) {
1327-
if (unownedEnqueueDecl && unownedEnqueueDecl->getLoc().isInvalid() &&
1328-
moveOnlyEnqueueDecl && moveOnlyEnqueueDecl->getLoc().isInvalid()) {
1329-
// Neither old nor new implementation have been found, but we provide default impls for them
1330-
// that are mutually recursive, so we must error and suggest implementing the right requirement.
1331-
auto ownedRequirement = C.getExecutorDecl()->getExecutorOwnedEnqueueFunction();
1332-
nominal->diagnose(diag::type_does_not_conform, nominalTy, proto->getDeclaredInterfaceType());
1333-
ownedRequirement->diagnose(diag::no_witnesses,
1334-
getProtocolRequirementKind(ownedRequirement),
1335-
ownedRequirement->getName(),
1336-
proto->getDeclaredInterfaceType(),
1337-
/*AddFixIt=*/true);
1338-
}
1339-
}
1330+
// --- Diagnose warnings and errors
1331+
1332+
// Old UnownedJob based impl is present, warn about it suggesting the new protocol requirement.
1333+
if (unownedEnqueueWitnessDecl && unownedEnqueueWitnessDecl->getLoc().isValid()) {
1334+
diags.diagnose(unownedEnqueueWitnessDecl->getLoc(), diag::executor_enqueue_unowned_implementation, nominalTy);
1335+
}
1336+
// Old Job based impl is present, warn about it suggesting the new protocol requirement.
1337+
if (legacyMoveOnlyEnqueueWitnessDecl && legacyMoveOnlyEnqueueWitnessDecl->getLoc().isValid()) {
1338+
diags.diagnose(legacyMoveOnlyEnqueueWitnessDecl->getLoc(), diag::executor_enqueue_deprecated_owned_job_implementation, nominalTy);
1339+
}
1340+
1341+
if ((!unownedEnqueueWitnessDecl || unownedEnqueueWitnessDecl->getLoc().isInvalid()) &&
1342+
(!moveOnlyEnqueueWitnessDecl || moveOnlyEnqueueWitnessDecl->getLoc().isInvalid()) &&
1343+
(!legacyMoveOnlyEnqueueWitnessDecl || legacyMoveOnlyEnqueueWitnessDecl->getLoc().isInvalid())) {
1344+
// Neither old nor new implementation have been found, but we provide default impls for them
1345+
// that are mutually recursive, so we must error and suggest implementing the right requirement.
1346+
//
1347+
// If we're running against an SDK that does not have the ExecutorJob enqueue function,
1348+
// try to diagnose using the next-best one available.
1349+
auto missingRequirement = C.getExecutorDecl()->getExecutorOwnedEnqueueFunction();
1350+
if (!missingRequirement)
1351+
missingRequirement = C.getExecutorDecl()->getExecutorLegacyOwnedEnqueueFunction();
1352+
if (!missingRequirement)
1353+
missingRequirement = C.getExecutorDecl()->getExecutorLegacyUnownedEnqueueFunction();
1354+
1355+
if (missingRequirement) {
1356+
nominal->diagnose(diag::type_does_not_conform, nominalTy, proto->getDeclaredInterfaceType());
1357+
missingRequirement->diagnose(diag::no_witnesses,
1358+
getProtocolRequirementKind(missingRequirement),
1359+
missingRequirement->getName(),
1360+
missingRequirement->getParameters()->get(0)->getInterfaceType(),
1361+
/*AddFixIt=*/true);
1362+
return;
13401363
}
13411364
}
13421365
}

stdlib/public/Concurrency/Executor.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public protocol Executor: AnyObject, Sendable {
2424
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
2525
func enqueue(_ job: UnownedJob)
2626

27+
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
28+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
29+
@available(*, deprecated, message: "Use enqueue(ExecutorJob) instead")
30+
func enqueue(_ job: __owned Job)
31+
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
32+
2733
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
2834
@available(SwiftStdlib 5.9, *)
2935
func enqueue(_ job: __owned ExecutorJob)
@@ -46,6 +52,17 @@ public protocol SerialExecutor: Executor {
4652
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
4753
func enqueue(_ job: UnownedJob)
4854

55+
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
56+
// This requirement is repeated here as a non-override so that we
57+
// get a redundant witness-table entry for it. This allows us to
58+
// avoid drilling down to the base conformance just for the basic
59+
// work-scheduling operation.
60+
@_nonoverride
61+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
62+
@available(*, deprecated, message: "Use enqueue(ExecutorJob) instead")
63+
func enqueue(_ job: __owned Job)
64+
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
65+
4966
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
5067
// This requirement is repeated here as a non-override so that we
5168
// get a redundant witness-table entry for it. This allows us to
@@ -92,6 +109,10 @@ extension Executor {
92109
public func enqueue(_ job: __owned ExecutorJob) {
93110
self.enqueue(UnownedJob(job))
94111
}
112+
113+
public func enqueue(_ job: __owned Job) {
114+
self.enqueue(UnownedJob(job))
115+
}
95116
}
96117
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
97118

0 commit comments

Comments
 (0)