Skip to content

CSGen, SILGen: Fix delegations to Optional initializers #59171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 42 additions & 22 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4158,23 +4158,31 @@ RValue RValueEmitter::visitRebindSelfInConstructorExpr(
auto ctorDecl = cast<ConstructorDecl>(selfDecl->getDeclContext());
auto selfIfaceTy = ctorDecl->getDeclContext()->getSelfInterfaceType();
auto selfTy = ctorDecl->mapTypeIntoContext(selfIfaceTy);

auto newSelfTy = E->getSubExpr()->getType();
bool outerIsOptional = false;
bool innerIsOptional = false;
auto objTy = newSelfTy->getOptionalObjectType();
if (objTy) {
outerIsOptional = true;
newSelfTy = objTy;

// "try? self.init()" can give us two levels of optional if the initializer
// we delegate to is failable.
objTy = newSelfTy->getOptionalObjectType();
if (objTy) {
innerIsOptional = true;
newSelfTy = objTy;

bool isChaining; // Ignored
auto *otherCtor = E->getCalledConstructor(isChaining)->getDecl();
assert(otherCtor);

auto getOptionalityDepth = [](Type ty) {
unsigned level = 0;
Type objTy = ty->getOptionalObjectType();
while (objTy) {
++level;
objTy = objTy->getOptionalObjectType();
}
}

return level;
};

// The optionality depth of the 'new self' value. This can be '2' if the ctor
// we are delegating/chaining to is both throwing and failable, or more if
// 'self' is optional.
auto srcOptionalityDepth = getOptionalityDepth(E->getSubExpr()->getType());

// The optionality depth of the result type of the enclosing initializer in
// this context.
const auto destOptionalityDepth = getOptionalityDepth(
ctorDecl->mapTypeIntoContext(ctorDecl->getResultInterfaceType()));

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

// Handle a nested optional case (see above).
if (innerIsOptional)
// Flatten a nested optional if 'new self' is a deeper optional than we
// can return.
if (srcOptionalityDepth > destOptionalityDepth) {
assert(destOptionalityDepth > 0);
assert(otherCtor->isFailable() && otherCtor->hasThrows());

--srcOptionalityDepth;
newSelf = flattenOptional(SGF, E, newSelf);

// If both the delegated-to initializer and our enclosing initializer can
// fail, deal with the failure.
if (outerIsOptional && ctorDecl->isFailable()) {
assert(srcOptionalityDepth == destOptionalityDepth &&
"Flattening a single level was not enough?");
}

// If the enclosing ctor is failable and the optionality depths match, switch
// on 'new self' to either return 'nil' or continue with the projected value.
if (srcOptionalityDepth == destOptionalityDepth && ctorDecl->isFailable()) {
assert(destOptionalityDepth > 0);
assert(otherCtor->isFailable() || otherCtor->hasThrows());

SILBasicBlock *someBB = SGF.createBasicBlock();

auto hasValue = SGF.emitDoesOptionalHaveValue(E, newSelf.getValue());
Expand All @@ -4216,7 +4236,7 @@ RValue RValueEmitter::visitRebindSelfInConstructorExpr(
SGF.getTypeLowering(newSelf.getType()),
SGFContext());
}

// If we called a constructor that requires a downcast, perform the downcast.
auto destTy = SGF.getLoweredType(selfTy);
if (newSelf.getType() != destTy) {
Expand Down
15 changes: 5 additions & 10 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2175,23 +2175,18 @@ namespace {

// The constructor was opened with the allocating type, not the
// initializer type. Map the former into the latter.
auto resultTy = solution.simplifyType(openedFullType);
auto *resultTy =
solution.simplifyType(openedFullType)->castTo<FunctionType>();

auto selfTy = getBaseType(resultTy->castTo<FunctionType>());

// Also replace the result type with the base type, so that calls
// to constructors defined in a superclass will know to cast the
// result to the derived type.
resultTy = resultTy->replaceCovariantResultType(selfTy, 2);
const auto selfTy = getBaseType(resultTy);

ParameterTypeFlags flags;
if (!selfTy->hasReferenceSemantics())
flags = flags.withInOut(true);

auto selfParam = AnyFunctionType::Param(selfTy, Identifier(), flags);
resultTy = FunctionType::get({selfParam},
resultTy->castTo<FunctionType>()->getResult(),
resultTy->castTo<FunctionType>()->getExtInfo());
resultTy = FunctionType::get({selfParam}, resultTy->getResult(),
resultTy->getExtInfo());

// Build the constructor reference.
Expr *ctorRef = cs.cacheType(
Expand Down
45 changes: 42 additions & 3 deletions lib/Sema/CSGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1718,9 +1718,48 @@ namespace {
if (!optTy)
return Type();

// Prior to Swift 5, 'try?' always adds an additional layer of optionality,
// even if the sub-expression was already optional.
if (CS.getASTContext().LangOpts.isSwiftVersionAtLeast(5)) {
bool isDelegationToOptionalInit = false;
{
Expr *e = expr->getSubExpr();
while (true) {
e = e->getSemanticsProvidingExpr();

// Look through force-value expressions.
if (auto *FVE = dyn_cast<ForceValueExpr>(e)) {
e = FVE->getSubExpr();
continue;
}

break;
}

if (auto *CE = dyn_cast<CallExpr>(e)) {
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(CE->getFn())) {
if (!CS.getType(UDE->getBase())->is<AnyMetatypeType>()) {
auto overload =
CS.findSelectedOverloadFor(CS.getConstraintLocator(
UDE, ConstraintLocator::ConstructorMember));
if (overload) {
auto *decl = overload->choice.getDeclOrNull();
if (decl && isa<ConstructorDecl>(decl) &&
decl->getDeclContext()
->getSelfNominalTypeDecl()
->isOptionalDecl())
isDelegationToOptionalInit = true;
}
}
}
}
}

// Prior to Swift 5, 'try?' always adds an additional layer of
// optionality, even if the sub-expression was already optional.
//
// NB Keep adding the additional layer in Swift 5 and on if this 'try?'
// applies to a delegation to an 'Optional' initializer, or else we won't
// discern the difference between a failure and a constructed value.
if (CS.getASTContext().LangOpts.isSwiftVersionAtLeast(5) &&
!isDelegationToOptionalInit) {
CS.addConstraint(ConstraintKind::Conversion,
CS.getType(expr->getSubExpr()), optTy,
CS.getConstraintLocator(expr));
Expand Down
Loading