Skip to content

Commit ffc4e50

Browse files
authored
Merge pull request #83315 from DougGregor/default-isolation-extensions
[SE-0466] Extensions and members thereof can apply default isolation
2 parents 3512fd5 + d4f0efc commit ffc4e50

File tree

8 files changed

+233
-39
lines changed

8 files changed

+233
-39
lines changed

include/swift/Basic/Features.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,10 @@ EXPERIMENTAL_FEATURE(ConsumeSelfInDeinit, true)
334334

335335
EXPERIMENTAL_FEATURE(AccessLevelOnImport, true)
336336

337+
/// Disable the special behavior of of explicit 'nonisolated' vs. being
338+
/// implicitly nonisolated.
339+
EXPERIMENTAL_FEATURE(NoExplicitNonIsolated, true)
340+
337341
/// Enables a module to allow non resilient access from other
338342
/// modules within a package if built from source.
339343
EXPERIMENTAL_FEATURE(AllowNonResilientAccessInPackage, false)

lib/AST/FeatureSet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ UNINTERESTING_FEATURE(MacrosOnImports)
125125
UNINTERESTING_FEATURE(NonisolatedNonsendingByDefault)
126126
UNINTERESTING_FEATURE(KeyPathWithMethodMembers)
127127
UNINTERESTING_FEATURE(ImportMacroAliases)
128+
UNINTERESTING_FEATURE(NoExplicitNonIsolated)
128129

129130
// TODO: Return true for inlinable function bodies with module selectors in them
130131
UNINTERESTING_FEATURE(ModuleSelector)

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1852,8 +1852,10 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
18521852
Opts.DefaultIsolationBehavior = DefaultIsolation::Nonisolated;
18531853
}
18541854

1855-
if (Opts.DefaultIsolationBehavior == DefaultIsolation::MainActor)
1855+
if (Opts.DefaultIsolationBehavior == DefaultIsolation::MainActor) {
18561856
Opts.enableFeature(Feature::InferIsolatedConformances);
1857+
Opts.enableFeature(Feature::NoExplicitNonIsolated);
1858+
}
18571859

18581860
#if !defined(NDEBUG) && SWIFT_ENABLE_EXPERIMENTAL_PARSER_VALIDATION
18591861
/// Enable round trip parsing via the new swift parser unless it is disabled

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 104 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5282,6 +5282,33 @@ getIsolationFromAttributes(const Decl *decl, bool shouldDiagnose = true,
52825282
llvm_unreachable("Forgot about an attribute?");
52835283
}
52845284

