Skip to content

Commit bd4c3c7

Browse files
committed
SILGen: Fix delegations to non-throwing Optional initializers
Relying on the optionality depth of the 'new self' value to flatten an extra level of optionality or handle a failure is not sufficient, because we may be delegating to an `Optional` initializer. Instead, flattening should occur if the result type of the enclosing initializer is less optional than 'new self', and failure handling — if the enclosing initializer is failable and its result type is as optional as the potentially flattened 'new self' value.
1 parent a5329bd commit bd4c3c7

File tree

3 files changed

+206
-22
lines changed

3 files changed

+206
-22
lines changed

lib/SILGen/SILGenExpr.cpp

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4158,23 +4158,31 @@ RValue RValueEmitter::visitRebindSelfInConstructorExpr(
41584158
auto ctorDecl = cast<ConstructorDecl>(selfDecl->getDeclContext());
41594159
auto selfIfaceTy = ctorDecl->getDeclContext()->getSelfInterfaceType();
41604160
auto selfTy = ctorDecl->mapTypeIntoContext(selfIfaceTy);
4161-
4162-
auto newSelfTy = E->getSubExpr()->getType();
4163-
bool outerIsOptional = false;
4164-
bool innerIsOptional = false;
4165-
auto objTy = newSelfTy->getOptionalObjectType();
4166-
if (objTy) {
4167-
outerIsOptional = true;
4168-
newSelfTy = objTy;
4169-
4170-
// "try? self.init()" can give us two levels of optional if the initializer
4171-
// we delegate to is failable.
4172-
objTy = newSelfTy->getOptionalObjectType();
4173-
if (objTy) {
4174-
innerIsOptional = true;
4175-
newSelfTy = objTy;
4161+
4162+
bool isChaining; // Ignored
4163+
auto *otherCtor = E->getCalledConstructor(isChaining)->getDecl();
4164+
assert(otherCtor);
4165+
4166+
auto getOptionalityDepth = [](Type ty) {
4167+
unsigned level = 0;
4168+
Type objTy = ty->getOptionalObjectType();
4169+
while (objTy) {
4170+
++level;
4171+
objTy = objTy->getOptionalObjectType();
41764172
}
4177-
}
4173+
4174+
return level;
4175+
};
4176+
4177+
// The optionality depth of the 'new self' value. This can be '2' if the ctor
4178+
// we are delegating/chaining to is both throwing and failable, or more if
4179+
// 'self' is optional.
4180+
auto srcOptionalityDepth = getOptionalityDepth(E->getSubExpr()->getType());
4181+
4182+
// The optionality depth of the result type of the enclosing initializer in
4183+
// this context.
4184+
const auto destOptionalityDepth = getOptionalityDepth(
4185+
ctorDecl->mapTypeIntoContext(ctorDecl->getResultInterfaceType()));
41784186

41794187
// The subexpression consumes the current 'self' binding.
41804188
assert(SGF.SelfInitDelegationState == SILGenFunction::NormalSelf
@@ -4191,13 +4199,25 @@ RValue RValueEmitter::visitRebindSelfInConstructorExpr(
41914199
SGF.emitAddressOfLocalVarDecl(E, selfDecl, selfTy->getCanonicalType(),
41924200
SGFAccessKind::Write).getLValueAddress();
41934201

4194-
// Handle a nested optional case (see above).
4195-
if (innerIsOptional)
4202+
// Flatten a nested optional if 'new self' is a deeper optional than we
4203+
// can return.
4204+
if (srcOptionalityDepth > destOptionalityDepth) {
4205+
assert(destOptionalityDepth > 0);
4206+
assert(otherCtor->isFailable() && otherCtor->hasThrows());
4207+
4208+
--srcOptionalityDepth;
41964209
newSelf = flattenOptional(SGF, E, newSelf);
41974210

4198-
// If both the delegated-to initializer and our enclosing initializer can
4199-
// fail, deal with the failure.
4200-
if (outerIsOptional && ctorDecl->isFailable()) {
4211+
assert(srcOptionalityDepth == destOptionalityDepth &&
4212+
"Flattening a single level was not enough?");
4213+
}
4214+
4215+
// If the enclosing ctor is failable and the optionality depths match, switch
4216+
// on 'new self' to either return 'nil' or continue with the projected value.
4217+
if (srcOptionalityDepth == destOptionalityDepth && ctorDecl->isFailable()) {
4218+
assert(destOptionalityDepth > 0);
4219+
assert(otherCtor->isFailable() || otherCtor->hasThrows());
4220+
42014221
SILBasicBlock *someBB = SGF.createBasicBlock();
42024222

42034223
auto hasValue = SGF.emitDoesOptionalHaveValue(E, newSelf.getValue());
@@ -4216,7 +4236,7 @@ RValue RValueEmitter::visitRebindSelfInConstructorExpr(
42164236
SGF.getTypeLowering(newSelf.getType()),
42174237
SGFContext());
42184238
}
4219-
4239+
42204240
// If we called a constructor that requires a downcast, perform the downcast.
42214241
auto destTy = SGF.getLoweredType(selfTy);
42224242
if (newSelf.getType() != destTy) {

test/SILGen/init_delegation_optional.swift

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,141 @@ extension Optional {
2525
// CHECK-NEXT: return [[RET]] : $()
2626
// CHECK-NEXT: }
2727
}
28+
29+
// CHECK-LABEL: sil hidden [ossa] @$sSq24init_delegation_optionalE9failable1xSgSgyt_tcfC
30+
init?(failable1: ()) {
31+
// CHECK: bb0([[OUT:%[0-9]+]] : $*Optional<Optional<Wrapped>>, [[SELF_META:%[0-9]+]] : $@thin Optional<Wrapped>.Type):
32+
// CHECK-NEXT: [[SELF_BOX:%[0-9]+]] = alloc_box $<τ_0_0> { var Optional<τ_0_0> } <Wrapped>, var
33+
// CHECK-NEXT: [[MARKED_SELF_BOX:%[0-9]+]] = mark_uninitialized [delegatingself] [[SELF_BOX]]
34+
// CHECK-NEXT: [[SELF_LIFETIME:%[0-9]+]] = begin_borrow [lexical] [[MARKED_SELF_BOX]]
35+
// CHECK-NEXT: [[PB:%[0-9]+]] = project_box [[SELF_LIFETIME]]
36+
// CHECK: [[RESULT_ADDR:%[0-9]+]] = alloc_stack $Optional<Wrapped>
37+
// CHECK: [[DELEG_INIT:%[0-9]+]] = function_ref @$sSq24init_delegation_optionalE12nonFailable1xSgyt_tcfC
38+
// CHECK-NEXT: apply [[DELEG_INIT]]<Wrapped>([[RESULT_ADDR]], [[SELF_META]])
39+
self.init(nonFailable1: ())
40+
// CHECK-NEXT: copy_addr [take] [[RESULT_ADDR]] to [[PB]]
41+
// CHECK-NEXT: dealloc_stack [[RESULT_ADDR]]
42+
// CHECK-NEXT: [[OUT_SOME_ADDR:%[0-9]+]] = init_enum_data_addr [[OUT]] : {{.*}}, #Optional.some!enumelt
43+
// CHECK-NEXT: copy_addr [[PB]] to [initialization] [[OUT_SOME_ADDR]]
44+
// CHECK-NEXT: inject_enum_addr [[OUT]] : {{.*}}, #Optional.some!enumelt
45+
// CHECK-NEXT: end_borrow [[SELF_LIFETIME]]
46+
// CHECK-NEXT: destroy_value [[MARKED_SELF_BOX]]
47+
// CHECK-NEXT: br bb2
48+
//
49+
// FIXME: Dead branch
50+
// CHECK: bb1:
51+
//
52+
// CHECK: bb2:
53+
// CHECK-NEXT: [[RET:%[0-9]+]] = tuple ()
54+
// CHECK-NEXT: return [[RET]] : $()
55+
// CHECK-NEXT: }
56+
}
57+
58+
// CHECK-LABEL: sil hidden [ossa] @$sSq24init_delegation_optionalE9failable2xSgSgyt_tcfC
59+
init?(failable2: ()) {
60+
// CHECK: bb0([[OUT:%[0-9]+]] : $*Optional<Optional<Wrapped>>, [[SELF_META:%[0-9]+]] : $@thin Optional<Wrapped>.Type):
61+
// CHECK-NEXT: [[SELF_BOX:%[0-9]+]] = alloc_box $<τ_0_0> { var Optional<τ_0_0> } <Wrapped>, var
62+
// CHECK-NEXT: [[MARKED_SELF_BOX:%[0-9]+]] = mark_uninitialized [delegatingself] [[SELF_BOX]]
63+
// CHECK-NEXT: [[SELF_LIFETIME:%[0-9]+]] = begin_borrow [lexical] [[MARKED_SELF_BOX]]
64+
// CHECK-NEXT: [[PB:%[0-9]+]] = project_box [[SELF_LIFETIME]]
65+
// CHECK: [[OPT_RESULT_ADDR:%[0-9]+]] = alloc_stack $Optional<Optional<Wrapped>>
66+
// CHECK: [[DELEG_INIT:%[0-9]+]] = function_ref @$sSq24init_delegation_optionalE9failable1xSgSgyt_tcfC
67+
// CHECK-NEXT: apply [[DELEG_INIT]]<Wrapped>([[OPT_RESULT_ADDR]], [[SELF_META]])
68+
self.init(failable1: ())
69+
// CHECK: [[SELECT:%[0-9]+]] = select_enum_addr [[OPT_RESULT_ADDR]]
70+
// CHECK-NEXT: cond_br [[SELECT]], [[SOME_BB:bb[0-9]]], [[NONE_BB:bb[0-9]]]
71+
//
72+
// CHECK: [[NONE_BB]]:
73+
// CHECK-NEXT: destroy_addr [[OPT_RESULT_ADDR]]
74+
// CHECK-NEXT: dealloc_stack [[OPT_RESULT_ADDR]]
75+
// CHECK-NEXT: br bb3
76+
//
77+
// CHECK: [[SOME_BB]]:
78+
// CHECK-NEXT: [[RESULT_ADDR:%[0-9]+]] = unchecked_take_enum_data_addr [[OPT_RESULT_ADDR]] : {{.*}}, #Optional.some!enumelt
79+
// CHECK-NEXT: copy_addr [take] [[RESULT_ADDR]] to [[PB]]
80+
// CHECK-NEXT: dealloc_stack [[OPT_RESULT_ADDR]]
81+
// CHECK-NEXT: [[OUT_SOME_ADDR:%[0-9]+]] = init_enum_data_addr [[OUT]] : {{.*}}, #Optional.some!enumelt
82+
// CHECK-NEXT: copy_addr [[PB]] to [initialization] [[OUT_SOME_ADDR]]
83+
// CHECK-NEXT: inject_enum_addr [[OUT]] : {{.*}}, #Optional.some!enumelt
84+
// CHECK-NEXT: end_borrow [[SELF_LIFETIME]]
85+
// CHECK-NEXT: destroy_value [[MARKED_SELF_BOX]]
86+
// CHECK-NEXT: br bb4
87+
//
88+
// CHECK: bb3:
89+
// CHECK-NEXT: end_borrow [[SELF_LIFETIME]]
90+
// CHECK-NEXT: destroy_value [[MARKED_SELF_BOX]]
91+
// CHECK-NEXT: inject_enum_addr [[OUT]] : {{.*}}, #Optional.none!enumelt
92+
// CHECK-NEXT: br bb4
93+
//
94+
// CHECK: bb4:
95+
// CHECK-NEXT: [[RET:%[0-9]+]] = tuple ()
96+
// CHECK-NEXT: return [[RET]] : $()
97+
// CHECK-NEXT: }
98+
}
99+
}
100+
101+
extension Optional where Wrapped == Optional<Bool> {
102+
// CHECK-LABEL: sil hidden [ossa] @$sSq24init_delegation_optionalSbSgRszlE13SpecFailable1ABSgSgyt_tcfC
103+
init?(SpecFailable1: ()) {
104+
// CHECK: bb0([[SELF_META:%[0-9]+]] : $@thin Optional<Optional<Bool>>.Type):
105+
// CHECK-NEXT: [[SELF_BOX:%[0-9]+]] = alloc_box ${ var Optional<Optional<Bool>> }, var
106+
// CHECK-NEXT: [[MARKED_SELF_BOX:%[0-9]+]] = mark_uninitialized [delegatingself] [[SELF_BOX]]
107+
// CHECK-NEXT: [[SELF_LIFETIME:%[0-9]+]] = begin_borrow [lexical] [[MARKED_SELF_BOX]]
108+
// CHECK-NEXT: [[PB:%[0-9]+]] = project_box [[SELF_LIFETIME]]
109+
// CHECK: [[RESULT_ADDR:%[0-9]+]] = alloc_stack $Optional<Optional<Bool>>
110+
// CHECK: [[DELEG_INIT:%[0-9]+]] = function_ref @$sSq24init_delegation_optionalE12nonFailable1xSgyt_tcfC
111+
// CHECK-NEXT: apply [[DELEG_INIT]]<Bool?>([[RESULT_ADDR]], [[SELF_META]])
112+
// CHECK-NEXT: [[RESULT:%[0-9]+]] = load [trivial] [[RESULT_ADDR]]
113+
// CHECK-NEXT: assign [[RESULT]] to [[PB]]
114+
// CHECK-NEXT: dealloc_stack [[RESULT_ADDR]]
115+
// CHECK-NEXT: [[RESULT:%[0-9]+]] = load [trivial] [[PB]]
116+
// CHECK-NEXT: [[INJECT_INTO_OPT:%[0-9]+]] = enum $Optional<Optional<Optional<Bool>>>, #Optional.some!enumelt, [[RESULT]]
117+
// CHECK-NEXT: end_borrow [[SELF_LIFETIME]]
118+
// CHECK-NEXT: destroy_value [[MARKED_SELF_BOX]]
119+
// CHECK-NEXT: br bb2([[INJECT_INTO_OPT]] : $Optional<Optional<Optional<Bool>>>)
120+
//
121+
// FIXME: Dead branch
122+
// CHECK: bb1:
123+
//
124+
// CHECK: bb2([[RET:%[0-9]+]] : $Optional<Optional<Optional<Bool>>>):
125+
// CHECK-NEXT: return [[RET]]
126+
// CHECK-NEXT: }
127+
self.init(nonFailable1: ())
128+
}
129+
130+
// CHECK-LABEL: sil hidden [ossa] @$sSq24init_delegation_optionalSbSgRszlE13SpecFailable2ABSgSgyt_tcfC
131+
init?(SpecFailable2: ()) {
132+
// CHECK: bb0([[SELF_META:%[0-9]+]] : $@thin Optional<Optional<Bool>>.Type):
133+
// CHECK-NEXT: [[SELF_BOX:%[0-9]+]] = alloc_box ${ var Optional<Optional<Bool>> }, var
134+
// CHECK-NEXT: [[MARKED_SELF_BOX:%[0-9]+]] = mark_uninitialized [delegatingself] [[SELF_BOX]]
135+
// CHECK-NEXT: [[SELF_LIFETIME:%[0-9]+]] = begin_borrow [lexical] [[MARKED_SELF_BOX]]
136+
// CHECK-NEXT: [[PB:%[0-9]+]] = project_box [[SELF_LIFETIME]]
137+
// CHECK: [[DELEG_INIT:%[0-9]+]] = function_ref @$sSq24init_delegation_optionalSbSgRszlE13SpecFailable1ABSgSgyt_tcfC
138+
// CHECK-NEXT: [[OPT_RESULT:%[0-9]+]] = apply [[DELEG_INIT]]([[SELF_META]])
139+
// CHECK: [[SELECT:%[0-9]+]] = select_enum [[OPT_RESULT]]
140+
// CHECK-NEXT: cond_br [[SELECT]], [[SOME_BB:bb[0-9]]], [[NONE_BB:bb[0-9]]]
141+
//
142+
// CHECK: [[NONE_BB]]:
143+
// CHECK-NEXT: br bb3
144+
//
145+
// CHECK: [[SOME_BB]]:
146+
// CHECK-NEXT: [[RESULT:%[0-9]+]] = unchecked_enum_data [[OPT_RESULT]] : {{.*}}, #Optional.some!enumelt
147+
// CHECK-NEXT: assign [[RESULT]] to [[PB]]
148+
// CHECK-NEXT: [[RESULT:%[0-9]+]] = load [trivial] [[PB]]
149+
// CHECK-NEXT: [[INJECT_INTO_OPT:%[0-9]+]] = enum $Optional<Optional<Optional<Bool>>>, #Optional.some!enumelt, [[RESULT]]
150+
// CHECK-NEXT: end_borrow [[SELF_LIFETIME]]
151+
// CHECK-NEXT: destroy_value [[MARKED_SELF_BOX]]
152+
// CHECK-NEXT: br bb4([[INJECT_INTO_OPT]] : $Optional<Optional<Optional<Bool>>>)
153+
//
154+
// CHECK: bb3:
155+
// CHECK-NEXT: end_borrow [[SELF_LIFETIME]]
156+
// CHECK-NEXT: destroy_value [[MARKED_SELF_BOX]]
157+
// CHECK-NEXT: [[NIL:%[0-9]+]] = enum $Optional<Optional<Optional<Bool>>>, #Optional.none!enumelt
158+
// CHECK-NEXT: br bb4([[NIL]] : $Optional<Optional<Optional<Bool>>>)
159+
//
160+
// CHECK: bb4([[RET:%[0-9]+]] : $Optional<Optional<Optional<Bool>>>):
161+
// CHECK-NEXT: return [[RET]]
162+
// CHECK-NEXT: }
163+
self.init(SpecFailable1: ())
164+
}
28165
}

test/SILGen/initializers.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,33 @@ class FailableBaseClass {
438438
self.init(failBeforeFullInitialization: ())! // unnecessary-but-correct '!'
439439
}
440440

441+
// Optional to non-optional
442+
//
443+
// CHECK-LABEL: sil hidden [ossa] @$s21failable_initializers17FailableBaseClassC21failDuringDelegation3ACSgyt_tcfC
444+
// CHECK: bb0([[SELF_META:%[0-9]+]] : $@thick FailableBaseClass.Type):
445+
// CHECK-NEXT: [[SELF_BOX:%[0-9]+]] = alloc_box ${ var FailableBaseClass }, let, name "self"
446+
// CHECK-NEXT: [[MARKED_SELF_BOX:%[0-9]+]] = mark_uninitialized [delegatingself] [[SELF_BOX]]
447+
// CHECK-NEXT: [[SELF_LIFETIME:%[0-9]+]] = begin_borrow [lexical] [[MARKED_SELF_BOX]]
448+
// CHECK-NEXT: [[PB_BOX:%[0-9]+]] = project_box [[SELF_LIFETIME]]
449+
// CHECK: [[DELEG_INIT:%[0-9]+]] = class_method [[SELF_META]] : $@thick FailableBaseClass.Type, #FailableBaseClass.init!allocator
450+
// CHECK-NEXT: [[RESULT:%[0-9]+]] = apply [[DELEG_INIT]]([[SELF_META]])
451+
// CHECK-NEXT: assign [[RESULT]] to [[PB_BOX]]
452+
// CHECK-NEXT: [[RESULT_COPY:%[0-9]+]] = load [copy] [[PB_BOX]]
453+
// CHECK-NEXT: [[INJECT_INTO_OPT:%[0-9]+]] = enum $Optional<FailableBaseClass>, #Optional.some!enumelt, [[RESULT_COPY]]
454+
// CHECK-NEXT: end_borrow [[SELF_LIFETIME]]
455+
// CHECK-NEXT: destroy_value [[MARKED_SELF_BOX]]
456+
// CHECK-NEXT: br bb2([[INJECT_INTO_OPT]] : $Optional<FailableBaseClass>)
457+
//
458+
// FIXME: Dead block
459+
// CHECK: bb1:
460+
//
461+
// CHECK: bb2([[ARG:%[0-9]+]] : @owned $Optional<FailableBaseClass>):
462+
// CHECK-NEXT: return [[ARG]]
463+
// CHECK-NEXT: }
464+
convenience init?(failDuringDelegation3: ()) {
465+
self.init(noFail: ())
466+
}
467+
441468
// IUO to IUO
442469
convenience init!(noFailDuringDelegation: ()) {
443470
self.init(failDuringDelegation2: ())! // unnecessary-but-correct '!'

0 commit comments

Comments
 (0)