Skip to content

Commit 16d0194

Browse files
committed
[sending] Improve the sending mismatch errors and make them warnings when not in swift 6.
This will ensure that we do not break anyone who has adopted APIs like CheckedContinuation.resume that now have sending parameters. An example of where this can come up is shown by the ProcessType in SwiftToolsCore: ```swift @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @discardableResult public func waitUntilExit() async throws -> ProcessResult { try await withCheckedThrowingContinuation { continuation in DispatchQueue.processConcurrent.async { self.waitUntilExit(continuation.resume(with:)) } } } ``` This fails to compile since self.waitUntilExit doesn't expect a function that takes a sending parameter. We want to give people time to fix such issues.
1 parent a451fe6 commit 16d0194

File tree

7 files changed

+157
-8
lines changed

7 files changed

+157
-8
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8012,6 +8012,19 @@ ERROR(sending_only_on_parameters_and_results, none,
80128012
"'sending' may only be used on parameters and results", ())
80138013
ERROR(sending_cannot_be_applied_to_tuple_elt, none,
80148014
"'sending' cannot be applied to tuple elements", ())
8015+
ERROR(sending_function_wrong_sending,none,
8016+
"converting a value of type %0 to type %1 risks causing data races",
8017+
(Type, Type))
8018+
NOTE(sending_function_param_with_sending_param_note, none,
8019+
"converting a function typed value with a sending parameter to one "
8020+
"without risks allowing actor-isolated values to escape their isolation "
8021+
"domain as an argument to an invocation of value",
8022+
())
8023+
NOTE(sending_function_result_with_sending_param_note, none,
8024+
"converting a function typed value without a sending result as one with "
8025+
"risks allowing actor-isolated values to escape their "
8026+
"isolation domain through a result of an invocation of value",
8027+
())
80158028

80168029
#define UNDEFINE_DIAGNOSTIC_MACROS
80178030
#include "DefineDiagnosticMacros.h"

include/swift/Sema/CSFix.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,17 @@ enum class FixKind : uint8_t {
474474
/// Ignore situations when key path subscript index gets passed an invalid
475475
/// type as an argument (something that is not a key path).
476476
IgnoreKeyPathSubscriptIndexMismatch,
477+
478+
/// Ignore the following situations:
479+
///
480+
/// 1. Where we have a function that expects a function typed parameter
481+
/// without a sendable parameter but is passed a function type with a sending
482+
/// parameter.
483+
///
484+
/// 2. Where we have a function that expects a function typed parameter with a
485+
/// sending result, but is passed a function typeed parameter without a
486+
/// sending result.
487+
AllowSendingMismatch,
477488
};
478489

479490
class ConstraintFix {
@@ -2648,6 +2659,49 @@ class TreatEphemeralAsNonEphemeral final : public AllowArgumentMismatch {
26482659
}
26492660
};
26502661