5285+
/// Determine the default isolation for the given declaration context.
5286+
static DefaultIsolation getDefaultIsolationForContext(const DeclContext *dc) {
5287+
// Check whether there is a file-specific setting.
5288+
if (auto *sourceFile = dc->getParentSourceFile()) {
5289+
if (auto defaultIsolationInFile = sourceFile->getDefaultIsolation())
5290+
return defaultIsolationInFile.value();
5291+
}
5292+
5293+
// If we're in the main module, check the language option.
5294+
ASTContext &ctx = dc->getASTContext();
5295+
if (dc->getParentModule() == ctx.MainModule)
5296+
return ctx.LangOpts.DefaultIsolationBehavior;
5297+
5298+
// Otherwise, default to nonisolated.
5299+
return DefaultIsolation::Nonisolated;
5300+
}
5301+
5302+
/// Determines whether explicit 'nonisolated' is different from 'unspecified'
5303+
/// in the given context.
5304+
static bool explicitNonisolatedIsSpecial(const DeclContext *dc) {
5305+
ASTContext &ctx = dc->getASTContext();
5306+
if (ctx.LangOpts.hasFeature(Feature::NoExplicitNonIsolated))
5307+
return false;
5308+
5309+
return getDefaultIsolationForContext(dc) == DefaultIsolation::Nonisolated;
5310+
}
5311+
52855312
/// Infer isolation from witnessed protocol requirements.
52865313
static std::optional<InferredActorIsolation>
52875314
getIsolationFromWitnessedRequirements(ValueDecl *value) {
@@ -5298,11 +5325,13 @@ getIsolationFromWitnessedRequirements(ValueDecl *value) {
52985325
if (dc->getSelfProtocolDecl())
52995326
return std::nullopt;
53005327

5301-
// Prevent isolation inference from requirements if the conforming type
5302-
// has an explicit `nonisolated` attribute.
5303-
if (auto *NTD = dc->getSelfNominalTypeDecl()) {
5304-
if (NTD->getAttrs().hasAttribute<NonisolatedAttr>())
5305-
return std::nullopt;
5328+
if (explicitNonisolatedIsSpecial(dc)) {
5329+
// Prevent isolation inference from requirements if the conforming type
5330+
// has an explicit `nonisolated` attribute.
5331+
if (auto *NTD = dc->getSelfNominalTypeDecl()) {
5332+
if (NTD->getAttrs().hasAttribute<NonisolatedAttr>())
5333+
return std::nullopt;
5334+
}
53065335
}
53075336

53085337
// Walk through each of the conformances in this context, collecting any
@@ -5360,8 +5389,13 @@ getIsolationFromWitnessedRequirements(ValueDecl *value) {
53605389
}
53615390

53625391
case ActorIsolation::GlobalActor:
5392+
break;
5393+
53635394
case ActorIsolation::Nonisolated:
53645395
case ActorIsolation::NonisolatedUnsafe:
5396+
if (!explicitNonisolatedIsSpecial(requirement->getDeclContext()))
5397+
continue;
5398+
53655399
break;
53665400
}
53675401

@@ -5468,7 +5502,8 @@ getIsolationFromConformances(NominalTypeDecl *nominal) {
54685502
case ActorIsolation::NonisolatedUnsafe:
54695503
break;
54705504
case ActorIsolation::Nonisolated:
5471-
if (inferredIsolation.source.kind == IsolationSource::Kind::Explicit) {
5505+
if (inferredIsolation.source.kind == IsolationSource::Kind::Explicit &&
5506+
explicitNonisolatedIsSpecial(nominal)) {
54725507
if (!foundIsolation) {
54735508
// We found an explicitly 'nonisolated' protocol.
54745509
foundIsolation = {
@@ -6131,12 +6166,27 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
61316166
return {};
61326167

61336168
if (dc != dyn_cast<DeclContext>(value)) {
6169+
// If the nominal type is global-actor-isolated, there's nothing
6170+
// more to look for.
61346171
if (getActorIsolation(nominal).isMainActor())
61356172
break;
61366173

6137-
return {};
6174+
// If this is an extension of a nonisolated type, its isolation
6175+
// is independent of the type.
6176+
if (auto ext = dyn_cast<ExtensionDecl>(dc)) {
6177+
// If there were isolation attributes on the extension, respect
6178+
// them.
6179+
if (getIsolationFromAttributes(ext).has_value())
6180+
return {};
6181+
6182+
// Keep looking.
6183+
} else {
6184+
// The type is nonisolated, so its members are nonisolated.
6185+
return {};
6186+
}
61386187
}
61396188
}
6189+
61406190
dc = dc->getParent();
61416191
}
61426192

@@ -6166,12 +6216,8 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
61666216
return {};
61676217
};
61686218

6169-
DefaultIsolation defaultIsolation = ctx.LangOpts.DefaultIsolationBehavior;
6170-
if (auto *SF = value->getDeclContext()->getParentSourceFile()) {
6171-
if (auto defaultIsolationInFile = SF->getDefaultIsolation())
6172-
defaultIsolation = defaultIsolationInFile.value();
6173-
}
6174-
6219+
DefaultIsolation defaultIsolation =
6220+
getDefaultIsolationForContext(value->getDeclContext());
61756221
// If we are required to use main actor... just use that.
61766222
if (defaultIsolation == DefaultIsolation::MainActor)
61776223
if (auto result =
@@ -6248,6 +6294,36 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
62486294
return {{ActorIsolation::forUnspecified(), {}}, nullptr, {}};
62496295
}
62506296

6297+
/// Determines when the given "self" isolation should override default
6298+
/// isolation.
6299+
static bool shouldSelfIsolationOverrideDefault(
6300+
ASTContext &ctx, const DeclContext *dc,
6301+
const ActorIsolation &selfIsolation) {
6302+
switch (selfIsolation) {
6303+
case ActorIsolation::ActorInstance:
6304+
case ActorIsolation::Erased:
6305+
case ActorIsolation::GlobalActor:
6306+
// Actor isolation always overrides.
6307+
return true;
6308+
6309+
case ActorIsolation::Unspecified:
6310+
// Unspecified isolation never overrides.
6311+
return false;
6312+
6313+
case ActorIsolation::Nonisolated:
6314+
case ActorIsolation::NonisolatedUnsafe:
6315+
case ActorIsolation::CallerIsolationInheriting:
6316+
// Explicit nonisolated used to overwrite default isolation all the time,
6317+
// but under NoExplicitNonIsolated it doesn't affect extensions.
6318+
if (isa<NominalTypeDecl>(dc))
6319+
return true;
6320+
6321+
/// Only allow explicit nonisolated to override the default when it's
6322+
/// treated as special.
6323+
return explicitNonisolatedIsSpecial(dc);
6324+
}
6325+
}
6326+
62516327
static InferredActorIsolation computeActorIsolation(Evaluator &evaluator,
62526328
ValueDecl *value) {
62536329
// If this declaration has actor-isolated "self", it's isolated to that
@@ -6580,7 +6656,8 @@ static InferredActorIsolation computeActorIsolation(Evaluator &evaluator,
65806656
// has isolation, use that.
65816657
if (auto selfTypeDecl = value->getDeclContext()->getSelfNominalTypeDecl()) {
65826658
auto selfTypeIsolation = getInferredActorIsolation(selfTypeDecl);
6583-
if (selfTypeIsolation.isolation) {
6659+
if (shouldSelfIsolationOverrideDefault(
6660+
ctx, value->getDeclContext(), selfTypeIsolation.isolation)) {
65846661
auto isolation = selfTypeIsolation.isolation;
65856662

65866663
if (ctx.LangOpts.hasFeature(Feature::NonisolatedNonsendingByDefault) &&
@@ -8426,6 +8503,19 @@ ActorIsolation swift::inferConformanceIsolation(
84268503
// isolated to a global actor, we may use the conforming type's isolation.
84278504
auto nominalIsolation = getActorIsolation(nominal);
84288505
if (!nominalIsolation.isGlobalActor()) {
8506+
// If we are in an extension of the type, we might still infer an
8507+
// isolated conformance depending on default isolation and on the extension
8508+
// itself.
8509+
if (auto ext = dyn_cast<ExtensionDecl>(dc)) {
8510+
// If there's an isolation-related attribute on the extension, use it.
8511+
if (auto attrIsolation = getIsolationFromAttributes(ext))
8512+
return *attrIsolation;
8513+
8514+
// If we're defaulting to main-actor isolation, use that.
8515+
if (getDefaultIsolationForContext(dc) == DefaultIsolation::MainActor)
8516+
return ActorIsolation::forMainActor(ctx);
8517+
}
8518+
84298519
return ActorIsolation::forNonisolated(false);
84308520
}
84318521

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public protocol ImportedSendableProto: Sendable { }
2+
3+
@MainActor public struct ImportedStruct: ImportedSendableProto { }
4+
5+
public struct ImportedOtherStruct { }
6+
7+
public protocol ImportedP { }

test/Concurrency/assume_mainactor.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -227,24 +227,6 @@ actor MyActor2 {
227227
print("123")
228228
}
229229

230-
// https://github.com/swiftlang/swift/issues/82168 - used to fail
231-
nonisolated protocol P {
232-
associatedtype AT
233-
static var at: AT { get }
234-
}
235-
236-
nonisolated struct KP<R: P, V> {
237-
init(keyPath: KeyPath<R, V>) {}
238-
}
239-
240-
struct S: P {
241-
let p: Int
242-
struct AT {
243-
let kp = KP(keyPath: \S.p)
244-
}
245-
static let at = AT() // used to fail here
246-
}
247-
248230
nonisolated func localDeclIsolation() async {
249231
struct Local {
250232
static func f() {}

test/Concurrency/assume_mainactor_typechecker_errors.swift

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
// RUN: %target-swift-frontend -swift-version 5 -emit-sil -default-isolation MainActor %s -verify -verify-additional-prefix swift5-
2-
// RUN: %target-swift-frontend -swift-version 6 -emit-sil -default-isolation MainActor %s -verify -verify-additional-prefix swift6-
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -swift-version 6 -module-name implicit_nonisolated_things -o %t/implicit_nonisolated_things.swiftmodule %S/Inputs/implicit_nonisolated_things.swift
3+
// RUN: %target-swift-frontend -I %t -swift-version 5 -emit-sil -default-isolation MainActor %s -verify -verify-additional-prefix swift5-
4+
// RUN: %target-swift-frontend -I %t -swift-version 6 -emit-sil -default-isolation MainActor %s -verify -verify-additional-prefix swift6-
35

46
// READ THIS! This test is meant to check the specific isolation when
57
// `-default-isolation` is set to `MainActor` in combination with validating
68
// behavior around explicitly non-Sendable types that trigger type checker
79
// specific errors. Please do not put other types of tests in here.
810

11+
import implicit_nonisolated_things
12+
913
// Fake Sendable Data
1014
class SendableData : @unchecked Sendable {}
1115
// expected-swift5-note@-1 {{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
@@ -79,3 +83,98 @@ func testTaskDetached() async {
7983
await k.doSomething()
8084
}
8185
}
86+
87+
// @MainActor
88+
extension Int {
89+
func memberOfInt() { } // expected-note 3{{calls to instance method 'memberOfInt()' from outside of its actor context are implicitly asynchronous}}
90+
}
91+
92+
nonisolated func testMemberOfInt(i: Int) {
93+
i.memberOfInt() // expected-swift5-warning{{call to main actor-isolated instance method 'memberOfInt()' in a synchronous nonisolated context}}
94+
// expected-swift6-error@-1{{call to main actor-isolated instance method 'memberOfInt()' in a synchronous nonisolated context}}
95+
}
96+
97+
protocol SendableProto: Sendable { }
98+
99+
struct MyStruct: SendableProto { }
100+
101+
extension MyStruct: CustomStringConvertible {
102+
var description: String {
103+
17.memberOfInt() // okay, on main actor
104+
return "hello"
105+
}
106+
}
107+
108+
nonisolated struct MyOtherStruct { }
109+
110+
extension MyOtherStruct {
111+
func f() {
112+
17.memberOfInt() // okay, on main actor
113+
}
114+
}
115+
116+
nonisolated
117+
extension MyOtherStruct {
118+
func g() {
119+
17.memberOfInt() // expected-swift5-warning{{call to main actor-isolated instance method 'memberOfInt()' in a synchronous nonisolated context}}
120+
// expected-swift6-error@-1{{call to main actor-isolated instance method 'memberOfInt()' in a synchronous nonisolated context}}
121+
}
122+
}
123+
124+
nonisolated protocol P {
125+
func g()
126+
}
127+
128+
struct MyP: P {
129+
func g() {
130+
17.memberOfInt() // okay, on main actor
131+
}
132+
}
133+
134+
// Above tests for imported types
135+
extension ImportedStruct: @retroactive CustomStringConvertible {
136+
public var description: String {
137+
17.memberOfInt() // okay, on main actor
138+
return "hello"
139+
}
140+
}
141+
142+
extension ImportedOtherStruct {
143+
func f() {
144+
17.memberOfInt() // okay, on main actor
145+
}
146+
}
147+
148+
nonisolated
149+
extension ImportedOtherStruct {
150+
func g() {
151+
17.memberOfInt() // expected-swift5-warning{{call to main actor-isolated instance method 'memberOfInt()' in a synchronous nonisolated context}}
152+
// expected-swift6-error@-1{{call to main actor-isolated instance method 'memberOfInt()' in a synchronous nonisolated context}}
153+
}
154+
}
155+
156+
struct MyImportedP: ImportedP {
157+
func g() {
158+
17.memberOfInt() // okay, on main actor
159+
}
160+
}
161+
162+
// https://github.com/swiftlang/swift/issues/82168 -
163+
nonisolated protocol OtherP {
164+
associatedtype AT
165+
static var at: AT { get }
166+
}
167+
168+
nonisolated struct KP<R: OtherP, V> {
169+
init(keyPath: KeyPath<R, V>) {}
170+
}
171+
172+
struct S: OtherP {
173+
let p: Int
174+
struct AT {
175+
let kp = KP(keyPath: \S.p)
176+
}
177+
178+
// FIXME: This should not be an error.
179+
static let at = AT() // expected-swift6-error{{'S.AT' cannot be constructed because it has no accessible initializers}}
180+
}

0 commit comments

Comments
 (0)