Skip to content

Commit 3c04f0a

Browse files
committed
[SR-15694] make isolation inference + override more consistent
During actor isolation inference, we would unconditionally choose the isolation of the overridden decl (when, say, there is no attribute on the decl). The overridden decl is identified with `getOverriddenDecl`. This works mostly fine, but initializers have some unusual overridden decls returned by that method. For example, in the following code: ```swift @objc class PictureFrame: NSObject { init(size: Int) { } } @mainactor class FooFrame: PictureFrame { init() { super.init(size: 0) } } ``` that method claims that `FooFrame.init()` overrides `PictureFrame.init()`, when it really does not! So, if we were to unconditionally take the isolation from `PictureFrame.init()` (and thus `NSObject.init()`), then we'd infer that `FooFrame.init()` has unspecified isolation, despite the nominal it resides in being marked as `MainActor`. This is in essence the problem in SR-15694, where placing the isolation directly on the initializer fixes this issue. If `FooFrame.init()` really does override, then why can it be `MainActor`? Well, we have a rule in one part of the type-checker saying that if an ObjC-imported decl has unspecified isolation, then overriding it with isolation is permitted. But the other part of the type-checker dealing with the isolation inference was not permitting that. So, this patch unifies how actor-isolation inference is conducted to reuse that same logic. In addition, the inference now effectively says that, if the decl has no inferred isolation, or the inferred isolation is invalid according to the overriding rules, then we use the isolation of the overridden decl. This preserves the old behavior of the inference, while also fixing this issue with ObjC declarations by expanding where isolation is allowed. For example, as a consequence of this change, in the following code: ```swift @mainactor class FooFrame: NotIsolatedPictureFrame { override func rotate() { mainActorFn() } } ``` if `NotIsolatedPictureFrame` is a plain-old not-isolated class imported from Objective-C, then `rotate` will now have `MainActor` isolation, instead of having unspecified isolation like it was inferred to have previously. This helps make things consistent, because `rotate` is allowed to be `@MainActor` if it were explicitly marked as such. resolves rdar://87217618 / SR-15694
1 parent d8ebecf commit 3c04f0a

File tree

5 files changed

+206
-42
lines changed

5 files changed

+206
-42
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3593,6 +3593,36 @@ static bool checkClassGlobalActorIsolation(
35933593
return true;
35943594
}
35953595

