Skip to content

Commit 7e375f7

Browse files
committed
Use new Sendable staging infrastructure consistently.
The main effect of this change is that diagnostics about Sendable conformances now follow the same minimal/full logic used for other Sendable diagnostics, rather than having their own separate computation.
1 parent 6381960 commit 7e375f7

File tree

8 files changed

+93
-140
lines changed

8 files changed

+93
-140
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4601,9 +4601,9 @@ WARNING(non_sendable_keypath_access,none,
46014601
"cannot form key path that accesses non-sendable type %0",
46024602
(Type))
46034603
ERROR(non_concurrent_type_member,none,
4604-
"%select{stored property %1|associated value %1}0 of "
4605-
"'Sendable'-conforming %2 %3 has non-sendable type %4",
4606-
(bool, DeclName, DescriptiveDeclKind, DeclName, Type))
4604+
"%select{stored property %2|associated value %2}1 of "
4605+
"'Sendable'-conforming %3 %4 has non-sendable type %0",
4606+
(Type, bool, DeclName, DescriptiveDeclKind, DeclName))
46074607
ERROR(concurrent_value_class_mutable_property,none,
46084608
"stored property %0 of 'Sendable'-conforming %1 %2 is mutable",
46094609
(DeclName, DescriptiveDeclKind, DeclName))

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 70 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -618,10 +618,25 @@ static DiagnosticBehavior defaultSendableDiagnosticBehavior(
618618
return DiagnosticBehavior::Unspecified;
619619
}
620620

