Skip to content

Commit ae6d30e

Browse files
committed
Make namelookup not allergic to async main
The typesystem was avoiding the async main function if any synchronous main was available. This is because the decl contexts where the main function is looked up from aren't async contexts (extension contexts, generic type contexts), so if a synchronous main function exists anywhere, it will be a better "fit" for the context. Since we're the main function, we decide on whether or not we want to be async, so looking at the decl-context's asyncness is really the wrong thing to do. Plumbing this info through the constraint system seems risky at best, catastrophic at worst, so instead, I'm just grabbing the buffer of resolved decls and doing my own scoring and selection. We want to ensure that we are picking the main function that is closest to the decl marked with `@main` in the protocol conformance chain. This means that if your `@main` struct directly conforms to a protocol that implements a main function, you should probably use it. If that protocol implements two main functions, one sync and one async, we look at whether we support async before selecting it. Things get a little bit weird if you have implemented an async main in the closest protocol, but are targeting something that doesn't support concurrency. The `@available` checking should handle this with a nice error saying you're doing bad things, but if you circumvent that checking for bad reasons, it will still fall back on a synchronous main function if one is available.
1 parent f67efdd commit ae6d30e

File tree

3 files changed

+170
-33
lines changed

3 files changed

+170
-33
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3180,6 +3180,7 @@ ERROR(nscopying_doesnt_conform,none,
31803180
#define SELECT_APPLICATION_DELEGATE "select{'UIApplicationDelegate'|'NSApplicationDelegate'}"
31813181
#define SELECT_APPLICATION_TYPE "select{class|class|type}"
31823182
#define SELECT_APPLICATION_TYPES "select{classes|classes|types}"
3183+
#define SELECT_APPLICATION_MAIN_TYPES "select{() -> Void or () throws -> Void|() -> Void, () throws -> Void, () async -> Void, or () async throws -> Void}"
31833184

31843185
ERROR(attr_ApplicationMain_not_ApplicationDelegate,none,
31853186
"%" SELECT_APPLICATION_MAIN "0 class must conform to the %" SELECT_APPLICATION_DELEGATE "0 protocol",
@@ -3199,9 +3200,11 @@ NOTE(attr_ApplicationMain_script_here,none,
31993200
())
32003201

32013202
ERROR(attr_MainType_without_main,none,
3202-
"%0 is annotated with @main and must provide a main static function of type () -> Void or () throws -> Void.",
3203-
(DeclName))
3203+
"%0 is annotated with @main and must provide a main static function of type %"
3204+
SELECT_APPLICATION_MAIN_TYPES "1",
3205+
(DeclName, bool))
32043206

3207+
#undef SELECT_APPLICATION_MAIN_TYPES
32053208
#undef SELECT_APPLICATION_MAIN
32063209
#undef SELECT_APPLICATION_DELEGATE
32073210

lib/Sema/TypeCheckAttr.cpp

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,6 +1994,70 @@ synthesizeMainBody(AbstractFunctionDecl *fn, void *arg) {
19941994
return std::make_pair(body, /*typechecked=*/false);
19951995
}
19961996

1997+
static FuncDecl *resolveMainFunctionDecl(DeclContext *declContext,
1998+
ResolvedMemberResult &resolution,
1999+
ASTContext &ctx) {
2000+
// The normal resolution mechanism won't choose the asynchronous main function
2001+
// unless no other options are available because extensions and generic types
2002+
// (structs/classes) are not considered asynchronous contexts.
2003+
// We want them to be promoted to a viable entrypoint if the deployment target
2004+
// is high enough.
2005+
SmallVector<FuncDecl *, 4> viableCandidates;
2006+
for (ValueDecl *candidate : resolution.getMemberDecls(Viable)) {
2007+
if (FuncDecl *function = dyn_cast<FuncDecl>(candidate)) {
2008+
if (function->isMainTypeMainMethod())
2009+
viableCandidates.push_back(function);
2010+
}
2011+
}
2012+
if (viableCandidates.empty()) {
2013+
return nullptr;
2014+
}
2015+
2016+
AvailabilityContext contextAvailability =
2017+
AvailabilityContext::forDeploymentTarget(ctx);
2018+
const bool hasAsyncSupport = contextAvailability.isContainedIn(
2019+
ctx.getBackDeployedConcurrencyAvailability());
2020+
2021+
FuncDecl *best = nullptr;
2022+
for (FuncDecl *candidate : viableCandidates) {
2023+
// The candidate will work if it's synchronous, or if we support concurrency
2024+
// and it is async, or if we are in YOLO mode
2025+
const bool candidateWorks = !candidate->hasAsync() ||
2026+
(hasAsyncSupport && candidate->hasAsync()) ||
2027+
ctx.LangOpts.DisableAvailabilityChecking;
2028+
2029+
// Skip it if it won't work
2030+
if (!candidateWorks)
2031+
continue;
2032+
2033+
// If we don't have a best, the candidate is the best so far
2034+
if (!best) {
2035+
best = candidate;
2036+
continue;
2037+
}
2038+
2039+
// If the candidate is better and it's synchronous, just swap it right in.
2040+
// If the candidate is better and it's async, make sure we support async
2041+
// before selecting it.
2042+
//
2043+
// If it's unordered (equally bestest), use the async version if we support
2044+
// it or use the sync version if we don't.
2045+
const Comparison rank =
2046+
TypeChecker::compareDeclarations(declContext, candidate, best);
2047+
const bool isBetter = rank == Comparison::Better;
2048+
const bool isUnordered = rank == Comparison::Unordered;
2049+
const bool swapForAsync =
2050+
hasAsyncSupport && candidate->hasAsync() && !best->hasAsync();
2051+
const bool swapForSync =
2052+
!hasAsyncSupport && !candidate->hasAsync() && best->hasAsync();
2053+
const bool selectCandidate =
2054+
isBetter || (isUnordered && (swapForAsync || swapForSync));
2055+
if (selectCandidate)
2056+
best = candidate;
2057+
}
2058+
return best;
2059+
}
2060+
19972061
FuncDecl *
19982062
SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
19992063
Decl *D) const {
@@ -2049,37 +2113,17 @@ SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
20492113

20502114
auto resolution = resolveValueMember(
20512115
*declContext, nominal->getInterfaceType(), context.Id_main);
2052-
2053-
FuncDecl *mainFunction = nullptr;
2054-
2055-
if (resolution.hasBestOverload()) {
2056-
auto best = resolution.getBestOverload();
2057-
if (auto function = dyn_cast<FuncDecl>(best)) {
2058-
if (function->isMainTypeMainMethod()) {
2059-
mainFunction = function;
2060-
}
2061-
}
2062-
}
2063-
2064-
if (mainFunction == nullptr) {
2065-
SmallVector<FuncDecl *, 4> viableCandidates;
2066-
2067-
for (auto *candidate : resolution.getMemberDecls(Viable)) {
2068-
if (auto func = dyn_cast<FuncDecl>(candidate)) {
2069-
if (func->isMainTypeMainMethod()) {
2070-
viableCandidates.push_back(func);
2071-
}
2072-
}
2073-
}
2074-
2075-
if (viableCandidates.size() != 1) {
2076-
context.Diags.diagnose(attr->getLocation(),
2077-
diag::attr_MainType_without_main,
2078-
nominal->getBaseName());
2079-
attr->setInvalid();
2080-
return nullptr;
2081-
}
2082-
mainFunction = viableCandidates[0];
2116+
FuncDecl *mainFunction =
2117+
resolveMainFunctionDecl(declContext, resolution, context);
2118+
if (!mainFunction) {
2119+
const bool hasAsyncSupport =
2120+
AvailabilityContext::forDeploymentTarget(context).isContainedIn(
2121+
context.getBackDeployedConcurrencyAvailability());
2122+
context.Diags.diagnose(attr->getLocation(),
2123+
diag::attr_MainType_without_main,
2124+
nominal->getBaseName(), hasAsyncSupport);
2125+
attr->setInvalid();
2126+
return nullptr;
20832127
}
20842128

20852129
auto where = ExportContext::forDeclSignature(D);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// async main is nested deeper in protocols than sync, should use sync (always)
2+
// sync main is nested deeper in protocols than async, use async if supported
3+
// async and sync are same level, use async if supported
4+
5+
// async main is nested in the protocol chain from `MyMain`
6+
// Always choose Sync overload
7+
// RUN: %target-swift-frontend -target x86_64-apple-macosx10.9 -DASYNC_NESTED -DINHERIT_SYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
8+
// RUN: %target-swift-frontend -target x86_64-apple-macosx11.0 -DASYNC_NESTED -DINHERIT_SYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
9+
10+
// sync main is deeper in the protocol chain from `MyMain`
11+
// Choose async when available
12+
// RUN: %target-swift-frontend -target x86_64-apple-macosx10.9 -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
13+
// RUN: %target-swift-frontend -target x86_64-apple-macosx11.0 -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
14+
15+
// sync and async main are at same level (In MainProtocol) to `MyMain`.
16+
// Choose async when available
17+
// RUN: %target-swift-frontend -target x86_64-apple-macosx10.9 -DBOTH -DINHERIT_SYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
18+
// RUN: %target-swift-frontend -target x86_64-apple-macosx11.9 -DBOTH -DINHERIT_SYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
19+
20+
// async main is the only option on the protocol chain
21+
// Choose async if we support it, error otherwise
22+
// RUN: not %target-swift-frontend -target x86_64-apple-macosx10.9 -DASYNC_NESTED -typecheck -dump-ast -parse-as-library %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR
23+
// RUN: %target-swift-frontend -target x86_64-apple-macosx11.9 -DASYNC_NESTED -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
24+
25+
// sync main is the only option on the protocol chain
26+
// Always choose sync
27+
// RUN: %target-swift-frontend -target x86_64-apple-macosx10.9 -DINHERIT_SYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
28+
// RUN: %target-swift-frontend -target x86_64-apple-macosx11.9 -DINHERIT_SYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
29+
30+
// No synchronous, choose async if we support it, error otherwise
31+
// RUN: not %target-swift-frontend -target x86_64-apple-macosx10.9 -DNO_SYNC -typecheck -dump-ast -parse-as-library %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR
32+
// RUN: %target-swift-frontend -target x86_64-apple-macosx11.9 -DNO_SYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
33+
34+
// No asynchronous, choose sync
35+
// RUN: %target-swift-frontend -target x86_64-apple-macosx10.9 -DNO_ASYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
36+
// RUN: %target-swift-frontend -target x86_64-apple-macosx11.9 -DNO_ASYNC -typecheck -dump-ast -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
37+
38+
// No main functions
39+
// RUN: not %target-swift-frontend -target x86_64-apple-macosx10.9 -DNO_SYNC -DNO_ASYNC -typecheck -dump-ast -parse-as-library %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR
40+
// RUN: not %target-swift-frontend -target x86_64-apple-macosx11.9 -DNO_SYNC -DNO_ASYNC -typecheck -dump-ast -parse-as-library %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR-ASYNC
41+
42+
// REQUIRES: concurrency
43+
44+
#if ASYNC_NESTED
45+
protocol AsyncMainProtocol { }
46+
protocol MainProtocol : AsyncMainProtocol { }
47+
#else
48+
protocol MainProtocol { }
49+
protocol AsyncMainProtocol : MainProtocol { }
50+
#endif
51+
52+
#if NO_SYNC
53+
#else
54+
extension MainProtocol {
55+
static func main() { }
56+
}
57+
#endif
58+
59+
#if NO_ASYNC
60+
#else
61+
extension AsyncMainProtocol {
62+
@available(macOS 10.15, *)
63+
static func main() async { }
64+
}
65+
#endif
66+
67+
#if BOTH
68+
extension MainProtocol {
69+
@available(macOS 10.15, *)
70+
static func main() async { }
71+
}
72+
#endif
73+
74+
75+
#if INHERIT_SYNC
76+
@main struct MyMain : MainProtocol {}
77+
#else
78+
@main struct MyMain : AsyncMainProtocol {}
79+
#endif
80+
81+
82+
// CHECK-IS-SYNC-LABEL: (func_decl implicit "$main()" interface type='(MyMain.Type) -> () -> ()'
83+
// CHECK-IS-SYNC: (declref_expr implicit type='(MyMain.Type) -> () -> ()'
84+
85+
// CHECK-IS-ASYNC-LABEL: (func_decl implicit "$main()" interface type='(MyMain.Type) -> () async -> ()'
86+
// CHECK-IS-ASYNC: (declref_expr implicit type='(MyMain.Type) -> () async -> ()'
87+
88+
// CHECK-IS-ERROR: error: 'MyMain' is annotated with @main and must provide a main static function of type () -> Void or () throws -> Void
89+
90+
// CHECK-IS-ERROR-ASYNC: error: 'MyMain' is annotated with @main and must provide a main static function of type () -> Void, () throws -> Void, () async -> Void, or () async throws -> Void

0 commit comments

Comments
 (0)