Skip to content

Commit 43e5bdc

Browse files
authored
Merge pull request #75095 from DougGregor/unsafe-inherit-executor-vs-pound-isolation-6.0
Unsafe inherit executor vs pound isolation 6.0
2 parents a9abcb6 + 527e52e commit 43e5bdc

13 files changed

+448
-14
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3977,6 +3977,12 @@ ERROR(reasync_without_async_parameter,none,
39773977

39783978
ERROR(inherits_executor_without_async,none,
39793979
"non-async functions cannot inherit an executor", ())
3980+
ERROR(isolation_in_inherits_executor,none,
3981+
"#isolation%select{| (introduced by a default argument)}0 cannot be used "
3982+
"within an '@_unsafeInheritExecutor' function", (bool))
3983+
ERROR(unsafe_inherits_executor_deprecated,none,
3984+
"@_unsafeInheritExecutor attribute is deprecated; consider an "
3985+
"'isolated' parameter defaulted to '#isolation' instead", ())
39803986

39813987
ERROR(lifetime_invalid_global_scope,none, "%0 is only valid on methods",
39823988
(DeclAttribute))

lib/Sema/ConstraintSystem.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ LookupResult &ConstraintSystem::lookupMember(Type base, DeclNameRef name,
292292
result = TypeChecker::lookupMember(DC, base, name, loc,
293293
defaultMemberLookupOptions);
294294

295+
// If we are in an @_unsafeInheritExecutor context, swap out
296+
// declarations for their _unsafeInheritExecutor_ counterparts if they
297+
// exist.
298+
if (enclosingUnsafeInheritsExecutor(DC)) {
299+
introduceUnsafeInheritExecutorReplacements(DC, base, loc, *result);
300+
}
301+
295302
// If we aren't performing dynamic lookup, we're done.
296303
if (!*result || !base->isAnyObject())
297304
return *result;

lib/Sema/PreCheckExpr.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//===----------------------------------------------------------------------===//
1717

1818
#include "TypeChecker.h"
19+
#include "TypeCheckConcurrency.h"
1920
#include "TypeCheckType.h"
2021
#include "TypoCorrection.h"
2122
#include "swift/AST/ASTVisitor.h"
@@ -737,6 +738,14 @@ Expr *TypeChecker::resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE,
737738
}
738739
}
739740

741+
// If we are in an @_unsafeInheritExecutor context, swap out
742+
// declarations for their _unsafeInheritExecutor_ counterparts if they
743+
// exist.
744+
if (enclosingUnsafeInheritsExecutor(DC)) {
745+
introduceUnsafeInheritExecutorReplacements(
746+
DC, UDRE->getNameLoc().getBaseNameLoc(), ResultValues);
747+
}
748+
740749
return buildRefExpr(ResultValues, DC, UDRE->getNameLoc(),
741750
UDRE->isImplicit(), UDRE->getFunctionRefKind());
742751
}

lib/Sema/TypeCheckAttr.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7213,6 +7213,13 @@ void AttributeChecker::visitUnsafeInheritExecutorAttr(
72137213
auto fn = cast<FuncDecl>(D);
72147214
if (!fn->isAsyncContext()) {
72157215
diagnose(attr->getLocation(), diag::inherits_executor_without_async);
7216+
} else {
7217+
bool inConcurrencyModule = D->getDeclContext()->getParentModule()->getName()
7218+
.str().equals("_Concurrency");
7219+
auto diag = fn->diagnose(diag::unsafe_inherits_executor_deprecated);
7220+
diag.warnUntilSwiftVersion(6);
7221+
diag.limitBehaviorIf(inConcurrencyModule, DiagnosticBehavior::Warning);
7222+
replaceUnsafeInheritExecutorWithDefaultedIsolationParam(fn, diag);
72167223
}
72177224
}
72187225

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2002,6 +2002,211 @@ bool swift::isAsyncDecl(ConcreteDeclRef declRef) {
20022002
return false;
20032003
}
20042004

