Skip to content

Commit 7dd295f

Browse files
authored
Merge pull request swiftlang#39503 from etcwilde/ewilde/mainactor-the-mains
Require MainActor isolation on main() function
2 parents cf4fc9f + fa00a32 commit 7dd295f

File tree

7 files changed

+130
-23
lines changed

7 files changed

+130
-23
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4339,6 +4339,10 @@ NOTE(note_add_globalactor_to_function,none,
43394339
"add '@%0' to make %1 %2 part of global actor %3",
43404340
(StringRef, DescriptiveDeclKind, DeclName, Type))
43414341
FIXIT(insert_globalactor_attr, "@%0 ", (Type))
4342+
4343+
ERROR(main_function_must_be_mainActor,none,
4344+
"main() must be '@MainActor'", ())
4345+
43424346
ERROR(not_objc_function_async,none,
43434347
"'async' %0 cannot be represented in Objective-C", (DescriptiveDeclKind))
43444348
NOTE(not_objc_function_type_async,none,

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2853,6 +2853,41 @@ static ActorIsolation getActorIsolationFromWrappedProperty(VarDecl *var) {
28532853
return ActorIsolation::forUnspecified();
28542854
}
28552855

2856+
static Optional<ActorIsolation>
2857+
getActorIsolationForMainFuncDecl(FuncDecl *fnDecl) {
2858+
// Ensure that the base type that this function is declared in has @main
2859+
// attribute
2860+
NominalTypeDecl *declContext =
2861+
dyn_cast<NominalTypeDecl>(fnDecl->getDeclContext());
2862+
if (ExtensionDecl *exDecl =
2863+
dyn_cast<ExtensionDecl>(fnDecl->getDeclContext())) {
2864+
declContext = exDecl->getExtendedNominal();
2865+
}
2866+
2867+
// We're not even in a nominal decl type, this can't be the main function decl
2868+
if (!declContext)
2869+
return {};
2870+
const bool isMainDeclContext =
2871+
declContext->getAttrs().hasAttribute<MainTypeAttr>();
2872+
2873+
ASTContext &ctx = fnDecl->getASTContext();
2874+
2875+
const bool isMainMain = fnDecl->isMainTypeMainMethod();
2876+
const bool isMain$Main =
2877+
fnDecl->getBaseIdentifier() == ctx.getIdentifier("$main") &&
2878+
!fnDecl->isInstanceMember() &&
2879+
fnDecl->getResultInterfaceType()->isVoid() &&
2880+
fnDecl->getParameters()->size() == 0;
2881+
const bool isMainFunction = isMainDeclContext && (isMainMain || isMain$Main);
2882+
const bool hasMainActor = !ctx.getMainActorType().isNull();
2883+
2884+
return isMainFunction && hasMainActor
2885+
? ActorIsolation::forGlobalActor(
2886+
ctx.getMainActorType()->mapTypeOutOfContext(),
2887+
/*isUnsafe*/ false)
2888+
: Optional<ActorIsolation>();
2889+
}
2890+
28562891
/// Check rules related to global actor attributes on a class declaration.
28572892
///
28582893
/// \returns true if an error occurred.
@@ -2922,9 +2957,26 @@ ActorIsolation ActorIsolationRequest::evaluate(
29222957
: ActorIsolation::forActorInstance(actor);
29232958
}
29242959

2960+
auto isolationFromAttr = getIsolationFromAttributes(value);
2961+
if (FuncDecl *fd = dyn_cast<FuncDecl>(value)) {
2962+
// Main.main() and Main.$main are implicitly MainActor-protected.
2963+
// Any other isolation is an error.
2964+
Optional<ActorIsolation> mainIsolation =
2965+
getActorIsolationForMainFuncDecl(fd);
2966+
if (mainIsolation) {
2967+
if (isolationFromAttr && isolationFromAttr->isGlobalActor()) {
2968+
if (!areTypesEqual(isolationFromAttr->getGlobalActor(),
2969+
mainIsolation->getGlobalActor())) {
2970+
fd->getASTContext().Diags.diagnose(
2971+
fd->getLoc(), diag::main_function_must_be_mainActor);
2972+
}
2973+
}
2974+
return *mainIsolation;
2975+
}
2976+
}
29252977
// If this declaration has one of the actor isolation attributes, report
29262978
// that.
2927-
if (auto isolationFromAttr = getIsolationFromAttributes(value)) {
2979+
if (isolationFromAttr) {
29282980
// Nonisolated declarations must involve Sendable types.
29292981
if (*isolationFromAttr == ActorIsolation::Independent) {
29302982
SubstitutionMap subs;

test/Concurrency/Runtime/class_resilience.swift

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,24 @@ func virtualWait<T>(orThrow: Bool, _ c: BaseClass<T>) async throws {
5555
return try await c.wait(orThrow: orThrow)
5656
}
5757

58-
59-
60-
6158
@main struct Main {
6259
static func main() async {
63-
var AsyncVTableMethodSuite = TestSuite("ResilientClass")
64-
AsyncVTableMethodSuite.test("AsyncVTableMethod") {
65-
let x = MyDerived(value: 321)
60+
let task = Task.detached {
61+
var AsyncVTableMethodSuite = TestSuite("ResilientClass")
62+
AsyncVTableMethodSuite.test("AsyncVTableMethod") {
63+
let x = MyDerived(value: 321)
6664

67-
await virtualWaitForNothing(x)
65+
await virtualWaitForNothing(x)
6866

69-
expectEqual(642, await virtualWait(x))
70-
expectEqual(246, await virtualWaitForInt(x))
67+
expectEqual(642, await virtualWait(x))
68+
expectEqual(246, await virtualWaitForInt(x))
7169

72-
expectNil(try? await virtualWait(orThrow: true, x))
73-
try! await virtualWait(orThrow: false, x)
70+
expectNil(try? await virtualWait(orThrow: true, x))
71+
try! await virtualWait(orThrow: false, x)
72+
}
73+
await runAllTestsAsync()
7474
}
75-
await runAllTestsAsync()
75+
76+
await task.value
7677
}
7778
}

test/Concurrency/Runtime/protocol_resilience.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,23 @@ func genericWait<T : Awaitable>(orThrow: Bool, _ t: T) async throws {
6262

6363
@main struct Main {
6464
static func main() async {
65-
var AsyncProtocolRequirementSuite = TestSuite("ResilientProtocol")
65+
let task = Task.detached {
66+
var AsyncProtocolRequirementSuite = TestSuite("ResilientProtocol")
6667

67-
AsyncProtocolRequirementSuite.test("AsyncProtocolRequirement") {
68-
let x = IntAwaitable()
68+
AsyncProtocolRequirementSuite.test("AsyncProtocolRequirement") {
69+
let x = IntAwaitable()
6970

70-
await genericWaitForNothing(x)
71+
await genericWaitForNothing(x)
7172

72-
expectEqual(123, await genericWait(x))
73-
expectEqual(321, await genericWaitForInt(x))
73+
expectEqual(123, await genericWait(x))
74+
expectEqual(321, await genericWaitForInt(x))
7475

75-
expectNil(try? await genericWait(orThrow: true, x))
76-
try! await genericWait(orThrow: false, x)
76+
expectNil(try? await genericWait(orThrow: true, x))
77+
try! await genericWait(orThrow: false, x)
78+
}
79+
await runAllTestsAsync()
7780
}
78-
await runAllTestsAsync()
81+
82+
await task.value
7983
}
8084
}

test/Concurrency/async_main.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,25 @@
1010
// REQUIRES: concurrency_runtime
1111
// UNSUPPORTED: back_deployment_runtime
1212

13+
@MainActor
14+
var foo: Int = 42
15+
1316
func asyncFunc() async {
1417
print("Hello World!")
1518
}
1619

1720
@main struct MyProgram {
1821
static func main() async throws {
22+
print(foo)
23+
foo += 1
1924
await asyncFunc()
25+
print(foo)
2026
}
2127
}
2228

23-
// CHECK-EXEC: Hello World!
29+
// CHECK-EXEC: 42
30+
// CHECK-EXEC-NEXT: Hello World!
31+
// CHECK-EXEC-NEXT: 43
2432

2533
// static MyProgram.main()
2634
// CHECK-SIL-LABEL: sil hidden @$s10async_main9MyProgramV0B0yyYaKFZ : $@convention(method) @async (@thin MyProgram.Type) -> @error Error
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking -parse-as-library
2+
3+
@globalActor
4+
actor Kat {
5+
static let shared = Kat()
6+
}
7+
8+
@Kat
9+
var poof: Int = 1337 // expected-note{{var declared here}}
10+
11+
@main struct Doggo {
12+
@Kat
13+
static func main() { // expected-error{{main() must be '@MainActor'}}
14+
// expected-error@+1{{var 'poof' isolated to global actor 'Kat' can not be referenced from different global actor 'MainActor' in a synchronous context}}
15+
print("Kat value: \(poof)")
16+
}
17+
}
18+
19+
struct Bunny {
20+
// Bunnies are not @main, so they can have a "main" function that is on
21+
// another actor. It's not actually the main function, so it's fine.
22+
@Kat
23+
static func main() {
24+
}
25+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %target-typecheck-verify-swift -disable-availability-checking -parse-as-library
2+
// This should pass without any warnings or errors
3+
4+
@MainActor
5+
var floofer: Int = 42
6+
7+
@main struct Doggo { }
8+
9+
extension Doggo {
10+
static func main() {
11+
print("Doggo value: \(floofer)")
12+
}
13+
}

0 commit comments

Comments
 (0)