Skip to content

Commit 70bd52e

Browse files
committed
Factor isolation-related diagnostics for conformances into a separate structure
Rather than emitting isolation-related diagnostics for conformances, such as witnesses that have incompatible isolation or associated conformances that are differently isolated, capture all of those diagnostics in a single side table and diagnose them all together. This refactoring doesn't change the way we actually diagnose the issue. That comes next.
1 parent cd99fb5 commit 70bd52e

File tree

3 files changed

+220
-107
lines changed

3 files changed

+220
-107
lines changed

include/swift/AST/ASTContextGlobalCache.h

+56-1
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,72 @@
1818
//===----------------------------------------------------------------------===//
1919

2020
#include "swift/AST/ASTContext.h"
21+
#include "swift/AST/ActorIsolation.h"
22+
#include <variant>
2123

2224
namespace swift {
2325

26+
class NormalProtocolConformance;
27+
class ValueDecl;
28+
29+
/// Describes an isolation error where the witness has actor isolation that
30+
/// conflicts with the requirement's expectation.
31+
struct WitnessIsolationError {
32+
/// The requirement declaration.
33+
ValueDecl *requirement;
34+
35+
/// The witness.
36+
ValueDecl *witness;
37+
38+
/// The pre-Swift-6 diagnostic behavior.
39+
DiagnosticBehavior behavior;
40+
41+
/// Whether the witness is missing "distributed".
42+
bool isMissingDistributed;
43+
44+
/// The isolation of the requirement, mapped into the conformance.
45+
ActorIsolation requirementIsolation;
46+
47+
/// The isolation domain that the witness would need to go into for it
48+
/// to be suitable for the requirement.
49+
ActorIsolation referenceIsolation;
50+
51+
/// Diagnose this witness isolation error.
52+
void diagnose(const NormalProtocolConformance *conformance,
53+
bool &suggestedPreconcurrencyOrIsolated) const;
54+
};
55+
56+
/// Describes an isolation error involving an associated conformance.
57+
struct AssociatedConformanceIsolationError {
58+
ProtocolConformance *isolatedConformance;
59+
60+
/// Diagnose this associated conformance isolation error.
61+
void diagnose(const NormalProtocolConformance *conformance) const;
62+
};
63+
64+
/// Describes an isolation error that occurred during conformance checking.
65+
using ConformanceIsolationError = std::variant<
66+
WitnessIsolationError, AssociatedConformanceIsolationError>;
67+
2468
/// A collection of side tables associated with the ASTContext itself, meant
25-
// as
69+
/// as a way to keep sparse information out of the AST nodes themselves and
70+
/// not require one to touch ASTContext.h to add such information.
2671
struct ASTContext::GlobalCache {
2772
/// Mapping from normal protocol conformances to the explicitly-specified
2873
/// global actor isolations, e.g., when the conformance was spelled
2974
/// `@MainActor P` or similar.
3075
llvm::DenseMap<const NormalProtocolConformance *, TypeExpr *>
3176
conformanceExplicitGlobalActorIsolation;
77+
78+
/// Mapping from normal protocol conformances to the set of actor isolation
79+
/// problems that occur within the conformances.
80+
///
81+
/// This map will be empty for well-formed code, and is used to accumulate
82+
/// information so that the diagnostics can be coalesced.
83+
llvm::DenseMap<
84+
const NormalProtocolConformance *,
85+
std::vector<ConformanceIsolationError>
86+
> conformanceIsolationErrors;
3287
};
3388

3489
} // end namespace

lib/Sema/TypeCheckProtocol.cpp

+164-103
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "TypeCheckObjC.h"
3030
#include "TypeCheckUnsafe.h"
3131
#include "swift/AST/ASTContext.h"
32+
#include "swift/AST/ASTContextGlobalCache.h"
3233
#include "swift/AST/ASTMangler.h"
3334
#include "swift/AST/ASTPrinter.h"
3435
#include "swift/AST/AccessScope.h"
@@ -2352,6 +2353,10 @@ static bool hasRuntimeConformanceInfo(ProtocolDecl *proto) {
23522353
|| proto->isSpecificProtocol(KnownProtocolKind::Escapable);
23532354
}
23542355

2356+
static void diagnoseConformanceIsolationErrors(
2357+
const NormalProtocolConformance *conformance
2358+
);
2359+
23552360
static void ensureRequirementsAreSatisfied(ASTContext &ctx,
23562361
NormalProtocolConformance *conformance);
23572362

