Skip to content

Commit c4d6650

Browse files
committed
Don’t recommend @preconcurrency on audited imports
If a type is explicitly non-Sendable, we don’t want to recommend softening errors with `@preconcurrency import` (even thought that would still work), because these probably represent *real* errors that you ought to fix. And at the extreme end of this, if a module is built with `-warn-concurrency` or `-swift-version 6`, *all* of its types should be treated as though they have explicit sendability, since we assume the entire module has been audited. (Eventually, we plan to do this at module build time by actually serializing out unavailable conformances rather than by looking at the settings used to build the module, but we aren’t there quite yet.) Implement this and test it by checking that the preconcurrency sendable tests never recommend adding `@preconcurrency` to an import of `StrictModule`, even when we do the same illegal things with it that cause that remark to be printed for `NonStrictModule`.
1 parent 823ff85 commit c4d6650

File tree

5 files changed

+54
-40
lines changed

5 files changed

+54
-40
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -770,10 +770,17 @@ DiagnosticBehavior SendableCheckContext::defaultDiagnosticBehavior() const {
770770
return defaultSendableDiagnosticBehavior(fromDC->getASTContext().LangOpts);
771771
}
772772

773-
/// Determine whether the given nominal type that is within the current module
774-
/// has an explicit Sendable.
775-
static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
773+
/// Determine whether the given nominal type has an explicit Sendable
774+
/// conformance (regardless of its availability).
775+
static bool hasExplicitSendableConformance(NominalTypeDecl *nominal,
776+
bool applyModuleDefault = true) {
776777
ASTContext &ctx = nominal->getASTContext();
778+
auto nominalModule = nominal->getParentModule();
779+
780+
// In a concurrency-checked module, a missing conformance is equivalent to
781+
// an explicitly unavailable one. If we want to apply this rule, do so now.
782+
if (applyModuleDefault && nominalModule->isConcurrencyChecked())
783+
return true;
777784

778785
// Look for any conformance to `Sendable`.
779786
auto proto = ctx.getProtocol(KnownProtocolKind::Sendable);
@@ -782,7 +789,7 @@ static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
782789

783790
// Look for a conformance. If it's present and not (directly) missing,
784791
// we're done.
785-
auto conformance = nominal->getParentModule()->lookupConformance(
792+
auto conformance = nominalModule->lookupConformance(
786793
nominal->getDeclaredInterfaceType(), proto, /*allowMissing=*/true);
787794
return conformance &&
788795
!(isa<BuiltinProtocolConformance>(conformance.getConcrete()) &&
@@ -826,18 +833,13 @@ static Optional<AttributedImport<ImportedModule>> findImportFor(
826833
/// nominal type.
827834
DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
828835
NominalTypeDecl *nominal) const {
829-
// Determine whether the type was explicitly non-Sendable.
830-
auto nominalModule = nominal->getParentModule();
831-
bool isExplicitlyNonSendable = nominalModule->isConcurrencyChecked() ||
832-
hasExplicitSendableConformance(nominal);
833-
834836
// Determine whether this nominal type is visible via a @preconcurrency
835837
// import.
836838
auto import = findImportFor(nominal, fromDC);
839+
auto sourceFile = fromDC->getParentSourceFile();
837840

838841
// When the type is explicitly non-Sendable...
839-
auto sourceFile = fromDC->getParentSourceFile();
840-
if (isExplicitlyNonSendable) {
842+
if (hasExplicitSendableConformance(nominal)) {
841843
// @preconcurrency imports downgrade the diagnostic to a warning in Swift 6,
842844
if (import && import->options.contains(ImportFlags::Preconcurrency)) {
843845
if (sourceFile)
@@ -857,7 +859,7 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
857859
if (sourceFile)
858860
sourceFile->setImportUsedPreconcurrency(*import);
859861

860-
return nominalModule->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
862+
return nominal->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
861863
? DiagnosticBehavior::Warning
862864
: DiagnosticBehavior::Ignore;
863865
}
@@ -917,29 +919,34 @@ static bool diagnoseSingleNonSendableType(
917919
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
918920
nominal->getName());
919921

920-
// This type was imported from another module; try to find the
921-
// corresponding import.
922-
Optional<AttributedImport<swift::ImportedModule>> import;
923-
SourceFile *sourceFile = fromContext.fromDC->getParentSourceFile();
924-
if (sourceFile) {
925-
import = findImportFor(nominal, fromContext.fromDC);
926-
}
922+
// When the type is explicitly Sendable *or* explicitly non-Sendable, we
923+
// assume it has been audited and `@preconcurrency` is not recommended even
924+
// though it would actually affect the diagnostic.
925+
if (!hasExplicitSendableConformance(nominal)) {
926+
// This type was imported from another module; try to find the
927+
// corresponding import.
928+
Optional<AttributedImport<swift::ImportedModule>> import;
929+
SourceFile *sourceFile = fromContext.fromDC->getParentSourceFile();
930+
if (sourceFile) {
931+
import = findImportFor(nominal, fromContext.fromDC);
932+
}
927933

928-
// If we found the import that makes this nominal type visible, remark
929-
// that it can be @preconcurrency import.
930-
// Only emit this remark once per source file, because it can happen a
931-
// lot.
932-
if (import && !import->options.contains(ImportFlags::Preconcurrency) &&
933-
import->importLoc.isValid() && sourceFile &&
934-
!sourceFile->hasImportUsedPreconcurrency(*import)) {
935-
SourceLoc importLoc = import->importLoc;
936-
ctx.Diags.diagnose(
937-
importLoc, diag::add_predates_concurrency_import,
938-
ctx.LangOpts.isSwiftVersionAtLeast(6),
939-
nominal->getParentModule()->getName())
940-
.fixItInsert(importLoc, "@preconcurrency ");
934+
// If we found the import that makes this nominal type visible, remark
935+
// that it can be @preconcurrency import.
936+
// Only emit this remark once per source file, because it can happen a
937+
// lot.
938+
if (import && !import->options.contains(ImportFlags::Preconcurrency) &&
939+
import->importLoc.isValid() && sourceFile &&
940+
!sourceFile->hasImportUsedPreconcurrency(*import)) {
941+
SourceLoc importLoc = import->importLoc;
942+
ctx.Diags.diagnose(
943+
importLoc, diag::add_predates_concurrency_import,
944+
ctx.LangOpts.isSwiftVersionAtLeast(6),
945+
nominal->getParentModule()->getName())
946+
.fixItInsert(importLoc, "@preconcurrency ");
941947

942-
sourceFile->setImportUsedPreconcurrency(*import);
948+
sourceFile->setImportUsedPreconcurrency(*import);
949+
}
943950
}
944951
}
945952

@@ -1156,7 +1163,7 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) {
11561163
return;
11571164

11581165
// If the conformance is explicitly stated, do nothing.
1159-
if (hasExplicitSendableConformance(nominal))
1166+
if (hasExplicitSendableConformance(nominal, /*applyModuleDefault=*/false))
11601167
return;
11611168

11621169
// Diagnose it.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
public struct StrictStruct: Hashable { }
2+
3+
open class StrictClass { }

test/Concurrency/sendable_preconcurrency.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
// REQUIRES: concurrency
77

8-
import StrictModule
8+
import StrictModule // no remark: we never recommend @preconcurrency due to an explicitly non-Sendable (via -warn-concurrency) type
99
@preconcurrency import NonStrictModule
1010

1111
actor A {
@@ -27,12 +27,14 @@ struct MyType3 {
2727
var nsc: NonStrictClass
2828
}
2929

30-
func testA(ns: NS, mt: MyType, mt2: MyType2, mt3: MyType3) async {
30+
func testA(ns: NS, mt: MyType, mt2: MyType2, mt3: MyType3, sc: StrictClass, nsc: NonStrictClass) async {
3131
Task {
3232
print(ns) // expected-warning{{capture of 'ns' with non-sendable type 'NS' in a `@Sendable` closure}}
3333
print(mt) // no warning: MyType is Sendable because we suppressed NonStrictClass's warning
3434
print(mt2)
3535
print(mt3)
36+
print(sc) // expected-warning {{capture of 'sc' with non-sendable type 'StrictClass' in a `@Sendable` closure}}
37+
print(nsc)
3638
}
3739
}
3840

test/Concurrency/sendable_without_preconcurrency.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
// REQUIRES: concurrency
77

8-
import StrictModule
8+
import StrictModule // no remark: we never recommend @preconcurrency due to an explicitly non-Sendable (via -warn-concurrency) type
99
import NonStrictModule
1010

1111
actor A {
@@ -23,11 +23,12 @@ struct MyType2 { // expected-note{{consider making struct 'MyType2' conform to t
2323
var ns: NS
2424
}
2525

26-
func testA(ns: NS, mt: MyType, mt2: MyType2) async {
26+
func testA(ns: NS, mt: MyType, mt2: MyType2, sc: StrictClass, nsc: NonStrictClass) async {
2727
Task {
2828
print(ns) // expected-warning{{capture of 'ns' with non-sendable type 'NS' in a `@Sendable` closure}}
2929
print(mt) // no warning: MyType is Sendable because we suppressed NonStrictClass's warning
3030
print(mt2) // expected-warning{{capture of 'mt2' with non-sendable type 'MyType2' in a `@Sendable` closure}}
31+
print(sc) // expected-warning{{capture of 'sc' with non-sendable type 'StrictClass' in a `@Sendable` closure}}
3132
}
3233
}
3334

test/Concurrency/sendable_without_preconcurrency_2.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
// REQUIRES: concurrency
77

8-
import StrictModule
8+
import StrictModule // no remark: we never recommend @preconcurrency due to an explicitly non-Sendable (via -warn-concurrency) type
99
import NonStrictModule // expected-remark{{add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'NonStrictModule'}}
1010

1111
actor A {
@@ -23,11 +23,13 @@ struct MyType2: Sendable {
2323
var ns: NS // expected-warning{{stored property 'ns' of 'Sendable'-conforming struct 'MyType2' has non-sendable type 'NS'}}
2424
}
2525

26-
func testA(ns: NS, mt: MyType, mt2: MyType2) async {
26+
func testA(ns: NS, mt: MyType, mt2: MyType2, sc: StrictClass, nsc: NonStrictClass) async {
2727
Task {
2828
print(ns) // expected-warning{{capture of 'ns' with non-sendable type 'NS' in a `@Sendable` closure}}
2929
print(mt) // no warning: MyType is Sendable because we suppressed NonStrictClass's warning
3030
print(mt2)
31+
print(sc) // expected-warning {{capture of 'sc' with non-sendable type 'StrictClass' in a `@Sendable` closure}}
32+
print(nsc) // expected-warning {{capture of 'nsc' with non-sendable type 'NonStrictClass' in a `@Sendable` closure}}
3133
}
3234
}
3335

0 commit comments

Comments
 (0)