Skip to content

Commit 4cb4a0b

Browse files
authored
Merge pull request #80810 from DougGregor/enable-se-0470-6.2
[SE-0470] Enable isolated conformances by default
2 parents 928e401 + 214ba6d commit 4cb4a0b

40 files changed

+480
-201
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,34 @@
3232
strategy, given that the code would eventually become ambiguous anyways when
3333
the deployment target is raised.
3434

35+
* [SE-0470][]:
36+
A protocol conformance can be isolated to a specific global actor, meaning that the conformance can only be used by code running on that actor. Isolated conformances are expressed by specifying the global actor on the conformance itself:
37+
38+
```swift
39+
protocol P {
40+
func f()
41+
}
42+
43+
@MainActor
44+
class MyType: @MainActor P {
45+
/*@MainActor*/ func f() {
46+
// must be called on the main actor
47+
}
48+
}
49+
```
50+
51+
Swift will produce diagnostics if the conformance is directly accessed in code that isn't guaranteed to execute in the same global actor. For example:
52+
53+
```swift
54+
func acceptP<T: P>(_ value: T) { }
55+
56+
/*nonisolated*/ func useIsolatedConformance(myType: MyType) {
57+
acceptP(myType) // error: main actor-isolated conformance of 'MyType' to 'P' cannot be used in nonisolated context
58+
}
59+
```
60+
61+
To address such issues, only use an isolated conformance from code that executes on the same global actor.
62+
3563
* [SE-0419][]:
3664
Introduced the new `Runtime` module, which contains a public API that can
3765
generate backtraces, presently supported on macOS and Linux. Capturing a
@@ -10759,6 +10787,7 @@ using the `.dynamicType` member to retrieve the type of an expression should mig
1075910787
[SE-0442]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0442-allow-taskgroup-childtaskresult-type-to-be-inferred.md
1076010788
[SE-0444]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md
1076110789
[SE-0458]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md
10790+
[SE-0470]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0470-isolated-conformances.md
1076210791
[#64927]: <https://github.com/apple/swift/issues/64927>
1076310792
[#42697]: <https://github.com/apple/swift/issues/42697>
1076410793
[#42728]: <https://github.com/apple/swift/issues/42728>

include/swift/AST/ASTContextGlobalCache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct WitnessIsolationError {
5555
/// Describes an isolation error involving an associated conformance.
5656
struct AssociatedConformanceIsolationError {
5757
ProtocolConformance *isolatedConformance;
58+
DiagnosticBehavior behavior = DiagnosticBehavior::Unspecified;
5859

5960
/// Diagnose this associated conformance isolation error.
6061
void diagnose(const NormalProtocolConformance *conformance) const;

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8486,10 +8486,6 @@ ERROR(attr_abi_failable_mismatch,none,
84868486
//===----------------------------------------------------------------------===//
84878487
// MARK: Isolated conformances
84888488
//===----------------------------------------------------------------------===//
8489-
GROUPED_ERROR(isolated_conformance_experimental_feature,IsolatedConformances,
8490-
none,
8491-
"isolated conformances require experimental feature "
8492-
" 'IsolatedConformances'", ())
84938489
NOTE(note_isolate_conformance_to_global_actor,none,
84948490
"isolate this conformance to the %select{global actor %0|main actor}1 "
84958491
"with '@%2'", (Type, bool, StringRef))

include/swift/AST/ExistentialLayout.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ struct ExistentialLayout {
105105
/// calling this on a temporary is likely to be incorrect.
106106
ArrayRef<ProtocolDecl*> getProtocols() const && = delete;
107107

108+
/// Determine whether this refers to any non-marker protocols.
109+
bool containsNonMarkerProtocols() const;
110+
108111
ArrayRef<ParameterizedProtocolType *> getParameterizedProtocols() const & {
109112
return parameterized;
110113
}

include/swift/AST/GenericSignature.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,19 @@ class alignas(1 << TypeAlignInBits) GenericSignatureImpl final
376376
/// the given protocol.
377377
bool requiresProtocol(Type type, ProtocolDecl *proto) const;
378378

379+
/// Determine whether a conformance requirement of the given type to the
380+
/// given protocol prohibits the use of an isolated conformance.
381+
///
382+
/// The use of an isolated conformance to satisfy a requirement T: P is
383+
/// prohibited when T is a type parameter and T, or some type that can be
384+
/// used to reach T, also conforms to Sendable or SendableMetatype. In that
385+
/// case, the conforming type and the protocol (Sendable or SendableMetatype)
386+
/// is returned.
387+
///
388+
/// If there is no such requirement, returns std::nullopt.
389+
std::optional<std::pair<Type, ProtocolDecl *>>
390+
prohibitsIsolatedConformance(Type type) const;
391+
379392
/// Determine whether the given dependent type is equal to a concrete type.
380393
bool isConcreteType(Type type) const;
381394

include/swift/Basic/Features.def

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ SUPPRESSIBLE_LANGUAGE_FEATURE(MemorySafetyAttributes, 458, "@unsafe attribute")
254254
LANGUAGE_FEATURE(ValueGenerics, 452, "Value generics feature (integer generics)")
255255
LANGUAGE_FEATURE(RawIdentifiers, 451, "Raw identifiers")
256256
LANGUAGE_FEATURE(SendableCompletionHandlers, 463, "Objective-C completion handler parameters are imported as @Sendable")
257+
LANGUAGE_FEATURE(IsolatedConformances, 470, "Global-actor isolated conformances")
257258

258259
// Swift 6
259260
UPCOMING_FEATURE(ConciseMagicFile, 274, 6)
@@ -276,6 +277,7 @@ UPCOMING_FEATURE(GlobalActorIsolatedTypesUsability, 0434, 6)
276277
ADOPTABLE_UPCOMING_FEATURE(ExistentialAny, 335, 7)
277278
UPCOMING_FEATURE(InternalImportsByDefault, 409, 7)
278279
UPCOMING_FEATURE(MemberImportVisibility, 444, 7)
280+
UPCOMING_FEATURE(InferIsolatedConformances, 470, 7)
279281

280282
// Optional language features / modes
281283

@@ -500,12 +502,6 @@ EXPERIMENTAL_FEATURE(CustomAvailability, true)
500502
/// is resilient or not.
501503
EXPERIMENTAL_FEATURE(ExtensibleEnums, true)
502504

503-
/// Allow isolated conformances.
504-
EXPERIMENTAL_FEATURE(IsolatedConformances, true)
505-
506-
/// Infer conformance isolation on global-actor-conforming types.
507-
EXPERIMENTAL_FEATURE(InferIsolatedConformances, true)
508-
509505
/// Allow SwiftSettings
510506
EXPERIMENTAL_FEATURE(SwiftSettings, false)
511507

lib/AST/ConformanceLookup.cpp

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ swift::collectExistentialConformances(CanType fromType,
6666
return fromType->getASTContext().AllocateCopy(conformances);
6767
}
6868

69+
static bool containsNonMarkerProtocols(ArrayRef<ProtocolDecl *> protocols) {
70+
for (auto proto : protocols) {
71+
if (!proto->isMarkerProtocol())
72+
return true;
73+
}
74+
75+
return false;
76+
}
77+
6978
ProtocolConformanceRef
7079
swift::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
7180
ASTContext &ctx = protocol->getASTContext();
@@ -146,6 +155,12 @@ swift::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
146155
if (auto conformance = lookupSuperclassConformance(layout.getSuperclass()))
147156
return conformance;
148157

158+
// If the protocol is SendableMetatype, and there are no non-marker protocol
159+
// requirements, allow it via self-conformance.
160+
if (protocol->isSpecificProtocol(KnownProtocolKind::SendableMetatype) &&
161+
!layout.containsNonMarkerProtocols())
162+
return ProtocolConformanceRef(ctx.getSelfConformance(protocol));
163+
149164
// We didn't find our protocol in the existential's list; it doesn't
150165
// conform.
151166
return ProtocolConformanceRef::forInvalid();
@@ -377,6 +392,48 @@ static ProtocolConformanceRef getBuiltinFunctionTypeConformance(
377392
return ProtocolConformanceRef::forMissingOrInvalid(type, protocol);
378393
}
379394

395+
/// Given the instance type of a metatype, determine whether the metatype is
396+
/// Sendable.
397+
///
398+
// Metatypes are generally Sendable, but with isolated conformances we
399+
// cannot assume that metatypes based on type parameters are Sendable.
400+
// Therefore, check for conformance to SendableMetatype.
401+
static bool metatypeWithInstanceTypeIsSendable(Type instanceType) {
402+
ASTContext &ctx = instanceType->getASTContext();
403+
404+
// If we don't have the SendableMetatype protocol at all, just assume all
405+
// metatypes are Sendable.
406+
auto sendableMetatypeProto =
407+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
408+
if (!sendableMetatypeProto)
409+
return true;
410+
411+
// If the instance type is a type parameter, it is not necessarily
412+
// SendableMetatype. There will need to be a SendableMetatype requirement,
413+
// but we do not have the generic environment to check that.
414+
if (instanceType->isTypeParameter())
415+
return false;
416+
417+
// If the instance type conforms to SendableMetatype, then its
418+
// metatype is Sendable.
419+
auto instanceConformance = lookupConformance(
420+
instanceType, sendableMetatypeProto);
421+
if (!instanceConformance.isInvalid() &&
422+
!instanceConformance.hasMissingConformance())
423+
return true;
424+
425+
// If this is an archetype that is non-SendableMetatype, but there are no
426+
// non-marker protocol requirements that could carry conformances, treat
427+
// the metatype as Sendable.
428+
if (auto archetype = instanceType->getAs<ArchetypeType>()) {
429+
if (!containsNonMarkerProtocols(archetype->getConformsTo()))
430+
return true;
431+
}
432+
433+
// The instance type is non-Sendable.
434+
return false;
435+
}
436+
380437
/// Synthesize a builtin metatype type conformance to the given protocol, if
381438
/// appropriate.
382439
static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
@@ -387,31 +444,9 @@ static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
387444
if (auto kp = protocol->getKnownProtocolKind()) {
388445
switch (*kp) {
389446
case KnownProtocolKind::Sendable:
390-
// Metatypes are generally Sendable, but with isolated conformances we
391-
// cannot assume that metatypes based on type parameters are Sendable.
392-
// Therefore, check for conformance to SendableMetatype.
393-
if (ctx.LangOpts.hasFeature(Feature::IsolatedConformances)) {
394-
auto sendableMetatypeProto =
395-
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
396-
if (sendableMetatypeProto) {
397-
Type instanceType = metatypeType->getInstanceType();
398-
399-
// If the instance type is a type parameter, it is not necessarily
400-
// Sendable. There will need to be a Sendable requirement.
401-
if (instanceType->isTypeParameter())
402-
break;
403-
404-
// If the instance type conforms to SendableMetatype, then its
405-
// metatype is Sendable.
406-
auto instanceConformance = lookupConformance(
407-
instanceType, sendableMetatypeProto);
408-
if (instanceConformance.isInvalid() ||
409-
instanceConformance.hasMissingConformance())
410-
break;
411-
}
447+
if (!metatypeWithInstanceTypeIsSendable(metatypeType->getInstanceType()))
448+
break;
412449

413-
// Every other metatype is Sendable.
414-
}
415450
LLVM_FALLTHROUGH;
416451

417452
case KnownProtocolKind::Copyable:

lib/AST/GenericSignature.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,44 @@ bool GenericSignatureImpl::requiresProtocol(Type type,
371371
return getRequirementMachine()->requiresProtocol(type, proto);
372372
}
373373

374+
std::optional<std::pair<Type, ProtocolDecl *>>
375+
GenericSignatureImpl::prohibitsIsolatedConformance(Type type) const {
376+
type = getReducedType(type);
377+
378+
if (!type->isTypeParameter())
379+
return std::nullopt;
380+
381+
// An isolated conformance cannot be used in a context where the type
382+
// parameter can escape the isolation domain in which the conformance
383+
// was formed. To establish this, we look for Sendable or SendableMetatype
384+
// requirements on the type parameter itself.
385+
ASTContext &ctx = type->getASTContext();
386+
auto sendableProto = ctx.getProtocol(KnownProtocolKind::Sendable);
387+
auto sendableMetatypeProto =
388+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
389+
390+
// Check for a conformance requirement to SendableMetatype, which is
391+
// implied by Sendable.
392+
if (sendableMetatypeProto && requiresProtocol(type, sendableMetatypeProto)) {
393+
// Check for a conformance requirement to Sendable and return that if
394+
// it exists, because it's more recognizable and specific.
395+
if (sendableProto && requiresProtocol(type, sendableProto))
396+
return std::make_pair(type, sendableProto);
397+
398+
return std::make_pair(type, sendableMetatypeProto);
399+
}
400+
401+
// If this is a nested type, also check whether the parent type conforms to
402+
// SendableMetatype, because one can derive this type from the parent type.
403+
// FIXME: This is not a complete check, because there are other ways in which
404+
// one might be able to derive this type. This needs to determine whether
405+
// there is any path from a SendableMetatype-conforming type to this type.
406+
if (auto depMemTy = type->getAs<DependentMemberType>())
407+
return prohibitsIsolatedConformance(depMemTy->getBase());
408+
409+
return std::nullopt;
410+
}
411+
374412
/// Determine whether the given dependent type is equal to a concrete type.
375413
bool GenericSignatureImpl::isConcreteType(Type type) const {
376414
assert(type->isTypeParameter() && "Expected a type parameter");

lib/AST/Type.cpp

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,15 @@ bool ExistentialLayout::isExistentialWithError(ASTContext &ctx) const {
11861186
return false;
11871187
}
11881188

1189+
bool ExistentialLayout::containsNonMarkerProtocols() const {
1190+
for (auto proto : getProtocols()) {
1191+
if (!proto->isMarkerProtocol())
1192+
return true;
1193+
}
1194+
1195+
return false;
1196+
}
1197+
11891198
LayoutConstraint ExistentialLayout::getLayoutConstraint() const {
11901199
if (hasExplicitAnyObject) {
11911200
return LayoutConstraint::getLayoutConstraint(
@@ -5006,11 +5015,15 @@ StringRef swift::getNameForParamSpecifier(ParamSpecifier specifier) {
50065015
llvm_unreachable("bad ParamSpecifier");
50075016
}
50085017

5009-
std::optional<DiagnosticBehavior>
5010-
TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
5011-
auto *self = const_cast<TypeBase *>(this);
5018+
static std::optional<DiagnosticBehavior>
5019+
getConcurrencyDiagnosticBehaviorLimitRec(
5020+
Type type, DeclContext *declCtx,
5021+
llvm::SmallPtrSetImpl<NominalTypeDecl *> &visited) {
5022+
if (auto *nomDecl = type->getNominalOrBoundGenericNominal()) {
5023+
// If we have already seen this type, treat it as having no limit.
5024+
if (!visited.insert(nomDecl).second)
5025+
return std::nullopt;
50125026

5013-
if (auto *nomDecl = self->getNominalOrBoundGenericNominal()) {
50145027
// First try to just grab the exact concurrency diagnostic behavior.
50155028
if (auto result =
50165029
swift::getConcurrencyDiagnosticBehaviorLimit(nomDecl, declCtx)) {
@@ -5021,11 +5034,12 @@ TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
50215034
// merging our fields if we have a struct.
50225035
if (auto *structDecl = dyn_cast<StructDecl>(nomDecl)) {
50235036
std::optional<DiagnosticBehavior> diagnosticBehavior;
5024-
auto substMap = self->getContextSubstitutionMap();
5037+
auto substMap = type->getContextSubstitutionMap();
50255038
for (auto storedProperty : structDecl->getStoredProperties()) {
50265039
auto lhs = diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified);
50275040
auto astType = storedProperty->getInterfaceType().subst(substMap);
5028-
auto rhs = astType->getConcurrencyDiagnosticBehaviorLimit(declCtx);
5041+
auto rhs = getConcurrencyDiagnosticBehaviorLimitRec(astType, declCtx,
5042+
visited);
50295043
auto result = lhs.merge(rhs.value_or(DiagnosticBehavior::Unspecified));
50305044
if (result != DiagnosticBehavior::Unspecified)
50315045
diagnosticBehavior = result;
@@ -5036,21 +5050,36 @@ TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
50365050

50375051
// When attempting to determine the diagnostic behavior limit of a tuple, just
50385052
// merge for each of the elements.
5039-
if (auto *tupleType = self->getAs<TupleType>()) {
5053+
if (auto *tupleType = type->getAs<TupleType>()) {
50405054
std::optional<DiagnosticBehavior> diagnosticBehavior;
50415055
for (auto tupleType : tupleType->getElements()) {
50425056
auto lhs = diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified);
50435057

50445058
auto type = tupleType.getType()->getCanonicalType();
5045-
auto rhs = type->getConcurrencyDiagnosticBehaviorLimit(declCtx);
5059+
auto rhs = getConcurrencyDiagnosticBehaviorLimitRec(type, declCtx,
5060+
visited);
50465061
auto result = lhs.merge(rhs.value_or(DiagnosticBehavior::Unspecified));
50475062
if (result != DiagnosticBehavior::Unspecified)
50485063
diagnosticBehavior = result;
50495064
}
50505065
return diagnosticBehavior;
50515066
}
50525067

5053-
return {};
5068+
// Metatypes that aren't Sendable were introduced in Swift 6.2, so downgrade
5069+
// them to warnings prior to Swift 7.
5070+
if (type->is<AnyMetatypeType>()) {
5071+
if (!type->getASTContext().LangOpts.isSwiftVersionAtLeast(7))
5072+
return DiagnosticBehavior::Warning;
5073+
}
5074+
5075+
return std::nullopt;
5076+
}
5077+
5078+
std::optional<DiagnosticBehavior>
5079+
TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
5080+
auto *self = const_cast<TypeBase *>(this);
5081+
llvm::SmallPtrSet<NominalTypeDecl *, 16> visited;
5082+
return getConcurrencyDiagnosticBehaviorLimitRec(Type(self), declCtx, visited);
50545083
}
50555084

50565085
GenericTypeParamKind

lib/Parse/ParseType.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,7 @@ ParserResult<TypeRepr> Parser::parseTypeSimple(
163163
Diag<> MessageID, ParseTypeReason reason) {
164164
ParserResult<TypeRepr> ty;
165165

166-
if (isParameterSpecifier() &&
167-
!(!Context.LangOpts.hasFeature(Feature::IsolatedConformances) &&
168-
Tok.isContextualKeyword("isolated"))) {
166+
if (isParameterSpecifier()) {
169167
// Type specifier should already be parsed before here. This only happens
170168
// for construct like 'P1 & inout P2'.
171169
diagnose(Tok.getLoc(), diag::attr_only_on_parameters, Tok.getRawText());

0 commit comments

Comments
 (0)