Skip to content

Commit 0bdb39f

Browse files
authored
Merge pull request #65455 from ktoso/wip-bincompat-job-rename
2 parents 607f4eb + b847ee1 commit 0bdb39f

File tree

11 files changed

+328
-45
lines changed

11 files changed

+328
-45
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
@@ -6526,7 +6526,11 @@ WARNING(hashvalue_implementation,Deprecation,
65266526

65276527
WARNING(executor_enqueue_unowned_implementation,Deprecation,
65286528
"'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; "
6529-
"conform type %0 to 'Executor' by implementing 'func enqueue(Job)' instead",
6529+
"conform type %0 to 'Executor' by implementing 'func enqueue(ExecutorJob)' instead",
6530+
(Type))
6531+
WARNING(executor_enqueue_deprecated_owned_job_implementation,Deprecation,
6532+
"'Executor.enqueue(Job)' is deprecated as a protocol requirement; "
6533+
"conform type %0 to 'Executor' by implementing 'func enqueue(ExecutorJob)' instead",
65306534
(Type))
65316535

65326536
//------------------------------------------------------------------------------

lib/AST/Decl.cpp

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5318,6 +5318,48 @@ VarDecl *NominalTypeDecl::getGlobalActorInstance() const {
53185318
AbstractFunctionDecl *
53195319
NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
53205320
auto &C = getASTContext();
5321+
StructDecl *executorJobDecl = C.getExecutorJobDecl();
5322+
if (!executorJobDecl)
5323+
return nullptr;
5324+
5325+
auto proto = dyn_cast<ProtocolDecl>(this);
5326+
if (!proto)
5327+
return nullptr;
5328+
5329+
llvm::SmallVector<ValueDecl *, 2> results;
5330+
lookupQualified(getSelfNominalTypeDecl(),
5331+
DeclNameRef(C.Id_enqueue),
5332+
NL_ProtocolMembers,
5333+
results);
5334+
5335+
for (auto candidate: results) {
5336+
// we're specifically looking for the Executor protocol requirement
5337+
if (!isa<ProtocolDecl>(candidate->getDeclContext()))
5338+
continue;
5339+
5340+
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
5341+
auto params = funcDecl->getParameters();
5342+
5343+
if (params->size() != 1)
5344+
continue;
5345+
5346+
if ((params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned ||
5347+
params->get(0)->getSpecifier() == ParamSpecifier::Consuming) &&
5348+
params->get(0)->getInterfaceType()->isEqual(executorJobDecl->getDeclaredInterfaceType())) {
5349+
return funcDecl;
5350+
}
5351+
}
5352+
}
5353+
5354+
return nullptr;
5355+
}
5356+
5357+
AbstractFunctionDecl *
5358+
NominalTypeDecl::getExecutorLegacyOwnedEnqueueFunction() const {
5359+
auto &C = getASTContext();
5360+
StructDecl *legacyJobDecl = C.getJobDecl();
5361+
if (!legacyJobDecl)
5362+
return nullptr;
53215363

53225364
auto proto = dyn_cast<ProtocolDecl>(this);
53235365
if (!proto)
@@ -5335,11 +5377,51 @@ NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
53355377
continue;
53365378

53375379
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
5338-
if (funcDecl->getParameters()->size() != 1)
5380+
auto params = funcDecl->getParameters();
5381+
5382+
if (params->size() != 1)
53395383
continue;
53405384

5385+
if ((params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned ||
5386+
params->get(0)->getSpecifier() == ParamSpecifier::Consuming) &&
5387+
params->get(0)->getType()->isEqual(legacyJobDecl->getDeclaredInterfaceType())) {
5388+
return funcDecl;
5389+
}
5390+
}
5391+
}
5392+
5393+
return nullptr;
5394+
}
5395+
5396+
AbstractFunctionDecl *
5397+
NominalTypeDecl::getExecutorLegacyUnownedEnqueueFunction() const {
5398+
auto &C = getASTContext();
5399+
StructDecl *unownedJobDecl = C.getUnownedJobDecl();
5400+
if (!unownedJobDecl)
5401+
return nullptr;
5402+
5403+
auto proto = dyn_cast<ProtocolDecl>(this);
5404+
if (!proto)
5405+
return nullptr;
5406+
5407+
llvm::SmallVector<ValueDecl *, 2> results;
5408+
lookupQualified(getSelfNominalTypeDecl(),
5409+
DeclNameRef(C.Id_enqueue),
5410+
NL_ProtocolMembers,
5411+
results);
5412+
5413+
for (auto candidate: results) {
5414+
// we're specifically looking for the Executor protocol requirement
5415+
if (!isa<ProtocolDecl>(candidate->getDeclContext()))
5416+
continue;
5417+
5418+
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
53415419
auto params = funcDecl->getParameters();
5342-
if (params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned) { // TODO: make this Consuming
5420+
5421+
if (params->size() != 1)
5422+
continue;
5423+
5424+
if (params->get(0)->getType()->isEqual(unownedJobDecl->getDeclaredInterfaceType())) {
53435425
return funcDecl;
53445426
}
53455427
}

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)