621+
bool SendableCheckContext::isExplicitSendableConformance() const {
622+
if (!conformanceCheck)
623+
return false;
624+
625+
switch (*conformanceCheck) {
626+
case SendableCheck::Explicit:
627+
return true;
628+
629+
case SendableCheck::ImpliedByStandardProtocol:
630+
case SendableCheck::Implicit:
631+
return false;
632+
}
633+
}
634+
621635
DiagnosticBehavior SendableCheckContext::defaultDiagnosticBehavior() const {
622636
// If we're not supposed to diagnose existing data races from this context,
623637
// ignore the diagnostic entirely.
624-
if (!shouldDiagnoseExistingDataRaces(fromDC))
638+
if (!isExplicitSendableConformance() &&
639+
!shouldDiagnoseExistingDataRaces(fromDC))
625640
return DiagnosticBehavior::Ignore;
626641

627642
return defaultSendableDiagnosticBehavior(fromDC->getASTContext().LangOpts);
@@ -633,21 +648,8 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
633648
NominalTypeDecl *nominal) const {
634649
// Determine whether the type was explicitly non-Sendable.
635650
auto nominalModule = nominal->getParentModule();
636-
bool isExplicitlyNonSendable = nominalModule->isConcurrencyChecked();
637-
638-
// If we are performing an explicit conformance check, always consider this
639-
// to be an explicitly non-Sendable type.
640-
if (conformanceCheck) {
641-
switch (*conformanceCheck) {
642-
case SendableCheck::Explicit:
643-
isExplicitlyNonSendable = true;
644-
break;
645-
646-
case SendableCheck::ImpliedByStandardProtocol:
647-
case SendableCheck::Implicit:
648-
break;
649-
}
650-
}
651+
bool isExplicitlyNonSendable = nominalModule->isConcurrencyChecked() ||
652+
isExplicitSendableConformance();
651653

652654
// Determine whether this nominal type is visible via a @_predatesConcurrency
653655
// import.
@@ -679,8 +681,7 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
679681
/// a Sendable type is required.
680682
static bool diagnoseSingleNonSendableType(
681683
Type type, SendableCheckContext fromContext, SourceLoc loc,
682-
llvm::function_ref<
683-
std::pair<DiagnosticBehavior, bool>(Type, DiagnosticBehavior)> diagnose) {
684+
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
684685

685686
auto behavior = DiagnosticBehavior::Unspecified;
686687

@@ -693,11 +694,9 @@ static bool diagnoseSingleNonSendableType(
693694
behavior = fromContext.defaultDiagnosticBehavior();
694695
}
695696

696-
DiagnosticBehavior actualBehavior;
697-
bool wasError;
698-
std::tie(actualBehavior, wasError) = diagnose(type, behavior);
697+
bool wasSuppressed = diagnose(type, behavior);
699698

700-
if (actualBehavior == DiagnosticBehavior::Ignore) {
699+
if (behavior == DiagnosticBehavior::Ignore || wasSuppressed) {
701700
// Don't emit any other diagnostics.
702701
} else if (type->is<FunctionType>()) {
703702
ctx.Diags.diagnose(loc, diag::nonsendable_function_type);
@@ -713,13 +712,12 @@ static bool diagnoseSingleNonSendableType(
713712
nominal->getName());
714713
}
715714

716-
return wasError;
715+
return behavior == DiagnosticBehavior::Unspecified && !wasSuppressed;
717716
}
718717

719718
bool swift::diagnoseNonSendableTypes(
720719
Type type, SendableCheckContext fromContext, SourceLoc loc,
721-
llvm::function_ref<
722-
std::pair<DiagnosticBehavior, bool>(Type, DiagnosticBehavior)> diagnose) {
720+
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
723721
auto module = fromContext.fromDC->getParentModule();
724722

725723
// If the Sendable protocol is missing, do nothing.
@@ -3640,44 +3638,6 @@ bool swift::contextUsesConcurrencyFeatures(const DeclContext *dc) {
36403638
return false;
36413639
}
36423640

3643-
/// Limit the diagnostic behavior used when performing checks for the Sendable
3644-
/// instance storage of Sendable types.
3645-
///
3646-
/// \returns a pair containing the diagnostic behavior that should be used
3647-
/// for this diagnostic, as well as a Boolean value indicating whether to
3648-
/// treat this as an error.
3649-
static std::pair<DiagnosticBehavior, bool> limitSendableInstanceBehavior(
3650-
const LangOptions &langOpts, SendableCheck check,
3651-
DiagnosticBehavior suggestedBehavior) {
3652-
// Is an error suggested?
3653-
bool suggestedError = suggestedBehavior == DiagnosticBehavior::Unspecified ||
3654-
suggestedBehavior == DiagnosticBehavior::Error;
3655-
switch (check) {
3656-
case SendableCheck::Implicit:
3657-
// For implicit checks, we always ignore the diagnostic and fail.
3658-
return std::make_pair(DiagnosticBehavior::Ignore, true);
3659-
3660-
case SendableCheck::Explicit:
3661-
// Bump warnings up to errors due to explicit Sendable conformance.
3662-
if (suggestedBehavior == DiagnosticBehavior::Warning)
3663-
return std::make_pair(DiagnosticBehavior::Unspecified, true);
3664-
3665-
return std::make_pair(suggestedBehavior, suggestedError);
3666-
3667-
case SendableCheck::ImpliedByStandardProtocol:
3668-
// If we aren't in Swift 6, downgrade diagnostics.
3669-
if (!langOpts.isSwiftVersionAtLeast(6)) {
3670-
if (langOpts.WarnConcurrency &&
3671-
suggestedBehavior != DiagnosticBehavior::Ignore)
3672-
return std::make_pair(DiagnosticBehavior::Warning, false);
3673-
3674-
return std::make_pair(DiagnosticBehavior::Ignore, false);
3675-
}
3676-
3677-
return std::make_pair(suggestedBehavior, suggestedError);
3678-
}
3679-
}
3680-
36813641
namespace {
36823642
/// Visit the instance storage of the given nominal type as seen through
36833643
/// the given declaration context.
@@ -3749,38 +3709,36 @@ static bool checkSendableInstanceStorage(
37493709
if (check == SendableCheck::Implicit)
37503710
return true;
37513711

3752-
auto action = limitSendableInstanceBehavior(
3753-
langOpts, check, DiagnosticBehavior::Unspecified);
3754-
3755-
property->diagnose(diag::concurrent_value_class_mutable_property,
3756-
property->getName(), nominal->getDescriptiveKind(),
3757-
nominal->getName())
3758-
.limitBehavior(action.first);
3759-
invalid = invalid || action.second;
3712+
auto behavior = SendableCheckContext(
3713+
dc, check).defaultDiagnosticBehavior();
3714+
if (behavior != DiagnosticBehavior::Ignore) {
3715+
property->diagnose(diag::concurrent_value_class_mutable_property,
3716+
property->getName(), nominal->getDescriptiveKind(),
3717+
nominal->getName())
3718+
.limitBehavior(behavior);
3719+
}
3720+
invalid = invalid || (behavior == DiagnosticBehavior::Unspecified);
37603721
return true;
37613722
}
37623723

37633724
// Check that the property type is Sendable.
3764-
bool diagnosedProperty = diagnoseNonSendableTypes(
3725+
diagnoseNonSendableTypes(
37653726
propertyType, SendableCheckContext(dc, check), property->getLoc(),
3766-
[&](Type type, DiagnosticBehavior suggestedBehavior) {
3767-
auto action = limitSendableInstanceBehavior(
3768-
langOpts, check, suggestedBehavior);
3769-
if (check == SendableCheck::Implicit)
3770-
return action;
3727+
[&](Type type, DiagnosticBehavior behavior) {
3728+
if (check == SendableCheck::Implicit) {
3729+
invalid = true;
3730+
return true;
3731+
}
37713732

37723733
property->diagnose(diag::non_concurrent_type_member,
3773-
false, property->getName(),
3734+
propertyType, false, property->getName(),
37743735
nominal->getDescriptiveKind(),
3775-
nominal->getName(),
3776-
propertyType)
3777-
.limitBehavior(action.first);
3778-
return action;
3736+
nominal->getName())
3737+
.limitBehavior(behavior);
3738+
return false;
37793739
});
37803740

3781-
if (diagnosedProperty) {
3782-
invalid = true;
3783-
3741+
if (invalid) {
37843742
// For implicit checks, bail out early if anything failed.
37853743
if (check == SendableCheck::Implicit)
37863744
return true;
@@ -3791,26 +3749,23 @@ static bool checkSendableInstanceStorage(
37913749

37923750
/// Handle an enum associated value.
37933751
bool operator()(EnumElementDecl *element, Type elementType) {
3794-
bool diagnosedElement = diagnoseNonSendableTypes(
3752+
diagnoseNonSendableTypes(
37953753
elementType, SendableCheckContext(dc, check), element->getLoc(),
3796-
[&](Type type, DiagnosticBehavior suggestedBehavior) {
3797-
auto action = limitSendableInstanceBehavior(
3798-
langOpts, check, suggestedBehavior);
3799-
if (check == SendableCheck::Implicit)
3800-
return action;
3754+
[&](Type type, DiagnosticBehavior behavior) {
3755+
if (check == SendableCheck::Implicit) {
3756+
invalid = true;
3757+
return true;
3758+
}
38013759

3802-
element->diagnose(diag::non_concurrent_type_member,
3760+
element->diagnose(diag::non_concurrent_type_member, type,
38033761
true, element->getName(),
38043762
nominal->getDescriptiveKind(),
3805-
nominal->getName(),
3806-
type)
3807-
.limitBehavior(action.first);
3808-
return action;
3763+
nominal->getName())
3764+
.limitBehavior(behavior);
3765+
return false;
38093766
});
38103767

3811-
if (diagnosedElement) {
3812-
invalid = true;
3813-
3768+
if (invalid) {
38143769
// For implicit checks, bail out early if anything failed.
38153770
if (check == SendableCheck::Implicit)
38163771
return true;
@@ -3859,46 +3814,43 @@ bool swift::checkSendableConformance(
38593814

38603815
// Sendable can only be used in the same source file.
38613816
auto conformanceDecl = conformanceDC->getAsDecl();
3862-
const LangOptions &langOpts = conformanceDC->getASTContext().LangOpts;
3863-
DiagnosticBehavior behavior;
3864-
bool diagnosticCausesFailure;
3865-
std::tie(behavior, diagnosticCausesFailure) = limitSendableInstanceBehavior(
3866-
langOpts, check, DiagnosticBehavior::Unspecified);
3867-
if (!conformanceDC->getParentSourceFile() ||
3817+
auto behavior = SendableCheckContext(conformanceDC)
3818+
.defaultDiagnosticBehavior();
3819+
if (conformanceDC->getParentSourceFile() &&
3820+
nominal->getParentSourceFile() &&
38683821
conformanceDC->getParentSourceFile() != nominal->getParentSourceFile()) {
38693822
conformanceDecl->diagnose(diag::concurrent_value_outside_source_file,
38703823
nominal->getDescriptiveKind(),
38713824
nominal->getName())
3872-
.limitBehavior(behavior);
3825+
.limitBehavior(behavior);
38733826

3874-
if (diagnosticCausesFailure)
3827+
if (behavior == DiagnosticBehavior::Unspecified)
38753828
return true;
38763829
}
38773830

3878-
if (classDecl) {
3831+
if (classDecl && classDecl->getParentSourceFile()) {
3832+
bool isInherited = isa<InheritedProtocolConformance>(conformance);
3833+
38793834
// An non-final class cannot conform to `Sendable`.
38803835
if (!classDecl->isSemanticallyFinal()) {
38813836
classDecl->diagnose(diag::concurrent_value_nonfinal_class,
38823837
classDecl->getName())
3883-
.limitBehavior(behavior);
3838+
.limitBehavior(behavior);
38843839

3885-
if (diagnosticCausesFailure)
3840+
if (behavior == DiagnosticBehavior::Unspecified)
38863841
return true;
38873842
}
38883843

3889-
// A 'Sendable' class cannot inherit from another class, although
3890-
// we allow `NSObject` for Objective-C interoperability.
3891-
if (!isa<InheritedProtocolConformance>(conformance)) {
3844+
if (!isInherited) {
3845+
// A 'Sendable' class cannot inherit from another class, although
3846+
// we allow `NSObject` for Objective-C interoperability.
38923847
if (auto superclassDecl = classDecl->getSuperclassDecl()) {
38933848
if (!superclassDecl->isNSObject()) {
38943849
classDecl->diagnose(
38953850
diag::concurrent_value_inherit,
38963851
nominal->getASTContext().LangOpts.EnableObjCInterop,
3897-
classDecl->getName())
3898-
.limitBehavior(behavior);
3899-
3900-
if (diagnosticCausesFailure)
3901-
return true;
3852+
classDecl->getName());
3853+
return true;
39023854
}
39033855
}
39043856
}

lib/Sema/TypeCheckConcurrency.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ struct SendableCheckContext {
279279
/// Determine the diagnostic behavior when referencing the given nominal
280280
/// type in this context.
281281
DiagnosticBehavior diagnosticBehavior(NominalTypeDecl *nominal) const;
282+
283+
/// Whether we are in an explicit conformance to Sendable.
284+
bool isExplicitSendableConformance() const;
282285
};
283286

284287
/// Diagnose any non-Sendable types that occur within the given type, using
@@ -291,8 +294,7 @@ struct SendableCheckContext {
291294
/// \returns \c true if any diagnostics were produced, \c false otherwise.
292295
bool diagnoseNonSendableTypes(
293296
Type type, SendableCheckContext fromContext, SourceLoc loc,
294-
llvm::function_ref<
295-
std::pair<DiagnosticBehavior, bool>(Type, DiagnosticBehavior)> diagnose);
297+
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose);
296298

297299
namespace detail {
298300
template<typename T>
@@ -314,10 +316,12 @@ bool diagnoseNonSendableTypes(
314316
return diagnoseNonSendableTypes(
315317
type, fromContext, loc, [&](Type specificType,
316318
DiagnosticBehavior behavior) {
317-
ctx.Diags.diagnose(loc, diag, type, diagArgs...)
318-
.limitBehavior(behavior);
319-
return std::pair<DiagnosticBehavior, bool>(
320-
behavior, behavior == DiagnosticBehavior::Unspecified);
319+
if (behavior != DiagnosticBehavior::Ignore) {
320+
ctx.Diags.diagnose(loc, diag, type, diagArgs...)
321+
.limitBehavior(behavior);
322+
}
323+
324+
return false;
321325
});
322326
}
323327

test/Concurrency/concurrent_value_checking.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,11 @@ func concurrentClosures<T>(_: T) {
231231
// Sendable checking
232232
// ----------------------------------------------------------------------
233233
struct S1: Sendable {
234-
var nc: NotConcurrent // expected-error{{stored property 'nc' of 'Sendable'-conforming struct 'S1' has non-sendable type 'NotConcurrent'}}
234+
var nc: NotConcurrent // expected-warning{{stored property 'nc' of 'Sendable'-conforming struct 'S1' has non-sendable type 'NotConcurrent'}}
235235
}
236236

237237
struct S2<T>: Sendable {
238-
var nc: T // expected-error{{stored property 'nc' of 'Sendable'-conforming generic struct 'S2' has non-sendable type 'T'}}
238+
var nc: T // expected-warning{{stored property 'nc' of 'Sendable'-conforming generic struct 'S2' has non-sendable type 'T'}}
239239
}
240240

241241
struct S3<T> {
@@ -246,7 +246,7 @@ struct S3<T> {
246246
extension S3: Sendable where T: Sendable { }
247247

248248
enum E1: Sendable {
249-
case payload(NotConcurrent) // expected-error{{associated value 'payload' of 'Sendable'-conforming enum 'E1' has non-sendable type 'NotConcurrent'}}
249+
case payload(NotConcurrent) // expected-warning{{associated value 'payload' of 'Sendable'-conforming enum 'E1' has non-sendable type 'NotConcurrent'}}
250250
}
251251

252252
enum E2<T> {
@@ -256,8 +256,8 @@ enum E2<T> {
256256
extension E2: Sendable where T: Sendable { }
257257

258258
final class C1: Sendable {
259-
let nc: NotConcurrent? = nil // expected-error{{stored property 'nc' of 'Sendable'-conforming class 'C1' has non-sendable type 'NotConcurrent?'}}
260-
var x: Int = 0 // expected-error{{stored property 'x' of 'Sendable'-conforming class 'C1' is mutable}}
259+
let nc: NotConcurrent? = nil // expected-warning{{stored property 'nc' of 'Sendable'-conforming class 'C1' has non-sendable type 'NotConcurrent?'}}
260+
var x: Int = 0 // expected-warning{{stored property 'x' of 'Sendable'-conforming class 'C1' is mutable}}
261261
let i: Int = 0
262262
}
263263

@@ -281,7 +281,7 @@ class C6: C5 {
281281

282282
final class C7<T>: Sendable { }
283283

284-
class C9: Sendable { } // expected-error{{non-final class 'C9' cannot conform to 'Sendable'; use '@unchecked Sendable'}}
284+
class C9: Sendable { } // expected-warning{{non-final class 'C9' cannot conform to 'Sendable'; use '@unchecked Sendable'}}
285285

286286
// ----------------------------------------------------------------------
287287
// @unchecked Sendable disabling checking

0 commit comments

Comments
 (0)