Skip to content

Commit 23cc638

Browse files
committed
Suggest @_predatesConcurrency import to suppress diagnostics.
When diagnosing a `Sendable` violation, suggest the use of `@_predatesConcurrency` on the corresponding import to suppress those diagnostics.
1 parent 0367213 commit 23cc638

File tree

6 files changed

+70
-33
lines changed

6 files changed

+70
-33
lines changed

Diff for: include/swift/AST/DiagnosticsSema.def

+4
Original file line numberDiff line numberDiff line change
@@ -1952,6 +1952,10 @@ NOTE(non_sendable_nominal,none,
19521952
NOTE(add_nominal_sendable_conformance,none,
19531953
"consider making %0 %1 conform to the 'Sendable' protocol",
19541954
(DescriptiveDeclKind, DeclName))
1955+
NOTE(add_predates_concurrency_import,none,
1956+
"add '@_predatesConcurrency' to %select{suppress|treat}0 "
1957+
"'Sendable'-related %select{warnings|errors}0 from module %1"
1958+
"%select{| as warnings}0", (bool, Identifier))
19551959
WARNING(public_decl_needs_sendable,none,
19561960
"public %0 %1 does not specify whether it is 'Sendable' or not",
19571961
(DescriptiveDeclKind, DeclName))

Diff for: include/swift/AST/Import.h

+17-7
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,9 @@ struct AttributedImport {
537537
/// Information about the module and access path being imported.
538538
ModuleInfo module;
539539

540+
/// The location of the 'import' keyword, for an explicit import.
541+
SourceLoc importLoc;
542+
540543
/// Flags indicating which attributes of this import are present.
541544
ImportOptions options;
542545

@@ -551,19 +554,20 @@ struct AttributedImport {
551554
/// is the source range covering the annotation.
552555
SourceRange predatesConcurrencyRange;
553556

554-
AttributedImport(ModuleInfo module, ImportOptions options = ImportOptions(),
557+
AttributedImport(ModuleInfo module, SourceLoc importLoc = SourceLoc(),
558+
ImportOptions options = ImportOptions(),
555559
StringRef filename = {}, ArrayRef<Identifier> spiGroups = {})
556-
: module(module), options(options), sourceFileArg(filename),
557-
spiGroups(spiGroups) {
560+
: module(module), importLoc(importLoc), options(options),
561+
sourceFileArg(filename), spiGroups(spiGroups) {
558562
assert(!(options.contains(ImportFlags::Exported) &&
559563
options.contains(ImportFlags::ImplementationOnly)) ||
560564
options.contains(ImportFlags::Reserved));
561565
}
562566

563567
template<class OtherModuleInfo>
564568
AttributedImport(ModuleInfo module, AttributedImport<OtherModuleInfo> other)
565-
: AttributedImport(module, other.options, other.sourceFileArg,
566-
other.spiGroups) { }
569+
: AttributedImport(module, other.importLoc, other.options,
570+
other.sourceFileArg, other.spiGroups) { }
567571

568572
friend bool operator==(const AttributedImport<ModuleInfo> &lhs,
569573
const AttributedImport<ModuleInfo> &rhs) {
@@ -713,17 +717,20 @@ struct DenseMapInfo<swift::AttributedImport<ModuleInfo>> {
713717
using ModuleInfoDMI = DenseMapInfo<ModuleInfo>;
714718
using ImportOptionsDMI = DenseMapInfo<swift::ImportOptions>;
715719
using StringRefDMI = DenseMapInfo<StringRef>;
720+
using SourceLocDMI = DenseMapInfo<swift::SourceLoc>;
716721
// We can't include spiGroups in the hash because ArrayRef<Identifier> is not
717722
// DenseMapInfo-able, but we do check that the spiGroups match in isEqual().
718723

719724
static inline AttributedImport getEmptyKey() {
720725
return AttributedImport(ModuleInfoDMI::getEmptyKey(),
726+
SourceLocDMI::getEmptyKey(),
721727
ImportOptionsDMI::getEmptyKey(),
722728
StringRefDMI::getEmptyKey(),
723729
{});
724730
}
725731
static inline AttributedImport getTombstoneKey() {
726732
return AttributedImport(ModuleInfoDMI::getTombstoneKey(),
733+
SourceLocDMI::getEmptyKey(),
727734
ImportOptionsDMI::getTombstoneKey(),
728735
StringRefDMI::getTombstoneKey(),
729736
{});
@@ -732,12 +739,15 @@ struct DenseMapInfo<swift::AttributedImport<ModuleInfo>> {
732739
return detail::combineHashValue(
733740
ModuleInfoDMI::getHashValue(import.module),
734741
detail::combineHashValue(
735-
ImportOptionsDMI::getHashValue(import.options),
736-
StringRefDMI::getHashValue(import.sourceFileArg)));
742+
SourceLocDMI::getHashValue(import.importLoc),
743+
detail::combineHashValue(
744+
ImportOptionsDMI::getHashValue(import.options),
745+
StringRefDMI::getHashValue(import.sourceFileArg))));
737746
}
738747
static bool isEqual(const AttributedImport &a,
739748
const AttributedImport &b) {
740749
return ModuleInfoDMI::isEqual(a.module, b.module) &&
750+
SourceLocDMI::isEqual(a.importLoc, b.importLoc),
741751
ImportOptionsDMI::isEqual(a.options, b.options) &&
742752
StringRefDMI::isEqual(a.sourceFileArg, b.sourceFileArg) &&
743753
a.spiGroups == b.spiGroups;

Diff for: lib/Frontend/Frontend.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,8 @@ ImplicitImportInfo CompilerInstance::getImplicitImportInfo() const {
881881
ImportPath::Builder importPath(Context->getIdentifier(moduleStr));
882882
UnloadedImportedModule import(importPath.copyTo(*Context),
883883
/*isScoped=*/false);
884-
imports.AdditionalUnloadedImports.emplace_back(import, options);
884+
imports.AdditionalUnloadedImports.emplace_back(
885+
import, SourceLoc(), options);
885886
};
886887

887888
for (auto &moduleStrAndTestable : frontendOpts.getImplicitImportModuleNames()) {

Diff for: lib/Sema/ImportResolution.cpp

+19-18
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ struct UnboundImport {
5656
/// determine the behavior expected for this import.
5757
AttributedImport<UnloadedImportedModule> import;
5858

59-
/// The source location to use when diagnosing errors for this import.
60-
SourceLoc importLoc;
61-
6259
/// If this UnboundImport directly represents an ImportDecl, contains the
6360
/// ImportDecl it represents. This should only be used for diagnostics and
6461
/// for updating the AST; if you want to read information about the import,
@@ -480,7 +477,8 @@ ModuleImplicitImportsRequest::evaluate(Evaluator &evaluator,
480477
!clangImporter->importBridgingHeader(bridgingHeaderPath, module)) {
481478
auto *headerModule = clangImporter->getImportedHeaderModule();
482479
assert(headerModule && "Didn't load bridging header?");
483-
imports.emplace_back(ImportedModule(headerModule), ImportFlags::Exported);
480+
imports.emplace_back(
481+
ImportedModule(headerModule), SourceLoc(), ImportFlags::Exported);
484482
}
485483

486484
// Implicitly import the underlying Clang half of this module if needed.
@@ -490,7 +488,7 @@ ModuleImplicitImportsRequest::evaluate(Evaluator &evaluator,
490488
ImportPath::Builder importPath(module->getName());
491489
unloadedImports.emplace_back(UnloadedImportedModule(importPath.copyTo(ctx),
492490
/*isScoped=*/false),
493-
ImportFlags::Exported);
491+
SourceLoc(), ImportFlags::Exported);
494492
}
495493

496494
return { ctx.AllocateCopy(imports), ctx.AllocateCopy(unloadedImports) };
@@ -513,7 +511,7 @@ void ImportResolver::addImplicitImports() {
513511
}
514512

515513
UnboundImport::UnboundImport(AttributedImport<UnloadedImportedModule> implicit)
516-
: import(implicit), importLoc(),
514+
: import(implicit),
517515
importOrUnderlyingModuleDecl(static_cast<ImportDecl *>(nullptr)) {}
518516

519517
//===----------------------------------------------------------------------===//
@@ -523,8 +521,8 @@ UnboundImport::UnboundImport(AttributedImport<UnloadedImportedModule> implicit)
523521
/// Create an UnboundImport for a user-written import declaration.
524522
UnboundImport::UnboundImport(ImportDecl *ID)
525523
: import(UnloadedImportedModule(ID->getImportPath(), ID->getImportKind()),
526-
{}),
527-
importLoc(ID->getLoc()), importOrUnderlyingModuleDecl(ID)
524+
ID->getStartLoc(), {}),
525+
importOrUnderlyingModuleDecl(ID)
528526
{
529527
if (ID->isExported())
530528
import.options |= ImportFlags::Exported;
@@ -571,10 +569,11 @@ bool UnboundImport::checkNotTautological(const SourceFile &SF) {
571569

572570
StringRef filename = llvm::sys::path::filename(SF.getFilename());
573571
if (filename.empty())
574-
ctx.Diags.diagnose(importLoc, diag::sema_import_current_module,
572+
ctx.Diags.diagnose(import.importLoc, diag::sema_import_current_module,
575573
modulePath.front().Item);
576574
else
577-
ctx.Diags.diagnose(importLoc, diag::sema_import_current_module_with_file,
575+
ctx.Diags.diagnose(import.importLoc,
576+
diag::sema_import_current_module_with_file,
578577
filename, modulePath.front().Item);
579578

580579
return false;
@@ -584,7 +583,7 @@ bool UnboundImport::checkModuleLoaded(ModuleDecl *M, SourceFile &SF) {
584583
if (M)
585584
return true;
586585

587-
diagnoseNoSuchModule(SF.getParentModule(), importLoc,
586+
diagnoseNoSuchModule(SF.getParentModule(), import.importLoc,
588587
import.module.getModulePath(), /*nonfatalInREPL=*/true);
589588
return false;
590589
}
@@ -963,9 +962,9 @@ UnboundImport::UnboundImport(
963962
ASTContext &ctx, const UnboundImport &base, Identifier overlayName,
964963
const AttributedImport<ImportedModule> &declaringImport,
965964
const AttributedImport<ImportedModule> &bystandingImport)
966-
: import(makeUnimportedCrossImportOverlay(ctx, overlayName, base,
967-
declaringImport), {}),
968-
importLoc(base.importLoc),
965+
: import(makeUnimportedCrossImportOverlay(
966+
ctx, overlayName, base, declaringImport),
967+
base.import.importLoc, {}),
969968
importOrUnderlyingModuleDecl(declaringImport.module.importedModule)
970969
{
971970
// A cross-import is never private or testable, and never comes from a private
@@ -1099,15 +1098,16 @@ void ImportResolver::findCrossImports(
10991098
// Find modules we need to import.
11001099
SmallVector<Identifier, 4> names;
11011100
declaringImport.module.importedModule->findDeclaredCrossImportOverlays(
1102-
bystandingImport.module.importedModule->getName(), names, I.importLoc);
1101+
bystandingImport.module.importedModule->getName(), names,
1102+
I.import.importLoc);
11031103

11041104
// If we're diagnosing cases where we cross-import in both directions, get the
11051105
// inverse list. Otherwise, leave the list empty.
11061106
SmallVector<Identifier, 4> oppositeNames;
11071107
if (shouldDiagnoseRedundantCrossImports)
11081108
bystandingImport.module.importedModule->findDeclaredCrossImportOverlays(
11091109
declaringImport.module.importedModule->getName(), oppositeNames,
1110-
I.importLoc);
1110+
I.import.importLoc);
11111111

11121112
if (ctx.Stats && !names.empty())
11131113
++ctx.Stats->getFrontendCounters().NumCrossImportsFound;
@@ -1124,13 +1124,14 @@ void ImportResolver::findCrossImports(
11241124
declaringImport, bystandingImport);
11251125

11261126
if (llvm::is_contained(oppositeNames, name))
1127-
ctx.Diags.diagnose(I.importLoc, diag::cross_imported_by_both_modules,
1127+
ctx.Diags.diagnose(I.import.importLoc,
1128+
diag::cross_imported_by_both_modules,
11281129
declaringImport.module.importedModule->getName(),
11291130
bystandingImport.module.importedModule->getName(),
11301131
name);
11311132

11321133
if (ctx.LangOpts.EnableCrossImportRemarks)
1133-
ctx.Diags.diagnose(I.importLoc, diag::cross_import_added,
1134+
ctx.Diags.diagnose(I.import.importLoc, diag::cross_import_added,
11341135
declaringImport.module.importedModule->getName(),
11351136
bystandingImport.module.importedModule->getName(),
11361137
name);

Diff for: lib/Sema/TypeCheckConcurrency.cpp

+27-6
Original file line numberDiff line numberDiff line change
@@ -756,16 +756,37 @@ static bool diagnoseSingleNonSendableType(
756756
// Don't emit any other diagnostics.
757757
} else if (type->is<FunctionType>()) {
758758
ctx.Diags.diagnose(loc, diag::nonsendable_function_type);
759-
} else if (nominal && nominal->getParentModule() == module &&
760-
(isa<StructDecl>(nominal) || isa<EnumDecl>(nominal))) {
761-
auto note = nominal->diagnose(
762-
diag::add_nominal_sendable_conformance,
763-
nominal->getDescriptiveKind(), nominal->getName());
764-
addSendableFixIt(nominal, note, /*unchecked=*/false);
759+
} else if (nominal && nominal->getParentModule() == module) {
760+
// If the nominal type is in the current module, suggest adding
761+
// `Sendable` if it might make sense. Otherwise, just complain.
762+
if (isa<StructDecl>(nominal) || isa<EnumDecl>(nominal)) {
763+
auto note = nominal->diagnose(
764+
diag::add_nominal_sendable_conformance,
765+
nominal->getDescriptiveKind(), nominal->getName());
766+
addSendableFixIt(nominal, note, /*unchecked=*/false);
767+
} else {
768+
nominal->diagnose(
769+
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
770+
nominal->getName());
771+
}
765772
} else if (nominal) {
773+
// Note which nominal type does not conform to `Sendable`.
766774
nominal->diagnose(
767775
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
768776
nominal->getName());
777+
778+
// If we can find an import in this context that makes this nominal
779+
// type visible, note that it can be `@_predatesConcurrency` import.
780+
auto import = findImportFor(nominal, fromContext.fromDC);
781+
if (import && !import->options.contains(ImportFlags::PredatesConcurrency) &&
782+
import->importLoc.isValid()) {
783+
SourceLoc importLoc = import->importLoc;
784+
ctx.Diags.diagnose(
785+
importLoc, diag::add_predates_concurrency_import,
786+
ctx.LangOpts.isSwiftVersionAtLeast(6),
787+
nominal->getParentModule()->getName())
788+
.fixItInsert(importLoc, "@_predatesConcurrency ");
789+
}
769790
}
770791

771792
return behavior == DiagnosticBehavior::Unspecified && !wasSuppressed;

Diff for: test/Concurrency/actor_isolation.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// RUN: %target-typecheck-verify-swift -I %t -disable-availability-checking -warn-concurrency
44
// REQUIRES: concurrency
55

6-
import OtherActors
6+
import OtherActors // expected-note 2{{add '@_predatesConcurrency' to suppress 'Sendable'-related warnings from module 'OtherActors'}}{{1-1=@_predatesConcurrency }}
77

88
let immutableGlobal: String = "hello"
99
var mutableGlobal: String = "can't touch this" // expected-note 5{{var declared here}}

0 commit comments

Comments
 (0)