Skip to content

Commit 4a5f4d1

Browse files
authored
Merge pull request swiftlang#42255 from beccadax/witness-accounts-are-only-approximate-2
Make @preconcurrency import hide sendable variance
2 parents dc58361 + ecdd42f commit 4a5f4d1

11 files changed

+254
-87
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 78 additions & 46 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
}
@@ -755,48 +757,31 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
755757
return defaultBehavior;
756758
}
757759

758-
/// Produce a diagnostic for a single instance of a non-Sendable type where
759-
/// a Sendable type is required.
760-
static bool diagnoseSingleNonSendableType(
761-
Type type, SendableCheckContext fromContext, SourceLoc loc,
762-
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
763-
760+
bool swift::diagnoseSendabilityErrorBasedOn(
761+
NominalTypeDecl *nominal, SendableCheckContext fromContext,
762+
llvm::function_ref<bool(DiagnosticBehavior)> diagnose) {
764763
auto behavior = DiagnosticBehavior::Unspecified;
765764

766-
auto module = fromContext.fromDC->getParentModule();
767-
ASTContext &ctx = module->getASTContext();
768-
auto nominal = type->getAnyNominal();
769765
if (nominal) {
770766
behavior = fromContext.diagnosticBehavior(nominal);
771767
} else {
772768
behavior = fromContext.defaultDiagnosticBehavior();
773769
}
774770

775-
bool wasSuppressed = diagnose(type, behavior);
776-
777-
if (behavior == DiagnosticBehavior::Ignore || wasSuppressed) {
778-
// Don't emit any other diagnostics.
779-
} else if (type->is<FunctionType>()) {
780-
ctx.Diags.diagnose(loc, diag::nonsendable_function_type);
781-
} else if (nominal && nominal->getParentModule() == module) {
782-
// If the nominal type is in the current module, suggest adding
783-
// `Sendable` if it might make sense. Otherwise, just complain.
784-
if (isa<StructDecl>(nominal) || isa<EnumDecl>(nominal)) {
785-
auto note = nominal->diagnose(
786-
diag::add_nominal_sendable_conformance,
787-
nominal->getDescriptiveKind(), nominal->getName());
788-
addSendableFixIt(nominal, note, /*unchecked=*/false);
789-
} else {
790-
nominal->diagnose(
791-
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
792-
nominal->getName());
793-
}
794-
} else if (nominal) {
795-
// Note which nominal type does not conform to `Sendable`.
796-
nominal->diagnose(
797-
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
798-
nominal->getName());
771+
bool wasSuppressed = diagnose(behavior);
772+
773+
bool emittedDiagnostics =
774+
behavior != DiagnosticBehavior::Ignore && !wasSuppressed;
775+
776+
// When the type is explicitly Sendable *or* explicitly non-Sendable, we
777+
// assume it has been audited and `@preconcurrency` is not recommended even
778+
// though it would actually affect the diagnostic.
779+
bool nominalIsImportedAndHasImplicitSendability =
780+
nominal &&
781+
nominal->getParentModule() != fromContext.fromDC->getParentModule() &&
782+
!hasExplicitSendableConformance(nominal);
799783

784+
if (emittedDiagnostics && nominalIsImportedAndHasImplicitSendability) {
800785
// This type was imported from another module; try to find the
801786
// corresponding import.
802787
Optional<AttributedImport<swift::ImportedModule>> import;
@@ -813,6 +798,8 @@ static bool diagnoseSingleNonSendableType(
813798
import->importLoc.isValid() && sourceFile &&
814799
!sourceFile->hasImportUsedPreconcurrency(*import)) {
815800
SourceLoc importLoc = import->importLoc;
801+
ASTContext &ctx = nominal->getASTContext();
802+
816803
ctx.Diags.diagnose(
817804
importLoc, diag::add_predates_concurrency_import,
818805
ctx.LangOpts.isSwiftVersionAtLeast(6),
@@ -826,6 +813,51 @@ static bool diagnoseSingleNonSendableType(
826813
return behavior == DiagnosticBehavior::Unspecified && !wasSuppressed;
827814
}
828815

816+
/// Produce a diagnostic for a single instance of a non-Sendable type where
817+
/// a Sendable type is required.
818+
static bool diagnoseSingleNonSendableType(
819+
Type type, SendableCheckContext fromContext, SourceLoc loc,
820+
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
821+
822+
auto module = fromContext.fromDC->getParentModule();
823+
auto nominal = type->getAnyNominal();
824+
825+
return diagnoseSendabilityErrorBasedOn(nominal, fromContext,
826+
[&](DiagnosticBehavior behavior) {
827+
bool wasSuppressed = diagnose(type, behavior);
828+
829+
// Don't emit the following notes if we didn't have any diagnostics to
830+
// attach them to.
831+
if (wasSuppressed || behavior == DiagnosticBehavior::Ignore)
832+
return true;
833+
834+
if (type->is<FunctionType>()) {
835+
module->getASTContext().Diags
836+
.diagnose(loc, diag::nonsendable_function_type);
837+
} else if (nominal && nominal->getParentModule() == module) {
838+
// If the nominal type is in the current module, suggest adding
839+
// `Sendable` if it might make sense. Otherwise, just complain.
840+
if (isa<StructDecl>(nominal) || isa<EnumDecl>(nominal)) {
841+
auto note = nominal->diagnose(
842+
diag::add_nominal_sendable_conformance,
843+
nominal->getDescriptiveKind(), nominal->getName());
844+
addSendableFixIt(nominal, note, /*unchecked=*/false);
845+
} else {
846+
nominal->diagnose(
847+
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
848+
nominal->getName());
849+
}
850+
} else if (nominal) {
851+
// Note which nominal type does not conform to `Sendable`.
852+
nominal->diagnose(
853+
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
854+
nominal->getName());
855+
}
856+
857+
return false;
858+
});
859+
}
860+
829861
bool swift::diagnoseNonSendableTypes(
830862
Type type, SendableCheckContext fromContext, SourceLoc loc,
831863
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
@@ -1036,7 +1068,7 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) {
10361068
return;
10371069

10381070
// If the conformance is explicitly stated, do nothing.
1039-
if (hasExplicitSendableConformance(nominal))
1071+
if (hasExplicitSendableConformance(nominal, /*applyModuleDefault=*/false))
10401072
return;
10411073

10421074
// Diagnose it.

lib/Sema/TypeCheckConcurrency.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,10 @@ struct SendableCheckContext {
353353
///
354354
/// \param diagnose Emit a diagnostic indicating that the current type
355355
/// is non-Sendable, with the suggested behavior limitation. Returns \c true
356-
/// if an error was produced.
356+
/// if it did not emit any diagnostics.
357357
///
358-
/// \returns \c true if any diagnostics were produced, \c false otherwise.
358+
/// \returns \c true if any errors were produced, \c false if no diagnostics or
359+
/// only warnings and notes were produced.
359360
bool diagnoseNonSendableTypes(
360361
Type type, SendableCheckContext fromContext, SourceLoc loc,
361362
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose);
@@ -370,7 +371,8 @@ namespace detail {
370371
/// Diagnose any non-Sendable types that occur within the given type, using
371372
/// the given diagnostic.
372373
///
373-
/// \returns \c true if any errors were produced, \c false otherwise.
374+
/// \returns \c true if any errors were produced, \c false if no diagnostics or
375+
/// only warnings and notes were produced.
374376
template<typename ...DiagArgs>
375377
bool diagnoseNonSendableTypes(
376378
Type type, SendableCheckContext fromContext, SourceLoc loc,
@@ -389,6 +391,26 @@ bool diagnoseNonSendableTypes(
389391
});
390392
}
391393

394+
/// Diagnose this sendability error with behavior based on the import of
395+
/// \p nominal . For instance, depending on how \p nominal is imported into
396+
/// \p fromContext , the diagnostic behavior limitation may be lower or the
397+
/// compiler might emit a fix-it adding \c \@preconcurrency to the \c import .
398+
///
399+
/// \param nominal The declaration whose import we should check, or \c nullptr
400+
/// to get behavior for a non-nominal type.
401+
///
402+
/// \param fromContext The context where the error will be emitted.
403+
///
404+
/// \param diagnose Emit a diagnostic indicating a sendability problem,
405+
/// with the suggested behavior limitation. Returns \c true
406+
/// if it did \em not emit any diagnostics.
407+
///
408+
/// \returns \c true if any errors were produced, \c false if no diagnostics or
409+
/// only warnings and notes were produced.
410+
bool diagnoseSendabilityErrorBasedOn(
411+
NominalTypeDecl *nominal, SendableCheckContext fromContext,
412+
llvm::function_ref<bool(DiagnosticBehavior)> diagnose);
413+
392414
/// Given a set of custom attributes, pick out the global actor attributes
393415
/// and perform any necessary resolution and diagnostics, returning the
394416
/// global actor attribute and type it refers to (or \c None).

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//===----------------------------------------------------------------------===//
1616
#include "MiscDiagnostics.h"
1717
#include "TypeCheckAvailability.h"
18+
#include "TypeCheckConcurrency.h"
1819
#include "TypeCheckDecl.h"
1920
#include "TypeCheckObjC.h"
2021
#include "TypeChecker.h"
@@ -548,12 +549,20 @@ static void diagnoseGeneralOverrideFailure(ValueDecl *decl,
548549
diags.diagnose(decl, diag::override_multiple_decls_base,
549550
decl->getName());
550551
break;
551-
case OverrideCheckingAttempt::MismatchedSendability:
552-
// FIXME: suppress if any matches brought in via @preconcurrency import?
553-
diags.diagnose(decl, diag::override_sendability_mismatch,
554-
decl->getName())
555-
.warnUntilSwiftVersion(6);
552+
case OverrideCheckingAttempt::MismatchedSendability: {
553+
SendableCheckContext fromContext(decl->getDeclContext(),
554+
SendableCheck::Explicit);
555+
auto baseDeclClass =
556+
decl->getOverriddenDecl()->getDeclContext()->getSelfClassDecl();
557+
558+
diagnoseSendabilityErrorBasedOn(baseDeclClass, fromContext,
559+
[&](DiagnosticBehavior limit) {
560+
diags.diagnose(decl, diag::override_sendability_mismatch, decl->getName())
561+
.limitBehavior(limit);
562+
return false;
563+
});
556564
break;
565+
}
557566
case OverrideCheckingAttempt::BaseName:
558567
diags.diagnose(decl, diag::override_multiple_decls_arg_mismatch,
559568
decl->getName());
@@ -1288,11 +1297,19 @@ bool OverrideMatcher::checkOverride(ValueDecl *baseDecl,
12881297
return true;
12891298

12901299
if (attempt == OverrideCheckingAttempt::MismatchedSendability) {
1291-
// FIXME: suppress if any matches brought in via @preconcurrency import?
1292-
diags.diagnose(decl, diag::override_sendability_mismatch,
1293-
decl->getName())
1294-
.warnUntilSwiftVersion(6);
1295-
diags.diagnose(baseDecl, diag::overridden_here);
1300+
SendableCheckContext fromContext(decl->getDeclContext(),
1301+
SendableCheck::Explicit);
1302+
auto baseDeclClass = baseDecl->getDeclContext()->getSelfClassDecl();
1303+
1304+
diagnoseSendabilityErrorBasedOn(baseDeclClass, fromContext,
1305+
[&](DiagnosticBehavior limit) {
1306+
diags.diagnose(decl, diag::override_sendability_mismatch,
1307+
decl->getName())
1308+
.limitBehavior(limit);
1309+
diags.diagnose(baseDecl, diag::overridden_here)
1310+
.limitBehavior(limit);
1311+
return false;
1312+
});
12961313
}
12971314
// Catch-all to make sure we don't silently accept something we shouldn't.
12981315
else if (attempt != OverrideCheckingAttempt::PerfectMatch) {

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4154,19 +4154,32 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
41544154
requirement->getName());
41554155
});
41564156
}
4157-
if (best.Kind == MatchKind::RequiresNonSendable)
4158-
diagnoseOrDefer(requirement, getASTContext().isSwiftVersionAtLeast(6),
4159-
[this, requirement, witness]
4160-
(NormalProtocolConformance *conformance) {
4161-
auto &diags = DC->getASTContext().Diags;
4157+
if (best.Kind == MatchKind::RequiresNonSendable) {
4158+
SendableCheckContext sendFrom(witness->getDeclContext(),
4159+
SendableCheck::Explicit);
41624160

4163-
diags.diagnose(getLocForDiagnosingWitness(conformance, witness),
4164-
diag::witness_not_as_sendable,
4165-
witness->getDescriptiveKind(), witness->getName(),
4166-
conformance->getProtocol()->getName())
4167-
.warnUntilSwiftVersion(6);
4168-
diags.diagnose(requirement, diag::less_sendable_reqt_here);
4169-
});
4161+
auto behavior = sendFrom.diagnosticBehavior(Conformance->getProtocol());
4162+
if (behavior != DiagnosticBehavior::Ignore) {
4163+
bool isError = behavior < DiagnosticBehavior::Warning;
4164+
4165+
diagnoseOrDefer(requirement, isError,
4166+
[this, requirement, witness, sendFrom](
4167+
NormalProtocolConformance *conformance) {
4168+
diagnoseSendabilityErrorBasedOn(conformance->getProtocol(), sendFrom,
4169+
[&](DiagnosticBehavior limit) {
4170+
auto &diags = DC->getASTContext().Diags;
4171+
diags.diagnose(getLocForDiagnosingWitness(conformance, witness),
4172+
diag::witness_not_as_sendable,
4173+
witness->getDescriptiveKind(), witness->getName(),
4174+
conformance->getProtocol()->getName())
4175+
.limitBehavior(limit);
4176+
diags.diagnose(requirement, diag::less_sendable_reqt_here)
4177+
.limitBehavior(limit);
4178+
return false;
4179+
});
4180+
});
4181+
}
4182+
}
41704183

41714184
auto check = checkWitness(requirement, best);
41724185

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1-
public class NonStrictClass { }
2-
31
public struct NonStrictStruct { }
2+
3+
open class NonStrictClass {
4+
open func send(_ body: @Sendable () -> Void) {}
5+
open func dontSend(_ body: () -> Void) {}
6+
}
7+
8+
public protocol NonStrictProtocol {
9+
func send(_ body: @Sendable () -> Void)
10+
func dontSend(_ body: () -> Void)
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
11
public struct StrictStruct: Hashable { }
2+
3+
open class StrictClass {
4+
open func send(_ body: @Sendable () -> Void) {}
5+
open func dontSend(_ body: () -> Void) {}
6+
}
7+
8+
public protocol StrictProtocol {
9+
func send(_ body: @Sendable () -> Void)
10+
func dontSend(_ body: () -> Void)
11+
}

0 commit comments

Comments
 (0)