Merge pull request #77900 from gottesmm/rdar127477211

[region-isolation] Perform checking of non-Sendable results using rbi rather than Sema.
This commit is contained in:
Michael Gottesman
2024-12-03 22:08:49 -08:00
committed by GitHub
19 changed files with 522 additions and 126 deletions

View File

@@ -1098,6 +1098,28 @@ NOTE(regionbasedisolation_out_sending_cannot_be_actor_isolated_note_named, none,
"returning %1 %0 risks causing data races since the caller assumes that %0 can be safely sent to other isolation domains",
(Identifier, StringRef))
//===
// non-Sendable Results
// Example: returning main-actor isolated result to a custom-actor isolated context risks causing data races
ERROR(rbi_isolation_crossing_result, none,
"non-Sendable %0-typed result can not be returned from %1 %kind2 to %3 context",
(Type, ActorIsolation, const ValueDecl *, ActorIsolation))
ERROR(rbi_isolation_crossing_result_no_decl, none,
"non-Sendable %0-typed result can not be returned from %1 function to %2 context",
(Type, ActorIsolation, ActorIsolation))
NOTE(rbi_non_sendable_nominal,none,
"%kind0 does not conform to the 'Sendable' protocol",
(const ValueDecl *))
NOTE(rbi_nonsendable_function_type,none,
"a function type must be marked '@Sendable' to conform to 'Sendable'", ())
NOTE(rbi_add_nominal_sendable_conformance,none,
"consider making %kind0 conform to the 'Sendable' protocol",
(const ValueDecl *))
NOTE(rbi_add_generic_parameter_sendable_conformance,none,
"consider making generic parameter %0 conform to the 'Sendable' protocol",
(Type))
//===----------------------------------------------------------------------===//
// MARK: Misc Diagnostics
//===----------------------------------------------------------------------===//

View File

@@ -59,4 +59,8 @@ PARTITION_OP_ERROR(InOutSendingNotDisconnectedAtExit)
/// to our user so that we can emit that error as we process.
PARTITION_OP_ERROR(UnknownCodePattern)
/// Used to signify that an isolation crossing function is returning a
/// non-Sendable value.
PARTITION_OP_ERROR(NonSendableIsolationCrossingResult)
#undef PARTITION_OP_ERROR

View File

@@ -462,6 +462,14 @@ enum class PartitionOpKind : uint8_t {
///
/// Takes one parameter, the inout parameter that we need to check.
InOutSendingAtFunctionExit,
/// This is the result of an isolation crossing apply site. We need to emit a
/// special error since we never allow this.
///
/// DISCUSSION: This is actually just a form of "send". Sadly, we can not use
/// "send" directly since "send" expects a SILOperand and these are values. So
/// to work around the API issue, we have to use a different, specific entry.
NonSendableIsolationCrossingResult,
};
/// PartitionOp represents a primitive operation that can be performed on
@@ -574,6 +582,12 @@ public:
sourceInst);
}
static PartitionOp
NonSendableIsolationCrossingResult(Element elt, SILInstruction *sourceInst) {
return PartitionOp(PartitionOpKind::NonSendableIsolationCrossingResult, elt,
sourceInst);
}
bool operator==(const PartitionOp &other) const {
return opKind == other.opKind && opArgs == other.opArgs &&
source == other.source;
@@ -1053,6 +1067,22 @@ public:
}
};
struct NonSendableIsolationCrossingResultError {
const PartitionOp *op;
Element returnValueElement;
NonSendableIsolationCrossingResultError(const PartitionOp &op,
Element returnValue)
: op(&op), returnValueElement(returnValue) {}
void print(llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const;
SWIFT_DEBUG_DUMPER(dump(RegionAnalysisValueMap &valueMap)) {
print(llvm::dbgs(), valueMap);
}
};
#define PARTITION_OP_ERROR(NAME) \
static_assert(std::is_copy_constructible_v<NAME##Error>, \
#NAME " must be copy constructable");
@@ -1482,8 +1512,15 @@ public:
// Then emit an unknown code pattern error.
return handleError(UnknownCodePatternError(op));
}
case PartitionOpKind::NonSendableIsolationCrossingResult:
// Grab the dynamic dataflow isolation information for our element's
// region.
Region region = p.getRegion(op.getOpArgs()[0]);
// Then emit the error.
return handleError(
NonSendableIsolationCrossingResultError(op, op.getOpArgs()[0]));
}
llvm_unreachable("Covered switch isn't covered?!");
}

View File

