Skip to content

Commit 4d2b01b

Browse files
committed
Group Actor Isolation errors
The number of errors in a function scope can scale exponentially, making it hard to root cause and resolve unless we group them into a single error. Grouping the error diagnostics will considerably improve the concurrency user experience.
1 parent f365316 commit 4d2b01b

File tree

5 files changed

+176
-55
lines changed

5 files changed

+176
-55
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5258,11 +5258,10 @@ NOTE(note_add_async_and_throws_to_decl,none,
52585258
NOTE(note_add_distributed_to_decl,none,
52595259
"add 'distributed' to %0 to make this %kindonly0 satisfy the protocol requirement",
52605260
(const ValueDecl *))
5261-
NOTE(note_add_globalactor_to_function,none,
5261+
ERROR(add_globalactor_to_function,none,
52625262
"add '@%0' to make %kind1 part of global actor %2",
52635263
(StringRef, const ValueDecl *, Type))
52645264
FIXIT(insert_globalactor_attr, "@%0 ", (Type))
5265-
52665265
ERROR(main_function_must_be_mainActor,none,
52675266
"main() must be '@MainActor'", ())
52685267

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ EXPERIMENTAL_FEATURE(BitwiseCopyable, true)
278278
/// Enables the FixedArray data type.
279279
EXPERIMENTAL_FEATURE(FixedArrays, false)
280280

281+
// Group Main Actor Isolation Errors by Scope
282+
EXPERIMENTAL_FEATURE(GroupActorErrors, true)
283+
281284
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
282285
#undef EXPERIMENTAL_FEATURE
283286
#undef UPCOMING_FEATURE

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3300,6 +3300,10 @@ static bool usesFeatureDeprecateApplicationMain(Decl *decl) {
33003300
return false;
33013301
}
33023302

3303+
static bool usesFeatureGroupActorErrors(Decl *decl) {
3304+
return false;
3305+
}
3306+
33033307
static bool usesFeatureImportObjcForwardDeclarations(Decl *decl) {
33043308
ClangNode clangNode = decl->getClangNode();
33053309
if (!clangNode)

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 110 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,39 +1921,6 @@ static FuncDecl *findAnnotatableFunction(DeclContext *dc) {
19211921
return fn;
19221922
}
19231923

1924-
/// Note when the enclosing context could be put on a global actor.
1925-
// FIXME: This should handle closures too.
1926-
static void noteGlobalActorOnContext(DeclContext *dc, Type globalActor) {
1927-
// If we are in a synchronous function on the global actor,
1928-
// suggest annotating with the global actor itself.
1929-
if (auto fn = findAnnotatableFunction(dc)) {
1930-
// Suppress this for accessories because you can't change the
1931-
// actor isolation of an individual accessor. Arguably we could
1932-
// add this to the entire storage declaration, though.
1933-
// Suppress this for async functions out of caution; but don't
1934-
// suppress it if we looked through a defer.
1935-
if (!isa<AccessorDecl>(fn) &&
1936-
(!fn->isAsyncContext() || fn != dc)) {
1937-
switch (getActorIsolation(fn)) {
1938-
case ActorIsolation::ActorInstance:
1939-
case ActorIsolation::GlobalActor:
1940-
case ActorIsolation::GlobalActorUnsafe:
1941-
case ActorIsolation::Nonisolated:
1942-
case ActorIsolation::NonisolatedUnsafe:
1943-
return;
1944-
1945-
case ActorIsolation::Unspecified:
1946-
fn->diagnose(diag::note_add_globalactor_to_function,
1947-
globalActor->getWithoutParens().getString(),
1948-
fn, globalActor)
1949-
.fixItInsert(fn->getAttributeInsertionLoc(false),
1950-
diag::insert_globalactor_attr, globalActor);
1951-
return;
1952-
}
1953-
}
1954-
}
1955-
}
1956-
19571924
bool swift::diagnoseApplyArgSendability(ApplyExpr *apply, const DeclContext *declContext) {
19581925
auto isolationCrossing = apply->getIsolationCrossing();
19591926
if (!isolationCrossing.has_value())
@@ -2036,6 +2003,12 @@ namespace {
20362003
/// an expression or function.
20372004
llvm::SmallDenseMap<const DeclContext *, ActorIsolation> requiredIsolation;
20382005

2006+
using IsolationPair = std::pair<ReferencedActor::Kind, ActorIsolation>;
2007+
2008+
using DiagnosticList = std::vector<IsolationError>;
2009+
2010+
llvm::DenseMap<IsolationPair, DiagnosticList> isoErrors;
2011+
20392012
/// Keeps track of the capture context of variables that have been
20402013
/// explicitly captured in closures.
20412014
llvm::SmallDenseMap<VarDecl *, TinyPtrVector<const DeclContext *>>
@@ -2054,6 +2027,70 @@ namespace {
20542027
return applyStack.back().dyn_cast<ApplyExpr *>();
20552028
}
20562029

2030+
/// Note when the enclosing context could be put on a global actor.
2031+
// FIXME: This should handle closures too.
2032+
static bool missingGlobalActorOnContext(DeclContext *dc, Type globalActor) {
2033+
// If we are in a synchronous function on the global actor,
2034+
// suggest annotating with the global actor itself.
2035+
if (auto fn = findAnnotatableFunction(dc)) {
2036+
// Suppress this for accessories because you can't change the
2037+
// actor isolation of an individual accessor. Arguably we could
2038+
// add this to the entire storage declaration, though.
2039+
// Suppress this for async functions out of caution; but don't
2040+
// suppress it if we looked through a defer.
2041+
if (!isa<AccessorDecl>(fn) &&
2042+
(!fn->isAsyncContext() || fn != dc)) {
2043+
switch (getActorIsolation(fn)) {
2044+
case ActorIsolation::ActorInstance:
2045+
case ActorIsolation::GlobalActor:
2046+
case ActorIsolation::GlobalActorUnsafe:
2047+
case ActorIsolation::Nonisolated:
2048+
case ActorIsolation::NonisolatedUnsafe:
2049+
return false;
2050+
2051+
case ActorIsolation::Unspecified:
2052+
fn->diagnose(diag::add_globalactor_to_function,
2053+
globalActor->getWithoutParens().getString(),
2054+
fn, globalActor)
2055+
.fixItInsert(fn->getAttributeInsertionLoc(false),
2056+
diag::insert_globalactor_attr, globalActor);
2057+
return true;
2058+
}
2059+
}
2060+
}
2061+
return false;
2062+
}
2063+
2064+
public:
2065+
bool diagnoseIsolationErrors() {
2066+
bool diagnosedError = false;
2067+
2068+
for (auto list : isoErrors) {
2069+
IsolationPair key = list.getFirst();
2070+
DiagnosticList errors = list.getSecond();
2071+
ActorIsolation isolation = key.second;
2072+
2073+
auto behavior = DiagnosticBehavior::Error;
2074+
2075+
// Add Fix-it for missing @SomeActor annotation
2076+
if (isolation.isGlobalActor()) {
2077+
if (missingGlobalActorOnContext(
2078+
const_cast<DeclContext*>(getDeclContext()), isolation.getGlobalActor())) {
2079+
behavior= DiagnosticBehavior::Note;
2080+
}
2081+
}
2082+
2083+
for (IsolationError error : errors) {
2084+
// Diagnose actor_isolated_non_self_reference as note
2085+
// if we provide fix-it in missingGlobalActorOnContext
2086+
ctx.Diags.diagnose(error.loc, error.diag)
2087+
.limitBehavior(behavior);
2088+
}
2089+
}
2090+
return diagnosedError;
2091+
}
2092+
2093+
private:
20572094
const PatternBindingDecl *getTopPatternBindingDecl() const {
20582095
return patternBindingStack.empty() ? nullptr : patternBindingStack.back();
20592096
}
@@ -2385,6 +2422,8 @@ namespace {
23852422
requiredIsolationLoc = expr->getLoc();
23862423

23872424
expr->walk(*this);
2425+
// print all diagnostics here :)
2426+
//diagnoseIsolationErrors(getDeclContext(), isoErrors);
23882427
requiredIsolationLoc = SourceLoc();
23892428
return requiredIsolation[getDeclContext()];
23902429
}
@@ -3203,11 +3242,11 @@ namespace {
32033242
.warnUntilSwiftVersionIf(getContextIsolation().preconcurrency(), 6);
32043243
}
32053244

3206-
if (unsatisfiedIsolation->isGlobalActor()) {
3207-
noteGlobalActorOnContext(
3208-
const_cast<DeclContext *>(getDeclContext()),
3209-
unsatisfiedIsolation->getGlobalActor());
3210-
}
3245+
// if (unsatisfiedIsolation->isGlobalActor()) {
3246+
// noteGlobalActorOnContext(
3247+
// const_cast<DeclContext *>(getDeclContext()),
3248+
// unsatisfiedIsolation->getGlobalActor());
3249+
// }
32113250

32123251
return true;
32133252
}
@@ -3576,22 +3615,38 @@ namespace {
35763615
bool preconcurrencyContext =
35773616
result.options.contains(ActorReferenceResult::Flags::Preconcurrency);
35783617

3579-
ctx.Diags.diagnose(
3580-
loc, diag::actor_isolated_non_self_reference,
3581-
decl,
3582-
useKind,
3583-
refKind + 1, refGlobalActor,
3584-
result.isolation)
3585-
.warnUntilSwiftVersionIf(preconcurrencyContext, 6);
3586-
3587-
noteIsolatedActorMember(decl, context);
3588-
3589-
if (result.isolation.isGlobalActor()) {
3590-
noteGlobalActorOnContext(
3591-
const_cast<DeclContext *>(getDeclContext()),
3592-
result.isolation.getGlobalActor());
3593-
}
3618+
if (ctx.LangOpts.hasFeature(Feature::GroupActorErrors)) {
3619+
IsolationError isoMismatch = IsolationError(loc, Diagnostic(diag::actor_isolated_non_self_reference,
3620+
decl,
3621+
useKind,
3622+
refKind + 1, refGlobalActor,
3623+
result.isolation));
35943624

3625+
auto iter = isoErrors.find(std::make_pair(refKind,result.isolation));
3626+
if (iter != isoErrors.end()){
3627+
iter->second.push_back(isoMismatch);
3628+
} else {
3629+
DiagnosticList list;
3630+
list.push_back(isoMismatch);
3631+
auto keyPair = std::make_pair(refKind,result.isolation);
3632+
isoErrors.insert(std::make_pair(keyPair, list));
3633+
}
3634+
} else {
3635+
ctx.Diags.diagnose(
3636+
loc, diag::actor_isolated_non_self_reference,
3637+
decl,
3638+
useKind,
3639+
refKind + 1, refGlobalActor,
3640+
result.isolation)
3641+
.warnUntilSwiftVersionIf(preconcurrencyContext, 6);
3642+
3643+
noteIsolatedActorMember(decl, context);
3644+
if (result.isolation.isGlobalActor()) {
3645+
missingGlobalActorOnContext(
3646+
const_cast<DeclContext *>(getDeclContext()),
3647+
result.isolation.getGlobalActor());
3648+
}
3649+
}
35953650
return true;
35963651
}
35973652
}
@@ -3733,9 +3788,11 @@ void swift::checkFunctionActorIsolation(AbstractFunctionDecl *decl) {
37333788
if (decl->getAttrs().hasAttribute<LLDBDebuggerFunctionAttr>())
37343789
return;
37353790

3791+
auto &ctx = decl->getASTContext();
37363792
ActorIsolationChecker checker(decl);
37373793
if (auto body = decl->getBody()) {
37383794
body->walk(checker);
3795+
if(ctx.LangOpts.hasFeature(Feature::GroupActorErrors)){ checker.diagnoseIsolationErrors(); }
37393796
}
37403797
if (auto ctor = dyn_cast<ConstructorDecl>(decl)) {
37413798
if (auto superInit = ctor->getSuperInitCall())

lib/Sema/TypeCheckConcurrency.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,19 @@ struct ActorReferenceResult {
248248
operator Kind() const { return kind; }
249249
};
250250

251+
struct IsolationError {
252+
253+
SourceLoc loc;
254+
255+
Diagnostic diag;
256+
257+
public:
258+
IsolationError(SourceLoc loc, Diagnostic diag) : loc(loc), diag(diag) {}
259+
260+
};
261+
262+
static bool diagnoseIsolationErrors(DeclContext *dc, ArrayRef<IsolationError> errors);
263+
251264
/// Individual options used with \c FunctionCheckOptions
252265
enum class FunctionCheckKind {
253266
/// Check params
@@ -580,4 +593,49 @@ bool diagnoseApplyArgSendability(
580593

581594
} // end namespace swift
582595

596+
namespace llvm {
597+
598+
template <>
599+
struct DenseMapInfo<swift::ReferencedActor::Kind> {
600+
using RefActorKind = swift::ReferencedActor::Kind;
601+
602+
static RefActorKind getEmptyKey() {
603+
return RefActorKind::NonIsolatedContext;
604+
}
605+
606+
static RefActorKind getTombstoneKey() {
607+
return RefActorKind::NonIsolatedContext;
608+
}
609+
610+
static unsigned getHashValue(RefActorKind Val) {
611+
return static_cast<unsigned>(Val);
612+
}
613+
614+
static bool isEqual(const RefActorKind &LHS, const RefActorKind &RHS) {
615+
return LHS == RHS;
616+
}
617+
};
618+
619+
template <>
620+
struct DenseMapInfo<swift::ActorIsolation> {
621+
using RefActor = swift::ActorIsolation;
622+
623+
static RefActor getEmptyKey() {
624+
return RefActor(swift::ActorIsolation::Kind::Unspecified);
625+
}
626+
627+
static RefActor getTombstoneKey() {
628+
return RefActor(swift::ActorIsolation::Kind::Unspecified);
629+
}
630+
631+
static unsigned getHashValue(RefActor Val) {
632+
return static_cast<unsigned>(Val.getKind());
633+
}
634+
635+
static bool isEqual(const RefActor &LHS, const RefActor &RHS) {
636+
return LHS == RHS;
637+
}
638+
};
639+
} // end namespace llvm
640+
583641
#endif /* SWIFT_SEMA_TYPECHECKCONCURRENCY_H */

0 commit comments

Comments
 (0)