2662+
/// Error if a user passes let f: (sending T) -> () as a (T) -> ().
2663+
///
2664+
/// This prevents data races since f assumes its parameter if the parameter is
2665+
/// non-Sendable is safe to transfer onto other situations. The caller though
2666+
/// that this is being sent to does not enforce that invariants within its body.
2667+
class AllowSendingMismatch final : public ContextualMismatch {
2668+
public:
2669+
enum class Kind {
2670+
Parameter,
2671+
Result,
2672+
};
2673+
2674+
private:
2675+
Kind kind;
2676+
2677+
AllowSendingMismatch(ConstraintSystem &cs, Type argType, Type paramType,
2678+
ConstraintLocator *locator, Kind kind,
2679+
FixBehavior fixBehavior)
2680+
: ContextualMismatch(cs, FixKind::AllowSendingMismatch, argType,
2681+
paramType, locator, fixBehavior),
2682+
kind(kind) {}
2683+
2684+
public:
2685+
std::string getName() const override {
2686+
return "treat a function argument with sending parameter as a function "
2687+
"argument without sending parameters";
2688+
}
2689+
2690+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2691+
2692+
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
2693+
return diagnose(*commonFixes.front().first);
2694+
}
2695+
2696+
static AllowSendingMismatch *create(ConstraintSystem &cs,
2697+
ConstraintLocator *locator, Type srcType,
2698+
Type dstType, Kind kind);
2699+
2700+
static bool classof(const ConstraintFix *fix) {
2701+
return fix->getKind() == FixKind::AllowSendingMismatch;
2702+
}
2703+
};
2704+
26512705
class SpecifyBaseTypeForContextualMember final : public ConstraintFix {
26522706
DeclNameRef MemberName;
26532707

lib/Sema/CSDiagnostics.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8020,6 +8020,24 @@ bool NonEphemeralConversionFailure::diagnoseAsError() {
80208020
return true;
80218021
}
80228022

8023+
bool SendingOnFunctionParameterMismatchFail::diagnoseAsError() {
8024+
emitDiagnosticAt(getLoc(), diag::sending_function_wrong_sending,
8025+
getFromType(), getToType())
8026+
.warnUntilSwiftVersion(6);
8027+
emitDiagnosticAt(getLoc(),
8028+
diag::sending_function_param_with_sending_param_note);
8029+
return true;
8030+
}
8031+
8032+
bool SendingOnFunctionResultMismatchFailure::diagnoseAsError() {
8033+
emitDiagnosticAt(getLoc(), diag::sending_function_wrong_sending,
8034+
getFromType(), getToType())
8035+
.warnUntilSwiftVersion(6);
8036+
emitDiagnosticAt(getLoc(),
8037+
diag::sending_function_result_with_sending_param_note);
8038+
return true;
8039+
}
8040+
80238041
bool AssignmentTypeMismatchFailure::diagnoseMissingConformance() const {
80248042
auto srcType = getFromType();
80258043
auto dstType = getToType()->lookThroughAllOptionalTypes();

lib/Sema/CSDiagnostics.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,6 +2306,28 @@ class NonEphemeralConversionFailure final : public ArgumentMismatchFailure {
23062306
void emitSuggestionNotes() const;
23072307
};
23082308

2309+
class SendingOnFunctionParameterMismatchFail final : public ContextualFailure {
2310+
public:
2311+
SendingOnFunctionParameterMismatchFail(const Solution &solution, Type srcType,
2312+
Type dstType,
2313+
ConstraintLocator *locator,
2314+
FixBehavior fixBehavior)
2315+
: ContextualFailure(solution, srcType, dstType, locator, fixBehavior) {}
2316+
2317+
bool diagnoseAsError() override;
2318+
};
2319+
2320+
class SendingOnFunctionResultMismatchFailure final : public ContextualFailure {
2321+
public:
2322+
SendingOnFunctionResultMismatchFailure(const Solution &solution, Type srcType,
2323+
Type dstType,
2324+
ConstraintLocator *locator,
2325+
FixBehavior fixBehavior)
2326+
: ContextualFailure(solution, srcType, dstType, locator, fixBehavior) {}
2327+
2328+
bool diagnoseAsError() override;
2329+
};
2330+
23092331
class AssignmentTypeMismatchFailure final : public ContextualFailure {
23102332
public:
23112333
AssignmentTypeMismatchFailure(const Solution &solution,

lib/Sema/CSFix.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,34 @@ std::string TreatEphemeralAsNonEphemeral::getName() const {
18841884
return name;
18851885
}
18861886

1887+
bool AllowSendingMismatch::diagnose(const Solution &solution,
1888+
bool asNote) const {
1889+
switch (kind) {
1890+
case Kind::Parameter: {
1891+
SendingOnFunctionParameterMismatchFail failure(
1892+
solution, getFromType(), getToType(), getLocator(), fixBehavior);
1893+
return failure.diagnose(asNote);
1894+
}
1895+
case Kind::Result: {
1896+
SendingOnFunctionResultMismatchFailure failure(
1897+
solution, getFromType(), getToType(), getLocator(), fixBehavior);
1898+
return failure.diagnose(asNote);
1899+
}
1900+
}
1901+
llvm_unreachable("Covered switch isn't covered?!");
1902+
}
1903+
1904+
AllowSendingMismatch *AllowSendingMismatch::create(ConstraintSystem &cs,
1905+
ConstraintLocator *locator,
1906+
Type srcType, Type dstType,
1907+
Kind kind) {
1908+
auto fixBehavior = cs.getASTContext().LangOpts.isSwiftVersionAtLeast(6)
1909+
? FixBehavior::Error
1910+
: FixBehavior::DowngradeToWarning;
1911+
return new (cs.getAllocator())
1912+
AllowSendingMismatch(cs, srcType, dstType, locator, kind, fixBehavior);
1913+
}
1914+
18871915
bool SpecifyBaseTypeForContextualMember::diagnose(const Solution &solution,
18881916
bool asNote) const {
18891917
MissingContextualBaseInMemberRefFailure failure(solution, MemberName,

lib/Sema/CSSimplify.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3238,7 +3238,11 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
32383238
// () -> sending T can be a subtype of () -> T... but not vis-a-versa.
32393239
if (func1->hasSendingResult() != func2->hasSendingResult() &&
32403240
(!func1->hasSendingResult() || kind < ConstraintKind::Subtype)) {
3241-
return getTypeMatchFailure(locator);
3241+
auto *fix = AllowSendingMismatch::create(
3242+
*this, getConstraintLocator(locator), func1, func2,
3243+
AllowSendingMismatch::Kind::Result);
3244+
if (recordFix(fix))
3245+
return getTypeMatchFailure(locator);
32423246
}
32433247

32443248
if (!matchFunctionIsolations(func1, func2, kind, flags, locator))
@@ -3675,7 +3679,11 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
36753679
// with a function that expects a non-sending parameter.
36763680
if (func1Param.getParameterFlags().isSending() &&
36773681
!func2Param.getParameterFlags().isSending()) {
3678-
return getTypeMatchFailure(argumentLocator);
3682+
auto *fix = AllowSendingMismatch::create(
3683+
*this, getConstraintLocator(argumentLocator), func1, func2,
3684+
AllowSendingMismatch::Kind::Parameter);
3685+
if (recordFix(fix))
3686+
return getTypeMatchFailure(argumentLocator);
36793687
}
36803688

36813689
// FIXME: We should check value ownership too, but it's not completely
@@ -15144,6 +15152,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1514415152
}
1514515153
}
1514615154

15155+
case FixKind::AllowSendingMismatch:
1514715156
case FixKind::InsertCall:
1514815157
case FixKind::RemoveReturn:
1514915158
case FixKind::RemoveAddressOf:

test/Concurrency/transfernonsendable_functionsubtyping.swift

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-typecheck-verify-swift -swift-version 6 %s
1+
// RUN: %target-typecheck-verify-swift -swift-version 6
22

33
// READ THIS! This file only contains tests that validate that the relevant
44
// function subtyping rules for sending work. Please do not put other tests in
@@ -38,15 +38,17 @@ func takeFnWithoutSendingParam(_ fn: (NonSendableKlass) -> ()) {}
3838

3939
func testFunctionMatching() {
4040
let _: (NonSendableKlass) -> () = functionWithSendingParameter
41-
// expected-error @-1 {{cannot convert value of type '@Sendable (sending NonSendableKlass) -> ()' to specified type '(NonSendableKlass) -> ()'}}
41+
// expected-error @-1 {{converting a value of type '@Sendable (sending NonSendableKlass) -> ()' to type '(NonSendableKlass) -> ()' risks causing data races}}
42+
// expected-note @-2 {{converting a function typed value with a sending parameter to one without risks allowing actor-isolated values to escape their isolation domain as an argument to an invocation of value}}
4243
let _: (sending NonSendableKlass) -> () = functionWithSendingParameter
4344

4445
let _: (NonSendableKlass) -> () = functionWithoutSendingParameter
4546
let _: (sending NonSendableKlass) -> () = functionWithoutSendingParameter
4647

4748
takeFnWithSendingParam(functionWithSendingParameter)
4849
takeFnWithoutSendingParam(functionWithSendingParameter)
49-
// expected-error @-1 {{@Sendable (sending NonSendableKlass) -> ()' to expected argument type '(NonSendableKlass) -> ()}}
50+
// expected-error @-1 {{converting a value of type '@Sendable (sending NonSendableKlass) -> ()' to type '(NonSendableKlass) -> ()' risks causing data races}}
51+
// expected-note @-2 {{converting a function typed value with a sending parameter to one without risks allowing actor-isolated values to escape their isolation domain as an argument to an invocation of value}}
5052
takeFnWithSendingParam(functionWithoutSendingParameter)
5153
takeFnWithoutSendingParam(functionWithoutSendingParameter)
5254
}
@@ -56,14 +58,17 @@ func testReturnValueMatching() {
5658
let _: () -> sending NonSendableKlass = functionWithSendingResult
5759
let _: () -> NonSendableKlass = functionWithoutSendingResult
5860
let _: () -> sending NonSendableKlass = functionWithoutSendingResult
59-
// expected-error @-1 {{cannot convert value of type '@Sendable () -> NonSendableKlass' to specified type '() -> sending NonSendableKlass'}}
61+
// expected-error @-1 {{converting a value of type '@Sendable () -> NonSendableKlass' to type '() -> sending NonSendableKlass' risks causing data races}}
62+
// expected-note @-2 {{converting a function typed value without a sending result as one with risks allowing actor-isolated values to escape their isolation domain through a result of an invocation of value}}
6063

6164
takeFnWithSendingResult(functionWithSendingResult)
6265
takeFnWithSendingResult(functionWithoutSendingResult)
63-
// expected-error @-1 {{cannot convert value of type '@Sendable () -> NonSendableKlass' to expected argument type '() -> sending NonSendableKlass'}}
66+
// expected-error @-1 {{converting a value of type '@Sendable () -> NonSendableKlass' to type '() -> sending NonSendableKlass' risks causing data races}}
67+
// expected-note @-2 {{converting a function typed value without a sending result as one with risks allowing actor-isolated values to escape their isolation domain through a result of an invocation of value}}
6468
let x: () -> NonSendableKlass = { fatalError() }
6569
takeFnWithSendingResult(x)
66-
// expected-error @-1 {{cannot convert value of type '() -> NonSendableKlass' to expected argument type '() -> sending NonSendableKlass'}}
70+
// expected-error @-1 {{converting a value of type '() -> NonSendableKlass' to type '() -> sending NonSendableKlass' risks causing data races}}
71+
// expected-note @-2 {{converting a function typed value without a sending result as one with risks allowing actor-isolated values to escape their isolation domain through a result of an invocation of value}}
6772

6873
takeFnWithoutSendingResult(functionWithSendingResult)
6974
takeFnWithoutSendingResult(functionWithoutSendingResult)

0 commit comments

Comments
 (0)