Skip to content

[Concurrency] Fix SendableMetatype conformance failures to behave l… #82004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2794,9 +2794,9 @@ NOTE(non_sendable_nominal,none,
NOTE(add_nominal_sendable_conformance,none,
"consider making %kind0 conform to the 'Sendable' protocol",
(const ValueDecl *))
NOTE(add_generic_parameter_sendable_conformance,none,
"consider making generic parameter %0 conform to the 'Sendable' protocol",
(Type))
NOTE(add_generic_parameter_conformance,none,
"consider making generic parameter %0 conform to the %1 protocol",
(Type, ProtocolDecl *))
WARNING(add_predates_concurrency_import,none,
"add '@preconcurrency' to %select{suppress|treat}0 "
"'Sendable'-related %select{warnings|errors}0 from module %1"
Expand Down Expand Up @@ -5941,6 +5941,8 @@ ERROR(concurrent_value_inherit,none,
(bool, DeclName))
ERROR(non_sendable_type,none,
"type %0 does not conform to the 'Sendable' protocol", (Type))
GROUPED_ERROR(non_sendable_metatype_type,SendableMetatypes,none,
"type %0 does not conform to the 'SendableMetatype' protocol", (Type))
ERROR(sendable_raw_storage,none,
"struct %0 with '@_rawLayout' does not conform to the 'Sendable' "
"protocol", (DeclName))
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/ConformanceLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ static bool shouldCreateMissingConformances(Type type, ProtocolDecl *proto) {
return true;
}

// SendableMetatype behaves similarly to Sendable.
if (proto->isSpecificProtocol(KnownProtocolKind::SendableMetatype))
return true;

return false;
}

Expand Down
11 changes: 9 additions & 2 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3320,8 +3320,15 @@ void swift::diagnoseTypeAvailability(const TypeRepr *TR, Type T, SourceLoc loc,
static void diagnoseMissingConformance(
SourceLoc loc, Type type, ProtocolDecl *proto, const DeclContext *fromDC,
bool preconcurrency) {
assert(proto->isSpecificProtocol(KnownProtocolKind::Sendable));
diagnoseMissingSendableConformance(loc, type, fromDC, preconcurrency);
assert(proto->isSpecificProtocol(KnownProtocolKind::Sendable) ||
proto->isSpecificProtocol(KnownProtocolKind::SendableMetatype));

if (proto->isSpecificProtocol(KnownProtocolKind::Sendable))
diagnoseMissingSendableConformance(loc, type, fromDC, preconcurrency);

if (proto->isSpecificProtocol(KnownProtocolKind::SendableMetatype))
diagnoseMissingSendableMetatypeConformance(loc, type, fromDC,
preconcurrency);
}

bool
Expand Down
53 changes: 46 additions & 7 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -764,18 +764,30 @@ static void addSendableFixIt(
}
}

