Skip to content

Commit b5759c9

Browse files
committed
[Concurrency] Allow overload 'async' with non-async and disambiguate uses.
Allow an 'async' function to overload a non-'async' one, e.g., func performOperation(_: String) throws -> String { ... } func performOperation(_: String) async throws -> String { ... } Extend the scoring system in the type checker to penalize cases where code in an asynchronous context (e.g., an `async` function or closure) references an asychronous declaration or vice-versa, so that asynchronous code prefers the 'async' functions and synchronous code prefers the non-'async' functions. This allows the above overloading to be a legitimate approach to introducing asynchronous functionality to existing (blocking) APIs and letting code migrate over.
1 parent 6eebf61 commit b5759c9

File tree

9 files changed

+330
-219
lines changed

9 files changed

+330
-219
lines changed

include/swift/AST/Decl.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,14 @@ struct OverloadSignature {
239239
/// Whether this declaration has an opaque return type.
240240
unsigned HasOpaqueReturnType : 1;
241241

242+
/// Whether this declaration is 'async'
243+
unsigned HasAsync : 1;
244+
242245
OverloadSignature()
243246
: UnaryOperator(UnaryOperatorKind::None), IsInstanceMember(false),
244247
IsVariable(false), IsFunction(false), InProtocolExtension(false),
245-
InExtensionOfGenericType(false), HasOpaqueReturnType(false) {}
248+
InExtensionOfGenericType(false), HasOpaqueReturnType(false),
249+
HasAsync(false) {}
246250
};
247251

248252
/// Determine whether two overload signatures conflict.

