Skip to content

Commit a585c5c

Browse files
committed
Implement predates-concurrency imports for Sendable diagnostic downgrades
(cherry picked from commit 0367213)
1 parent b59cd0d commit a585c5c

File tree

6 files changed

+76
-6
lines changed

6 files changed

+76
-6
lines changed

include/swift/AST/Attr.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ SIMPLE_DECL_ATTR(_noAllocation, NoAllocation,
690690

691691
SIMPLE_DECL_ATTR(_predatesConcurrency, PredatesConcurrency,
692692
OnFunc | OnConstructor | OnProtocol | OnGenericType | OnVar | OnSubscript |
693-
OnEnumElement | UserInaccessible |
693+
OnEnumElement | OnImport | UserInaccessible |
694694
ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
695695
125)
696696

include/swift/AST/Import.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ enum class ImportFlags {
8080
/// implementation detail of this file.
8181
SPIAccessControl = 0x10,
8282

83+
/// The module is imported assuming that the module itself predates
84+
/// concurrency.
85+
PredatesConcurrency = 0x20,
86+
8387
/// Used for DenseMap.
8488
Reserved = 0x80
8589
};
@@ -543,6 +547,10 @@ struct AttributedImport {
543547
/// Names of explicitly imported SPI groups.
544548
ArrayRef<Identifier> spiGroups;
545549

550+
/// When the import declaration has a `@_predatesConcurrency` annotation, this
551+
/// is the source range covering the annotation.
552+
SourceRange predatesConcurrencyRange;
553+
546554
AttributedImport(ModuleInfo module, ImportOptions options = ImportOptions(),
547555
StringRef filename = {}, ArrayRef<Identifier> spiGroups = {})
548556
: module(module), options(options), sourceFileArg(filename),

lib/Sema/ImportResolution.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,11 @@ UnboundImport::UnboundImport(ImportDecl *ID)
548548
spiGroups.append(attrSPIs.begin(), attrSPIs.end());
549549
}
550550
import.spiGroups = ID->getASTContext().AllocateCopy(spiGroups);
551+
552+
if (auto attr = ID->getAttrs().getAttribute<PredatesConcurrencyAttr>()) {
553+
import.options |= ImportFlags::PredatesConcurrency;
554+
import.predatesConcurrencyRange = attr->getRangeWithAt();
555+
}
551556
}
552557

553558
bool UnboundImport::checkNotTautological(const SourceFile &SF) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,26 @@ static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
674674
conformance.getConcrete())->isMissing());
675675
}
676676

677+
/// Find the import that makes the given nominal declaration available.
678+
static Optional<AttributedImport<ImportedModule>> findImportFor(
679+
NominalTypeDecl *nominal, const DeclContext *fromDC) {
680+
// If the nominal type is from the current module, there's no import.
681+
auto nominalModule = nominal->getParentModule();
682+
if (nominalModule == fromDC->getParentModule())
683+
return None;
684+
685+
auto fromSourceFile = fromDC->getParentSourceFile();
686+
if (!fromSourceFile)
687+
return None;
688+
689+
for (const auto &import : fromSourceFile->getImports()) {
690+
if (import.module.importedModule == nominalModule)
691+
return import;
692+
}
693+
694+
return None;
695+
}
696+
677697
/// Determine the diagnostic behavior for a Sendable reference to the given
678698
/// nominal type.
679699
DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
@@ -686,12 +706,12 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
686706

687707
// Determine whether this nominal type is visible via a @_predatesConcurrency
688708
// import.
689-
ImportDecl *predatesConcurrencyImport = nullptr;
709+
auto import = findImportFor(nominal, fromDC);
690710

691711
// When the type is explicitly non-Sendable...
692712
if (isExplicitlyNonSendable) {
693713
// @_predatesConcurrency imports downgrade the diagnostic to a warning.
694-
if (predatesConcurrencyImport) {
714+
if (import && import->options.contains(ImportFlags::PredatesConcurrency)) {
695715
// FIXME: Note that this @_predatesConcurrency import was "used".
696716
return DiagnosticBehavior::Warning;
697717
}
@@ -701,10 +721,13 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
701721

702722
// When the type is implicitly non-Sendable...
703723

704-
// @_predatesConcurrency always suppresses the diagnostic.
705-
if (predatesConcurrencyImport) {
724+
// @_predatesConcurrency suppresses the diagnostic in Swift 5.x, and
725+
// downgrades it to a warning in Swift 6 and later.
726+
if (import && import->options.contains(ImportFlags::PredatesConcurrency)) {
706727
// FIXME: Note that this @_predatesConcurrency import was "used".
707-
return DiagnosticBehavior::Ignore;
728+
return nominalModule->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
729+
? DiagnosticBehavior::Warning
730+
: DiagnosticBehavior::Ignore;
708731
}
709732

710733
return defaultDiagnosticBehavior();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -warn-concurrency %S/Inputs/StrictModule.swift
3+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
4+
5+
// RUN: %target-typecheck-verify-swift -typecheck -I %t %s
6+
7+
@_predatesConcurrency import NonStrictModule
8+
@_predatesConcurrency import StrictModule
9+
10+
func acceptSendable<T: Sendable>(_: T) { }
11+
12+
@available(SwiftStdlib 5.1, *)
13+
func test(ss: StrictStruct, ns: NonStrictClass) async {
14+
acceptSendable(ss) // expected-warning{{type 'StrictStruct' does not conform to the 'Sendable' protocol}}
15+
acceptSendable(ns) // silence issue entirely
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/StrictModule.swiftmodule -module-name StrictModule -warn-concurrency %S/Inputs/StrictModule.swift
3+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/NonStrictModule.swiftmodule -module-name NonStrictModule %S/Inputs/NonStrictModule.swift
4+
5+
// RUN: %target-typecheck-verify-swift -typecheck -swift-version 6 -I %t %s
6+
7+
// REQUIRES: asserts
8+
9+
@_predatesConcurrency import NonStrictModule
10+
@_predatesConcurrency import StrictModule
11+
12+
func acceptSendable<T: Sendable>(_: T) { }
13+
14+
@available(SwiftStdlib 5.1, *)
15+
func test(ss: StrictStruct, ns: NonStrictClass) {
16+
acceptSendable(ss) // expected-warning{{type 'StrictStruct' does not conform to the 'Sendable' protocol}}
17+
acceptSendable(ns) // expected-warning{{type 'NonStrictClass' does not conform to the 'Sendable' protocol}}
18+
}

0 commit comments

Comments
 (0)