@@ -1526,6 +1526,12 @@ struct PartitionOpBuilder {
PartitionOp::UnknownPatternError(lookupValueID(value), currentInst));
}
void addNonSendableIsolationCrossingResultError(SILValue value) {
currentInstPartitionOps.emplace_back(
PartitionOp::NonSendableIsolationCrossingResult(lookupValueID(value),
currentInst));
}
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
void print(llvm::raw_ostream &os) const;
@@ -2394,14 +2400,58 @@ public:
handleSILOperands(applySite.getOperandsWithoutIndirectResults());
}
// non-sendable results can't be returned from cross-isolation calls without
// a diagnostic emitted elsewhere. Here, give them a fresh value for better
// diagnostics hereafter
// Create a new assign fresh for each one of our values and unless our
// return value is sending, emit an extra error bit on the results that are
// non-Sendable.
SmallVector<SILValue, 8> applyResults;
getApplyResults(*applySite, applyResults);
for (auto result : applyResults)
if (auto value = tryToTrackValue(result))
auto substCalleeType = applySite.getSubstCalleeType();
// Today, all values in result info are sending or none are. So this is a
// safe to check that we have a sending result. In the future if we allow
// for sending to vary, then this code will need to be updated to vary with
// each result.
auto results = substCalleeType->getResults();
auto *applyExpr = applySite->getLoc().getAsASTNode<ApplyExpr>();
// We only emit the error if we do not have a sending result and if our
// callee isn't nonisolated.
//
// DISCUSSION: If our callee is non-isolated, we know that the value must
// have been returned as a non-sent disconnected value. The reason why this
// is different from a sending result is that the result may be part of the
// region of the operands while the sending result will not be. In either
// case though, we do not want to emit the error.
bool emitIsolationCrossingResultError =
(results.empty() || !results[0].hasOption(SILResultInfo::IsSending)) &&
// We have to check if we actually have an apply expr since we may not
// have one if we are processing a SIL test case since SIL does not have
// locations (which is how we grab our AST information).
!(applyExpr && applyExpr->getIsolationCrossing()
->getCalleeIsolation()
.isNonisolated());
for (auto result : applyResults) {
if (auto value = tryToTrackValue(result)) {
builder.addAssignFresh(value->getRepresentative().getValue());
if (emitIsolationCrossingResultError)
builder.addNonSendableIsolationCrossingResultError(
value->getRepresentative().getValue());
}
}
// If we are supposed to emit isolation crossing errors, go through our
// parameters and add the error on any indirect results that are
// non-Sendable.
if (emitIsolationCrossingResultError) {
for (auto result : applySite.getIndirectSILResults()) {
if (auto value = tryToTrackValue(result)) {
builder.addNonSendableIsolationCrossingResultError(
value->getRepresentative().getValue());
}
}
}
}
template <typename DestValues>

View File