@@ -2685,6 +2690,8 @@ checkIndividualConformance(NormalProtocolConformance *conformance) {
26852690
diagnoseUnsafeUse(unsafeUse);
26862691
}
26872692
}
2693+
2694+
diagnoseConformanceIsolationErrors(conformance);
26882695
}
26892696

26902697
/// Add the next associated type deduction to the string representation
@@ -3543,61 +3550,14 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
35433550
}
35443551
}
35453552

3546-
// Complain that this witness cannot conform to the requirement due to
3547-
// actor isolation.
3548-
witness
3549-
->diagnose(diag::actor_isolated_witness,
3550-
isDistributed && !isDistributedDecl(witness),
3551-
refResult.isolation, witness, requirementIsolation,
3552-
Conformance->getProtocol())
3553-
.limitBehaviorUntilSwiftVersion(behavior, 6);
3554-
3555-
// If we need 'distributed' on the witness, add it.
3556-
if (missingOptions.contains(MissingFlags::WitnessDistributed)) {
3557-
witness->diagnose(
3558-
diag::note_add_distributed_to_decl, witness)
3559-
.fixItInsert(witness->getAttributeInsertionLoc(true),
3560-
"distributed ");
3561-
missingOptions -= MissingFlags::WitnessDistributed;
3562-
}
3563-
3564-
// If 'nonisolated' or 'preconcurrency' might help us, provide those as
3565-
// options.
3566-
if (!isDistributedDecl(requirement) && !isDistributedDecl(witness)) {
3567-
// One way to address the issue is to make the witness function nonisolated.
3568-
if ((isa<AbstractFunctionDecl>(witness) || isa<SubscriptDecl>(witness)) &&
3569-
!hasExplicitGlobalActorAttr(witness)) {
3570-
witness->diagnose(diag::note_add_nonisolated_to_decl, witness)
3571-
.fixItInsert(witness->getAttributeInsertionLoc(true), "nonisolated ");
3572-
}
3573-
3574-
// Another way to address the issue is to mark the conformance as
3575-
// isolated to the global actor or "@preconcurrency".
3576-
if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit &&
3577-
!Conformance->isIsolated() &&
3578-
!Conformance->isPreconcurrency() &&
3579-
!suggestedPreconcurrencyOrIsolated &&
3580-
!requirementIsolation.isActorIsolated()) {
3581-
if (Context.LangOpts.hasFeature(Feature::IsolatedConformances) &&
3582-
refResult.isolation.isGlobalActor()) {
3583-
std::string globalActorStr = "@" +
3584-
refResult.isolation.getGlobalActor().getString();
3585-
Context.Diags.diagnose(Conformance->getProtocolNameLoc(),
3586-
diag::add_isolated_to_conformance,
3587-
globalActorStr,
3588-
Proto->getName(), refResult.isolation)
3589-
.fixItInsert(Conformance->getProtocolNameLoc(),
3590-
globalActorStr + " ");
3591-
}
3592-
3593-
Context.Diags.diagnose(Conformance->getProtocolNameLoc(),
3594-
diag::add_preconcurrency_to_conformance,
3595-
Proto->getName())
3596-
.fixItInsert(Conformance->getProtocolNameLoc(), "@preconcurrency ");
3597-
3598-
suggestedPreconcurrencyOrIsolated = true;
3599-
}
3600-
}
3553+
// Note the isolation issue with this witness, to be diagnosed later.
3554+
ASTContext &ctx = DC->getASTContext();
3555+
ctx.getGlobalCache().conformanceIsolationErrors[Conformance].push_back(
3556+
WitnessIsolationError{
3557+
requirement, witness, behavior,
3558+
missingOptions.contains(MissingFlags::WitnessDistributed),
3559+
requirementIsolation, refResult.isolation
3560+
});
36013561

36023562
return std::nullopt;
36033563
}
@@ -4829,6 +4789,140 @@ void ConformanceChecker::resolveSingleWitness(ValueDecl *requirement) {
48294789
}
48304790
}
48314791

