Skip to content

Commit 41134ea

Browse files
committed
Remove the need for convenience on actor inits to delegate.
This is possible because actors do not support inheritance. There is one specific exception to that rule, which is that an actor can inherit from `NSObject` just to support ObjC interop. This means an actor is effectively a final class. resolves rdar://87568153
1 parent 25b69d1 commit 41134ea

10 files changed

+266
-49
lines changed

docs/SILInitializerConventions.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# SIL Initializer Conventions
2+
3+
A nominal type can define a number of initializers, some of which may
4+
delegate initialization to another initializer. There are specific calling
5+
conventions for these initializers within SIL that make up a part of the ABI
6+
for a type. This document aims to summarize the key calling conventions for
7+
these initializers.
8+
9+
10+
# Structs and Enums
11+
12+
The delegation status for the initializer of a struct or enum is not encoded
13+
in the definitions of these initializers. Thus, all of these initializers
14+
have an implicit `metatype` argument for the instance to be passed in as the
15+
last argument to the initializer. Using `<...>` as a stand-in for other
16+
arguments that are part of the usual function calling convention, consider this
17+
example:
18+
19+
```swift
20+
// the non-delegating init MyStruct.init(final:)
21+
sil hidden [ossa] @$s4test8MyStructV5finalACSi_tcfC : $@convention(method) (<...>, @thin MyStruct.Type) -> MyStruct {
22+
bb0(<...>, %meta : $@thin MyStruct.Type):
23+
%a = alloc_box ${ var MyStruct }, var, name "self"
24+
%b = mark_uninitialized [rootself] %a : ${ var MyStruct }
25+
%c = begin_borrow [lexical] %b : ${ var MyStruct }
26+
%d = project_box %c : ${ var MyStruct }, 0
27+
28+
// ... initialize properties, etc ...
29+
30+
%end = load [trivial] %d : $*MyStruct
31+
end_borrow %c : ${ var MyStruct }
32+
destroy_value %b : ${ var MyStruct }
33+
return %end : $MyStruct
34+
}
35+
36+
37+
// the delegating init MyStruct.init(delegates:)
38+
sil hidden [ossa] @$s4test8MyStructV9delegatesACyt_tcfC : $@convention(method) (<...>, @thin MyStruct.Type) -> MyStruct {
39+
bb0(<...>, %meta : $@thin MyStruct.Type):
40+
// Same allocation as the non-delegating:
41+
%a = alloc_box ${ var MyStruct }, var, name "self"
42+
%b = mark_uninitialized [rootself] %a : ${ var MyStruct }
43+
%c = begin_borrow [lexical] %b : ${ var MyStruct }
44+
%d = project_box %c : ${ var MyStruct }, 0
45+
46+
// ... delegate to MyStruct.init(final:) ...
47+
48+
%ctor = function_ref @$s4test8MyStructV5finalACSi_tcfC : $@convention(method) (Int, @thin MyStruct.Type) -> MyStruct
49+
%ret = apply %ctor(<...>, %meta) : $@convention(method) (Int, @thin MyStruct.Type) -> MyStruct
50+
51+
assign %ret to %d : $*MyStruct
52+
%end = load [trivial] %d : $*MyStruct
53+
end_borrow %c : ${ var MyStruct }
54+
destroy_value %b : ${ var MyStruct }
55+
return %end : $MyStruct
56+
}
57+
```
58+
59+
It's important to note that all initializers take a metadata argument,
60+
regardless of whether it is a delegating initializer. There is also no
61+
separation between allocating and non-allocating initializer entrypoints.
62+
All initializers may perform allocation.
63+
64+
# Classes
65+
66+
Every designated initializer has two entry-points. One performs allocation
67+
(i.e., the "allocating" entry) before continuing at the second entrypoint
68+
which does the initialization (i.e., the "initializing" entrypoint).
69+
Here's an example of `MyClass.init(final:)`, which is a designated initializer,
70+
with its two entry-points:
71+
72+
```swift
73+
// MyClass.__allocating_init(final:)
74+
sil hidden [exact_self_class] [ossa] @$s4test7MyClassC5finalACSi_tcfC : $@convention(method) (<...>, @thick MyClass.Type) -> @owned MyClass {
75+
bb0(%0 : $Int, %1 : $@thick MyClass.Type):
76+
%2 = alloc_ref $MyClass
77+
// function_ref MyClass.init(final:)
78+
%3 = function_ref @$s4test7MyClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyClass) -> @owned MyClass
79+
%4 = apply %3(%0, %2) : $@convention(method) (Int, @owned MyClass) -> @owned MyClass // user: %5
80+
return %4 : $MyClass
81+
}
82+
83+
// MyClass.init(final:)
84+
sil hidden [ossa] @$s4test7MyClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyClass) -> @owned MyClass {
85+
bb0(<...>, %1 : @owned $MyClass):
86+
%4 = mark_uninitialized [rootself] %1 : $MyClass
87+
88+
// ... initialize MyClass ...
89+
90+
%11 = copy_value %4 : $MyClass
91+
destroy_value %4 : $MyClass
92+
return %11 : $MyClass
93+
}
94+
```
95+
96+
In the mangling of these entrypoint labels, the uppercase `C` suffix indicates
97+
that it's the allocating entrypoint, whereas the lowercase `c` is the
98+
initializing entrypoint. Only the allocating entrypoint is published in the
99+
type's vtable:
100+
101+
```swift
102+
sil_vtable MyClass {
103+
// ...
104+
#MyClass.init!allocator: (MyClass.Type) -> (<...>) -> MyClass : @$s4test7MyClassC5finalACSi_tcfC // MyClass.__allocating_init(final:)
105+
}
106+
```
107+
108+
The initializing entrypoint is only referenced by either it's corresponding
109+
allocating entrypoint, or by a sub-class that is delegating up in a `super.init`
110+
call. For example, if we had:
111+
112+
```swift
113+
class MyClass {
114+
var x: Int
115+
init(final x: Int) {
116+
self.x = x
117+
}
118+
}
119+
120+
class MyDerivedClass: MyClass {
121+
var y: Int
122+
init(subFinal y: Int) {
123+
self.y = y
124+
super.init(final: y)
125+
}
126+
}
127+
```
128+
129+
Then the `super.init(final: y)` call directly invokes `MyClass.init(final:)`'s
130+
initializing entrypoint, bypassing its allocating init. Here's what that looks
131+
like in SIL:
132+
133+
```
134+
// MyDerivedClass.__allocating_init(final:)
135+
sil hidden [exact_self_class] [ossa] @$s4test14MyDerivedClassC5finalACSi_tcfC : $@convention(method) (Int, @thick MyDerivedClass.Type) -> @owned MyDerivedClass {
136+
// ... calls $s4test14MyDerivedClassC5finalACSi_tcfc in the usual way ...
137+
}
138+
139+
// MyDerivedClass.init(final:)
140+
sil hidden [ossa] @$s4test14MyDerivedClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyDerivedClass) -> @owned MyDerivedClass {
141+
bb0(%0 : $Int, %1 : @owned $MyDerivedClass):
142+
%2 = alloc_box ${ var MyDerivedClass }, let, name "self"
143+
%3 = mark_uninitialized [derivedself] %2 : ${ var MyDerivedClass }
144+
%4 = begin_borrow [lexical] %3 : ${ var MyDerivedClass }
145+
%5 = project_box %4 : ${ var MyDerivedClass }, 0
146+
debug_value %0 : $Int, let, name "y", argno 1
147+
store %1 to [init] %5 : $*MyDerivedClass
148+
149+
// ... initialize self.y ...
150+
151+
// perform the super call. notice the ownership transfer to the super.init.
152+
%14 = load [take] %5 : $*MyDerivedClass
153+
%15 = upcast %14 : $MyDerivedClass to $MyClass
154+
// function_ref MyClass.init(final:)
155+
%16 = function_ref @$s4test7MyClassC5finalACSi_tcfc : $@convention(method) (Int, @owned MyClass) -> @owned MyClass // user: %17
156+
%17 = apply %16(%0, %15) : $@convention(method) (Int, @owned MyClass) -> @owned MyClass // user: %18
157+
%18 = unchecked_ref_cast %17 : $MyClass to $MyDerivedClass
158+
store %18 to [init] %5 : $*MyDerivedClass // id: %19
159+
160+
// return as usual
161+
%20 = load [copy] %5 : $*MyDerivedClass
162+
end_borrow %4 : ${ var MyDerivedClass }
163+
destroy_value %3 : ${ var MyDerivedClass }
164+
return %20 : $MyDerivedClass
165+
}
166+
```
167+
168+
# Actors
169+
170+
There does not exist a sub-actor that inherits from some other actor in the type
171+
system. As a result, the `convenience` keyword is not required for actor
172+
initializers in the source code. Without inheritance, only the allocating
173+
entry-points can ever be used by an actor.
174+
175+
Nevertheless, internally the compiler will still differentiate between
176+
convenience and designated initializers. So everything discussed
177+
earlier for classes also apply to actors. The body of the initializer determines
178+
whether the compiler internally treats it as `convenience` or not. For example,
179+
an internally designated initializer for an actor still emits two entry-points,
180+
but the initializing entrypoint is exclusively used by its corresponding
181+
allocating entrypoint.
182+
183+
184+