/// Add Fix-It text for the given generic param declaration type to adopt
/// Sendable.
static void addSendableFixIt(const GenericTypeParamDecl *genericArgument,
InFlightDiagnostic &diag) {
static void addProtocolFixIt(const GenericTypeParamDecl *genericArgument,
InFlightDiagnostic &diag, StringRef protocolName) {
if (genericArgument->getInherited().empty()) {
auto fixItLoc = genericArgument->getLoc();
diag.fixItInsertAfter(fixItLoc, ": Sendable");
diag.fixItInsertAfter(fixItLoc, llvm::Twine(": ", protocolName).str());
} else {
auto fixItLoc = genericArgument->getInherited().getEndLoc();
diag.fixItInsertAfter(fixItLoc, " & Sendable");
diag.fixItInsertAfter(fixItLoc, llvm::Twine(" & ", protocolName).str());
}
}
/// Add Fix-It text for the given generic param declaration type to adopt
/// Sendable.
static void addSendableFixIt(const GenericTypeParamDecl *genericArgument,
InFlightDiagnostic &diag) {
addProtocolFixIt(genericArgument, diag,
getProtocolName(KnownProtocolKind::Sendable));
}

static void
addSendableMetatypeFixIt(const GenericTypeParamDecl *genericArgument,
InFlightDiagnostic &diag) {
addProtocolFixIt(genericArgument, diag,
getProtocolName(KnownProtocolKind::SendableMetatype));
}

static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc) {
return contextRequiresStrictConcurrencyChecking(dc, [](const AbstractClosureExpr *) {
Expand Down Expand Up @@ -1025,7 +1037,8 @@ static bool diagnoseSingleNonSendableType(
if (genericParamTypeDecl &&
genericParamTypeDecl->getModuleContext() == module) {
auto diag = genericParamTypeDecl->diagnose(
diag::add_generic_parameter_sendable_conformance, type);
diag::add_generic_parameter_conformance, type,
ctx.getProtocol(KnownProtocolKind::Sendable));
addSendableFixIt(genericParamTypeDecl, diag);
}
}
Expand Down Expand Up @@ -1242,6 +1255,32 @@ void swift::diagnoseMissingSendableConformance(
loc, diag::non_sendable_type);
}

void swift::diagnoseMissingSendableMetatypeConformance(
SourceLoc loc, Type type, const DeclContext *fromDC, bool preconcurrency) {
SendableCheckContext sendableContext(fromDC, preconcurrency);
DiagnosticBehavior behavior =
sendableContext.implicitSendableDiagnosticBehavior();

auto &ctx = type->getASTContext();
ctx.Diags.diagnose(loc, diag::non_sendable_metatype_type, type)
.limitBehaviorWithPreconcurrency(behavior, preconcurrency);

if (auto genericArchetype = type->getAs<ArchetypeType>()) {
auto interfaceType = genericArchetype->getInterfaceType();
if (auto genericParamType = interfaceType->getAs<GenericTypeParamType>()) {
auto module = fromDC->getParentModule();
auto *genericParamTypeDecl = genericParamType->getDecl();
if (genericParamTypeDecl &&
genericParamTypeDecl->getModuleContext() == module) {
auto diag = genericParamTypeDecl->diagnose(
diag::add_generic_parameter_conformance, type,
ctx.getProtocol(KnownProtocolKind::SendableMetatype));
addSendableMetatypeFixIt(genericParamTypeDecl, diag);
}
}
}
}

namespace {
/// Infer Sendable from the instance storage of the given nominal type.
/// \returns \c std::nullopt if there is no way to make the type \c Sendable,
Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ bool diagnoseNonSendableTypesInReference(
void diagnoseMissingSendableConformance(
SourceLoc loc, Type type, const DeclContext *fromDC, bool preconcurrency);

/// Produce a diagnostic for a missing conformance to SendableMetatype
void diagnoseMissingSendableMetatypeConformance(SourceLoc loc, Type type,
const DeclContext *fromDC,
bool preconcurrency);

/// If the given nominal type is public and does not explicitly
/// state whether it conforms to Sendable, provide a diagnostic.
void diagnoseMissingExplicitSendable(NominalTypeDecl *nominal);
Expand Down
13 changes: 13 additions & 0 deletions test/Concurrency/predates_concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,16 @@ do {
}
}
}

func testSendableMetatypeDowngrades() {
@preconcurrency
func acceptsSendableMetatype<T: SendableMetatype>(_: T.Type) {}
func acceptsSendableMetatypeStrict<T: SendableMetatype>(_: T.Type) {}

func test<T>(t: T.Type) { // expected-complete-tns-note 2 {{consider making generic parameter 'T' conform to the 'SendableMetatype' protocol}} {{14-14=: SendableMetatype}}
acceptsSendableMetatype(t)
// expected-complete-tns-warning@-1 {{type 'T' does not conform to the 'SendableMetatype' protocol}}
acceptsSendableMetatypeStrict(t)
// expected-complete-tns-warning@-1 {{type 'T' does not conform to the 'SendableMetatype' protocol}}
}
}
15 changes: 15 additions & 0 deletions test/Concurrency/predates_concurrency_swift6.swift
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,18 @@ do {
case test(NS) // expected-warning {{associated value 'test' of 'Sendable'-conforming enum 'E' has non-Sendable type 'NS'}}
}
}

func testSendableMetatypeDowngrades() {
@preconcurrency
func acceptsSendableMetatype<T: SendableMetatype>(_: T.Type) {}
func acceptsSendableMetatypeStrict<T: SendableMetatype>(_: T.Type) {}

func testWarning<T>(t: T.Type) { // expected-note 2 {{consider making generic parameter 'T' conform to the 'SendableMetatype' protocol}} {{21-21=: SendableMetatype}}
acceptsSendableMetatype(t) // expected-warning {{type 'T' does not conform to the 'SendableMetatype' protocol}}
acceptsSendableMetatypeStrict(t) // expected-error {{type 'T' does not conform to the 'SendableMetatype' protocol}}
}

func testOK<T: P>(t: T.Type) {
acceptsSendableMetatype(t) // Ok (because P is `Sendable`
}
}
15 changes: 15 additions & 0 deletions test/Concurrency/sendable_metatype.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,18 @@ nonisolated func passMetaSmuggledAnyFromExistential(_ pqT: (P & Q).Type) {
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
}
}


func testSendableMetatypeDowngrades() {
@preconcurrency
func acceptsSendableMetatype<T: SendableMetatype>(_: T.Type) {
}

func testWarning<T>(t: T.Type) { // expected-note {{consider making generic parameter 'T' conform to the 'SendableMetatype' protocol}} {{21-21=: SendableMetatype}}
acceptsSendableMetatype(t) // expected-warning {{type 'T' does not conform to the 'SendableMetatype' protocol}}
}

func testWarning<T: P>(t: T.Type) { // expected-note {{consider making generic parameter 'T' conform to the 'SendableMetatype' protocol}} {{24-24= & SendableMetatype}}
acceptsSendableMetatype(t) // expected-warning {{type 'T' does not conform to the 'SendableMetatype' protocol}}
}
}