Skip to content

Commit 9cbac44

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 4caaa3b commit 9cbac44

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
@@ -650,10 +650,17 @@ DiagnosticBehavior SendableCheckContext::defaultDiagnosticBehavior() const {
650650
return defaultSendableDiagnosticBehavior(fromDC->getASTContext().LangOpts);
651651
}
652652

653-
/// Determine whether the given nominal type that is within the current module
654-
/// has an explicit Sendable.
655-
static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
653+
/// Determine whether the given nominal type has an explicit Sendable
654+
/// conformance (regardless of its availability).
655+
static bool hasExplicitSendableConformance(NominalTypeDecl *nominal,
656+
bool applyModuleDefault = true) {
656657
ASTContext &ctx = nominal->getASTContext();
658+
auto nominalModule = nominal->getParentModule();
659+
660+
// In a concurrency-checked module, a missing conformance is equivalent to
661+
// an explicitly unavailable one. If we want to apply this rule, do so now.
662+
if (applyModuleDefault && nominalModule->isConcurrencyChecked())
663+
return true;
657664

658665
// Look for any conformance to `Sendable`.
659666
auto proto = ctx.getProtocol(KnownProtocolKind::Sendable);
@@ -662,7 +669,7 @@ static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
662669

663670
// Look for a conformance. If it's present and not (directly) missing,
664671
// we're done.
665-
auto conformance = nominal->getParentModule()->lookupConformance(
672+
auto conformance = nominalModule->lookupConformance(
666673
nominal->getDeclaredInterfaceType(), proto, /*allowMissing=*/true);
667674
return conformance &&
668675
!(isa<BuiltinProtocolConformance>(conformance.getConcrete()) &&
@@ -706,18 +713,13 @@ static Optional<AttributedImport<ImportedModule>> findImportFor(
706713
/// nominal type.
707714
DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
708715
NominalTypeDecl *nominal) const {
709-
// Determine whether the type was explicitly non-Sendable.
710-
auto nominalModule = nominal->getParentModule();
711-
bool isExplicitlyNonSendable = nominalModule->isConcurrencyChecked() ||
712-
hasExplicitSendableConformance(nominal);
713-
714716
// Determine whether this nominal type is visible via a @preconcurrency
715717
// import.
716718
auto import = findImportFor(nominal, fromDC);
719+
auto sourceFile = fromDC->getParentSourceFile();
717720

718721
// When the type is explicitly non-Sendable...
719-
auto sourceFile = fromDC->getParentSourceFile();
720-
if (isExplicitlyNonSendable) {
722+
if (hasExplicitSendableConformance(nominal)) {
721723
// @preconcurrency imports downgrade the diagnostic to a warning in Swift 6,
722724
if (import && import->options.contains(ImportFlags::Preconcurrency)) {
723725
if (sourceFile)
@@ -737,7 +739,7 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
737739
if (sourceFile)
738740
sourceFile->setImportUsedPreconcurrency(*import);
739741

740-
return nominalModule->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
742+
return nominal->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
741743
? DiagnosticBehavior::Warning
742744
: DiagnosticBehavior::Ignore;
743745
}
@@ -797,29 +799,34 @@ static bool diagnoseSingleNonSendableType(
797799
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
798800
nominal->getName());
799801

800-
// This type was imported from another module; try to find the
801-
// corresponding import.
802-
Optional<AttributedImport<swift::ImportedModule>> import;
803-
SourceFile *sourceFile = fromContext.fromDC->getParentSourceFile();
804-
if (sourceFile) {
805-
import = findImportFor(nominal, fromContext.fromDC);
806-
}
802+
// When the type is explicitly Sendable *or* explicitly non-Sendable, we
803+
// assume it has been audited and `@preconcurrency` is not recommended even
804+
// though it would actually affect the diagnostic.
805+
if (!hasExplicitSendableConformance(nominal)) {
806+
// This type was imported from another module; try to find the
807+
// corresponding import.
808+
Optional<AttributedImport<swift::ImportedModule>> import;
809+
SourceFile *sourceFile = fromContext.fromDC->getParentSourceFile();
810+
if (sourceFile) {
811+
import = findImportFor(nominal, fromContext.fromDC);
812+
}
807813

808-
// If we found the import that makes this nominal type visible, remark
809-
// that it can be @preconcurrency import.
810-
// Only emit this remark once per source file, because it can happen a
811-
// lot.
812-
if (import && !import->options.contains(ImportFlags::Preconcurrency) &&
813-
import->importLoc.isValid() && sourceFile &&
814-
!sourceFile->hasImportUsedPreconcurrency(*import)) {
815-
SourceLoc importLoc = import->importLoc;
816-
ctx.Diags.diagnose(
817-
importLoc, diag::add_predates_concurrency_import,
818-
ctx.LangOpts.isSwiftVersionAtLeast(6),
819-
nominal->getParentModule()->getName())
820-
.fixItInsert(importLoc, "@preconcurrency ");
814+
// If we found the import that makes this nominal type visible, remark
815+
// that it can be @preconcurrency import.
816+
// Only emit this remark once per source file, because it can happen a
817+
// lot.
818+
if (import && !import->options.contains(ImportFlags::Preconcurrency) &&
819+
import->importLoc.isValid() && sourceFile &&
820+
!sourceFile->hasImportUsedPreconcurrency(*import)) {
821+
SourceLoc importLoc = import->importLoc;
822+
ctx.Diags.diagnose(
823+
importLoc, diag::add_predates_concurrency_import,
824+
ctx.LangOpts.isSwiftVersionAtLeast(6),
825+
nominal->getParentModule()->getName())
826+
.fixItInsert(importLoc, "@preconcurrency ");
821827

822-
sourceFile->setImportUsedPreconcurrency(*import);
828+
sourceFile->setImportUsedPreconcurrency(*import);
829+
}
823830
}
824831
}
825832

@@ -1036,7 +1043,7 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) {
10361043
return;
10371044

10381045
// If the conformance is explicitly stated, do nothing.
1039-
if (hasExplicitSendableConformance(nominal))
1046+
if (hasExplicitSendableConformance(nominal, /*applyModuleDefault=*/false))
10401047
return;
10411048

10421049
// 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)