lib/AST/Decl.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2451,7 +2451,11 @@ bool swift::conflicting(const OverloadSignature& sig1,
24512451
return !((sig1.IsVariable && !sig2.Name.getArgumentNames().empty()) ||
24522452
(sig2.IsVariable && !sig1.Name.getArgumentNames().empty()));
24532453
}
2454-
2454+
2455+
// If one is asynchronous and the other is not, they can't conflict.
2456+
if (sig1.HasAsync != sig2.HasAsync)
2457+
return false;
2458+
24552459
// Note that we intentionally ignore the HasOpaqueReturnType bit here.
24562460
// For declarations that can't be overloaded by type, we want them to be
24572461
// considered conflicting independent of their type.
@@ -2674,6 +2678,8 @@ OverloadSignature ValueDecl::getOverloadSignature() const {
26742678
signature.IsTypeAlias = isa<TypeAliasDecl>(this);
26752679
signature.HasOpaqueReturnType =
26762680
!signature.IsVariable && (bool)getOpaqueResultTypeDecl();
2681+
signature.HasAsync = isa<AbstractFunctionDecl>(this) &&
2682+
cast<AbstractFunctionDecl>(this)->hasAsync();
26772683

26782684
// Unary operators also include prefix/postfix.
26792685
if (auto func = dyn_cast<FuncDecl>(this)) {

lib/Sema/CSGen.cpp

Lines changed: 1 addition & 209 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,7 +2045,7 @@ namespace {
20452045
}
20462046
}
20472047

2048-
auto extInfo = closureEffects(closure);
2048+
auto extInfo = CS.closureEffects(closure);
20492049

20502050
// Closure expressions always have function type. In cases where a
20512051
// parameter or return type is omitted, a fresh type variable is used to
@@ -2501,214 +2501,6 @@ namespace {
25012501
return CS.getType(expr->getClosureBody());
25022502
}
25032503

2504-
/// Walk a closure AST to determine its effects.
2505-
///
2506-
/// \returns a function's extended info describing the effects, as
2507-
/// determined syntactically.
2508-
FunctionType::ExtInfo closureEffects(ClosureExpr *expr) {
2509-
// A walker that looks for 'try' and 'throw' expressions
2510-
// that aren't nested within closures, nested declarations,
2511-
// or exhaustive catches.
2512-
class FindInnerThrows : public ASTWalker {
2513-
ConstraintSystem &CS;
2514-
DeclContext *DC;
2515-
bool FoundThrow = false;
2516-
2517-
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
2518-
// If we've found a 'try', record it and terminate the traversal.
2519-
if (isa<TryExpr>(expr)) {
2520-
FoundThrow = true;
2521-
return { false, nullptr };
2522-
}
2523-
2524-
// Don't walk into a 'try!' or 'try?'.
2525-
if (isa<ForceTryExpr>(expr) || isa<OptionalTryExpr>(expr)) {
2526-
return { false, expr };
2527-
}
2528-
2529-
// Do not recurse into other closures.
2530-
if (isa<ClosureExpr>(expr))
2531-
return { false, expr };
2532-
2533-
return { true, expr };
2534-
}
2535-
2536-
bool walkToDeclPre(Decl *decl) override {
2537-
// Do not walk into function or type declarations.
2538-
if (!isa<PatternBindingDecl>(decl))
2539-
return false;
2540-
2541-
return true;
2542-
}
2543-
2544-
bool isSyntacticallyExhaustive(DoCatchStmt *stmt) {
2545-
for (auto catchClause : stmt->getCatches()) {
2546-
for (auto &LabelItem : catchClause->getMutableCaseLabelItems()) {
2547-
if (isSyntacticallyExhaustive(catchClause->getStartLoc(),
2548-
LabelItem))
2549-
return true;
2550-
}
2551-
}
2552-
2553-
return false;
2554-
}
2555-
2556-
bool isSyntacticallyExhaustive(SourceLoc CatchLoc,
2557-
CaseLabelItem &LabelItem) {
2558-
// If it's obviously non-exhaustive, great.
2559-
if (LabelItem.getGuardExpr())
2560-
return false;
2561-
2562-
// If we can show that it's exhaustive without full
2563-
// type-checking, great.
2564-
if (LabelItem.isSyntacticallyExhaustive())
2565-
return true;
2566-
2567-
// Okay, resolve the pattern.
2568-
Pattern *pattern = LabelItem.getPattern();
2569-
if (!LabelItem.isPatternResolved()) {
2570-
pattern = TypeChecker::resolvePattern(pattern, CS.DC,
2571-
/*isStmtCondition*/false);
2572-
if (!pattern) return false;
2573-
2574-
// Save that aside while we explore the type.
2575-
LabelItem.setPattern(pattern, /*resolved=*/true);
2576-
}
2577-
2578-
// Require the pattern to have a particular shape: a number
2579-
// of is-patterns applied to an irrefutable pattern.
2580-
pattern = pattern->getSemanticsProvidingPattern();
2581-
while (auto isp = dyn_cast<IsPattern>(pattern)) {
2582-
const Type castType = TypeResolution::forContextual(
2583-
CS.DC, TypeResolverContext::InExpression,
2584-
/*unboundTyOpener*/ nullptr)
2585-
.resolveType(isp->getCastTypeRepr());
2586-
if (castType->hasError()) {
2587-
return false;
2588-
}
2589-
2590-
if (!isp->hasSubPattern()) {
2591-
pattern = nullptr;
2592-
break;
2593-
} else {
2594-
pattern = isp->getSubPattern()->getSemanticsProvidingPattern();
2595-
}
2596-
}
2597-
if (pattern && pattern->isRefutablePattern()) {
2598-
return false;
2599-
}
2600-
2601-
// Okay, now it should be safe to coerce the pattern.
2602-
// Pull the top-level pattern back out.
2603-
pattern = LabelItem.getPattern();
2604-
Type exnType = CS.getASTContext().getErrorDecl()->getDeclaredInterfaceType();
2605-
2606-
if (!exnType)
2607-
return false;
2608-
auto contextualPattern =
2609-
ContextualPattern::forRawPattern(pattern, DC);
2610-
pattern = TypeChecker::coercePatternToType(
2611-
contextualPattern, exnType, TypeResolverContext::InExpression);
2612-
if (!pattern)
2613-
return false;
2614-
2615-
LabelItem.setPattern(pattern, /*resolved=*/true);
2616-
return LabelItem.isSyntacticallyExhaustive();
2617-
}
2618-
2619-
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
2620-
// If we've found a 'throw', record it and terminate the traversal.
2621-
if (isa<ThrowStmt>(stmt)) {
2622-
FoundThrow = true;
2623-
return { false, nullptr };
2624-
}
2625-
2626-
// Handle do/catch differently.
2627-
if (auto doCatch = dyn_cast<DoCatchStmt>(stmt)) {
2628-
// Only walk into the 'do' clause of a do/catch statement
2629-
// if the catch isn't syntactically exhaustive.
2630-
if (!isSyntacticallyExhaustive(doCatch)) {
2631-
if (!doCatch->getBody()->walk(*this))
2632-
return { false, nullptr };
2633-
}
2634-
2635-
// Walk into all the catch clauses.
2636-
for (auto catchClause : doCatch->getCatches()) {
2637-
if (!catchClause->walk(*this))
2638-
return { false, nullptr };
2639-
}
2640-
2641-
// We've already walked all the children we care about.
2642-
return { false, stmt };
2643-
}
2644-
2645-
return { true, stmt };
2646-
}
2647-
2648-
public:
2649-
FindInnerThrows(ConstraintSystem &cs, DeclContext *dc)
2650-
: CS(cs), DC(dc) {}
2651-
2652-
bool foundThrow() { return FoundThrow; }
2653-
};
2654-
2655-
// A walker that looks for 'async' and 'await' expressions
2656-
// that aren't nested within closures or nested declarations.
2657-
class FindInnerAsync : public ASTWalker {
2658-
bool FoundAsync = false;
2659-
2660-
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
2661-
// If we've found an 'await', record it and terminate the traversal.
2662-
if (isa<AwaitExpr>(expr)) {
2663-
FoundAsync = true;
2664-
return { false, nullptr };
2665-
}
2666-
2667-
// Do not recurse into other closures.
2668-
if (isa<ClosureExpr>(expr))
2669-
return { false, expr };
2670-
2671-
return { true, expr };
2672-
}
2673-
2674-
bool walkToDeclPre(Decl *decl) override {
2675-
// Do not walk into function or type declarations.
2676-
if (!isa<PatternBindingDecl>(decl))
2677-
return false;
2678-
2679-
return true;
2680-
}
2681-
2682-
public:
2683-
bool foundAsync() { return FoundAsync; }
2684-
};
2685-
2686-
// If either 'throws' or 'async' was explicitly specified, use that
2687-
// set of effects.
2688-
bool throws = expr->getThrowsLoc().isValid();
2689-
bool async = expr->getAsyncLoc().isValid();
2690-
if (throws || async) {
2691-
return ASTExtInfoBuilder()
2692-
.withThrows(throws)
2693-
.withAsync(async)
2694-
.build();
2695-
}
2696-
2697-
// Scan the body to determine the effects.
2698-
auto body = expr->getBody();
2699-
if (!body)
2700-
return FunctionType::ExtInfo();
2701-
2702-
auto throwFinder = FindInnerThrows(CS, expr);
2703-
body->walk(throwFinder);
2704-
auto asyncFinder = FindInnerAsync();
2705-
body->walk(asyncFinder);
2706-
return ASTExtInfoBuilder()
2707-
.withThrows(throwFinder.foundThrow())
2708-
.withAsync(asyncFinder.foundAsync())
2709-
.build();
2710-
}
2711-
27122504
Type visitClosureExpr(ClosureExpr *closure) {
27132505
auto *locator = CS.getConstraintLocator(closure);
27142506
auto closureType = CS.createTypeVariable(locator, TVO_CanBindToNoEscape);

lib/Sema/CSRanking.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ void ConstraintSystem::increaseScore(ScoreKind kind, unsigned value) {
4848
llvm::errs() << "use of an unavailable declaration";
4949
break;
5050

51+
case SK_AsyncSyncMismatch:
52+
llvm::errs() << "async/synchronous mismatch";
53+
break;
54+
5155
case SK_ForwardTrailingClosure:
5256
llvm::errs() << "forward scan when matching a trailing closure";
5357
break;

0 commit comments

Comments
 (0)