Skip to content

Commit 243f550

Browse files
authored
Merge pull request #84777 from xedin/tilde-sendable
[AST/Sema] Implement `~Sendable` support behind an experimental feature flag
2 parents ce32846 + 9d1df1f commit 243f550

File tree

13 files changed

+237
-7
lines changed

13 files changed

+237
-7
lines changed

include/swift/AST/Decl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ namespace swift {
8585
struct ExternalSourceLocs;
8686
class CaptureListExpr;
8787
class DeclRefExpr;
88+
class InverseTypeRepr;
8889
class LiteralExpr;
8990
class BraceStmt;
9091
class DeclAttributes;
@@ -1970,6 +1971,12 @@ class InheritedTypes {
19701971
Type getResolvedType(unsigned i, TypeResolutionStage stage =
19711972
TypeResolutionStage::Interface) const;
19721973

1974+
/// If the given index corresponds to a "suppressed" entry, returns the
1975+
/// information associated with it, and `std::nullopt` otherwise.
1976+
std::optional<std::pair<Type, InverseTypeRepr *>> getAsSuppressed(
1977+
unsigned i,
1978+
TypeResolutionStage stage = TypeResolutionStage::Interface) const;
1979+
19731980
/// Returns the underlying array of inherited type entries.
19741981
///
19751982
/// NOTE: The `Type` associated with an entry may not be resolved yet.

include/swift/AST/DiagnosticsSema.def

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8943,6 +8943,24 @@ ERROR(attr_inline_always_on_accessor,none,
89438943
"'@inline(always)' on class variable accessors whose variable declaration"
89448944
" is not final are not allowed", ())
89458945

8946+
//===----------------------------------------------------------------------===//
8947+
// MARK: `~Sendable`
8948+
//===----------------------------------------------------------------------===//
8949+
ERROR(tilde_sendable_requires_feature_flag,none,
8950+
"'~Sendable' requires -enable-experimental-feature TildeSendable",
8951+
())
8952+
8953+
NOTE(sendable_conformance_is_suppressed, none,
8954+
"%kind0 explicitly suppresses conformance to 'Sendable' protocol",
8955+
(const NominalTypeDecl *))
8956+
8957+
ERROR(non_sendable_type_suppressed,none,
8958+
"cannot both conform to and suppress conformance to 'Sendable'", ())
8959+
8960+
ERROR(conformance_repression_only_on_struct_class_enum,none,
8961+
"conformance to %0 can only be suppressed on structs, classes, and enums",
8962+
(const ProtocolDecl *))
8963+
89468964
//===----------------------------------------------------------------------===//
89478965
// MARK: Swift Performance hints
89488966
//===----------------------------------------------------------------------===//

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ PROTOCOL(CodingKey)
107107
PROTOCOL(Encodable)
108108
PROTOCOL(Decodable)
109109

110-
PROTOCOL(Sendable)
110+
REPRESSIBLE_PROTOCOL(Sendable)
111111
PROTOCOL(SendableMetatype)
112112
PROTOCOL(UnsafeSendable)
113113

include/swift/AST/PrintOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,9 @@ struct PrintOptions {
406406
/// Suppress @inline(always) attribute and emit @inline(__always) instead.
407407
bool SuppressInlineAlways = false;
408408

409+
/// Suppress printing of ~Sendable in inheritance and requirement lists.
410+
bool SuppressTildeSendable = false;
411+
409412
/// Whether to print the \c{/*not inherited*/} comment on factory initializers.
410413
bool PrintFactoryInitializerComment = true;
411414

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(InlineAlways, false)
559559
/// Allow use of 'Swift' (Swift runtime version) in @available attributes
560560
EXPERIMENTAL_FEATURE(SwiftRuntimeAvailability, true)
561561

562+
/// Allow use of `~Sendable`.
563+
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(TildeSendable, false)
564+
562565
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
563566
#undef EXPERIMENTAL_FEATURE
564567
#undef UPCOMING_FEATURE

lib/AST/ASTPrinter.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3285,6 +3285,12 @@ suppressingFeatureLifetimes(PrintOptions &options,
32853285
action();
32863286
}
32873287

3288+
static void suppressingFeatureTildeSendable(PrintOptions &options,
3289+
llvm::function_ref<void()> action) {
3290+
llvm::SaveAndRestore<bool> scope(options.SuppressTildeSendable, true);
3291+
action();
3292+
}
3293+
32883294
namespace {
32893295
struct ExcludeAttrRAII {
32903296
std::vector<AnyAttrKind> &ExcludeAttrList;
@@ -3302,7 +3308,7 @@ struct ExcludeAttrRAII {
33023308
ExcludeAttrList.resize(OriginalExcludeAttrCount);
33033309
}
33043310
};
3305-
}
3311+
} // namespace
33063312

33073313
static void
33083314
suppressingFeatureCoroutineAccessors(PrintOptions &options,
@@ -8217,9 +8223,19 @@ swift::getInheritedForPrinting(
82178223
}
82188224
}
82198225

8220-
if (options.SuppressConformanceSuppression &&
8221-
inherited.getEntry(i).isSuppressed()) {
8222-
continue;
8226+
if (inherited.getEntry(i).isSuppressed()) {
8227+
// All `~<<Protocol>>` inheritances are suppressed.
8228+
if (options.SuppressConformanceSuppression)
8229+
continue;
8230+
8231+
if (options.SuppressTildeSendable) {
8232+
if (auto type = inherited.getAsSuppressed(i)->first) {
8233+
auto *sendable =
8234+
decl->getASTContext().getProtocol(KnownProtocolKind::Sendable);
8235+
if (sendable && sendable == type->getAnyNominal())
8236+
continue;
8237+
}
8238+
}
82238239
}
82248240

82258241
Results.push_back(inherited.getEntry(i));

lib/AST/Decl.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,6 +2034,20 @@ Type InheritedTypes::getResolvedType(unsigned i,
20342034
.getInheritedTypeOrNull(getASTContext());
20352035
}
20362036

2037+
std::optional<std::pair<Type, InverseTypeRepr *>>
2038+
InheritedTypes::getAsSuppressed(unsigned i, TypeResolutionStage stage) const {
2039+
ASTContext &ctx = isa<const ExtensionDecl *>(Decl)
2040+
? cast<const ExtensionDecl *>(Decl)->getASTContext()
2041+
: cast<const TypeDecl *>(Decl)->getASTContext();
2042+
auto result =
2043+
evaluateOrDefault(ctx.evaluator, InheritedTypeRequest{Decl, i, stage},
2044+
InheritedTypeResult::forDefault());
2045+
if (result != InheritedTypeResult::Kind::Suppressed)
2046+
return std::nullopt;
2047+
2048+
return result.getSuppressed();
2049+
}
2050+
20372051
ExtensionDecl::ExtensionDecl(SourceLoc extensionLoc,
20382052
TypeRepr *extendedType,
20392053
ArrayRef<InheritedEntry> inherited,

lib/AST/FeatureSet.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,26 @@ static bool usesFeatureInlineAlways(Decl *decl) {
450450

451451
UNINTERESTING_FEATURE(SwiftRuntimeAvailability)
452452

453+
static bool usesFeatureTildeSendable(Decl *decl) {
454+
auto *TD = dyn_cast<TypeDecl>(decl);
455+
if (!TD)
456+
return false;
457+
458+
return llvm::any_of(
459+
TD->getInherited().getEntries(), [&decl](const auto &entry) {
460+
if (!entry.isSuppressed())
461+
return false;
462+
463+
auto T = entry.getType();
464+
if (!T)
465+
return false;
466+
467+
auto &C = decl->getASTContext();
468+
return C.getProtocol(KnownProtocolKind::Sendable) == T->getAnyNominal();
469+
});
470+
}
471+
472+
453473
// ----------------------------------------------------------------------------
454474
// MARK: - FeatureSet
455475
// ----------------------------------------------------------------------------

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,12 +1016,15 @@ static bool diagnoseSingleNonSendableType(
10161016

10171017
if (type->is<FunctionType>()) {
10181018
ctx.Diags.diagnose(loc, diag::nonsendable_function_type);
1019+
} else if (nominal &&
1020+
nominal->suppressesConformance(KnownProtocolKind::Sendable)) {
1021+
nominal->diagnose(diag::sendable_conformance_is_suppressed, nominal);
10191022
} else if (nominal && nominal->getParentModule() == module) {
10201023
// If the nominal type is in the current module, suggest adding
10211024
// `Sendable` if it might make sense. Otherwise, just complain.
10221025
if (isa<StructDecl>(nominal) || isa<EnumDecl>(nominal)) {
1023-
auto note = nominal->diagnose(
1024-
diag::add_nominal_sendable_conformance, nominal);
1026+
auto note =
1027+
nominal->diagnose(diag::add_nominal_sendable_conformance, nominal);
10251028
addSendableFixIt(nominal, note, /*unchecked=*/false);
10261029
} else {
10271030
nominal->diagnose(diag::non_sendable_nominal, nominal);
@@ -1302,6 +1305,9 @@ inferSendableFromInstanceStorage(NominalTypeDecl *nominal,
13021305
}
13031306
}
13041307

1308+
if (nominal->suppressesConformance(KnownProtocolKind::Sendable))
1309+
return std::nullopt;
1310+
13051311
class Visitor : public StorageVisitor {
13061312
public:
13071313
NominalTypeDecl *nominal;
@@ -1395,6 +1401,10 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) {
13951401
/*treatUsableFromInlineAsPublic=*/true).isPublic())
13961402
return;
13971403

1404+
// If `Sendable` conformance is explicitly suppressed, do nothing.
1405+
if (nominal->suppressesConformance(KnownProtocolKind::Sendable))
1406+
return;
1407+
13981408
// If the conformance is explicitly stated, do nothing.
13991409
if (hasExplicitSendableConformance(nominal, /*applyModuleDefault=*/false))
14001410
return;
@@ -7173,6 +7183,14 @@ static bool checkSendableInstanceStorage(
71737183
}
71747184
}
71757185

7186+
if (nominal->suppressesConformance(KnownProtocolKind::Sendable)) {
7187+
auto *conformanceDecl = dc->getAsDecl() ? dc->getAsDecl() : nominal;
7188+
if (!isImplicitSendableCheck(check)) {
7189+
conformanceDecl->diagnose(diag::non_sendable_type_suppressed);
7190+
}
7191+
return true;
7192+
}
7193+
71767194
// Stored properties of structs and classes must have
71777195
// Sendable-conforming types.
71787196
class Visitor: public StorageVisitor {
@@ -7499,6 +7517,10 @@ ProtocolConformance *swift::deriveImplicitSendableConformance(
74997517
if (isa<ProtocolDecl>(nominal))
75007518
return nullptr;
75017519

7520+
// An explicit use `~Sendable` suppresses the conformance.
7521+
if (nominal->suppressesConformance(KnownProtocolKind::Sendable))
7522+
return nullptr;
7523+
75027524
// Actor types are always Sendable; they don't get it via this path.
75037525
auto classDecl = dyn_cast<ClassDecl>(nominal);
75047526
if (classDecl && classDecl->isActor())

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,27 @@ class CheckRepressions {
123123
ctx.getProtocol(*kp));
124124
return Type();
125125
}
126+
127+
if (rpk == RepressibleProtocolKind::Sendable) {
128+
if (!ctx.LangOpts.hasFeature(Feature::TildeSendable)) {
129+
diagnoseInvalid(repr, repr.getLoc(),
130+
diag::tilde_sendable_requires_feature_flag);
131+
return Type();
132+
}
133+
}
134+
135+
if (auto *TD = dyn_cast<const TypeDecl *>(decl)) {
136+
if (!(isa<StructDecl>(TD) || isa<ClassDecl>(TD) || isa<EnumDecl>(TD))) {
137+
diagnoseInvalid(repr, repr.getLoc(),
138+
diag::conformance_repression_only_on_struct_class_enum,
139+
ctx.getProtocol(*kp))
140+
// Downgrade to a warning for `~BitwiseCopyable` because it was accepted
141+
// in some incorrect positions before.
142+
.warnUntilFutureSwiftVersionIf(kp == KnownProtocolKind::BitwiseCopyable);
143+
return Type();
144+
}
145+
}
146+
126147
if (auto *extension = dyn_cast<const ExtensionDecl *>(decl)) {
127148
diagnoseInvalid(repr, extension,
128149
diag::suppress_inferrable_protocol_extension,

0 commit comments

Comments
 (0)