@@ -849,26 +849,6 @@ private:
void initForApply(Operand *op, ApplyExpr *expr);
void initForAutoclosure(Operand *op, AutoClosureExpr *expr);
Expr *getFoundExprForSelf(ApplyExpr *sourceApply) {
if (auto callExpr = dyn_cast<CallExpr>(sourceApply))
if (auto calledExpr =
dyn_cast<DotSyntaxCallExpr>(callExpr->getDirectCallee()))
return calledExpr->getBase();
return nullptr;
}
Expr *getFoundExprForParam(ApplyExpr *sourceApply, unsigned argNum) {
auto *expr = sourceApply->getArgs()->getExpr(argNum);
// If we have an erasure expression, lets use the original type. We do
// this since we are not saying the specific parameter that is the
// issue and we are using the type to explain it to the user.
if (auto *erasureExpr = dyn_cast<ErasureExpr>(expr))
expr = erasureExpr->getSubExpr();
return expr;
}
};
} // namespace
@@ -2284,6 +2264,223 @@ void AssignIsolatedIntoSendingResultDiagnosticEmitter::emit() {
type);
}
//===----------------------------------------------------------------------===//
// MARK: NonSendableIsolationCrossingResult Emitter
//===----------------------------------------------------------------------===//
/// Add Fix-It text for the given nominal type to adopt Sendable.
static void addSendableFixIt(const NominalTypeDecl *nominal,
InFlightDiagnostic &diag, bool unchecked) {
if (nominal->getInherited().empty()) {
SourceLoc fixItLoc = nominal->getBraces().Start;
diag.fixItInsert(fixItLoc,
unchecked ? ": @unchecked Sendable" : ": Sendable");
} else {
auto fixItLoc = nominal->getInherited().getEndLoc();
diag.fixItInsertAfter(fixItLoc,
unchecked ? ", @unchecked Sendable" : ", Sendable");
}
}
/// Add Fix-It text for the given generic param declaration type to adopt
/// Sendable.
static void addSendableFixIt(const GenericTypeParamDecl *genericArgument,
InFlightDiagnostic &diag, bool unchecked) {
if (genericArgument->getInherited().empty()) {
auto fixItLoc = genericArgument->getLoc();
diag.fixItInsertAfter(fixItLoc,
unchecked ? ": @unchecked Sendable" : ": Sendable");
} else {
auto fixItLoc = genericArgument->getInherited().getEndLoc();
diag.fixItInsertAfter(fixItLoc,
unchecked ? ", @unchecked Sendable" : ", Sendable");
}
}
namespace {
struct NonSendableIsolationCrossingResultDiagnosticEmitter {
RegionAnalysisValueMap &valueMap;
using Error = PartitionOpError::NonSendableIsolationCrossingResultError;
Error error;
bool emittedErrorDiagnostic = false;
/// The value assigned as the equivalence class representative. It is
/// guaranteed to be from the isolation crossing function since we never treat
/// isolation crossing functions as being look through.
SILValue representative;
NonSendableIsolationCrossingResultDiagnosticEmitter(
RegionAnalysisValueMap &valueMap, Error error)
: valueMap(valueMap), error(error),
representative(valueMap.getRepresentative(error.returnValueElement)) {}
void emit();
ASTContext &getASTContext() const {
return error.op->getSourceInst()->getFunction()->getASTContext();
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SourceLoc loc, Diag<T...> diag,
U &&...args) {
emittedErrorDiagnostic = true;
return std::move(getASTContext()
.Diags.diagnose(loc, diag, std::forward<U>(args)...)
.warnUntilSwiftVersion(6));
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseError(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseError(inst->getLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag<T...> diag, U &&...args) {
return getASTContext().Diags.diagnose(loc, diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILLocation loc, Diag<T...> diag,
U &&...args) {
return diagnoseNote(loc.getSourceLoc(), diag, std::forward<U>(args)...);
}
template <typename... T, typename... U>
InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag<T...> diag,
U &&...args) {
return diagnoseNote(inst->getLoc(), diag, std::forward<U>(args)...);
}
std::optional<DiagnosticBehavior> getBehaviorLimit() const {
return representative->getType().getConcurrencyDiagnosticBehavior(
representative->getFunction());
}
void emitUnknownPatternError() {
if (shouldAbortOnUnknownPatternMatchError()) {
llvm::report_fatal_error(
"RegionIsolation: Aborting on unknown pattern match error");
}
diagnoseError(error.op->getSourceInst(),
diag::regionbasedisolation_unknown_pattern)
.limitBehaviorIf(getBehaviorLimit());
}
Type getType() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
return applyExpr->getType();
}
// If we do not have an ApplyExpr, see if we can just infer the type from
// the SILFunction type. This is only used in SIL test cases.
if (auto fas = FullApplySite::isa(error.op->getSourceInst())) {
return fas.getSubstCalleeType()
->getAllResultsSubstType(fas.getModule(),
fas.getFunction()->getTypeExpansionContext())
.getASTType();
}
return Type();
}
const ValueDecl *getCalledDecl() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto calledValue =
applyExpr->getCalledValue(true /*look through conversions*/)) {
return calledValue;
}
}
return nullptr;
}
std::optional<ApplyIsolationCrossing> getIsolationCrossing() const {
if (auto *applyExpr =
error.op->getSourceInst()->getLoc().getAsASTNode<ApplyExpr>()) {
if (auto isolationCrossing = applyExpr->getIsolationCrossing()) {
return *isolationCrossing;
}
}
// If we have a SIL based test case, just return the actual isolation
// crossing.
if (auto fas = FullApplySite::isa(error.op->getSourceInst())) {
if (auto isolationCrossing = fas.getIsolationCrossing())
return *isolationCrossing;
}
return {};
}
};
} // namespace
void NonSendableIsolationCrossingResultDiagnosticEmitter::emit() {
auto isolationCrossing = getIsolationCrossing();
if (!isolationCrossing)
return emitUnknownPatternError();
auto type = getType();
if (auto *decl = getCalledDecl()) {
diagnoseError(error.op->getSourceInst(), diag::rbi_isolation_crossing_result,
type, isolationCrossing->getCalleeIsolation(), getCalledDecl(),
isolationCrossing->getCallerIsolation())
.limitBehaviorIf(getBehaviorLimit());
} else {
diagnoseError(error.op->getSourceInst(), diag::rbi_isolation_crossing_result_no_decl,
type, isolationCrossing->getCalleeIsolation(),
isolationCrossing->getCallerIsolation())
.limitBehaviorIf(getBehaviorLimit());
}
if (type->is<FunctionType>()) {
diagnoseNote(error.op->getSourceInst(),
diag::rbi_nonsendable_function_type);
return;
}
auto *moduleDecl = error.op->getSourceInst()->getModule().getSwiftModule();
if (auto *nominal = type->getNominalOrBoundGenericNominal()) {
// If the nominal type is in the current module, suggest adding `Sendable`
// if it makes sense.
if (nominal->getParentModule() == moduleDecl &&
(isa<StructDecl>(nominal) || isa<EnumDecl>(nominal))) {
auto note = nominal->diagnose(diag::rbi_add_nominal_sendable_conformance,
nominal);
addSendableFixIt(nominal, note, /*unchecked*/ false);
} else {
nominal->diagnose(diag::rbi_non_sendable_nominal, nominal);
}
return;
}
if (auto genericArchetype = type->getAs<ArchetypeType>()) {
auto interfaceType = genericArchetype->getInterfaceType();
if (auto genericParamType = interfaceType->getAs<GenericTypeParamType>()) {
auto *genericParamTypeDecl = genericParamType->getDecl();
if (genericParamTypeDecl &&
genericParamTypeDecl->getModuleContext() == moduleDecl) {
auto diag = genericParamTypeDecl->diagnose(
diag::rbi_add_generic_parameter_sendable_conformance, type);
addSendableFixIt(genericParamTypeDecl, diag, /*unchecked=*/false);
return;
}
}
}
}
//===----------------------------------------------------------------------===//
// MARK: Diagnostic Evaluator
//===----------------------------------------------------------------------===//
@@ -2370,6 +2567,7 @@ struct DiagnosticEvaluator final
case PartitionOpError::InOutSendingNotDisconnectedAtExit:
case PartitionOpError::SentNeverSendable:
case PartitionOpError::AssignNeverSendableIntoSendingResult:
case PartitionOpError::NonSendableIsolationCrossingResult:
// We are going to process these later... but dump so we can see that we
// handled an error here. The rest of the explicit handlers will dump as
// appropriate if they want to emit an error here (some will squelch the
@@ -2510,6 +2708,14 @@ void SendNonSendableImpl::emitVerbatimErrors() {
diagnosticInferrer.run();
continue;
}
case PartitionOpError::NonSendableIsolationCrossingResult: {
auto e = erasedError.getNonSendableIsolationCrossingResultError();
REGIONBASEDISOLATION_LOG(e.print(llvm::dbgs(), info->getValueMap()));
NonSendableIsolationCrossingResultDiagnosticEmitter diagnosticInferrer(
info->getValueMap(), e);
diagnosticInferrer.emit();
continue;
}
}
llvm_unreachable("Covered switch isn't covered?!");
}

