diff --git a/include/swift/AST/DiagnosticsSIL.def b/include/swift/AST/DiagnosticsSIL.def index 4060a53762f8b..7cc5be8fa11b5 100644 --- a/include/swift/AST/DiagnosticsSIL.def +++ b/include/swift/AST/DiagnosticsSIL.def @@ -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 //===----------------------------------------------------------------------===// diff --git a/include/swift/SILOptimizer/Utils/PartitionOpError.def b/include/swift/SILOptimizer/Utils/PartitionOpError.def index 93cb15dc6bf44..98253626e95ac 100644 --- a/include/swift/SILOptimizer/Utils/PartitionOpError.def +++ b/include/swift/SILOptimizer/Utils/PartitionOpError.def @@ -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 diff --git a/include/swift/SILOptimizer/Utils/PartitionUtils.h b/include/swift/SILOptimizer/Utils/PartitionUtils.h index 8e9e37be789d5..faf8334b5daa8 100644 --- a/include/swift/SILOptimizer/Utils/PartitionUtils.h +++ b/include/swift/SILOptimizer/Utils/PartitionUtils.h @@ -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 @@ class PartitionOp { 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 @@ class PartitionOpError { } }; + 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 " must be copy constructable"); @@ -1482,8 +1512,15 @@ struct PartitionOpEvaluator { // 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?!"); } diff --git a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp index d2cd28a7ced68..df35e0761caa5 100644 --- a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp @@ -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 @@ class PartitionOpTranslator { 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 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(); + + // 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 diff --git a/lib/SILOptimizer/Mandatory/SendNonSendable.cpp b/lib/SILOptimizer/Mandatory/SendNonSendable.cpp index 96a869d786eea..9f8c0b769ad4c 100644 --- a/lib/SILOptimizer/Mandatory/SendNonSendable.cpp +++ b/lib/SILOptimizer/Mandatory/SendNonSendable.cpp @@ -849,26 +849,6 @@ class UseAfterSendDiagnosticInferrer { void initForApply(Operand *op, ApplyExpr *expr); void initForAutoclosure(Operand *op, AutoClosureExpr *expr); - - Expr *getFoundExprForSelf(ApplyExpr *sourceApply) { - if (auto callExpr = dyn_cast(sourceApply)) - if (auto calledExpr = - dyn_cast(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(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 + InFlightDiagnostic diagnoseError(SourceLoc loc, Diag diag, + U &&...args) { + emittedErrorDiagnostic = true; + return std::move(getASTContext() + .Diags.diagnose(loc, diag, std::forward(args)...) + .warnUntilSwiftVersion(6)); + } + + template + InFlightDiagnostic diagnoseError(SILLocation loc, Diag diag, + U &&...args) { + return diagnoseError(loc.getSourceLoc(), diag, std::forward(args)...); + } + + template + InFlightDiagnostic diagnoseError(SILInstruction *inst, Diag diag, + U &&...args) { + return diagnoseError(inst->getLoc(), diag, std::forward(args)...); + } + + template + InFlightDiagnostic diagnoseNote(SourceLoc loc, Diag diag, U &&...args) { + return getASTContext().Diags.diagnose(loc, diag, std::forward(args)...); + } + + template + InFlightDiagnostic diagnoseNote(SILLocation loc, Diag diag, + U &&...args) { + return diagnoseNote(loc.getSourceLoc(), diag, std::forward(args)...); + } + + template + InFlightDiagnostic diagnoseNote(SILInstruction *inst, Diag diag, + U &&...args) { + return diagnoseNote(inst->getLoc(), diag, std::forward(args)...); + } + + std::optional 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()) { + 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()) { + if (auto calledValue = + applyExpr->getCalledValue(true /*look through conversions*/)) { + return calledValue; + } + } + + return nullptr; + } + + std::optional getIsolationCrossing() const { + if (auto *applyExpr = + error.op->getSourceInst()->getLoc().getAsASTNode()) { + 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()) { + 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(nominal) || isa(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()) { + auto interfaceType = genericArchetype->getInterfaceType(); + if (auto genericParamType = interfaceType->getAs()) { + 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?!"); } diff --git a/lib/SILOptimizer/Utils/PartitionUtils.cpp b/lib/SILOptimizer/Utils/PartitionUtils.cpp index f5501cdfcde5a..d0dd74a1e2759 100644 --- a/lib/SILOptimizer/Utils/PartitionUtils.cpp +++ b/lib/SILOptimizer/Utils/PartitionUtils.cpp @@ -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(); } diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 44d55f9e567fe..73909a2d871ef 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -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(apply->getFn()); - if (!autoclosure) - return false; - auto *await = - dyn_cast(autoclosure->getSingleExpressionBody()); - if (!await) - return false; - auto *subCallExpr = dyn_cast(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; } diff --git a/test/ClangImporter/objc_async.swift b/test/ClangImporter/objc_async.swift index 35368883d294a..a02117c3ae2e4 100644 --- a/test/ClangImporter/objc_async.swift +++ b/test/ClangImporter/objc_async.swift @@ -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, *) diff --git a/test/Concurrency/actor_call_implicitly_async.swift b/test/Concurrency/actor_call_implicitly_async.swift index 553064f61a1eb..6c79a12a0e994 100644 --- a/test/Concurrency/actor_call_implicitly_async.swift +++ b/test/Concurrency/actor_call_implicitly_async.swift @@ -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) } diff --git a/test/Concurrency/concurrent_value_checking.swift b/test/Concurrency/concurrent_value_checking.swift index b5974050eac5a..95614ea3c9d3d 100644 --- a/test/Concurrency/concurrent_value_checking.swift +++ b/test/Concurrency/concurrent_value_checking.swift @@ -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 diff --git a/test/Concurrency/sendable_checking.swift b/test/Concurrency/sendable_checking.swift index bfb8fd4b5ecf5..4b0e03c170409 100644 --- a/test/Concurrency/sendable_checking.swift +++ b/test/Concurrency/sendable_checking.swift @@ -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() } } diff --git a/test/Concurrency/sendable_module_checking.swift b/test/Concurrency/sendable_module_checking.swift index 6da3f9ca29dbd..47cbb27b008ce 100644 --- a/test/Concurrency/sendable_module_checking.swift +++ b/test/Concurrency/sendable_module_checking.swift @@ -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 { } diff --git a/test/Concurrency/transfernonsendable.sil b/test/Concurrency/transfernonsendable.sil index 4ea454526ada7..292c01744c120 100644 --- a/test/Concurrency/transfernonsendable.sil +++ b/test/Concurrency/transfernonsendable.sil @@ -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(%1, %0) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0 + apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %f(%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(%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(%1, %0) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> @out τ_0_0 + apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %f(%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(%1) : $@convention(thin) @async <τ_0_0> (@in_guaranteed τ_0_0) -> () diff --git a/test/Concurrency/transfernonsendable_asynclet.swift b/test/Concurrency/transfernonsendable_asynclet.swift index 238dc72761e3f..350353c05398b 100644 --- a/test/Concurrency/transfernonsendable_asynclet.swift +++ b/test/Concurrency/transfernonsendable_asynclet.swift @@ -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 } } diff --git a/test/Concurrency/transfernonsendable_defer_and_typecheck_only.swift b/test/Concurrency/transfernonsendable_defer_and_typecheck_only.swift index 4ba64c8ebe487..c6e2e372f1c5b 100644 --- a/test/Concurrency/transfernonsendable_defer_and_typecheck_only.swift +++ b/test/Concurrency/transfernonsendable_defer_and_typecheck_only.swift @@ -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()'}} } } diff --git a/test/Concurrency/transfernonsendable_rbi_result.swift b/test/Concurrency/transfernonsendable_rbi_result.swift new file mode 100644 index 0000000000000..ea730c83b2d92 --- /dev/null +++ b/test/Concurrency/transfernonsendable_rbi_result.swift @@ -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) async { } + +@MainActor +func mainActorGenericReturnNonSendable() 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}} +} diff --git a/test/Concurrency/transfernonsendable_sending_results.swift b/test/Concurrency/transfernonsendable_sending_results.swift index 72fdd4adfa398..a3615b29546fe 100644 --- a/test/Concurrency/transfernonsendable_sending_results.swift +++ b/test/Concurrency/transfernonsendable_sending_results.swift @@ -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 diff --git a/test/Sema/moveonly_sendable.swift b/test/Sema/moveonly_sendable.swift index 8cba965343a81..0ea0334be56c9 100644 --- a/test/Sema/moveonly_sendable.swift +++ b/test/Sema/moveonly_sendable.swift @@ -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): diff --git a/unittests/SILOptimizer/PartitionUtilsTest.cpp b/unittests/SILOptimizer/PartitionUtilsTest.cpp index 2d4ab2bfe69ec..9247017ef66d7 100644 --- a/unittests/SILOptimizer/PartitionUtilsTest.cpp +++ b/unittests/SILOptimizer/PartitionUtilsTest.cpp @@ -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();