Skip to content

Commit fea6ff6

Browse files
authored
Merge pull request swiftlang#70697 from hborla/nonisolated-unsafe-local
[Concurrency] Suppress diagnostics about non-`Sendable` async iterator arguments in implicit calls to `next()`.
2 parents 39aa516 + 0c6c369 commit fea6ff6

File tree

5 files changed

+59
-4
lines changed

5 files changed

+59
-4
lines changed

include/swift/AST/Expr.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
448448
return const_cast<Expr *>(this)->getValueProvidingExpr();
449449
}
450450

451+
/// Find the original expression value, looking through various
452+
/// implicit conversions.
453+
const Expr *findOriginalValue() const;
454+
451455
/// Find the original type of a value, looking through various implicit
452456
/// conversions.
453457
Type findOriginalType() const;

lib/AST/Expr.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2875,7 +2875,7 @@ FrontendStatsTracer::getTraceFormatter<const Expr *>() {
28752875
return &TF;
28762876
}
28772877

2878-
Type Expr::findOriginalType() const {
2878+
const Expr *Expr::findOriginalValue() const {
28792879
auto *expr = this;
28802880
do {
28812881
expr = expr->getSemanticsProvidingExpr();
@@ -2898,6 +2898,11 @@ Type Expr::findOriginalType() const {
28982898
break;
28992899
} while (true);
29002900

2901+
return expr;
2902+
}
2903+
2904+
Type Expr::findOriginalType() const {
2905+
auto *expr = findOriginalValue();
29012906
return expr->getType()->getRValueType();
29022907
}
29032908

lib/Sema/CSGen.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4516,6 +4516,19 @@ generateForEachStmtConstraints(ConstraintSystem &cs, DeclContext *dc,
45164516
sequenceExpr->getStartLoc(), ctx.getIdentifier(name), dc);
45174517
makeIteratorVar->setImplicit();
45184518

4519+
// FIXME: Apply `nonisolated(unsafe)` to async iterators.
4520+
//
4521+
// Async iterators are not `Sendable`; they're only meant to be used from
4522+
// the isolation domain that creates them. But the `next()` method runs on
4523+
// the generic executor, so calling it from an actor-isolated context passes
4524+
// non-`Sendable` state across the isolation boundary. `next()` should
4525+
// inherit the isolation of the caller, but for now, use the opt out.
4526+
if (isAsync) {
4527+
auto *nonisolated = new (ctx)
4528+
NonisolatedAttr(/*unsafe=*/true, /*implicit=*/true);
4529+
makeIteratorVar->getAttrs().add(nonisolated);
4530+
}
4531+
45194532
// First, let's form a call from sequence to `.makeIterator()` and save
45204533
// that in a special variable which is going to be used by SILGen.
45214534
{

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1954,6 +1954,15 @@ static FuncDecl *findAnnotatableFunction(DeclContext *dc) {
19541954
return fn;
19551955
}
19561956

1957+
static bool shouldCheckSendable(Expr *expr) {
1958+
if (auto *declRef = dyn_cast<DeclRefExpr>(expr->findOriginalValue())) {
1959+
auto isolation = getActorIsolation(declRef->getDecl());
1960+
return isolation != ActorIsolation::NonisolatedUnsafe;
1961+
}
1962+
1963+
return true;
1964+
}
1965+
19571966
bool swift::diagnoseApplyArgSendability(ApplyExpr *apply, const DeclContext *declContext) {
19581967
auto isolationCrossing = apply->getIsolationCrossing();
19591968
if (!isolationCrossing.has_value())
@@ -1966,7 +1975,9 @@ bool swift::diagnoseApplyArgSendability(ApplyExpr *apply, const DeclContext *dec
19661975
// Check the 'self' argument.
19671976
if (auto *selfApply = dyn_cast<SelfApplyExpr>(apply->getFn())) {
19681977
auto *base = selfApply->getBase();
1969-
if (diagnoseNonSendableTypes(
1978+
1979+
if (shouldCheckSendable(base) &&
1980+
diagnoseNonSendableTypes(
19701981
base->getType(),
19711982
declContext, /*inDerivedConformance*/Type(),
19721983
base->getStartLoc(),
@@ -1986,6 +1997,7 @@ bool swift::diagnoseApplyArgSendability(ApplyExpr *apply, const DeclContext *dec
19861997
// Dig out the location of the argument.
19871998
SourceLoc argLoc = apply->getLoc();
19881999
Type argType;
2000+
bool checkSendable = true;
19892001
if (auto argList = apply->getArgs()) {
19902002
auto arg = argList->get(paramIdx);
19912003
if (arg.getStartLoc().isValid())
@@ -1994,6 +2006,7 @@ bool swift::diagnoseApplyArgSendability(ApplyExpr *apply, const DeclContext *dec
19942006
// Determine the type of the argument, ignoring any implicit
19952007
// conversions that could have stripped sendability.
19962008
if (Expr *argExpr = arg.getExpr()) {
2009+
checkSendable = shouldCheckSendable(argExpr);
19972010
argType = argExpr->findOriginalType();
19982011

19992012
// If this is a default argument expression, don't check Sendability
@@ -2008,7 +2021,8 @@ bool swift::diagnoseApplyArgSendability(ApplyExpr *apply, const DeclContext *dec
20082021
}
20092022
}
20102023

2011-
if (diagnoseNonSendableTypes(
2024+
if (checkSendable &&
2025+
diagnoseNonSendableTypes(
20122026
argType ? argType : param.getParameterType(),
20132027
declContext, /*inDerivedConformance*/Type(),
20142028
argLoc, diag::non_sendable_call_argument,

test/Concurrency/experimental_feature_strictconcurrency.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency -enable-experimental-feature GlobalConcurrency -emit-sil -o /dev/null -verify %s
22
// RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency=complete -enable-experimental-feature GlobalConcurrency -emit-sil -o /dev/null -verify %s
3-
// RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency=complete -enable-experimental-feature GlobalConcurrency -emit-sil -o /dev/null -verify -enable-experimental-feature RegionBasedIsolation %s
3+
// RUN: %target-swift-frontend -disable-availability-checking -parse-as-library -enable-experimental-feature StrictConcurrency=complete -enable-experimental-feature GlobalConcurrency -emit-sil -o /dev/null -verify -verify-additional-prefix region-isolation- -enable-experimental-feature RegionBasedIsolation %s
44

55
// REQUIRES: concurrency
66
// REQUIRES: asserts
@@ -84,3 +84,22 @@ func testLocalNonisolatedUnsafe() async {
8484
}
8585
print(await task.value)
8686
}
87+
88+
@MainActor
89+
func iterate(stream: AsyncStream<Int>) async {
90+
nonisolated(unsafe) var it = stream.makeAsyncIterator()
91+
// FIXME: Region isolation should consider a value from a 'nonisolated(unsafe)'
92+
// declaration to be in a disconnected region
93+
94+
// expected-region-isolation-warning@+2 {{passing argument of non-sendable type 'AsyncStream<Int>.Iterator' from main actor-isolated context to nonisolated context at this call site could yield a race with accesses later in this function}}
95+
// expected-region-isolation-note@+1 {{access here could race}}
96+
while let element = await it.next() {
97+
print(element)
98+
}
99+
100+
// expected-region-isolation-warning@+2 {{passing argument of non-sendable type 'AsyncStream<Int>.Iterator' from main actor-isolated context to nonisolated context at this call site could yield a race with accesses later in this function}}
101+
// expected-region-isolation-note@+1 {{access here could race}}
102+
for await x in stream {
103+
print(x)
104+
}
105+
}

0 commit comments

Comments
 (0)