View File

@@ -94,14 +94,22 @@ void PartitionOpError::InOutSendingNotDisconnectedAtExitError::print(
os << '\n';
}
void PartitionOpError::NonSendableIsolationCrossingResultError::print(
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
os << " Emitting Error. Kind: NonSendableIsolationCrossingResultError\n"
" Inst: "
<< *op->getSourceInst() << " Result ID: %%" << returnValueElement
<< '\n';
}
//===----------------------------------------------------------------------===//
// MARK: PartitionOp
//===----------------------------------------------------------------------===//
void PartitionOp::print(llvm::raw_ostream &os, bool extraSpace) const {
constexpr static char extraSpaceLiteral[10] = " ";
switch (opKind) {
case PartitionOpKind::Assign: {
constexpr static char extraSpaceLiteral[10] = " ";
os << "assign ";
if (extraSpace)
os << extraSpaceLiteral;
@@ -112,7 +120,6 @@ void PartitionOp::print(llvm::raw_ostream &os, bool extraSpace) const {
os << "assign_fresh %%" << opArgs[0];
break;
case PartitionOpKind::Send: {
constexpr static char extraSpaceLiteral[10] = " ";
os << "send ";
if (extraSpace)
os << extraSpaceLiteral;
@@ -120,7 +127,6 @@ void PartitionOp::print(llvm::raw_ostream &os, bool extraSpace) const {
break;
}
case PartitionOpKind::UndoSend: {
constexpr static char extraSpaceLiteral[10] = " ";
os << "undo_send ";
if (extraSpace)
os << extraSpaceLiteral;
@@ -128,7 +134,6 @@ void PartitionOp::print(llvm::raw_ostream &os, bool extraSpace) const {
break;
}
case PartitionOpKind::Merge: {
constexpr static char extraSpaceLiteral[10] = " ";
os << "merge ";
if (extraSpace)
os << extraSpaceLiteral;
@@ -136,7 +141,6 @@ void PartitionOp::print(llvm::raw_ostream &os, bool extraSpace) const {
break;
}
case PartitionOpKind::Require: {
constexpr static char extraSpaceLiteral[10] = " ";
os << "require ";
if (extraSpace)
os << extraSpaceLiteral;
@@ -148,12 +152,17 @@ void PartitionOp::print(llvm::raw_ostream &os, bool extraSpace) const {
os << "%%" << opArgs[0];
break;
case PartitionOpKind::InOutSendingAtFunctionExit:
constexpr static char extraSpaceLiteral[10] = " ";
os << "inout_sending_at_function_exit ";
if (extraSpace)
os << extraSpaceLiteral;
os << "%%" << opArgs[0];
break;
case PartitionOpKind::NonSendableIsolationCrossingResult:
os << "nonsendable_isolationcrossing_result ";
if (extraSpace)
os << extraSpaceLiteral;
os << "%%" << opArgs[0];
break;
}
os << ": " << *getSourceInst();
}

View File

@@ -3865,54 +3865,8 @@ namespace {
unsatisfiedIsolation, setThrows, usesDistributedThunk);
}
// Sendable checking for arguments is deferred to region isolation.
// FIXME: Defer sendable checking for result types to region isolation
// always.
//
// Check for sendability of the result type if we do not have a
// sending result.
if ((!ctx.LangOpts.hasFeature(Feature::RegionBasedIsolation) ||
!fnType->hasSendingResult())) {
assert(ctx.LangOpts.hasFeature(Feature::SendingArgsAndResults) &&
"SendingArgsAndResults should be enabled if RegionIsolation is "
"enabled");
// See if we are a autoclosure that has a direct callee that has the
// same non-transferred type value returned. If so, do not emit an
// error... we are going to emit an error on the call expr and do not
// want to emit the error twice.
auto willDoubleError = [&]() -> bool {
auto *autoclosure = dyn_cast<AutoClosureExpr>(apply->getFn());
if (!autoclosure)
return false;
auto *await =
dyn_cast<AwaitExpr>(autoclosure->getSingleExpressionBody());
if (!await)
return false;
auto *subCallExpr = dyn_cast<CallExpr>(await->getSubExpr());
if (!subCallExpr)
return false;
return subCallExpr->getType().getPointer() ==
fnType->getResult().getPointer();
};
if (!willDoubleError()) {
if (calleeDecl) {
return diagnoseNonSendableTypes(fnType->getResult(), getDeclContext(),
/*inDerivedConformance*/ Type(),
apply->getLoc(),
diag::non_sendable_result_into_actor,
calleeDecl,
*unsatisfiedIsolation);
}
return diagnoseNonSendableTypes(fnType->getResult(), getDeclContext(),
/*inDerivedConformance*/ Type(),
apply->getLoc(),
diag::non_sendable_call_result_type,
*unsatisfiedIsolation);
}
}
// Sendable checking for arguments and results are deferred to region
// isolation.
return false;
}

View File

@@ -298,7 +298,6 @@ class BarFrame: PictureFrame {
@available(SwiftStdlib 5.5, *)
@SomeGlobalActor
class BazFrame: NotIsolatedPictureFrame {
// expected-note@-1 2 {{class 'BazFrame' does not conform to the 'Sendable' protocol}}
init() {
super.init(size: 0)
}
@@ -322,12 +321,10 @@ func check() async {
_ = await BarFrame()
_ = await FooFrame()
_ = await BazFrame()
// expected-warning@-1 {{non-sendable result type 'BazFrame' cannot be sent from global actor 'SomeGlobalActor'-isolated context in call to initializer 'init()'; this is an error in the Swift 6 language mode}}
_ = await BarFrame(size: 0)
_ = await FooFrame(size: 0)
_ = await BazFrame(size: 0)
// expected-warning@-1 {{non-sendable result type 'BazFrame' cannot be sent from global actor 'SomeGlobalActor'-isolated context in call to initializer 'init(size:)'; this is an error in the Swift 6 language mode}}
}
@available(SwiftStdlib 5.5, *)

View File

@@ -356,25 +356,17 @@ actor Calculator {
}
@OrangeActor func doSomething() async {
// We will error on the next line when we get past type checking. But since we
// error in the type checker, we do not make further progress.
let _ = (await bananaAdd(1))(2)
// expected-warning@-1{{non-sendable result type '(Int) -> Int' cannot be sent from global actor 'BananaActor'-isolated context in call to global function 'bananaAdd'}}
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let _ = await (await bananaAdd(1))(2) // expected-warning{{no 'async' operations occur within 'await' expression}}
// expected-warning@-1{{non-sendable result type '(Int) -> Int' cannot be sent from global actor 'BananaActor'-isolated context in call to global function 'bananaAdd'}}
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let calc = Calculator()
let _ = (await calc.addCurried(1))(2)
// expected-warning@-1{{non-sendable result type '(Int) -> Int' cannot be sent from actor-isolated context in call to instance method 'addCurried'}}
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let _ = await (await calc.addCurried(1))(2) // expected-warning{{no 'async' operations occur within 'await' expression}}
// expected-warning@-1{{non-sendable result type '(Int) -> Int' cannot be sent from actor-isolated context in call to instance method 'addCurried'}}
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let plusOne = await calc.addCurried(await calc.add(0, 1))
// expected-warning@-1{{non-sendable result type '(Int) -> Int' cannot be sent from actor-isolated context in call to instance method 'addCurried'}}
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let _ = plusOne(2)
}

View File

@@ -5,7 +5,7 @@
// REQUIRES: concurrency
// REQUIRES: asserts
class NotConcurrent { } // expected-note 13{{class 'NotConcurrent' does not conform to the 'Sendable' protocol}}
class NotConcurrent { } // expected-note 12{{class 'NotConcurrent' does not conform to the 'Sendable' protocol}}
// expected-tns-allow-typechecker-note @-1 {{class 'NotConcurrent' does not conform to the 'Sendable' protocol}}
// ----------------------------------------------------------------------
@@ -102,7 +102,7 @@ extension A1 {
_ = other.localLet // expected-warning{{non-sendable type 'NotConcurrent' of property 'localLet' cannot exit actor-isolated context}}
// expected-warning@-1 {{expression is 'async' but is not marked with 'await'}}
// expected-note@-2 {{property access is 'async'}}
_ = await other.synchronous() // expected-warning{{non-sendable result type 'NotConcurrent?' cannot be sent from actor-isolated context in call to instance method 'synchronous()'}}
_ = await other.synchronous() // expected-tns-warning {{non-Sendable 'NotConcurrent?'-typed result can not be returned from actor-isolated instance method 'synchronous()' to actor-isolated context}}
_ = await other.asynchronous(nil)
}
}
@@ -164,7 +164,7 @@ struct HasSubscript {
subscript (i: Int) -> NotConcurrent? { nil }
}
class ClassWithGlobalActorInits { // expected-note 2{{class 'ClassWithGlobalActorInits' does not conform to the 'Sendable' protocol}}
class ClassWithGlobalActorInits { // expected-tns-note 2{{class 'ClassWithGlobalActorInits' does not conform to the 'Sendable' protocol}}
@SomeGlobalActor
init(_: NotConcurrent) { }
@@ -182,10 +182,10 @@ func globalTestMain(nc: NotConcurrent) async {
// expected-tns-note @-1 {{sending global actor 'SomeGlobalActor'-isolated 'a' to global actor 'SomeGlobalActor'-isolated global function 'globalAsync' risks causing data races between global actor 'SomeGlobalActor'-isolated and local main actor-isolated uses}}
await globalSync(a) // expected-tns-note {{access can happen concurrently}}
_ = await ClassWithGlobalActorInits(nc)
// expected-warning @-1 {{non-sendable result type 'ClassWithGlobalActorInits' cannot be sent from global actor 'SomeGlobalActor'-isolated context in call to initializer 'init(_:)'}}
// expected-tns-warning @-1 {{non-Sendable 'ClassWithGlobalActorInits'-typed result can not be returned from global actor 'SomeGlobalActor'-isolated initializer 'init(_:)' to main actor-isolated context}}
// expected-tns-warning @-2 {{sending 'nc' risks causing data races}}
// expected-tns-note @-3 {{sending main actor-isolated 'nc' to global actor 'SomeGlobalActor'-isolated initializer 'init(_:)' risks causing data races between global actor 'SomeGlobalActor'-isolated and main actor-isolated uses}}
_ = await ClassWithGlobalActorInits() // expected-warning{{non-sendable result type 'ClassWithGlobalActorInits' cannot be sent from global actor 'SomeGlobalActor'-isolated context in call to initializer 'init()'}}
_ = await ClassWithGlobalActorInits() // expected-tns-warning {{non-Sendable 'ClassWithGlobalActorInits'-typed result can not be returned from global actor 'SomeGlobalActor'-isolated initializer 'init()' to main actor-isolated context}}
}
@SomeGlobalActor

View File

@@ -7,7 +7,6 @@
@available(SwiftStdlib 5.1, *)
struct NS1 { }
// expected-note @-1 {{consider making struct 'NS1' conform to the 'Sendable' protocol}}
@available(SwiftStdlib 5.1, *)
@available(*, unavailable)
@@ -96,7 +95,7 @@ public actor MyActor: MyProto {
await nonisolatedAsyncFunc1(ns1)
// expected-tns-warning @-1 {{sending 'ns1' risks causing data races}}
// expected-tns-note @-2 {{sending 'self'-isolated 'ns1' to nonisolated global function 'nonisolatedAsyncFunc1' risks causing data races between nonisolated and 'self'-isolated uses}}
_ = await nonisolatedAsyncFunc2() // expected-warning{{non-sendable result type 'NS1' cannot be sent from nonisolated context in call to global function 'nonisolatedAsyncFunc2()'}}
_ = await nonisolatedAsyncFunc2()
}
}

View File

@@ -2,8 +2,7 @@
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -strict-concurrency=complete %S/Inputs/StrictModule.swift
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
// We leave this as just type check since we are checking something that is cross module.
// RUN: %target-swift-frontend -typecheck -strict-concurrency=targeted -disable-availability-checking -I %t 2>&1 %s | %FileCheck %s
// RUN: %target-swift-frontend -c -strict-concurrency=complete -disable-availability-checking -I %t 2>&1 %s | %FileCheck %s
// REQUIRES: concurrency
@@ -15,9 +14,9 @@ actor A {
}
func testA(a: A) async {
_ = await a.f() // CHECK: warning: cannot call function returning non-sendable type '[StrictStruct : NonStrictClass]' across actors}}
// CHECK: note: struct 'StrictStruct' does not conform to the 'Sendable' protocol
// CHECK: note: class 'NonStrictClass' does not conform to the 'Sendable' protocol
_ = await a.f()
// CHECK: warning: non-Sendable '[StrictStruct : NonStrictClass]'-typed result can not be returned from actor-isolated instance method 'f()' to nonisolated context; this is an error in the Swift 6 language mode
// CHECK: note: note: generic struct 'Dictionary' does not conform to the 'Sendable' protocol
}
extension NonStrictStruct: @unchecked Sendable { }

View File

@@ -21,7 +21,7 @@ import _Concurrency
class Klass {}
class NonSendableKlass {
class NonSendableKlass { // expected-note 2{{}}
var klass: Klass
func asyncCall() async
@@ -121,10 +121,6 @@ bb0(%0 : $*{ var NonSendableKlass }):
return %9999 : $()
}
// This doesn't error since the @out parameter is not transferred when it is initialized.
//
// DISCUSSION: The frontend prevents us from using such a value. But we
// shouldn't crash on such values.
sil [ossa] @transfer_does_not_transfer_out_parameters_1 : $@convention(thin) @async () -> () {
bb0:
%0 = alloc_stack $NonSendableKlass
@@ -133,7 +129,7 @@ bb0:
%1 = alloc_stack $NonSendableKlass
%f = function_ref @transferIndirectWithOutResult : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0
apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %f<NonSendableKlass>(%1, %0) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0
apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %f<NonSendableKlass>(%1, %0) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0 // expected-warning {{}}
%useIndirect = function_ref @useIndirect : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
apply %useIndirect<NonSendableKlass>(%1) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
@@ -155,7 +151,7 @@ bb0:
%1 = alloc_stack $NonSendableKlass
%f = function_ref @transferIndirectWithOutResult : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0
apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %f<NonSendableKlass>(%1, %0) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0
apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %f<NonSendableKlass>(%1, %0) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0 // expected-warning {{}}
%f2 = function_ref @transferIndirect : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> ()
apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %f2<NonSendableKlass>(%1) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> ()

View File

@@ -18,7 +18,7 @@ class NonSendableKlass {
class SendableKlass : @unchecked Sendable {}
struct NonSendableStruct { // expected-note {{}}
struct NonSendableStruct {
var ns = NonSendableKlass()
}
@@ -722,7 +722,7 @@ func asyncLetWithoutCapture() async {
//
// NOTE: Error below will go away in next commit.
async let x: NonSendableKlass = await returnValueFromMain()
// expected-warning @-1 {{non-sendable result type 'NonSendableKlass' cannot be sent from main actor-isolated context in call to global function 'returnValueFromMain()'}}
// expected-warning @-1 {{non-Sendable 'NonSendableKlass'-typed result can not be returned from main actor-isolated global function 'returnValueFromMain()' to nonisolated context}}
let y = await x
await transferToMain(y) // expected-warning {{sending 'y' risks causing data races}}
// expected-note @-1 {{sending 'y' to main actor-isolated global function 'transferToMain' risks causing data races between main actor-isolated and local nonisolated uses}}
@@ -774,7 +774,6 @@ extension NonSendableStruct {
async let subTask6: NonSendableStruct = self
// expected-warning @-1 {{sending 'self' risks causing data races}}
// expected-note @-2 {{sending 'actor'-isolated 'self' into async let risks causing data races between nonisolated and 'actor'-isolated uses}}
// expected-warning @-3 {{non-sendable result type 'NonSendableStruct' cannot be sent from nonisolated context in call to async function}}
_ = await subTask6
}
}

View File

@@ -14,7 +14,6 @@
*/
class NonSendable {
// expected-note@-1 3{{class 'NonSendable' does not conform to the 'Sendable' protocol}}
var x = 0
}
@@ -31,10 +30,8 @@ func callActorFuncsFromNonisolated(a : A, ns : NonSendable) async {
// Non-sendable value passed from actor isolated to nonisolated
await a.actorTakesNS(ns)
//deferred-warning@-1{{passing argument of non-sendable type 'NonSendable' into actor-isolated context may introduce data races}}
_ = await a.actorRetsNS()
//expected-warning@-1{{non-sendable result type 'NonSendable' cannot be sent from actor-isolated context in call to instance method 'actorRetsNS()'}}
}
@available(SwiftStdlib 5.1, *)
@@ -52,7 +49,6 @@ actor A {
//deferred-warning@-1{{passing argument of non-sendable type 'NonSendable' outside of actor-isolated context may introduce data races}}
_ = await retsNS()
//expected-warning@-1{{non-sendable result type 'NonSendable' cannot be sent from nonisolated context in call to global function 'retsNS()'}}
}
func callActorFuncsFromDiffActor(ns : NonSendable, a : A) async {
@@ -62,6 +58,5 @@ actor A {
//deferred-warning@-1{{passing argument of non-sendable type 'NonSendable' into actor-isolated context may introduce data races}}
_ = await a.actorRetsNS()
//expected-warning@-1{{non-sendable result type 'NonSendable' cannot be sent from actor-isolated context in call to instance method 'actorRetsNS()'}}
}
}

View File

@@ -0,0 +1,137 @@
// RUN: %target-swift-frontend -target %target-swift-5.1-abi-triple -swift-version 6 -parse-as-library %s -emit-sil -o /dev/null -verify
// REQUIRES: asserts
// REQUIRES: concurrency
///////////////////////
// MARK: Declaration //
///////////////////////
actor Custom {
}
@globalActor
struct CustomActor {
static var shared: Custom {
return Custom()
}
}
class NonSendable {} // expected-note 3{{}}
func passNonSendable(_: NonSendable) async { }
func returnsNonSendable() async -> NonSendable { NonSendable() }
@MainActor
func mainActorPassNonSendable(_: NonSendable) async { }
@MainActor
func mainActorReturnNonSendable() async -> NonSendable { NonSendable() }
@MainActor
func mainActorGenericPassNonSendable<T>(_: T) async { }
@MainActor
func mainActorGenericReturnNonSendable<T>() async -> T { fatalError() }
@MainActor
func mainActorAsyncFunc3() async -> ((Int) -> Int) {
return { (_ y: Int) in y }
}
/////////////////
// MARK: Tests //
/////////////////
@MainActor func mainActorResult(_ x : Int) -> ((Int) -> Int) {
return { (_ y : Int) in x + y }
}
actor Calculator {
func addCurried(_ x : Int) -> ((Int) -> Int) {
return { (_ y : Int) in x + y }
}
func add(_ x : Int, _ y : Int) -> Int {
return x + y
}
}
@CustomActor
func testActorCrossingBoundary() async {
let _ = (await mainActorResult(1))(5)
// expected-error @-1 {{non-Sendable '(Int) -> Int'-typed result can not be returned from main actor-isolated global function 'mainActorResult' to global actor 'CustomActor'-isolated context}}
// expected-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let _ = await (await mainActorResult(1))(2)
// expected-error @-1 {{non-Sendable '(Int) -> Int'-typed result can not be returned from main actor-isolated global function 'mainActorResult' to global actor 'CustomActor'-isolated context}}
// expected-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
// expected-warning @-3 {{no 'async' operations occur within 'await' expression}}
let calc = Calculator()
let _ = (await calc.addCurried(1))(2)
// expected-error @-1 {{non-Sendable '(Int) -> Int'-typed result can not be returned from actor-isolated instance method 'addCurried' to global actor 'CustomActor'-isolated context}}
// expected-note@-2{{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let _ = await (await calc.addCurried(1))(2) // expected-warning{{no 'async' operations occur within 'await' expression}}
// expected-error @-1 {{non-Sendable '(Int) -> Int'-typed result can not be returned from actor-isolated instance method 'addCurried' to global actor 'CustomActor'-isolated context}}
// expected-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let plusOne = await calc.addCurried(await calc.add(0, 1))
// expected-error @-1 {{non-Sendable '(Int) -> Int'-typed result can not be returned from actor-isolated instance method 'addCurried' to global actor 'CustomActor'-isolated context}}
// expected-note @-2 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
let _ = plusOne(2)
}
actor A {
let actorNS = NonSendable()
func actorTakesNS(_ : NonSendable) async {}
func actorRetsNS() async -> NonSendable { NonSendable() }
func callNonisolatedFuncsFromActor(ns: NonSendable) async {
// Non-sendable value passed from nonisolated to actor isolated
await passNonSendable(ns)
// expected-error @-1 {{sending 'ns' risks causing data races}}
// expected-note @-2 {{sending 'self'-isolated 'ns' to nonisolated global function 'passNonSendable' risks causing data races between nonisolated and 'self'-isolated uses}}
_ = await returnsNonSendable()
}
func callActorFuncsFromDiffActor(ns : NonSendable, a : A) async {
// Non-sendable value passed between the isolation of two different actors
await a.actorTakesNS(ns)
// expected-error @-1 {{sending 'ns' risks causing data races}}
// expected-note @-2 {{sending 'self'-isolated 'ns' to actor-isolated instance method 'actorTakesNS' risks causing data races between actor-isolated and 'self'-isolated uses}}
_ = await a.actorRetsNS()
// expected-error @-1 {{non-Sendable 'NonSendable'-typed result can not be returned from actor-isolated instance method 'actorRetsNS()' to actor-isolated context}}
}
func validateErrorForPassingIsolatedNonSendable(_ ns: NonSendable) async {
await mainActorGenericPassNonSendable(ns)
// expected-error @-1 {{sending 'ns' risks causing data races}}
// expected-note @-2 {{sending 'self'-isolated 'ns' to main actor-isolated global function 'mainActorGenericPassNonSendable' risks causing data races between main actor-isolated and 'self'-isolated uses}}
}
func validateErrorReturningFromNonIsolated() async {
let _ = await returnsNonSendable()
}
}
func callActorFuncsFromNonisolated(a : A, ns : NonSendable) async {
await a.actorTakesNS(ns)
// expected-error @-1 {{sending 'ns' risks causing data races}}
// expected-note @-2 {{sending task-isolated 'ns' to actor-isolated instance method 'actorTakesNS' risks causing data races between actor-isolated and task-isolated uses}}
_ = await a.actorRetsNS()
// expected-error @-1 {{non-Sendable 'NonSendable'-typed result can not be returned from actor-isolated instance method 'actorRetsNS()' to nonisolated context}}
}
func testGenericResults() async {
let _: NonSendable = await mainActorGenericReturnNonSendable()
// expected-error @-1 {{non-Sendable 'NonSendable'-typed result can not be returned from main actor-isolated global function 'mainActorGenericReturnNonSendable()' to nonisolated context}}
}

View File

@@ -238,7 +238,7 @@ func asyncLetReabstractionThunkTest() async {
func asyncLetReabstractionThunkTest2() async {
// We emit the error here since we are returning a main actor isolated value.
async let newValue: NonSendableKlass = await getMainActorValueAsync()
// expected-warning @-1 {{non-sendable result type 'NonSendableKlass' cannot be sent from main actor-isolated context in call to global function 'getMainActorValueAsync()'}}
// expected-warning @-1 {{non-Sendable 'NonSendableKlass'-typed result can not be returned from main actor-isolated global function 'getMainActorValueAsync()' to nonisolated context}}
let _ = await newValue
@@ -261,7 +261,7 @@ func asyncLetReabstractionThunkTest2() async {
@MainActor func asyncLetReabstractionThunkTestGlobalActor2() async {
// We emit the error here since we are returning a main actor isolated value.
async let newValue: NonSendableKlass = await getMainActorValueAsync()
// expected-warning @-1 {{non-sendable result type 'NonSendableKlass' cannot be sent from main actor-isolated context in call to global function 'getMainActorValueAsync()'}}
// expected-warning @-1 {{non-Sendable 'NonSendableKlass'-typed result can not be returned from main actor-isolated global function 'getMainActorValueAsync()' to nonisolated context}}
let _ = await newValue

View File

@@ -16,8 +16,7 @@ enum MaybeFile: ~Copyable { // should implicitly conform
case closed
}
struct NotSendableMO: ~Copyable { // expected-note {{consider making struct 'NotSendableMO' conform to the 'Sendable' protocol}}
// expected-complete-note @-1 {{consider making struct 'NotSendableMO' conform to the 'Sendable' protocol}}
struct NotSendableMO: ~Copyable {
var ref: Ref
}
@@ -48,8 +47,8 @@ func processFiles(_ a: A, _ anotherFile: borrowing FileDescriptor) async {
await a.takeMaybeFile(.available(anotherFile))
_ = A(.available(anotherFile))
let ns = await a.getRef() // expected-warning {{non-sendable result type 'NotSendableMO' cannot be sent from actor-isolated context in call to instance method 'getRef()'}}
await takeNotSendable(ns) // expected-complete-warning {{passing argument of non-sendable type 'NotSendableMO' outside of main actor-isolated context may introduce data races}}
let ns = await a.getRef()
await takeNotSendable(ns)
switch (await a.giveFileDescriptor()) {
case let .available(fd):

View File

@@ -97,6 +97,7 @@ struct MockedPartitionOpEvaluatorWithFailureCallback final
case PartitionOpError::AssignNeverSendableIntoSendingResult:
case PartitionOpError::InOutSendingNotInitializedAtExit:
case PartitionOpError::InOutSendingNotDisconnectedAtExit:
case PartitionOpError::NonSendableIsolationCrossingResult:
llvm_unreachable("Unsupported");
case PartitionOpError::LocalUseAfterSend: {
auto state = error.getLocalUseAfterSendError();