include/swift/AST/DiagnosticsSema.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3572,7 +3572,7 @@ ERROR(designated_init_in_extension,none,
35723572
ERROR(cfclass_designated_init_in_extension,none,
35733573
"designated initializer cannot be declared in an extension of %0",
35743574
(DeclName))
3575-
ERROR(enumstruct_convenience_init,none,
3575+
ERROR(no_convenience_keyword_init,none,
35763576
"initializers in %0 are not marked with 'convenience'",
35773577
(StringRef))
35783578
ERROR(nonclass_convenience_init,none,

lib/Sema/TypeCheckDecl.cpp

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -379,40 +379,64 @@ CtorInitializerKind
379379
InitKindRequest::evaluate(Evaluator &evaluator, ConstructorDecl *decl) const {
380380
auto &diags = decl->getASTContext().Diags;
381381

382-
// Convenience inits are only allowed on classes and in extensions thereof.
383-
if (decl->getAttrs().hasAttribute<ConvenienceAttr>()) {
384-
if (auto nominal = decl->getDeclContext()->getSelfNominalTypeDecl()) {
385-
auto classDecl = dyn_cast<ClassDecl>(nominal);
386-
387-
// Forbid convenience inits on Foreign CF types, as Swift does not yet
388-
// support user-defined factory inits.
389-
if (classDecl &&
390-
classDecl->getForeignClassKind() == ClassDecl::ForeignKind::CFType) {
391-
diags.diagnose(decl->getLoc(), diag::cfclass_convenience_init);
392-
}
382+
if (auto nominal = decl->getDeclContext()->getSelfNominalTypeDecl()) {
383+
384+
// Convenience inits are only allowed on classes and in extensions thereof.
385+
if (auto convenAttr = decl->getAttrs().getAttribute<ConvenienceAttr>()) {
386+
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
387+
if (classDecl->isAnyActor()) {
388+
// For an actor "convenience" is not required, but we'll honor it.
389+
diags.diagnose(decl->getLoc(),
390+
diag::no_convenience_keyword_init, "actors")
391+
.fixItRemove(convenAttr->getLocation())
392+
.warnUntilSwiftVersion(6);
393+
394+
} else { // not an actor
395+
// Forbid convenience inits on Foreign CF types, as Swift does not yet
396+
// support user-defined factory inits.
397+
if (classDecl->getForeignClassKind() == ClassDecl::ForeignKind::CFType)
398+
diags.diagnose(decl->getLoc(), diag::cfclass_convenience_init);
399+
}
393400

394-
if (!classDecl) {
395-
auto ConvenienceLoc =
396-
decl->getAttrs().getAttribute<ConvenienceAttr>()->getLocation();
401+
} else { // not a ClassDecl
402+
auto ConvenienceLoc = convenAttr->getLocation();
397403

398-
// Produce a tailored diagnostic for structs and enums.
404+
// Produce a tailored diagnostic for structs and enums. They should
405+
// not have `convenience`.
399406
bool isStruct = dyn_cast<StructDecl>(nominal) != nullptr;
400407
if (isStruct || dyn_cast<EnumDecl>(nominal)) {
401-
diags.diagnose(decl->getLoc(), diag::enumstruct_convenience_init,
408+
diags.diagnose(decl->getLoc(), diag::no_convenience_keyword_init,
402409
isStruct ? "structs" : "enums")
403410
.fixItRemove(ConvenienceLoc);
404411
} else {
405-
diags.diagnose(decl->getLoc(), diag::nonclass_convenience_init,
406-
nominal->getName())
412+
diags.diagnose(decl->getLoc(), diag::no_convenience_keyword_init,
413+
nominal->getName().str())
407414
.fixItRemove(ConvenienceLoc);
408415
}
409416
return CtorInitializerKind::Designated;
410417
}
418+
419+
return CtorInitializerKind::Convenience;
411420
}
412421

413-
return CtorInitializerKind::Convenience;
422+
// if there is no `convenience` keyword...
423+
424+
// actors infer whether they are `convenience` from their body kind.
425+
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
426+
if (classDecl->isAnyActor()) {
427+
auto kind = decl->getDelegatingOrChainedInitKind();
428+
switch (kind.initKind) {
429+
case BodyInitKind::ImplicitChained:
430+
case BodyInitKind::Chained:
431+
case BodyInitKind::None:
432+
return CtorInitializerKind::Designated;
433+
434+
case BodyInitKind::Delegating:
435+
return CtorInitializerKind::Convenience;
436+
}
437+
}
438+
}
414439

415-
} else if (auto nominal = decl->getDeclContext()->getSelfNominalTypeDecl()) {
416440
// A designated init for a class must be written within the class itself.
417441
//
418442
// This is because designated initializers of classes get a vtable entry,
@@ -437,10 +461,11 @@ InitKindRequest::evaluate(Evaluator &evaluator, ConstructorDecl *decl) const {
437461
return CtorInitializerKind::Convenience;
438462
}
439463
}
464+
} // end of Nominal context
440465

441-
if (decl->getDeclContext()->getExtendedProtocolDecl()) {
442-
return CtorInitializerKind::Convenience;
443-
}
466+
// initializers in protocol extensions must be convenience inits
467+
if (decl->getDeclContext()->getExtendedProtocolDecl()) {
468+
return CtorInitializerKind::Convenience;
444469
}
445470

446471
return CtorInitializerKind::Designated;

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2832,6 +2832,13 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
28322832
return false;
28332833
}
28342834

2835+
// do not skip the body of an actor initializer.
2836+
// they are checked to determine delegation status
2837+
if (auto *ctor = dyn_cast<ConstructorDecl>(AFD))
2838+
if (auto *nom = ctor->getParent()->getSelfNominalTypeDecl())
2839+
if (nom->isAnyActor())
2840+
return false;
2841+
28352842
// Skipping all bodies won't serialize anything, so can skip regardless
28362843
if (getASTContext().TypeCheckerOpts.SkipFunctionBodies ==
28372844
FunctionBodySkipping::All)

lib/Sema/TypeCheckStmt.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,7 @@ static void checkClassConstructorBody(ClassDecl *classDecl,
17021702
ASTContext &ctx = classDecl->getASTContext();
17031703
bool wantSuperInitCall = false;
17041704
bool isDelegating = false;
1705+
17051706
auto initKindAndExpr = ctor->getDelegatingOrChainedInitKind();
17061707
switch (initKindAndExpr.initKind) {
17071708
case BodyInitKind::Delegating:

test/Concurrency/actor_isolation.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -861,14 +861,14 @@ actor SomeActorWithInits {
861861
self.nonisolated()
862862
}
863863

864-
convenience init(i3: Bool) {
864+
convenience init(i3: Bool) { // expected-warning{{initializers in actors are not marked with 'convenience'; this is an error in Swift 6}}{{3-15=}}
865865
self.init(i1: i3)
866866
_ = mutableState // expected-error{{actor-isolated property 'mutableState' can not be referenced from a non-isolated context}}
867867
self.isolated() // expected-error{{actor-isolated instance method 'isolated()' can not be referenced from a non-isolated context}}
868868
self.nonisolated()
869869
}
870870

871-
convenience init(i4: Bool) async {
871+
convenience init(i4: Bool) async { // expected-warning{{initializers in actors are not marked with 'convenience'; this is an error in Swift 6}}{{3-15=}}
872872
self.init(i1: i4)
873873
_ = mutableState
874874
self.isolated()
@@ -894,14 +894,14 @@ actor SomeActorWithInits {
894894
_ = mutableState // will be caught by flow-isolation
895895
}
896896

897-
@MainActor convenience init(i7: Bool) {
897+
@MainActor convenience init(i7: Bool) { // expected-warning{{initializers in actors are not marked with 'convenience'; this is an error in Swift 6}}{{14-26=}}
898898
self.init(i1: i7)
899899
_ = mutableState // expected-error{{actor-isolated property 'mutableState' can not be referenced from the main actor}}
900900
self.isolated() // expected-error{{actor-isolated instance method 'isolated()' can not be referenced from the main actor}}
901901
self.nonisolated()
902902
}
903903

904-
@MainActor convenience init(i8: Bool) async {
904+
@MainActor convenience init(i8: Bool) async { // expected-warning{{initializers in actors are not marked with 'convenience'; this is an error in Swift 6}}{{14-26=}}
905905
self.init(i1: i8)
906906
_ = await mutableState
907907
await self.isolated()
@@ -1244,9 +1244,9 @@ actor Counter {
12441244

12451245
}
12461246

1247-
convenience init(material: Int) async {
1247+
init(material: Int) async {
12481248
self.init()
1249-
self.counter = 10 // FIXME: this should work, and also needs to work in definite initialization by injecting hops
1249+
self.counter = 10
12501250
}
12511251
}
12521252

@@ -1273,17 +1273,17 @@ actor Butterfly {
12731273

12741274
nonisolated init(async: Void) async {}
12751275

1276-
nonisolated convenience init(icecream: Void) { // expected-warning {{'nonisolated' on an actor's synchronous initializer is invalid; this is an error in Swift 6}} {{3-15=}}
1276+
nonisolated init(icecream: Void) { // expected-warning {{'nonisolated' on an actor's synchronous initializer is invalid; this is an error in Swift 6}} {{3-15=}}
12771277
self.init()
12781278
self.flapsPerSec += 1 // expected-error {{actor-isolated property 'flapsPerSec' can not be mutated from a non-isolated context}}
12791279
}
12801280

1281-
nonisolated convenience init(cookies: Void) async {
1281+
nonisolated init(cookies: Void) async {
12821282
self.init()
12831283
self.flapsPerSec += 1 // expected-error {{actor-isolated property 'flapsPerSec' can not be mutated from a non-isolated context}}
12841284
}
12851285

1286-
convenience init(brownies: Void) {
1286+
init(brownies: Void) {
12871287
self.init()
12881288
self.flapsPerSec = 0 // expected-error {{actor-isolated property 'flapsPerSec' can not be mutated from a non-isolated context}}
12891289
}

0 commit comments

Comments
 (0)