4792+
void WitnessIsolationError::diagnose(
4793+
const NormalProtocolConformance *conformance,
4794+
bool &suggestedPreconcurrencyOrIsolated
4795+
) const {
4796+
bool isDistributed = referenceIsolation.isDistributedActor() &&
4797+
!witness->getAttrs().hasAttribute<NonisolatedAttr>();
4798+
4799+
// Complain that this witness cannot conform to the requirement due to
4800+
// actor isolation.
4801+
witness
4802+
->diagnose(diag::actor_isolated_witness,
4803+
isDistributed && !isDistributedDecl(witness),
4804+
referenceIsolation, witness, requirementIsolation,
4805+
conformance->getProtocol())
4806+
.limitBehaviorUntilSwiftVersion(behavior, 6);
4807+
4808+
// If we need 'distributed' on the witness, add it.
4809+
if (isMissingDistributed) {
4810+
witness->diagnose(
4811+
diag::note_add_distributed_to_decl, witness)
4812+
.fixItInsert(witness->getAttributeInsertionLoc(true), "distributed ");
4813+
}
4814+
4815+
// If either of these are distributed, there's nothing more to say.
4816+
if (isDistributedDecl(requirement) || isDistributedDecl(witness))
4817+
return;
4818+
4819+
// If 'nonisolated' or 'preconcurrency' might help us, provide those as
4820+
// options.
4821+
// One way to address the issue is to make the witness function nonisolated.
4822+
if ((isa<AbstractFunctionDecl>(witness) || isa<SubscriptDecl>(witness)) &&
4823+
!hasExplicitGlobalActorAttr(witness)) {
4824+
witness->diagnose(diag::note_add_nonisolated_to_decl, witness)
4825+
.fixItInsert(witness->getAttributeInsertionLoc(true), "nonisolated ");
4826+
}
4827+
4828+
// Another way to address the issue is to mark the conformance as
4829+
// isolated to the global actor or "@preconcurrency".
4830+
if (conformance->getSourceKind() == ConformanceEntryKind::Explicit &&
4831+
!conformance->isIsolated() &&
4832+
!conformance->isPreconcurrency() &&
4833+
!suggestedPreconcurrencyOrIsolated &&
4834+
!requirementIsolation.isActorIsolated()) {
4835+
auto proto = conformance->getProtocol();
4836+
ASTContext &ctx = proto->getASTContext();
4837+
if (ctx.LangOpts.hasFeature(Feature::IsolatedConformances) &&
4838+
referenceIsolation.isGlobalActor()) {
4839+
std::string globalActorStr = "@" +
4840+
referenceIsolation.getGlobalActor().getString();
4841+
ctx.Diags.diagnose(conformance->getProtocolNameLoc(),
4842+
diag::add_isolated_to_conformance,
4843+
globalActorStr,
4844+
proto->getName(), referenceIsolation)
4845+
.fixItInsert(conformance->getProtocolNameLoc(),
4846+
globalActorStr + " ");
4847+
}
4848+
4849+
ctx.Diags.diagnose(conformance->getProtocolNameLoc(),
4850+
diag::add_preconcurrency_to_conformance,
4851+
proto->getName())
4852+
.fixItInsert(conformance->getProtocolNameLoc(), "@preconcurrency ");
4853+
4854+
suggestedPreconcurrencyOrIsolated = true;
4855+
}
4856+
}
4857+
4858+
void AssociatedConformanceIsolationError::diagnose(
4859+
const NormalProtocolConformance *conformance
4860+
) const {
4861+
auto outerIsolation = conformance->getIsolation();
4862+
auto innerIsolation = isolatedConformance->getIsolation();
4863+
4864+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
4865+
4866+
// If the conformance we're checking isn't isolated at all, it
4867+
// needs to be isolated to the given global actor.
4868+
if (!outerIsolation.isGlobalActor()) {
4869+
std::string globalActorStr = "@" +
4870+
innerIsolation.getGlobalActor().getString();
4871+
ctx.Diags.diagnose(
4872+
conformance->getLoc(),
4873+
diag::nonisolated_conformance_depends_on_isolated_conformance,
4874+
conformance->getType(),
4875+
conformance->getProtocol()->getName(),
4876+
innerIsolation,
4877+
isolatedConformance->getType(),
4878+
isolatedConformance->getProtocol()->getName(),
4879+
globalActorStr
4880+
).fixItInsert(conformance->getProtocolNameLoc(),
4881+
globalActorStr + " ");
4882+
4883+
return;
4884+
}
4885+
4886+
assert(outerIsolation != innerIsolation);
4887+
ctx.Diags.diagnose(
4888+
conformance->getLoc(),
4889+
diag::isolated_conformance_mismatch_with_associated_isolation,
4890+
outerIsolation,
4891+
conformance->getType(),
4892+
conformance->getProtocol()->getName(),
4893+
innerIsolation,
4894+
isolatedConformance->getType(),
4895+
isolatedConformance->getProtocol()->getName()
4896+
);
4897+
}
4898+
4899+
static void diagnoseConformanceIsolationErrors(
4900+
const NormalProtocolConformance *conformance
4901+
) {
4902+
// Check whether we have any conformance isolation errors.
4903+
ASTContext &ctx = conformance->getDeclContext()->getASTContext();
4904+
auto &globalCache = ctx.getGlobalCache();
4905+
auto known = globalCache.conformanceIsolationErrors.find(conformance);
4906+
if (known == globalCache.conformanceIsolationErrors.end())
4907+
return;
4908+
4909+
// Take the isolation errors out of the global cache.
4910+
auto isolationErrors = std::move(known->second);
4911+
globalCache.conformanceIsolationErrors.erase(known);
4912+
4913+
bool suggestedPreconcurrencyOrIsolated = false;
4914+
for (const auto &error : isolationErrors) {
4915+
if (std::holds_alternative<WitnessIsolationError>(error)) {
4916+
const auto &witnessError = std::get<WitnessIsolationError>(error);
4917+
witnessError.diagnose(conformance, suggestedPreconcurrencyOrIsolated);
4918+
} else {
4919+
const auto &assocConformanceError =
4920+
std::get<AssociatedConformanceIsolationError>(error);
4921+
assocConformanceError.diagnose(conformance);
4922+
}
4923+
}
4924+
}
4925+
48324926
/// FIXME: It feels like this could be part of findExistentialSelfReferences().
48334927
static std::optional<Requirement>
48344928
hasInvariantSelfRequirement(const ProtocolDecl *proto,
@@ -5136,8 +5230,6 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
51365230
if (where.isImplicit())
51375231
return;
51385232

5139-
bool diagnosedIsolatedConformanceIssue = false;
5140-
51415233
conformance->forEachAssociatedConformance(
51425234
[&](Type depTy, ProtocolDecl *proto, unsigned index) {
51435235
auto assocConf = conformance->getAssociatedConformance(depTy, proto);
@@ -5161,54 +5253,23 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
51615253
where.withRefinedAvailability(availability), depTy, replacementTy);
51625254
}
51635255

5164-
if (!diagnosedIsolatedConformanceIssue) {
5165-
auto outerIsolation = conformance->getIsolation();
5166-
bool foundIssue = ProtocolConformanceRef(assocConf)
5167-
.forEachIsolatedConformance(
5168-
[&](ProtocolConformance *isolatedConformance) {
5169-
auto innerIsolation = isolatedConformance->getIsolation();
5170-
5171-
// If the conformance we're checking isn't isolated at all, it
5172-
// needs "isolated".
5173-
if (!outerIsolation.isGlobalActor()) {
5174-
std::string globalActorStr = "@" +
5175-
innerIsolation.getGlobalActor().getString();
5176-
ctx.Diags.diagnose(
5177-
conformance->getLoc(),
5178-
diag::nonisolated_conformance_depends_on_isolated_conformance,
5179-
typeInContext, conformance->getProtocol()->getName(),
5180-
innerIsolation,
5181-
isolatedConformance->getType(),
5182-
isolatedConformance->getProtocol()->getName(),
5183-
globalActorStr
5184-
).fixItInsert(conformance->getProtocolNameLoc(),
5185-
globalActorStr + " ");
5186-
5187-
return true;
5188-
}
5189-
5190-
// The conformance is isolated, but we need it to have the same
5191-
// isolation as the other isolated conformance we found.
5192-
if (outerIsolation != innerIsolation) {
5193-
ctx.Diags.diagnose(
5194-
conformance->getLoc(),
5195-
diag::isolated_conformance_mismatch_with_associated_isolation,
5196-
outerIsolation,
5197-
typeInContext, conformance->getProtocol()->getName(),
5198-
innerIsolation,
5199-
isolatedConformance->getType(),
5200-
isolatedConformance->getProtocol()->getName()
5201-
);
5202-
5203-
return true;
5204-
}
5205-
5206-
return false;
5256+
auto outerIsolation = conformance->getIsolation();
5257+
ProtocolConformanceRef(assocConf).forEachIsolatedConformance(
5258+
[&](ProtocolConformance *isolatedConformance) {
5259+
auto innerIsolation = isolatedConformance->getIsolation();
5260+
5261+
// If the isolation doesn't match, record an error.
5262+
if (!outerIsolation.isGlobalActor() ||
5263+
outerIsolation != innerIsolation) {
5264+
ctx.getGlobalCache().conformanceIsolationErrors[conformance]
5265+
.push_back(
5266+
AssociatedConformanceIsolationError{isolatedConformance});
5267+
return true;
52075268
}
5208-
);
52095269

5210-
diagnosedIsolatedConformanceIssue = foundIssue;
5211-
}
5270+
return false;
5271+
}
5272+
);
52125273

52135274
return false;
52145275
});

0 commit comments

Comments
 (0)