2005+
AbstractFunctionDecl *swift::enclosingUnsafeInheritsExecutor(
2006+
const DeclContext *dc) {
2007+
for (; dc; dc = dc->getParent()) {
2008+
if (auto func = dyn_cast<AbstractFunctionDecl>(dc)) {
2009+
if (func->getAttrs().hasAttribute<UnsafeInheritExecutorAttr>()) {
2010+
return const_cast<AbstractFunctionDecl *>(func);
2011+
}
2012+
2013+
return nullptr;
2014+
}
2015+
2016+
if (isa<AbstractClosureExpr>(dc))
2017+
return nullptr;
2018+
2019+
if (dc->isTypeContext())
2020+
return nullptr;
2021+
}
2022+
2023+
return nullptr;
2024+
}
2025+
2026+
/// Adjust the location used for diagnostics about #isolation to account for
2027+
/// the fact that they show up in macro expansions.
2028+
///
2029+
/// Returns a pair containing the updated location and whether it's part of
2030+
/// a default argument.
2031+
static std::pair<SourceLoc, bool> adjustPoundIsolationDiagLoc(
2032+
CurrentContextIsolationExpr *isolationExpr,
2033+
ModuleDecl *module
2034+
) {
2035+
// Not part of a macro expansion.
2036+
SourceLoc diagLoc = isolationExpr->getLoc();
2037+
auto sourceFile = module->getSourceFileContainingLocation(diagLoc);
2038+
if (!sourceFile)
2039+
return { diagLoc, false };
2040+
auto macroExpansionRange = sourceFile->getMacroInsertionRange();
2041+
if (macroExpansionRange.Start.isInvalid())
2042+
return { diagLoc, false };
2043+
2044+
diagLoc = macroExpansionRange.Start;
2045+
2046+
// If this is from a default argument, note that and go one more
2047+
// level "out" to the place where the default argument was
2048+
// introduced.
2049+
auto expansionSourceFile = module->getSourceFileContainingLocation(diagLoc);
2050+
if (!expansionSourceFile ||
2051+
expansionSourceFile->Kind != SourceFileKind::DefaultArgument)
2052+
return { diagLoc, false };
2053+
2054+
return {
2055+
expansionSourceFile->getNodeInEnclosingSourceFile().getStartLoc(),
2056+
true
2057+
};
2058+
}
2059+
2060+
void swift::replaceUnsafeInheritExecutorWithDefaultedIsolationParam(
2061+
AbstractFunctionDecl *func, InFlightDiagnostic &diag) {
2062+
auto attr = func->getAttrs().getAttribute<UnsafeInheritExecutorAttr>();
2063+
assert(attr && "Caller didn't validate the presence of the attribute");
2064+
2065+
// Look for the place where we should insert the new 'isolation' parameter.
2066+
// We insert toward the back, but skip over any parameters that have function
2067+
// type.
2068+
unsigned insertionPos = func->getParameters()->size();
2069+
while (insertionPos > 0) {
2070+
Type paramType = func->getParameters()->get(insertionPos - 1)->getInterfaceType();
2071+
if (paramType->lookThroughSingleOptionalType()->is<AnyFunctionType>()) {
2072+
--insertionPos;
2073+
continue;
2074+
}
2075+
2076+
break;
2077+
}
2078+
2079+
// Determine the text to insert. We put the commas before and after, then
2080+
// slice them away depending on whether we have parameters before or after.
2081+
StringRef newParameterText = ", isolation: isolated (any Actor)? = #isolation, ";
2082+
if (insertionPos == 0)
2083+
newParameterText = newParameterText.drop_front(2);
2084+
if (insertionPos == func->getParameters()->size())
2085+
newParameterText = newParameterText.drop_back(2);
2086+
2087+
// Determine where to insert the new parameter.
2088+
SourceLoc insertionLoc;
2089+
if (insertionPos < func->getParameters()->size()) {
2090+
insertionLoc = func->getParameters()->get(insertionPos)->getStartLoc();
2091+
} else {
2092+
insertionLoc = func->getParameters()->getRParenLoc();
2093+
}
2094+
2095+
diag.fixItRemove(attr->getRangeWithAt());
2096+
diag.fixItInsert(insertionLoc, newParameterText);
2097+
}
2098+
2099+
/// Whether this declaration context is in the _Concurrency module.
2100+
static bool inConcurrencyModule(const DeclContext *dc) {
2101+
return dc->getParentModule()->getName().str().equals("_Concurrency");
2102+
}
2103+
2104+
void swift::introduceUnsafeInheritExecutorReplacements(
2105+
const DeclContext *dc, SourceLoc loc, SmallVectorImpl<ValueDecl *> &decls) {
2106+
if (decls.empty())
2107+
return;
2108+
2109+
auto isReplaceable = [&](ValueDecl *decl) {
2110+
return isa<FuncDecl>(decl) && inConcurrencyModule(decl->getDeclContext()) &&
2111+
decl->getDeclContext()->isModuleScopeContext();
2112+
};
2113+
2114+
// Make sure at least some of the entries are functions in the _Concurrency
2115+
// module.
2116+
ModuleDecl *concurrencyModule = nullptr;
2117+
DeclBaseName baseName;
2118+
for (auto decl: decls) {
2119+
if (isReplaceable(decl)) {
2120+
concurrencyModule = decl->getDeclContext()->getParentModule();
2121+
baseName = decl->getName().getBaseName();
2122+
break;
2123+
}
2124+
}
2125+
if (!concurrencyModule)
2126+
return;
2127+
2128+
// Ignore anything with a special name.
2129+
if (baseName.isSpecial())
2130+
return;
2131+
2132+
// Look for entities with the _unsafeInheritExecutor_ prefix on the name.
2133+
ASTContext &ctx = decls.front()->getASTContext();
2134+
Identifier newIdentifier = ctx.getIdentifier(
2135+
("_unsafeInheritExecutor_" + baseName.getIdentifier().str()).str());
2136+
2137+
NameLookupOptions lookupOptions = defaultUnqualifiedLookupOptions;
2138+
LookupResult lookup = TypeChecker::lookupUnqualified(
2139+
const_cast<DeclContext *>(dc), DeclNameRef(newIdentifier), loc,
2140+
lookupOptions);
2141+
if (!lookup)
2142+
return;
2143+
2144+
// Drop all of the _Concurrency entries in favor of the ones found by this
2145+
// lookup.
2146+
decls.erase(std::remove_if(decls.begin(), decls.end(), [&](ValueDecl *decl) {
2147+
return isReplaceable(decl);
2148+
}),
2149+
decls.end());
2150+
for (const auto &lookupResult: lookup) {
2151+
if (auto decl = lookupResult.getValueDecl())
2152+
decls.push_back(decl);
2153+
}
2154+
}
2155+
2156+
void swift::introduceUnsafeInheritExecutorReplacements(
2157+
const DeclContext *dc, Type base, SourceLoc loc, LookupResult &lookup) {
2158+
if (lookup.empty())
2159+
return;
2160+
2161+
auto baseNominal = base->getAnyNominal();
2162+
if (!baseNominal || !inConcurrencyModule(baseNominal))
2163+
return;
2164+
2165+
auto isReplaceable = [&](ValueDecl *decl) {
2166+
return isa<FuncDecl>(decl) && inConcurrencyModule(decl->getDeclContext());
2167+
};
2168+
2169+
// Make sure at least some of the entries are functions in the _Concurrency
2170+
// module.
2171+
ModuleDecl *concurrencyModule = nullptr;
2172+
DeclBaseName baseName;
2173+
for (auto &result: lookup) {
2174+
auto decl = result.getValueDecl();
2175+
if (isReplaceable(decl)) {
2176+
concurrencyModule = decl->getDeclContext()->getParentModule();
2177+
baseName = decl->getBaseName();
2178+
break;
2179+
}
2180+
}
2181+
if (!concurrencyModule)
2182+
return;
2183+
2184+
// Ignore anything with a special name.
2185+
if (baseName.isSpecial())
2186+
return;
2187+
2188+
// Look for entities with the _unsafeInheritExecutor_ prefix on the name.
2189+
ASTContext &ctx = base->getASTContext();
2190+
Identifier newIdentifier = ctx.getIdentifier(
2191+
("_unsafeInheritExecutor_" + baseName.getIdentifier().str()).str());
2192+
2193+
LookupResult replacementLookup = TypeChecker::lookupMember(
2194+
const_cast<DeclContext *>(dc), base, DeclNameRef(newIdentifier), loc,
2195+
defaultMemberLookupOptions);
2196+
if (replacementLookup.innerResults().empty())
2197+
return;
2198+
2199+
// Drop all of the _Concurrency entries in favor of the ones found by this
2200+
// lookup.
2201+
lookup.filter([&](const LookupResultEntry &entry, bool) {
2202+
return !isReplaceable(entry.getValueDecl());
2203+
});
2204+
2205+
for (const auto &entry: replacementLookup.innerResults()) {
2206+
lookup.add(entry, /*isOuter=*/false);
2207+
}
2208+
}
2209+
20052210
/// Check if it is safe for the \c globalActor qualifier to be removed from
20062211
/// \c ty, when the function value of that type is isolated to that actor.
20072212
///
@@ -3745,6 +3950,23 @@ namespace {
37453950
if (isolationExpr->getActor())
37463951
return;
37473952

3953+
// #isolation does not work within an `@_unsafeInheritExecutor` function.
3954+
if (auto func = enclosingUnsafeInheritsExecutor(getDeclContext())) {
3955+
// This expression is always written as a macro #isolation in source,
3956+
// so find the enclosing macro expansion expression's location.
3957+
SourceLoc diagLoc;
3958+
bool inDefaultArgument;
3959+
std::tie(diagLoc, inDefaultArgument) = adjustPoundIsolationDiagLoc(
3960+
isolationExpr, getDeclContext()->getParentModule());
3961+
3962+
bool inConcurrencyModule = ::inConcurrencyModule(getDeclContext());
3963+
auto diag = ctx.Diags.diagnose(diagLoc,
3964+
diag::isolation_in_inherits_executor,
3965+
inDefaultArgument);
3966+
diag.limitBehaviorIf(inConcurrencyModule, DiagnosticBehavior::Warning);
3967+
replaceUnsafeInheritExecutorWithDefaultedIsolationParam(func, diag);
3968+
}
3969+
37483970
auto loc = isolationExpr->getLoc();
37493971
auto isolation = getActorIsolationOfContext(
37503972
const_cast<DeclContext *>(getDeclContext()),

lib/Sema/TypeCheckConcurrency.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class EnumElementDecl;
4444
class Expr;
4545
class FuncDecl;
4646
class Initializer;
47+
class LookupResult;
4748
class PatternBindingDecl;
4849
class ProtocolConformance;
4950
class TopLevelCodeDecl;
@@ -648,6 +649,38 @@ bool isPotentiallyIsolatedActor(
648649
bool diagnoseApplyArgSendability(
649650
swift::ApplyExpr *apply, const DeclContext *declContext);
650651

652+
/// If the enclosing function has @_unsafeInheritExecutorAttr, return it.
653+
AbstractFunctionDecl *enclosingUnsafeInheritsExecutor(const DeclContext *dc);
654+
655+
/// Add Fix-Its to the given function to replace the @_unsafeInheritExecutor
656+
/// attribute with a defaulted isolation parameter.
657+
void replaceUnsafeInheritExecutorWithDefaultedIsolationParam(
658+
AbstractFunctionDecl *func, InFlightDiagnostic &diag);
659+
660+
/// Replace any functions in this list that were found in the _Concurrency
661+
/// module and have _unsafeInheritExecutor_-prefixed versions with those
662+
/// _unsafeInheritExecutor_-prefixed versions.
663+
///
664+
/// This function is an egregious hack that allows us to introduce the
665+
/// #isolation-based versions of functions into the concurrency library
666+
/// without breaking clients that use @_unsafeInheritExecutor. Since those
667+
/// clients can't use #isolation (it doesn't work with @_unsafeInheritExecutor),
668+
/// we route them to the @_unsafeInheritExecutor versions implicitly.
669+
void introduceUnsafeInheritExecutorReplacements(
670+
const DeclContext *dc, SourceLoc loc, SmallVectorImpl<ValueDecl *> &decls);
671+
672+
/// Replace any functions in this list that were found in the _Concurrency
673+
/// module as a member on "base" and have _unsafeInheritExecutor_-prefixed
674+
/// versions with those _unsafeInheritExecutor_-prefixed versions.
675+
///
676+
/// This function is an egregious hack that allows us to introduce the
677+
/// #isolation-based versions of functions into the concurrency library
678+
/// without breaking clients that use @_unsafeInheritExecutor. Since those
679+
/// clients can't use #isolation (it doesn't work with @_unsafeInheritExecutor),
680+
/// we route them to the @_unsafeInheritExecutor versions implicitly.
681+
void introduceUnsafeInheritExecutorReplacements(
682+
const DeclContext *dc, Type base, SourceLoc loc, LookupResult &result);
683+
651684
} // end namespace swift
652685

653686
namespace llvm {

lib/Sema/TypeCheckStmt.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3283,6 +3283,11 @@ FuncDecl *TypeChecker::getForEachIteratorNextFunction(
32833283
if (!nextElement)
32843284
return ctx.getAsyncIteratorNext();
32853285

3286+
// If the enclosing function has @_unsafeInheritsExecutor, then #isolation
3287+
// does not work and we need to avoid relying on it.
3288+
if (enclosingUnsafeInheritsExecutor(dc))
3289+
return ctx.getAsyncIteratorNext();
3290+
32863291
// If availability checking is disabled, use next(_:).
32873292
if (ctx.LangOpts.DisableAvailabilityChecking || loc.isInvalid())
32883293
return nextElement;

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,12 +299,17 @@ public func withCheckedContinuation<T>(
299299
}
300300
}
301301

302+
// Note: hack to stage out @_unsafeInheritExecutor forms of various functions
303+
// in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful
304+
// to the type checker.
305+
//
306+
// This function also doubles as an ABI-compatibility shim predating the
307+
// introduction of #isolation.
302308
@available(SwiftStdlib 5.1, *)
303-
@usableFromInline
304309
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
305310
@_unavailableInEmbedded
306311
@_silgen_name("$ss23withCheckedContinuation8function_xSS_yScCyxs5NeverOGXEtYalF")
307-
internal func __abi_withCheckedContinuation<T>(
312+
public func _unsafeInheritExecutor_withCheckedContinuation<T>(
308313
function: String = #function,
309314
_ body: (CheckedContinuation<T, Never>) -> Void
310315
) async -> T {
@@ -360,12 +365,17 @@ public func withCheckedThrowingContinuation<T>(
360365
}
361366
}
362367

368+
// Note: hack to stage out @_unsafeInheritExecutor forms of various functions
369+
// in favor of #isolation. The _unsafeInheritExecutor_ prefix is meaningful
370+
// to the type checker.
371+
//
372+
// This function also doubles as an ABI-compatibility shim predating the
373+
// introduction of #isolation.
363374
@available(SwiftStdlib 5.1, *)
364-
@usableFromInline
365375
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
366376
@_unavailableInEmbedded
367377
@_silgen_name("$ss31withCheckedThrowingContinuation8function_xSS_yScCyxs5Error_pGXEtYaKlF")
368-
internal func __abi_withCheckedThrowingContinuation<T>(
378+
public func _unsafeInheritExecutor_withCheckedThrowingContinuation<T>(
369379
function: String = #function,
370380
_ body: (CheckedContinuation<T, Error>) -> Void
371381
) async throws -> T {

0 commit comments

Comments
 (0)