Skip to content

Commit 8b2ccd9

Browse files
authored
Merge pull request #58429 from etcwilde/ewilde/disjunction-main-resolution
Use Disjunction Constraint to find main function
2 parents d4d1287 + b9405bb commit 8b2ccd9

13 files changed

+125
-104
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,6 @@ namespace swift {
223223
/// Emit a remark after loading a module.
224224
bool EnableModuleLoadingRemarks = false;
225225

226-
/// Resolve main function as though it were called from an async context
227-
bool EnableAsyncMainResolution = false;
228-
229226
///
230227
/// Support for alternate usage modes
231228
///

include/swift/Option/Options.td

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,6 @@ def pch_output_dir: Separate<["-"], "pch-output-dir">,
270270
Flags<[FrontendOption, HelpHidden, ArgumentIsPath]>,
271271
HelpText<"Directory to persist automatically created precompiled bridging headers">;
272272

273-
def async_main: Flag<["-"], "async-main">,
274-
Flags<[FrontendOption]>,
275-
HelpText<"Resolve main function as if it were called from an asynchronous context">;
276-
277273
// FIXME: Unhide this once it doesn't depend on an output file map.
278274
def incremental : Flag<["-"], "incremental">,
279275
Flags<[NoInteractiveOption, HelpHidden, DoesNotAffectIncrementalBuild]>,

include/swift/Sema/ConstraintSystem.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ enum class ConstraintSystemFlags {
15371537
/// Note that this flag is automatically applied to all constraint systems,
15381538
/// when \c DebugConstraintSolver is set in \c TypeCheckerOptions. It can be
15391539
/// automatically enabled for select constraint solving attempts by setting
1540-
/// \c DebugConstraintSolverAttempt. Finally, it can also be automatically
1540+
/// \c DebugConstraintSolverAttempt. Finally, it can also be automatically
15411541
/// enabled for a pre-configured set of expressions on line numbers by setting
15421542
/// \c DebugConstraintSolverOnLines.
15431543
DebugConstraints = 0x10,
@@ -1562,9 +1562,8 @@ enum class ConstraintSystemFlags {
15621562
/// `__attribute__((ns_consumed))`.
15631563
UseClangFunctionTypes = 0x80,
15641564

1565-
/// When set, nominal typedecl contexts are asynchronous contexts.
1566-
/// This is set while searching for the main function
1567-
ConsiderNominalTypeContextsAsync = 0x100,
1565+
/// When set, ignore async/sync mismatches
1566+
IgnoreAsyncSyncMismatch = 0x100,
15681567
};
15691568

15701569
/// Options that affect the constraint system as a whole.

include/swift/Sema/IDETypeChecking.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
#include "swift/AST/Identifier.h"
2323
#include "swift/Basic/SourceLoc.h"
24-
#include "swift/Basic/OptionSet.h"
2524
#include <memory>
2625
#include <tuple>
2726

@@ -51,8 +50,6 @@ namespace swift {
5150
class ConstraintSystem;
5251
class Solution;
5352
class SolutionApplicationTarget;
54-
enum class ConstraintSystemFlags;
55-
using ConstraintSystemOptions = OptionSet<ConstraintSystemFlags>;
5653
}
5754

5855
/// Typecheck binding initializer at \p bindingIndex.
@@ -96,9 +93,8 @@ namespace swift {
9693
/// Unlike other member lookup functions, \c swift::resolveValueMember()
9794
/// should be used when you want to look up declarations with the same name as
9895
/// one you already have.
99-
ResolvedMemberResult
100-
resolveValueMember(DeclContext &DC, Type BaseTy, DeclName Name,
101-
constraints::ConstraintSystemOptions Options = {});
96+
ResolvedMemberResult resolveValueMember(DeclContext &DC, Type BaseTy,
97+
DeclName Name);
10298

10399
/// Given a type and an extension to the original type decl of that type,
104100
/// decide if the extension has been applied, i.e. if the requirements of the

lib/Driver/ToolChains.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
300300
inputArgs.AddLastArg(arguments, options::OPT_access_notes_path);
301301
inputArgs.AddLastArg(arguments, options::OPT_library_level);
302302
inputArgs.AddLastArg(arguments, options::OPT_enable_bare_slash_regex);
303-
inputArgs.AddLastArg(arguments, options::OPT_async_main);
304303

305304
// Pass on any build config options
306305
inputArgs.AddAllArgs(arguments, options::OPT_D);

lib/Frontend/CompilerInvocation.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
497497
Diags.diagnose(SourceLoc(), diag::warn_flag_deprecated,
498498
"-enable-experimental-async-top-level");
499499

500-
Opts.EnableAsyncMainResolution = Args.hasArg(OPT_async_main);
501-
502500
Opts.DiagnoseInvalidEphemeralnessAsError |=
503501
Args.hasArg(OPT_enable_invalid_ephemeralness_as_error);
504502

lib/Sema/CSGen.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4417,11 +4417,10 @@ getMemberDecls(InterestedMemberKind Kind) {
44174417
llvm_unreachable("unhandled kind");
44184418
}
44194419

4420-
ResolvedMemberResult
4421-
swift::resolveValueMember(DeclContext &DC, Type BaseTy, DeclName Name,
4422-
ConstraintSystemOptions Options) {
4420+
ResolvedMemberResult swift::resolveValueMember(DeclContext &DC, Type BaseTy,
4421+
DeclName Name) {
44234422
ResolvedMemberResult Result;
4424-
ConstraintSystem CS(&DC, Options);
4423+
ConstraintSystem CS(&DC, None);
44254424

44264425
// Look up all members of BaseTy with the given Name.
44274426
MemberLookupResult LookupResult = CS.performMemberLookup(

lib/Sema/ConstraintSystem.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,11 +2907,6 @@ bool ConstraintSystem::isAsynchronousContext(DeclContext *dc) {
29072907
FunctionType::ExtInfo()).isAsync();
29082908
}
29092909

2910-
if (Options.contains(
2911-
ConstraintSystemFlags::ConsiderNominalTypeContextsAsync) &&
2912-
isa<NominalTypeDecl>(dc))
2913-
return true;
2914-
29152910
return false;
29162911
}
29172912

@@ -3316,7 +3311,8 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
33163311
// If we're choosing an asynchronous declaration within a synchronous
33173312
// context, or vice-versa, increase the async/async mismatch score.
33183313
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
3319-
if (!func->hasPolymorphicEffect(EffectKind::Async) &&
3314+
if (!Options.contains(ConstraintSystemFlags::IgnoreAsyncSyncMismatch) &&
3315+
!func->hasPolymorphicEffect(EffectKind::Async) &&
33203316
func->isAsyncContext() != isAsynchronousContext(useDC)) {
33213317
increaseScore(
33223318
func->isAsyncContext() ? SK_AsyncInSyncMismatch : SK_SyncInAsync);

lib/Sema/TypeCheckAttr.cpp

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,28 +2096,6 @@ synthesizeMainBody(AbstractFunctionDecl *fn, void *arg) {
20962096
return std::make_pair(body, /*typechecked=*/false);
20972097
}
20982098

2099-
static FuncDecl *resolveMainFunctionDecl(DeclContext *declContext,
2100-
ResolvedMemberResult &resolution,
2101-
ASTContext &ctx) {
2102-
// Choose the best overload if it's a main function
2103-
if (resolution.hasBestOverload()) {
2104-
ValueDecl *best = resolution.getBestOverload();
2105-
if (FuncDecl *func = dyn_cast<FuncDecl>(best)) {
2106-
if (func->isMainTypeMainMethod()) {
2107-
return func;
2108-
}
2109-
}
2110-
}
2111-
// Look for the most highly-ranked main-function candidate
2112-
for (ValueDecl *candidate : resolution.getMemberDecls(Viable)) {
2113-
if (FuncDecl *func = dyn_cast<FuncDecl>(candidate)) {
2114-
if (func->isMainTypeMainMethod())
2115-
return func;
2116-
}
2117-
}
2118-
return nullptr;
2119-
}
2120-
21212099
FuncDecl *
21222100
SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
21232101
Decl *D) const {
@@ -2170,17 +2148,91 @@ SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator,
21702148
// usual type-checking. The alternative would be to directly call
21712149
// mainType.main() from the entry point, and that would require fully
21722150
// type-checking the call to mainType.main().
2151+
using namespace constraints;
2152+
ConstraintSystem CS(declContext,
2153+
ConstraintSystemFlags::IgnoreAsyncSyncMismatch);
2154+
ConstraintLocator *locator =
2155+
CS.getConstraintLocator({}, ConstraintLocator::Member);
2156+
// Allowed main function types
2157+
// `() -> Void`
2158+
// `() async -> Void`
2159+
// `() throws -> Void`
2160+
// `() async throws -> Void`
2161+
// `@MainActor () -> Void`
2162+
// `@MainActor () async -> Void`
2163+
// `@MainActor () throws -> Void`
2164+
// `@MainActor () async throws -> Void`
2165+
{
2166+
llvm::SmallVector<Type, 8> mainTypes = {
2167+
2168+
FunctionType::get(/*params*/ {}, context.TheEmptyTupleType,
2169+
ASTExtInfo()),
2170+
FunctionType::get(
2171+
/*params*/ {}, context.TheEmptyTupleType,
2172+
ASTExtInfoBuilder().withAsync().build()),
2173+
2174+
FunctionType::get(/*params*/ {}, context.TheEmptyTupleType,
2175+
ASTExtInfoBuilder().withThrows().build()),
2176+
2177+
FunctionType::get(
2178+
/*params*/ {}, context.TheEmptyTupleType,
2179+
ASTExtInfoBuilder().withAsync().withThrows().build())};
2180+
2181+
Type mainActor = context.getMainActorType();
2182+
if (mainActor) {
2183+
mainTypes.push_back(FunctionType::get(
2184+
/*params*/ {}, context.TheEmptyTupleType,
2185+
ASTExtInfoBuilder().withGlobalActor(mainActor).build()));
2186+
mainTypes.push_back(FunctionType::get(
2187+
/*params*/ {}, context.TheEmptyTupleType,
2188+
ASTExtInfoBuilder().withAsync().withGlobalActor(mainActor).build()));
2189+
mainTypes.push_back(FunctionType::get(
2190+
/*params*/ {}, context.TheEmptyTupleType,
2191+
ASTExtInfoBuilder().withThrows().withGlobalActor(mainActor).build()));
2192+
mainTypes.push_back(FunctionType::get(/*params*/ {},
2193+
context.TheEmptyTupleType,
2194+
ASTExtInfoBuilder()
2195+
.withAsync()
2196+
.withThrows()
2197+
.withGlobalActor(mainActor)
2198+
.build()));
2199+
}
2200+
TypeVariableType *mainType =
2201+
CS.createTypeVariable(locator, /*options=*/0);
2202+
llvm::SmallVector<Constraint *, 4> typeEqualityConstraints;
2203+
typeEqualityConstraints.reserve(mainTypes.size());
2204+
for (const Type &candidateMainType : mainTypes) {
2205+
typeEqualityConstraints.push_back(
2206+
Constraint::create(CS, ConstraintKind::Equal, Type(mainType),
2207+
candidateMainType, locator));
2208+
}
2209+
2210+
CS.addDisjunctionConstraint(typeEqualityConstraints, locator);
2211+
CS.addValueMemberConstraint(
2212+
nominal->getInterfaceType(), DeclNameRef(context.Id_main),
2213+
Type(mainType), declContext, FunctionRefKind::SingleApply, {}, locator);
2214+
}
2215+
2216+
FuncDecl *mainFunction = nullptr;
2217+
llvm::SmallVector<Solution, 4> candidates;
2218+
2219+
if (!CS.solve(candidates, FreeTypeVariableBinding::Disallow)) {
2220+
// We can't use CS.diagnoseAmbiguity directly since the locator is empty
2221+
// Sticking the main type decl `D` in results in an assert due to a
2222+
// unsimplifiable locator anchor since it appears to be looking for an
2223+
// expression, which we don't have.
2224+
// (locator could not be simplified to anchor)
2225+
// TODO: emit notes for each of the ambiguous candidates
2226+
if (candidates.size() != 1) {
2227+
context.Diags.diagnose(nominal->getLoc(), diag::ambiguous_decl_ref,
2228+
DeclNameRef(context.Id_main));
2229+
attr->setInvalid();
2230+
return nullptr;
2231+
}
2232+
mainFunction = dyn_cast<FuncDecl>(
2233+
candidates[0].overloadChoices[locator].choice.getDecl());
2234+
}
21732235

2174-
constraints::ConstraintSystemOptions lookupOptions;
2175-
if (context.LangOpts.EnableAsyncMainResolution)
2176-
lookupOptions |=
2177-
constraints::ConstraintSystemFlags::ConsiderNominalTypeContextsAsync;
2178-
2179-
auto resolution = resolveValueMember(
2180-
*declContext, nominal->getInterfaceType(), context.Id_main,
2181-
lookupOptions);
2182-
FuncDecl *mainFunction =
2183-
resolveMainFunctionDecl(declContext, resolution, context);
21842236
if (!mainFunction) {
21852237
const bool hasAsyncSupport =
21862238
AvailabilityContext::forDeploymentTarget(context).isContainedIn(

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3380,7 +3380,8 @@ getActorIsolationForMainFuncDecl(FuncDecl *fnDecl) {
33803380
if (!declContext)
33813381
return {};
33823382
const bool isMainDeclContext =
3383-
declContext->getAttrs().hasAttribute<MainTypeAttr>();
3383+
declContext->getAttrs().hasAttribute<MainTypeAttr>(
3384+
/*allow invalid*/ true);
33843385

33853386
ASTContext &ctx = fnDecl->getASTContext();
33863387

test/Concurrency/async_main_resolution.swift

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
// This test aims to show that no preference is given to either the async or
2+
// sync main function. The most specific, valid, main function will be
3+
// selected if one exists. If two main functions could exist, the usage is
4+
// ambiguous.
5+
16
// async main is nested deeper in protocols than sync, use sync
27
// sync main is nested deeper in protocols than async, use async
3-
// async and sync are same level, use async
8+
// async and sync are same level, error
49

510
// REQUIRES: concurrency
611

@@ -10,23 +15,17 @@
1015
// BOTH: MainProtocol has both sync and async main
1116
// INHERIT_SYNC: main type directly conforms to synchronous main protocol
1217

13-
// | async flag | has async main | has sync main | both | inherits sync | nested async | Result | Run |
14-
// | | | | | | | Error | RUN: not %target-swift-frontend -disable-availability-checking -DNO_ASYNC -DNO_SYNC -parse-as-library -typecheck -dump-ast %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR
15-
// | | x | | | | | Async Main | RUN: %target-swift-frontend -disable-availability-checking -DNO_SYNC -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
16-
// | x | | x | | | | Sync Main | RUN: %target-swift-frontend -disable-availability-checking -DNO_ASYNC -async-main -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
17-
// | x | x | x | | | | Async Main | RUN: %target-swift-frontend -disable-availability-checking -async-main -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
18-
// | | x | x | | | | Sync Main | RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
19-
// | | x | x | | | x | Async Main | RUN: %target-swift-frontend -disable-availability-checking -DASYNC_NESTED -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
20-
// | | x | x | | x | x | Sync Main | RUN: %target-swift-frontend -disable-availability-checking -DINHERIT_SYNC -DASYNC_NESTED -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
21-
// | x | x | x | | x | x | Async Main | RUN: %target-swift-frontend -disable-availability-checking -DINHERIT_SYNC -DASYNC_NESTED -async-main -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
22-
// | x | | x | | x | x | Sync Main | RUN: %target-swift-frontend -disable-availability-checking -DNO_ASYNC -DINHERIT_SYNC -DASYNC_NESTED -async-main -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
23-
// | | | x | x | | | Sync Main | RUN: %target-swift-frontend -disable-availability-checking -DBOTH -DNO_ASYNC -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
24-
// | x | | x | x | | | Async Main | RUN: %target-swift-frontend -disable-availability-checking -DBOTH -DNO_ASYNC -async-main -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
25-
26-
// tldr;
27-
// If async flag is set, will pick an asynchronous main function if one is available and related. If none exist, will fall back on synchronous main.
28-
// If async flag is not set, will pick a asynchronous main function if one is available and related. If none exist, will fall back on an asynchronous main
29-
// If neither are available; error
18+
// | has async main | has sync main | both | inherits sync | nested async | Result | | Run |
19+
// | | | | | | Error | No main | RUN: not %target-swift-frontend -disable-availability-checking -DNO_SYNC -DNO_ASYNC -parse-as-library -typecheck -dump-ast %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR1
20+
// | x | x | x | x | | Error | Ambiguous main in MainP | RUN: not %target-swift-frontend -disable-availability-checking -DBOTH -DINHERIT_SYNC -parse-as-library -typecheck -dump-ast %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR2
21+
// | | x | x | x | | Error | Ambiguous main in MainP | RUN: not %target-swift-frontend -disable-availability-checking -DBOTH -DINHERIT_SYNC -parse-as-library -typecheck -dump-ast %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR2
22+
// | x | x | x | | | Async | Directly selected | RUN: %target-swift-frontend -disable-availability-checking -DBOTH -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
23+
// | x | x | | | | Async | Directly selected | RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
24+
// | | x | | | | Sync | Indirectly selected | RUN: %target-swift-frontend -disable-availability-checking -DNO_ASYNC -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
25+
// | x | x | | x | x | Sync | Directly selected | RUN: %target-swift-frontend -disable-availability-checking -DINHERIT_SYNC -DASYNC_NESTED -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-SYNC
26+
// | x | | | x | x | Async | Indirectly selected | RUN: %target-swift-frontend -disable-availability-checking -DNO_SYNC -DINHERIT_SYNC -DASYNC_NESTED -parse-as-library -typecheck -dump-ast %s | %FileCheck %s --check-prefix=CHECK-IS-ASYNC
27+
// | x | | | x | | Error | Unrelated async main | RUN: not %target-swift-frontend -disable-availability-checking -DNO_SYNC -DINHERIT_SYNC -parse-as-library -typecheck -dump-ast %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR1
28+
// | | x | | | x | Error | Unrelated sync main | RUN: not %target-swift-frontend -disable-availability-checking -DNO_ASYNC -DASYNC_NESTED -parse-as-library -typecheck -dump-ast %s 2>&1 | %FileCheck %s --check-prefix=CHECK-IS-ERROR1
3029

3130
#if ASYNC_NESTED
3231
protocol AsyncMainProtocol { }
@@ -71,4 +70,5 @@ extension MainProtocol {
7170
// CHECK-IS-ASYNC: (func_decl implicit "$main()" interface type='(MyMain.Type) -> () async -> ()'
7271
// CHECK-IS-ASYNC: (declref_expr implicit type='(MyMain.Type) -> () async -> ()'
7372

74-
// CHECK-IS-ERROR: error: 'MyMain' is annotated with @main and must provide a main static function of type {{\(\) -> Void or \(\) throws -> Void|\(\) -> Void, \(\) throws -> Void, \(\) async -> Void, or \(\) async throws -> Void}}
73+
// CHECK-IS-ERROR1: error: 'MyMain' is annotated with @main and must provide a main static function of type {{\(\) -> Void or \(\) throws -> Void|\(\) -> Void, \(\) throws -> Void, \(\) async -> Void, or \(\) async throws -> Void}}
74+
// CHECK-IS-ERROR2: error: ambiguous use of 'main'

0 commit comments

Comments
 (0)