Skip to content

Commit c2eac8b

Browse files
authored
Merge pull request #42273 from beccadax/witness-accounts-are-only-approximate-5.7
Make overloads and witnesses permit @sendable variance + Make @preconcurrency import hide sendable variance
2 parents 4f39baf + 74706b1 commit c2eac8b

15 files changed

+362
-75
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,6 +2155,13 @@ ERROR(witness_not_accessible_proto,none,
21552155
"requirement in %select{private|fileprivate|internal|public|%error}4 protocol "
21562156
"%5",
21572157
(RequirementKind, DeclName, bool, AccessLevel, AccessLevel, Identifier))
2158+
ERROR(witness_not_as_sendable,none,
2159+
"sendability of function types in %0 %1 does not match requirement in "
2160+
"protocol %2",
2161+
(DescriptiveDeclKind, DeclName, Identifier))
2162+
NOTE(less_sendable_reqt_here,none,
2163+
"expected sendability to match requirement here",
2164+
())
21582165
ERROR(witness_not_accessible_type,none,
21592166
"%select{initializer %1|method %1|%select{|setter for }2property %1"
21602167
"|subscript%select{| setter}2}0 must be as accessible as its enclosing "
@@ -2303,6 +2310,9 @@ NOTE(ambiguous_witnesses_type,none,
23032310
"multiple matching types named %0", (Identifier))
23042311
NOTE(protocol_witness_exact_match,none,
23052312
"candidate exactly matches%0", (StringRef))
2313+
NOTE(protocol_witness_non_sendable,none,
2314+
"candidate matches except for closure sendability%0%select{; this will be "
2315+
"an error in Swift 6|}1", (StringRef, bool))
23062316
NOTE(protocol_witness_renamed,none,
23072317
"rename to %0 to satisfy this requirement%1", (DeclName, StringRef))
23082318
NOTE(protocol_witness_kind_conflict,none,
@@ -2602,6 +2612,9 @@ WARNING(generic_param_usable_from_inline_warn,none,
26022612
ERROR(override_multiple_decls_base,none,
26032613
"declaration %0 cannot override more than one superclass declaration",
26042614
(DeclName))
2615+
ERROR(override_sendability_mismatch,none,
2616+
"declaration %0 has a type with different sendability from any potential "
2617+
"overrides", (DeclName))
26052618
ERROR(override_multiple_decls_arg_mismatch,none,
26062619
"declaration %0 has different argument labels from any potential "
26072620
"overrides", (DeclName))

include/swift/AST/Types.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,9 @@ enum class TypeMatchFlags {
295295
/// to be non-escaping, but Swift currently does not.
296296
IgnoreNonEscapingForOptionalFunctionParam = 1 << 4,
297297
/// Allow compatible opaque archetypes.
298-
AllowCompatibleOpaqueTypeArchetypes = 1 << 5
298+
AllowCompatibleOpaqueTypeArchetypes = 1 << 5,
299+
/// Ignore the @Sendable attributes on functions when matching types.
300+
IgnoreFunctionSendability = 1 << 6,
299301
};
300302
using TypeMatchOptions = OptionSet<TypeMatchFlags>;
301303

lib/AST/Type.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3167,6 +3167,16 @@ static bool matchesFunctionType(CanAnyFunctionType fn1, CanAnyFunctionType fn2,
31673167
matchMode.contains(TypeMatchFlags::AllowABICompatible))) {
31683168
ext1 = ext1.withThrows(true);
31693169
}
3170+
3171+
// Removing '@Sendable' is ABI-compatible because there's nothing wrong with
3172+
// a function being sendable when it doesn't need to be.
3173+
if (!ext2.isSendable())
3174+
ext1 = ext1.withConcurrent(false);
3175+
}
3176+
3177+
if (matchMode.contains(TypeMatchFlags::IgnoreFunctionSendability)) {
3178+
ext1 = ext1.withConcurrent(false);
3179+
ext2 = ext2.withConcurrent(false);
31703180
}
31713181

31723182
// If specified, allow an escaping function parameter to override a

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 78 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -770,10 +770,17 @@ DiagnosticBehavior SendableCheckContext::defaultDiagnosticBehavior() const {
770770
return defaultSendableDiagnosticBehavior(fromDC->getASTContext().LangOpts);
771771
}
772772

773-
/// Determine whether the given nominal type that is within the current module
774-
/// has an explicit Sendable.
775-
static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
773+
/// Determine whether the given nominal type has an explicit Sendable
774+
/// conformance (regardless of its availability).
775+
static bool hasExplicitSendableConformance(NominalTypeDecl *nominal,
776+
bool applyModuleDefault = true) {
776777
ASTContext &ctx = nominal->getASTContext();
778+
auto nominalModule = nominal->getParentModule();
779+
780+
// In a concurrency-checked module, a missing conformance is equivalent to
781+
// an explicitly unavailable one. If we want to apply this rule, do so now.
782+
if (applyModuleDefault && nominalModule->isConcurrencyChecked())
783+
return true;
777784

778785
// Look for any conformance to `Sendable`.
779786
auto proto = ctx.getProtocol(KnownProtocolKind::Sendable);
@@ -782,7 +789,7 @@ static bool hasExplicitSendableConformance(NominalTypeDecl *nominal) {
782789

783790
// Look for a conformance. If it's present and not (directly) missing,
784791
// we're done.
785-
auto conformance = nominal->getParentModule()->lookupConformance(
792+
auto conformance = nominalModule->lookupConformance(
786793
nominal->getDeclaredInterfaceType(), proto, /*allowMissing=*/true);
787794
return conformance &&
788795
!(isa<BuiltinProtocolConformance>(conformance.getConcrete()) &&
@@ -826,18 +833,13 @@ static Optional<AttributedImport<ImportedModule>> findImportFor(
826833
/// nominal type.
827834
DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
828835
NominalTypeDecl *nominal) const {
829-
// Determine whether the type was explicitly non-Sendable.
830-
auto nominalModule = nominal->getParentModule();
831-
bool isExplicitlyNonSendable = nominalModule->isConcurrencyChecked() ||
832-
hasExplicitSendableConformance(nominal);
833-
834836
// Determine whether this nominal type is visible via a @preconcurrency
835837
// import.
836838
auto import = findImportFor(nominal, fromDC);
839+
auto sourceFile = fromDC->getParentSourceFile();
837840

838841
// When the type is explicitly non-Sendable...
839-
auto sourceFile = fromDC->getParentSourceFile();
840-
if (isExplicitlyNonSendable) {
842+
if (hasExplicitSendableConformance(nominal)) {
841843
// @preconcurrency imports downgrade the diagnostic to a warning in Swift 6,
842844
if (import && import->options.contains(ImportFlags::Preconcurrency)) {
843845
if (sourceFile)
@@ -857,7 +859,7 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
857859
if (sourceFile)
858860
sourceFile->setImportUsedPreconcurrency(*import);
859861

860-
return nominalModule->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
862+
return nominal->getASTContext().LangOpts.isSwiftVersionAtLeast(6)
861863
? DiagnosticBehavior::Warning
862864
: DiagnosticBehavior::Ignore;
863865
}
@@ -875,48 +877,31 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
875877
return defaultBehavior;
876878
}
877879

878-
/// Produce a diagnostic for a single instance of a non-Sendable type where
879-
/// a Sendable type is required.
880-
static bool diagnoseSingleNonSendableType(
881-
Type type, SendableCheckContext fromContext, SourceLoc loc,
882-
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
883-
880+
bool swift::diagnoseSendabilityErrorBasedOn(
881+
NominalTypeDecl *nominal, SendableCheckContext fromContext,
882+
llvm::function_ref<bool(DiagnosticBehavior)> diagnose) {
884883
auto behavior = DiagnosticBehavior::Unspecified;
885884

886-
auto module = fromContext.fromDC->getParentModule();
887-
ASTContext &ctx = module->getASTContext();
888-
auto nominal = type->getAnyNominal();
889885
if (nominal) {
890886
behavior = fromContext.diagnosticBehavior(nominal);
891887
} else {
892888
behavior = fromContext.defaultDiagnosticBehavior();
893889
}
894890

895-
bool wasSuppressed = diagnose(type, behavior);
896-
897-
if (behavior == DiagnosticBehavior::Ignore || wasSuppressed) {
898-
// Don't emit any other diagnostics.
899-
} else if (type->is<FunctionType>()) {
900-
ctx.Diags.diagnose(loc, diag::nonsendable_function_type);
901-
} else if (nominal && nominal->getParentModule() == module) {
902-
// If the nominal type is in the current module, suggest adding
903-
// `Sendable` if it might make sense. Otherwise, just complain.
904-
if (isa<StructDecl>(nominal) || isa<EnumDecl>(nominal)) {
905-
auto note = nominal->diagnose(
906-
diag::add_nominal_sendable_conformance,
907-
nominal->getDescriptiveKind(), nominal->getName());
908-
addSendableFixIt(nominal, note, /*unchecked=*/false);
909-
} else {
910-
nominal->diagnose(
911-
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
912-
nominal->getName());
913-
}
914-
} else if (nominal) {
915-
// Note which nominal type does not conform to `Sendable`.
916-
nominal->diagnose(
917-
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
918-
nominal->getName());
891+
bool wasSuppressed = diagnose(behavior);
919892

893+
bool emittedDiagnostics =
894+
behavior != DiagnosticBehavior::Ignore && !wasSuppressed;
895+
896+
// When the type is explicitly Sendable *or* explicitly non-Sendable, we
897+
// assume it has been audited and `@preconcurrency` is not recommended even
898+
// though it would actually affect the diagnostic.
899+
bool nominalIsImportedAndHasImplicitSendability =
900+
nominal &&
901+
nominal->getParentModule() != fromContext.fromDC->getParentModule() &&
902+
!hasExplicitSendableConformance(nominal);
903+
904+
if (emittedDiagnostics && nominalIsImportedAndHasImplicitSendability) {
920905
// This type was imported from another module; try to find the
921906
// corresponding import.
922907
Optional<AttributedImport<swift::ImportedModule>> import;
@@ -933,6 +918,8 @@ static bool diagnoseSingleNonSendableType(
933918
import->importLoc.isValid() && sourceFile &&
934919
!sourceFile->hasImportUsedPreconcurrency(*import)) {
935920
SourceLoc importLoc = import->importLoc;
921+
ASTContext &ctx = nominal->getASTContext();
922+
936923
ctx.Diags.diagnose(
937924
importLoc, diag::add_predates_concurrency_import,
938925
ctx.LangOpts.isSwiftVersionAtLeast(6),
@@ -946,6 +933,51 @@ static bool diagnoseSingleNonSendableType(
946933
return behavior == DiagnosticBehavior::Unspecified && !wasSuppressed;
947934
}
948935

936+
/// Produce a diagnostic for a single instance of a non-Sendable type where
937+
/// a Sendable type is required.
938+
static bool diagnoseSingleNonSendableType(
939+
Type type, SendableCheckContext fromContext, SourceLoc loc,
940+
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
941+
942+
auto module = fromContext.fromDC->getParentModule();
943+
auto nominal = type->getAnyNominal();
944+
945+
return diagnoseSendabilityErrorBasedOn(nominal, fromContext,
946+
[&](DiagnosticBehavior behavior) {
947+
bool wasSuppressed = diagnose(type, behavior);
948+
949+
// Don't emit the following notes if we didn't have any diagnostics to
950+
// attach them to.
951+
if (wasSuppressed || behavior == DiagnosticBehavior::Ignore)
952+
return true;
953+
954+
if (type->is<FunctionType>()) {
955+
module->getASTContext().Diags
956+
.diagnose(loc, diag::nonsendable_function_type);
957+
} else if (nominal && nominal->getParentModule() == module) {
958+
// If the nominal type is in the current module, suggest adding
959+
// `Sendable` if it might make sense. Otherwise, just complain.
960+
if (isa<StructDecl>(nominal) || isa<EnumDecl>(nominal)) {
961+
auto note = nominal->diagnose(
962+
diag::add_nominal_sendable_conformance,
963+
nominal->getDescriptiveKind(), nominal->getName());
964+
addSendableFixIt(nominal, note, /*unchecked=*/false);
965+
} else {
966+
nominal->diagnose(
967+
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
968+
nominal->getName());
969+
}
970+
} else if (nominal) {
971+
// Note which nominal type does not conform to `Sendable`.
972+
nominal->diagnose(
973+
diag::non_sendable_nominal, nominal->getDescriptiveKind(),
974+
nominal->getName());
975+
}
976+
977+
return false;
978+
});
979+
}
980+
949981
bool swift::diagnoseNonSendableTypes(
950982
Type type, SendableCheckContext fromContext, SourceLoc loc,
951983
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose) {
@@ -1156,7 +1188,7 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) {
11561188
return;
11571189

11581190
// If the conformance is explicitly stated, do nothing.
1159-
if (hasExplicitSendableConformance(nominal))
1191+
if (hasExplicitSendableConformance(nominal, /*applyModuleDefault=*/false))
11601192
return;
11611193

11621194
// Diagnose it.

lib/Sema/TypeCheckConcurrency.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,10 @@ struct SendableCheckContext {
313313
///
314314
/// \param diagnose Emit a diagnostic indicating that the current type
315315
/// is non-Sendable, with the suggested behavior limitation. Returns \c true
316-
/// if an error was produced.
316+
/// if it did not emit any diagnostics.
317317
///
318-
/// \returns \c true if any diagnostics were produced, \c false otherwise.
318+
/// \returns \c true if any errors were produced, \c false if no diagnostics or
319+
/// only warnings and notes were produced.
319320
bool diagnoseNonSendableTypes(
320321
Type type, SendableCheckContext fromContext, SourceLoc loc,
321322
llvm::function_ref<bool(Type, DiagnosticBehavior)> diagnose);
@@ -330,7 +331,8 @@ namespace detail {
330331
/// Diagnose any non-Sendable types that occur within the given type, using
331332
/// the given diagnostic.
332333
///
333-
/// \returns \c true if any errors were produced, \c false otherwise.
334+
/// \returns \c true if any errors were produced, \c false if no diagnostics or
335+
/// only warnings and notes were produced.
334336
template<typename ...DiagArgs>
335337
bool diagnoseNonSendableTypes(
336338
Type type, SendableCheckContext fromContext, SourceLoc loc,
@@ -349,6 +351,26 @@ bool diagnoseNonSendableTypes(
349351
});
350352
}
351353

354+
/// Diagnose this sendability error with behavior based on the import of
355+
/// \p nominal . For instance, depending on how \p nominal is imported into
356+
/// \p fromContext , the diagnostic behavior limitation may be lower or the
357+
/// compiler might emit a fix-it adding \c \@preconcurrency to the \c import .
358+
///
359+
/// \param nominal The declaration whose import we should check, or \c nullptr
360+
/// to get behavior for a non-nominal type.
361+
///
362+
/// \param fromContext The context where the error will be emitted.
363+
///
364+
/// \param diagnose Emit a diagnostic indicating a sendability problem,
365+
/// with the suggested behavior limitation. Returns \c true
366+
/// if it did \em not emit any diagnostics.
367+
///
368+
/// \returns \c true if any errors were produced, \c false if no diagnostics or
369+
/// only warnings and notes were produced.
370+
bool diagnoseSendabilityErrorBasedOn(
371+
NominalTypeDecl *nominal, SendableCheckContext fromContext,
372+
llvm::function_ref<bool(DiagnosticBehavior)> diagnose);
373+
352374
/// Given a set of custom attributes, pick out the global actor attributes
353375
/// and perform any necessary resolution and diagnostics, returning the
354376
/// global actor attribute and type it refers to (or \c None).

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 37 additions & 2 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"
@@ -518,6 +519,7 @@ static bool noteFixableMismatchedTypes(ValueDecl *decl, const ValueDecl *base) {
518519
namespace {
519520
enum class OverrideCheckingAttempt {
520521
PerfectMatch,
522+
MismatchedSendability,
521523
MismatchedOptional,
522524
MismatchedTypes,
523525
BaseName,
@@ -547,6 +549,20 @@ static void diagnoseGeneralOverrideFailure(ValueDecl *decl,
547549
diags.diagnose(decl, diag::override_multiple_decls_base,
548550
decl->getName());
549551
break;
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+
});
564+
break;
565+
}
550566
case OverrideCheckingAttempt::BaseName:
551567
diags.diagnose(decl, diag::override_multiple_decls_arg_mismatch,
552568
decl->getName());
@@ -886,6 +902,7 @@ SmallVector<OverrideMatch, 2> OverrideMatcher::match(
886902
DeclName name;
887903
switch (attempt) {
888904
case OverrideCheckingAttempt::PerfectMatch:
905+
case OverrideCheckingAttempt::MismatchedSendability:
889906
case OverrideCheckingAttempt::MismatchedOptional:
890907
case OverrideCheckingAttempt::MismatchedTypes:
891908
name = decl->getName();
@@ -976,6 +993,8 @@ SmallVector<OverrideMatch, 2> OverrideMatcher::match(
976993
matchMode |= TypeMatchFlags::AllowNonOptionalForIUOParam;
977994
matchMode |= TypeMatchFlags::IgnoreNonEscapingForOptionalFunctionParam;
978995
}
996+
if (attempt == OverrideCheckingAttempt::MismatchedSendability)
997+
matchMode |= TypeMatchFlags::IgnoreFunctionSendability;
979998

980999
auto declFnTy = getDeclComparisonType()->getAs<AnyFunctionType>();
9811000
auto parentDeclTy = getMemberTypeForComparison(parentDecl, decl);
@@ -1277,8 +1296,23 @@ bool OverrideMatcher::checkOverride(ValueDecl *baseDecl,
12771296
if (emittedMatchError)
12781297
return true;
12791298

1299+
if (attempt == OverrideCheckingAttempt::MismatchedSendability) {
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+
});
1313+
}
12801314
// Catch-all to make sure we don't silently accept something we shouldn't.
1281-
if (attempt != OverrideCheckingAttempt::PerfectMatch) {
1315+
else if (attempt != OverrideCheckingAttempt::PerfectMatch) {
12821316
OverrideMatch match{decl, /*isExact=*/false};
12831317
diagnoseGeneralOverrideFailure(decl, match, attempt);
12841318
}
@@ -1374,11 +1408,12 @@ bool swift::checkOverrides(ValueDecl *decl) {
13741408
switch (attempt) {
13751409
case OverrideCheckingAttempt::PerfectMatch:
13761410
break;
1377-
case OverrideCheckingAttempt::MismatchedOptional:
1411+
case OverrideCheckingAttempt::MismatchedSendability:
13781412
// Don't keep looking if the user didn't indicate it's an override.
13791413
if (!decl->getAttrs().hasAttribute<OverrideAttr>())
13801414
return false;
13811415
break;
1416+
case OverrideCheckingAttempt::MismatchedOptional:
13821417
case OverrideCheckingAttempt::MismatchedTypes:
13831418
break;
13841419
case OverrideCheckingAttempt::BaseName:

0 commit comments

Comments
 (0)