Skip to content

Commit 2a8b3fc

Browse files
authored
Merge pull request #75093 from DougGregor/unsafe-inherit-executor-vs-pound-isolation
Ensure that a `@_unsafeInheritExecutor` function does not depend on `#isolation`
2 parents 55d4a56 + 817e1c1 commit 2a8b3fc

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
@@ -3997,6 +3997,12 @@ ERROR(reasync_without_async_parameter,none,
39973997

39983998
ERROR(inherits_executor_without_async,none,
39993999
"non-async functions cannot inherit an executor", ())
4000+
ERROR(isolation_in_inherits_executor,none,
4001+
"#isolation%select{| (introduced by a default argument)}0 cannot be used "
4002+
"within an '@_unsafeInheritExecutor' function", (bool))
4003+
ERROR(unsafe_inherits_executor_deprecated,none,
4004+
"@_unsafeInheritExecutor attribute is deprecated; consider an "
4005+
"'isolated' parameter defaulted to '#isolation' instead", ())
40004006

40014007
ERROR(lifetime_invalid_global_scope,none, "%0 is only valid on methods",
40024008
(DeclAttribute))

lib/Sema/ConstraintSystem.cpp

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

296+
// If we are in an @_unsafeInheritExecutor context, swap out
297+
// declarations for their _unsafeInheritExecutor_ counterparts if they
298+
// exist.
299+
if (enclosingUnsafeInheritsExecutor(DC)) {
300+
introduceUnsafeInheritExecutorReplacements(DC, base, loc, *result);
301+
}
302+
296303
// If we aren't performing dynamic lookup, we're done.
297304
if (!*result || !base->isAnyObject())
298305
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"
@@ -738,6 +739,14 @@ Expr *TypeChecker::resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE,
738739
}
739740
}
740741

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

lib/Sema/TypeCheckAttr.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7277,6 +7277,13 @@ void AttributeChecker::visitUnsafeInheritExecutorAttr(
72777277
auto fn = cast<FuncDecl>(D);
72787278
if (!fn->isAsyncContext()) {
72797279
diagnose(attr->getLocation(), diag::inherits_executor_without_async);
7280+
} else {
7281+
bool inConcurrencyModule = D->getDeclContext()->getParentModule()->getName()
7282+
.str().equals("_Concurrency");
7283+
auto diag = fn->diagnose(diag::unsafe_inherits_executor_deprecated);
7284+
diag.warnUntilSwiftVersion(6);
7285+
diag.limitBehaviorIf(inConcurrencyModule, DiagnosticBehavior::Warning);
7286+
replaceUnsafeInheritExecutorWithDefaultedIsolationParam(fn, diag);
72807287
}
72817288
}
72827289

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,211 @@ bool swift::isAsyncDecl(ConcreteDeclRef declRef) {
19991999
return false;
20002000
}
20012001

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

3956+
// #isolation does not work within an `@_unsafeInheritExecutor` function.
3957+
if (auto func = enclosingUnsafeInheritsExecutor(getDeclContext())) {
3958+
// This expression is always written as a macro #isolation in source,
3959+
// so find the enclosing macro expansion expression's location.
3960+
SourceLoc diagLoc;
3961+
bool inDefaultArgument;
3962+
std::tie(diagLoc, inDefaultArgument) = adjustPoundIsolationDiagLoc(
3963+
isolationExpr, getDeclContext()->getParentModule());
3964+
3965+
bool inConcurrencyModule = ::inConcurrencyModule(getDeclContext());
3966+
auto diag = ctx.Diags.diagnose(diagLoc,
3967+
diag::isolation_in_inherits_executor,
3968+
inDefaultArgument);
3969+
diag.limitBehaviorIf(inConcurrencyModule, DiagnosticBehavior::Warning);
3970+
replaceUnsafeInheritExecutorWithDefaultedIsolationParam(func, diag);
3971+
}
3972+
37513973
auto loc = isolationExpr->getLoc();
37523974
auto isolation = getActorIsolationOfContext(
37533975
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
@@ -3289,6 +3289,11 @@ FuncDecl *TypeChecker::getForEachIteratorNextFunction(
32893289
if (!nextElement)
32903290
return ctx.getAsyncIteratorNext();
32913291

3292+
// If the enclosing function has @_unsafeInheritsExecutor, then #isolation
3293+
// does not work and we need to avoid relying on it.
3294+
if (enclosingUnsafeInheritsExecutor(dc))
3295+
return ctx.getAsyncIteratorNext();
3296+
32923297
// If availability checking is disabled, use next(_:).
32933298
if (ctx.LangOpts.DisableAvailabilityChecking || loc.isInvalid())
32943299
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)