3596+
/// Generally speaking, the isolation of the decl that overrides
3597+
/// must match the overridden decl. But there are a number of exceptions,
3598+
/// e.g., the decl that overrides can be nonisolated.
3599+
/// \param newIso the isolation of the overriding declaration.
3600+
static bool validOverrideIsolation(ActorIsolation newIso,
3601+
ValueDecl *overriddenDecl,
3602+
ActorIsolation overriddenIso) {
3603+
// If the isolation matches, we're done.
3604+
if (newIso == overriddenIso)
3605+
return true;
3606+
3607+
// If the overriding declaration is non-isolated, it's okay.
3608+
if (newIso.isIndependent() || newIso.isUnspecified())
3609+
return true;
3610+
3611+
// If both are actor-instance isolated, we're done. This wasn't caught by
3612+
// the equality case above because the nominal type describing the actor
3613+
// will differ when we're overriding.
3614+
if (newIso.getKind() == overriddenIso.getKind() &&
3615+
newIso.getKind() == ActorIsolation::ActorInstance)
3616+
return true;
3617+
3618+
// If the overridden declaration is from Objective-C with no actor annotation,
3619+
// allow it.
3620+
if (overriddenDecl->hasClangNode() && !overriddenIso)
3621+
return true;
3622+
3623+
return false;
3624+
}
3625+
35963626
ActorIsolation ActorIsolationRequest::evaluate(
35973627
Evaluator &evaluator, ValueDecl *value) const {
35983628
// If this declaration has actor-isolated "self", it's isolated to that
@@ -3650,9 +3680,36 @@ ActorIsolation ActorIsolationRequest::evaluate(
36503680
if (!ctor->hasAsync())
36513681
defaultIsolation = ActorIsolation::forIndependent();
36523682

3683+
// Look for and remember the overridden declaration's isolation.
3684+
Optional<ActorIsolation> overriddenIso;
3685+
ValueDecl *overriddenValue = nullptr;
3686+
if ( (overriddenValue = value->getOverriddenDecl()) ) {
3687+
auto iso = getActorIsolation(overriddenValue);
3688+
SubstitutionMap subs;
3689+
3690+
if (Type selfType = value->getDeclContext()->getSelfInterfaceType()) {
3691+
subs = selfType->getMemberSubstitutionMap(
3692+
value->getModuleContext(), overriddenValue);
3693+
}
3694+
iso = iso.subst(subs);
3695+
3696+
// use the overridden decl's iso as the default isolation for this decl.
3697+
defaultIsolation = iso;
3698+
overriddenIso = iso;
3699+
}
3700+
36533701
// Function used when returning an inferred isolation.
36543702
auto inferredIsolation = [&](
36553703
ActorIsolation inferred, bool onlyGlobal = false) {
3704+
// check if the inferred isolation is valid in the context of
3705+
// its overridden isolation.
3706+
if (overriddenValue) {
3707+
// if the inferred isolation is not valid, then carry-over the overridden
3708+
// declaration's isolation as this decl's inferred isolation.
3709+
if (!validOverrideIsolation(inferred, overriddenValue, *overriddenIso))
3710+
inferred = *overriddenIso;
3711+
}
3712+
36563713
// Add an implicit attribute to capture the actor isolation that was
36573714
// inferred, so that (e.g.) it will be printed and serialized.
36583715
ASTContext &ctx = value->getASTContext();
@@ -3739,20 +3796,6 @@ ActorIsolation ActorIsolationRequest::evaluate(
37393796
}
37403797
}
37413798

3742-
// If the declaration overrides another declaration, it must have the same
3743-
// actor isolation.
3744-
if (auto overriddenValue = value->getOverriddenDecl()) {
3745-
auto isolation = getActorIsolation(overriddenValue);
3746-
SubstitutionMap subs;
3747-
3748-
if (Type selfType = value->getDeclContext()->getSelfInterfaceType()) {
3749-
subs = selfType->getMemberSubstitutionMap(
3750-
value->getModuleContext(), overriddenValue);
3751-
}
3752-
3753-
return inferredIsolation(isolation.subst(subs));
3754-
}
3755-
37563799
// If this is an accessor, use the actor isolation of its storage
37573800
// declaration.
37583801
if (auto accessor = dyn_cast<AccessorDecl>(value)) {
@@ -3947,30 +3990,7 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
39473990
overriddenIsolation = overriddenIsolation.subst(subs);
39483991
}
39493992

3950-
// If the isolation matches, we're done.
3951-
if (isolation == overriddenIsolation)
3952-
return;
3953-
3954-
// FIXME: do we need this?
3955-
if (overriddenIsolation == ActorIsolation::Unspecified &&
3956-
isolation == ActorIsolation::Independent) {
3957-
return;
3958-
}
3959-
3960-
// If the overriding declaration is non-isolated, it's okay.
3961-
if (isolation.isIndependent() || isolation.isUnspecified())
3962-
return;
3963-
3964-
// If both are actor-instance isolated, we're done. This wasn't caught by
3965-
// the equality case above because the nominal type describing the actor
3966-
// will differ when we're overriding.
3967-
if (isolation.getKind() == overriddenIsolation.getKind() &&
3968-
isolation.getKind() == ActorIsolation::ActorInstance)
3969-
return;
3970-
3971-
// If the overridden declaration is from Objective-C with no actor annotation,
3972-
// allow it.
3973-
if (overridden->hasClangNode() && !overriddenIsolation)
3993+
if (validOverrideIsolation(isolation, overridden, overriddenIsolation))
39743994
return;
39753995

39763996
// Isolation mismatch. Diagnose it.

test/ClangImporter/objc_async.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,69 @@ func testMirrored(instance: ClassWithAsync) async {
230230
func g() async { }
231231
}
232232

233+
234+
@MainActor func mainActorFn() {}
235+
@SomeGlobalActor func sgActorFn() {}
236+
237+
// Check inferred isolation for overridden decls from ObjC.
238+
// Note that even if the override is not present, it
239+
// can have an affect. -- rdar://87217618 / SR-15694
240+
@MainActor
241+
class FooFrame: PictureFrame {
242+
init() {
243+
super.init(size: 0)
244+
}
245+
246+
override init(size n: Int) {
247+
super.init(size: n)
248+
}
249+
250+
override func rotate() {
251+
mainActorFn()
252+
}
253+
}
254+
255+
class BarFrame: PictureFrame {
256+
init() {
257+
super.init(size: 0)
258+
}
259+
260+
override init(size n: Int) {
261+
super.init(size: n)
262+
}
263+
264+
override func rotate() {
265+
mainActorFn()
266+
}
267+
}
268+
269+
@SomeGlobalActor
270+
class BazFrame: NotIsolatedPictureFrame {
271+
init() {
272+
super.init(size: 0)
273+
}
274+
275+
override init(size n: Int) {
276+
super.init(size: n)
277+
}
278+
279+
override func rotate() {
280+
sgActorFn()
281+
}
282+
}
283+
284+
@SomeGlobalActor
285+
class BazFrameIso: PictureFrame { // expected-error {{global actor 'SomeGlobalActor'-isolated class 'BazFrameIso' has different actor isolation from main actor-isolated superclass 'PictureFrame'}}
286+
}
287+
288+
func check() async {
289+
_ = await BarFrame()
290+
_ = await FooFrame()
291+
_ = await BazFrame()
292+
293+
_ = await BarFrame(size: 0)
294+
_ = await FooFrame(size: 0)
295+
_ = await BazFrame(size: 0)
296+
}
297+
233298
} // SwiftStdlib 5.5

test/Concurrency/actor_isolation.swift

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,11 @@ struct GenericGlobalActor<T> {
403403
}
404404

405405
@available(SwiftStdlib 5.1, *)
406-
@SomeGlobalActor func onions() {} // expected-note{{calls to global function 'onions()' from outside of its actor context are implicitly asynchronous}}
406+
@SomeGlobalActor func onions_sga() {} // expected-note 2{{calls to global function 'onions_sga()' from outside of its actor context are implicitly asynchronous}}
407407

408408
@available(SwiftStdlib 5.1, *)
409-
@MainActor func beets() { onions() } // expected-error{{call to global actor 'SomeGlobalActor'-isolated global function 'onions()' in a synchronous main actor-isolated context}}
410-
// expected-note@-1{{calls to global function 'beets()' from outside of its actor context are implicitly asynchronous}}
409+
@MainActor func beets_ma() { onions_sga() } // expected-error{{call to global actor 'SomeGlobalActor'-isolated global function 'onions_sga()' in a synchronous main actor-isolated context}}
410+
// expected-note@-1 4{{calls to global function 'beets_ma()' from outside of its actor context are implicitly asynchronous}}
411411

412412
@available(SwiftStdlib 5.1, *)
413413
actor Crystal {
@@ -942,7 +942,7 @@ class SomeClassWithInits {
942942
deinit {
943943
print(mutableState) // Okay, we're actor-isolated
944944
print(SomeClassWithInits.shared) // expected-error{{static property 'shared' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
945-
beets() //expected-error{{call to main actor-isolated global function 'beets()' in a synchronous nonisolated context}}
945+
beets_ma() //expected-error{{call to main actor-isolated global function 'beets_ma()' in a synchronous nonisolated context}}
946946
}
947947

948948
func isolated() { }
@@ -1356,3 +1356,68 @@ actor DunkTracker {
13561356
}
13571357
}
13581358
}
1359+
1360+
@MainActor
1361+
class MA {
1362+
func method() {}
1363+
}
1364+
1365+
@SomeGlobalActor class SGA: MA {} // expected-error {{global actor 'SomeGlobalActor'-isolated class 'SGA' has different actor isolation from main actor-isolated superclass 'MA'}}
1366+
1367+
protocol SGA_Proto {
1368+
@SomeGlobalActor func method() // expected-note {{'method()' declared here}}
1369+
}
1370+
1371+
// try to override a MA method with inferred isolation from a protocol requirement
1372+
class SGA_MA: MA, SGA_Proto {
1373+
// expected-error@+2 {{call to global actor 'SomeGlobalActor'-isolated global function 'onions_sga()' in a synchronous main actor-isolated context}}
1374+
// expected-warning@+1 {{instance method 'method()' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'SGA_Proto' isolated to global actor 'SomeGlobalActor'}}
1375+
override func method() { onions_sga() }
1376+
}
1377+
1378+
class None_MA: MA {
1379+
override func method() { beets_ma() }
1380+
}
1381+
1382+
class None {
1383+
func method() {} // expected-note{{overridden declaration is here}}
1384+
}
1385+
1386+
// try to add inferred isolation while overriding
1387+
@MainActor
1388+
class MA_None1: None {
1389+
// FIXME: bad note, since the problem is a mismatch in overridden vs inferred isolation; this wont help.
1390+
// expected-note@+1 {{add '@MainActor' to make instance method 'method()' part of global actor 'MainActor'}}
1391+
override func method() {
1392+
beets_ma() // expected-error {{call to main actor-isolated global function 'beets_ma()' in a synchronous nonisolated context}}
1393+
}
1394+
}
1395+
1396+
class MA_None2: None {
1397+
@MainActor
1398+
override func method() { // expected-error {{main actor-isolated instance method 'method()' has different actor isolation from nonisolated overridden declaration}}
1399+
beets_ma()
1400+
}
1401+
}
1402+
1403+
class MADirect {
1404+
@MainActor func method1() {}
1405+
@MainActor func method2() {}
1406+
}
1407+
1408+
class None_MADirect: MADirect {
1409+
// default-isolation vs overridden-MainActor = mainactor
1410+
override func method1() { beets_ma() }
1411+
1412+
// directly-nonisolated vs overridden mainactor = nonisolated
1413+
nonisolated override func method2() { beets_ma() } // expected-error {{call to main actor-isolated global function 'beets_ma()' in a synchronous nonisolated context}}
1414+
}
1415+
1416+
@SomeGlobalActor
1417+
class SGA_MADirect: MADirect {
1418+
// inferred-SomeGlobalActor vs overridden-MainActor = mainactor
1419+
override func method1() { beets_ma() }
1420+
1421+
// directly-nonisolated vs overridden-MainActor = nonisolated
1422+
nonisolated override func method2() { beets_ma() } // expected-error {{call to main actor-isolated global function 'beets_ma()' in a synchronous nonisolated context}}
1423+
}

test/Incremental/Verifier/single-file-private/AnyObject.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ import Foundation
8080
// expected-member {{ObjectiveC.NSObject.Hasher}}
8181
// expected-member {{ObjectiveC.NSObjectProtocol.hash}}
8282

83+
// expected-member {{Swift.Hashable.init}}
8384
// expected-member {{Swift.Hashable.deinit}}
85+
// expected-member {{Swift.Equatable.init}}
8486
// expected-member {{Swift.Equatable.deinit}}
8587

8688
// expected-member {{Swift.Hashable.==}}

test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma clang assume_nonnull begin
55

66
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))
7+
#define UI_ACTOR __attribute__((swift_attr("@UIActor")))
78

89
#ifdef __SWIFT_ATTR_SUPPORTS_SENDABLE_DECLS
910
#define SENDABLE __attribute__((__swift_attr__("@Sendable")))
@@ -247,6 +248,17 @@ typedef NS_ERROR_ENUM(unsigned, NonSendableErrorCode, NonSendableErrorDomain) {
247248
} NONSENDABLE;
248249
// expected-warning@-3 {{cannot make error code type 'NonSendableErrorCode' non-sendable because Swift errors are always sendable}}
249250

251+
UI_ACTOR
252+
@interface PictureFrame : NSObject
253+
- (instancetype)initWithSize:(NSInteger)frame NS_DESIGNATED_INITIALIZER;
254+
- (void)rotate;
255+
@end
256+
257+
@interface NotIsolatedPictureFrame : NSObject
258+
- (instancetype)initWithSize:(NSInteger)frame NS_DESIGNATED_INITIALIZER;
259+
- (void)rotate;
260+
@end
261+
250262
typedef NSString *SendableStringEnum NS_STRING_ENUM;
251263
typedef NSString *NonSendableStringEnum NS_STRING_ENUM NONSENDABLE;
252264

0 commit comments

Comments
 (0)