Skip to content

Commit d131c37

Browse files
authored
Merge pull request #40679 from DougGregor/predates-concurrency-import-5.6
Support `@_predatesConcurrency` on import declarations
2 parents e800b4a + 8b04f0a commit d131c37

14 files changed

+234
-25
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/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,6 +1952,12 @@ 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+
REMARK(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))
1959+
REMARK(remove_predates_concurrency_import,none,
1960+
"'@_predatesConcurrency' attribute on module %0 is unused", (Identifier))
19551961
WARNING(public_decl_needs_sendable,none,
19561962
"public %0 %1 does not specify whether it is 'Sendable' or not",
19571963
(DescriptiveDeclKind, DeclName))

include/swift/AST/Import.h

Lines changed: 26 additions & 8 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
};
@@ -533,6 +537,9 @@ struct AttributedImport {
533537
/// Information about the module and access path being imported.
534538
ModuleInfo module;
535539

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

@@ -543,19 +550,27 @@ struct AttributedImport {
543550
/// Names of explicitly imported SPI groups.
544551
ArrayRef<Identifier> spiGroups;
545552

546-
AttributedImport(ModuleInfo module, ImportOptions options = ImportOptions(),
547-
StringRef filename = {}, ArrayRef<Identifier> spiGroups = {})
548-
: module(module), options(options), sourceFileArg(filename),
549-
spiGroups(spiGroups) {
553+
/// When the import declaration has a `@_predatesConcurrency` annotation, this
554+
/// is the source range covering the annotation.
555+
SourceRange predatesConcurrencyRange;
556+
557+
AttributedImport(ModuleInfo module, SourceLoc importLoc = SourceLoc(),
558+
ImportOptions options = ImportOptions(),
559+
StringRef filename = {}, ArrayRef<Identifier> spiGroups = {},
560+
SourceRange predatesConcurrencyRange = {})
561+
: module(module), importLoc(importLoc), options(options),
562+
sourceFileArg(filename), spiGroups(spiGroups),
563+
predatesConcurrencyRange(predatesConcurrencyRange) {
550564
assert(!(options.contains(ImportFlags::Exported) &&
551565
options.contains(ImportFlags::ImplementationOnly)) ||
552566
options.contains(ImportFlags::Reserved));
553567
}
554568

555569
template<class OtherModuleInfo>
556570
AttributedImport(ModuleInfo module, AttributedImport<OtherModuleInfo> other)
557-
: AttributedImport(module, other.options, other.sourceFileArg,
558-
other.spiGroups) { }
571+
: AttributedImport(module, other.importLoc, other.options,
572+
other.sourceFileArg, other.spiGroups,
573+
other.predatesConcurrencyRange) { }
559574

560575
friend bool operator==(const AttributedImport<ModuleInfo> &lhs,
561576
const AttributedImport<ModuleInfo> &rhs) {
@@ -705,17 +720,20 @@ struct DenseMapInfo<swift::AttributedImport<ModuleInfo>> {
705720
using ModuleInfoDMI = DenseMapInfo<ModuleInfo>;
706721
using ImportOptionsDMI = DenseMapInfo<swift::ImportOptions>;
707722
using StringRefDMI = DenseMapInfo<StringRef>;
723+
using SourceLocDMI = DenseMapInfo<swift::SourceLoc>;
708724
// We can't include spiGroups in the hash because ArrayRef<Identifier> is not
709725
// DenseMapInfo-able, but we do check that the spiGroups match in isEqual().
710726

711727
static inline AttributedImport getEmptyKey() {
712728
return AttributedImport(ModuleInfoDMI::getEmptyKey(),
729+
SourceLocDMI::getEmptyKey(),
713730
ImportOptionsDMI::getEmptyKey(),
714731
StringRefDMI::getEmptyKey(),
715732
{});
716733
}
717734
static inline AttributedImport getTombstoneKey() {
718735
return AttributedImport(ModuleInfoDMI::getTombstoneKey(),
736+
SourceLocDMI::getEmptyKey(),
719737
ImportOptionsDMI::getTombstoneKey(),
720738
StringRefDMI::getTombstoneKey(),
721739
{});
@@ -724,8 +742,8 @@ struct DenseMapInfo<swift::AttributedImport<ModuleInfo>> {
724742
return detail::combineHashValue(
725743
ModuleInfoDMI::getHashValue(import.module),
726744
detail::combineHashValue(
727-
ImportOptionsDMI::getHashValue(import.options),
728-
StringRefDMI::getHashValue(import.sourceFileArg)));
745+
ImportOptionsDMI::getHashValue(import.options),
746+
StringRefDMI::getHashValue(import.sourceFileArg)));
729747
}
730748
static bool isEqual(const AttributedImport &a,
731749
const AttributedImport &b) {

include/swift/AST/SourceFile.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ class SourceFile final : public FileUnit {
8484
/// This is \c None until it is filled in by the import resolution phase.
8585
Optional<ArrayRef<AttributedImport<ImportedModule>>> Imports;
8686

87+
/// Which imports have made use of @_predatesConcurrency.
88+
llvm::SmallDenseSet<AttributedImport<ImportedModule>>
89+
PredatesConcurrencyImportsUsed;
90+
8791
/// A unique identifier representing this file; used to mark private decls
8892
/// within the file to keep them from conflicting with other files in the
8993
/// same module.
@@ -293,6 +297,14 @@ class SourceFile final : public FileUnit {
293297
/// resolution.
294298
void setImports(ArrayRef<AttributedImport<ImportedModule>> imports);
295299

300+
/// Whether the given import has used @_predatesConcurrency.
301+
bool hasImportUsedPredatesConcurrency(
302+
AttributedImport<ImportedModule> import) const;
303+
304+
/// Note that the given import has used @_predatesConcurrency/
305+
void setImportUsedPredatesConcurrency(
306+
AttributedImport<ImportedModule> import);
307+
296308
enum ImportQueryKind {
297309
/// Return the results for testable or private imports.
298310
TestableAndPrivate,

lib/AST/Module.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,6 +2240,16 @@ SourceFile::setImports(ArrayRef<AttributedImport<ImportedModule>> imports) {
22402240
Imports = getASTContext().AllocateCopy(imports);
22412241
}
22422242

2243+
bool SourceFile::hasImportUsedPredatesConcurrency(
2244+
AttributedImport<ImportedModule> import) const {
2245+
return PredatesConcurrencyImportsUsed.count(import) != 0;
2246+
}
2247+
2248+
void SourceFile::setImportUsedPredatesConcurrency(
2249+
AttributedImport<ImportedModule> import) {
2250+
PredatesConcurrencyImportsUsed.insert(import);
2251+
}
2252+
22432253
bool HasImplementationOnlyImportsRequest::evaluate(Evaluator &evaluator,
22442254
SourceFile *SF) const {
22452255
return llvm::any_of(SF->getImports(),

lib/Frontend/Frontend.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,8 @@ ImplicitImportInfo CompilerInstance::getImplicitImportInfo() const {
844844
ImportPath::Builder importPath(Context->getIdentifier(moduleStr));
845845
UnloadedImportedModule import(importPath.copyTo(*Context),
846846
/*isScoped=*/false);
847-
imports.AdditionalUnloadedImports.emplace_back(import, options);
847+
imports.AdditionalUnloadedImports.emplace_back(
848+
import, SourceLoc(), options);
848849
};
849850

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

lib/Sema/ImportResolution.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,8 @@ ModuleImplicitImportsRequest::evaluate(Evaluator &evaluator,
480480
!clangImporter->importBridgingHeader(bridgingHeaderPath, module)) {
481481
auto *headerModule = clangImporter->getImportedHeaderModule();
482482
assert(headerModule && "Didn't load bridging header?");
483-
imports.emplace_back(ImportedModule(headerModule), ImportFlags::Exported);
483+
imports.emplace_back(
484+
ImportedModule(headerModule), SourceLoc(), ImportFlags::Exported);
484485
}
485486

486487
// Implicitly import the underlying Clang half of this module if needed.
@@ -490,7 +491,7 @@ ModuleImplicitImportsRequest::evaluate(Evaluator &evaluator,
490491
ImportPath::Builder importPath(module->getName());
491492
unloadedImports.emplace_back(UnloadedImportedModule(importPath.copyTo(ctx),
492493
/*isScoped=*/false),
493-
ImportFlags::Exported);
494+
SourceLoc(), ImportFlags::Exported);
494495
}
495496

496497
return { ctx.AllocateCopy(imports), ctx.AllocateCopy(unloadedImports) };
@@ -523,7 +524,7 @@ UnboundImport::UnboundImport(AttributedImport<UnloadedImportedModule> implicit)
523524
/// Create an UnboundImport for a user-written import declaration.
524525
UnboundImport::UnboundImport(ImportDecl *ID)
525526
: import(UnloadedImportedModule(ID->getImportPath(), ID->getImportKind()),
526-
{}),
527+
ID->getStartLoc(), {}),
527528
importLoc(ID->getLoc()), importOrUnderlyingModuleDecl(ID)
528529
{
529530
if (ID->isExported())
@@ -548,6 +549,11 @@ UnboundImport::UnboundImport(ImportDecl *ID)
548549
spiGroups.append(attrSPIs.begin(), attrSPIs.end());
549550
}
550551
import.spiGroups = ID->getASTContext().AllocateCopy(spiGroups);
552+
553+
if (auto attr = ID->getAttrs().getAttribute<PredatesConcurrencyAttr>()) {
554+
import.options |= ImportFlags::PredatesConcurrency;
555+
import.predatesConcurrencyRange = attr->getRangeWithAt();
556+
}
551557
}
552558

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

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/Strings.h"
2121
#include "swift/AST/ASTWalker.h"
2222
#include "swift/AST/GenericEnvironment.h"
23+
#include "swift/AST/ImportCache.h"
2324
#include "swift/AST/Initializer.h"
2425
#include "swift/AST/ParameterList.h"
2526
#include "swift/AST/ProtocolConformance.h"
@@ -674,6 +675,38 @@ static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
674675
conformance.getConcrete())->isMissing());
675676
}
676677

678+
/// Find the import that makes the given nominal declaration available.
679+
static Optional<AttributedImport<ImportedModule>> findImportFor(
680+
NominalTypeDecl *nominal, const DeclContext *fromDC) {
681+
// If the nominal type is from the current module, there's no import.
682+
auto nominalModule = nominal->getParentModule();
683+
if (nominalModule == fromDC->getParentModule())
684+
return None;
685+
686+
auto fromSourceFile = fromDC->getParentSourceFile();
687+
if (!fromSourceFile)
688+
return None;
689+
690+
// Look to see if the owning module was directly imported.
691+
for (const auto &import : fromSourceFile->getImports()) {
692+
if (import.module.importedModule == nominalModule)
693+
return import;
694+
}
695+
696+
// Now look for transitive imports.
697+
auto &importCache = nominal->getASTContext().getImportCache();
698+
for (const auto &import : fromSourceFile->getImports()) {
699+
auto &importSet = importCache.getImportSet(import.module.importedModule);
700+
for (const auto &transitive : importSet.getTransitiveImports()) {
701+
if (transitive.importedModule == nominalModule) {
702+
return import;
703+
}
704+
}
705+
}
706+
707+
return None;
708+
}
709+
677710
/// Determine the diagnostic behavior for a Sendable reference to the given
678711
/// nominal type.
679712
DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
@@ -686,12 +719,12 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
686719

687720
// Determine whether this nominal type is visible via a @_predatesConcurrency
688721
// import.
689-
ImportDecl *predatesConcurrencyImport = nullptr;
722+
auto import = findImportFor(nominal, fromDC);
690723

691724
// When the type is explicitly non-Sendable...
692725
if (isExplicitlyNonSendable) {
693726
// @_predatesConcurrency imports downgrade the diagnostic to a warning.
694-
if (predatesConcurrencyImport) {
727+
if (import && import->options.contains(ImportFlags::PredatesConcurrency)) {
695728
// FIXME: Note that this @_predatesConcurrency import was "used".
696729
return DiagnosticBehavior::Warning;
697730
}
@@ -701,10 +734,13 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
701734

702735
// When the type is implicitly non-Sendable...
703736

704-
// @_predatesConcurrency always suppresses the diagnostic.
705-
if (predatesConcurrencyImport) {
737+
// @_predatesConcurrency suppresses the diagnostic in Swift 5.x, and
738+
// downgrades it to a warning in Swift 6 and later.
739+
if (import && import->options.contains(ImportFlags::PredatesConcurrency)) {
706740
// FIXME: Note that this @_predatesConcurrency import was "used".
707-
return DiagnosticBehavior::Ignore;
741+
return nominalModule->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
742+
? DiagnosticBehavior::Warning
743+
: DiagnosticBehavior::Ignore;
708744
}
709745

710746
return defaultDiagnosticBehavior();
@@ -729,20 +765,61 @@ static bool diagnoseSingleNonSendableType(
729765

730766
bool wasSuppressed = diagnose(type, behavior);
731767

768+
// If this type was imported from another module, try to find the
769+
// corresponding import.
770+
Optional<AttributedImport<swift::ImportedModule>> import;
771+
SourceFile *sourceFile = fromContext.fromDC->getParentSourceFile();
772+
if (nominal && nominal->getParentModule() != module) {
773+
import = findImportFor(nominal, fromContext.fromDC);
774+
}
775+
732776
if (behavior == DiagnosticBehavior::Ignore || wasSuppressed) {
733777
// Don't emit any other diagnostics.
734778
} else if (type->is<FunctionType>()) {
735779
ctx.Diags.diagnose(loc, diag::nonsendable_function_type);
736-
} else if (nominal && nominal->getParentModule() == module &&
737-
(isa<StructDecl>(nominal) || isa<EnumDecl>(nominal))) {
738-
auto note = nominal->diagnose(
739-
diag::add_nominal_sendable_conformance,
740-
nominal->getDescriptiveKind(), nominal->getName());
741-
addSendableFixIt(nominal, note, /*unchecked=*/false);
780+
} else if (nominal && nominal->getParentModule() == module) {
781+
// If the nominal type is in the current module, suggest adding
782+
// `Sendable` if it might make sense. Otherwise, just complain.
783+
if (isa<StructDecl>(nominal) || isa<EnumDecl>(nominal)) {
784+
auto note = nominal->diagnose(
785+
diag::add_nominal_sendable_conformance,
786+
nominal->getDescriptiveKind(), nominal->getName());
787+
addSendableFixIt(nominal, note, /*unchecked=*/false);
788+
} else {
789+
nominal->diagnose(
790+
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
791+
nominal->getName());
792+
}
742793
} else if (nominal) {
794+
// Note which nominal type does not conform to `Sendable`.
743795
nominal->diagnose(
744796
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
745797
nominal->getName());
798+
799+
// If we found the import that makes this nominal type visible, remark
800+
// that it can be @_predatesConcurrency import.
801+
// Only emit this remark once per source file, because it can happen a
802+
// lot.
803+
if (import && !import->options.contains(ImportFlags::PredatesConcurrency) &&
804+
import->importLoc.isValid() && sourceFile &&
805+
!sourceFile->hasImportUsedPredatesConcurrency(*import)) {
806+
SourceLoc importLoc = import->importLoc;
807+
ctx.Diags.diagnose(
808+
importLoc, diag::add_predates_concurrency_import,
809+
ctx.LangOpts.isSwiftVersionAtLeast(6),
810+
nominal->getParentModule()->getName())
811+
.fixItInsert(importLoc, "@_predatesConcurrency ");
812+
813+
sourceFile->setImportUsedPredatesConcurrency(*import);
814+
}
815+
}
816+
817+
// If we found an import that makes this nominal type visible, and that
818+
// was a @_predatesConcurrency import, note that we have made use of the
819+
// attribute.
820+
if (import && import->options.contains(ImportFlags::PredatesConcurrency) &&
821+
sourceFile) {
822+
sourceFile->setImportUsedPredatesConcurrency(*import);
746823
}
747824

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

lib/Sema/TypeChecker.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,23 @@ void swift::performTypeChecking(SourceFile &SF) {
264264
TypeCheckSourceFileRequest{&SF}, {});
265265
}
266266

267+
/// If any of the imports in this source file was @_predatesConcurrency but
268+
/// there were no diagnostics downgraded or suppressed due to that
269+
/// @_predatesConcurrency, suggest that the attribute be removed.
270+
static void diagnoseUnnecessaryPredatesConcurrencyImports(SourceFile &sf) {
271+
ASTContext &ctx = sf.getASTContext();
272+
for (const auto &import : sf.getImports()) {
273+
if (import.options.contains(ImportFlags::PredatesConcurrency) &&
274+
import.importLoc.isValid() &&
275+
!sf.hasImportUsedPredatesConcurrency(import)) {
276+
ctx.Diags.diagnose(
277+
import.importLoc, diag::remove_predates_concurrency_import,
278+
import.module.importedModule->getName())
279+
.fixItRemove(import.predatesConcurrencyRange);
280+
}
281+
}
282+
}
283+
267284
evaluator::SideEffect
268285
TypeCheckSourceFileRequest::evaluate(Evaluator &eval, SourceFile *SF) const {
269286
assert(SF && "Source file cannot be null!");
@@ -305,6 +322,8 @@ TypeCheckSourceFileRequest::evaluate(Evaluator &eval, SourceFile *SF) const {
305322
typeCheckDelayedFunctions(*SF);
306323
}
307324

325+
diagnoseUnnecessaryPredatesConcurrencyImports(*SF);
326+
308327
// Check to see if there's any inconsistent @_implementationOnly imports.
309328
evaluateOrDefault(
310329
Ctx.evaluator,

test/ClangImporter/objc_async.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// REQUIRES: concurrency
55
import Foundation
66
import ObjCConcurrency
7+
// expected-remark@-1{{add '@_predatesConcurrency' to suppress 'Sendable'-related warnings from module 'ObjCConcurrency'}}
78

89
if #available(SwiftStdlib 5.5, *) {
910

0 commit comments

Comments
 (0)