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.
679712DiagnosticBehavior 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;
0 commit comments