Skip to content

[awaiting evolution] Allow convenience initializers to reassign self. #19311

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

Closed
Closed
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
19 changes: 11 additions & 8 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3098,6 +3098,7 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD,

if (auto *FD = dyn_cast<FuncDecl>(AFD)) {
isStatic = FD->isStatic();
assert(!FD->isMutating() || !containerTy->hasReferenceSemantics());
isMutating = FD->isMutating();

// Methods returning 'Self' have a dynamic 'self'.
Expand All @@ -3108,8 +3109,13 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD,
} else if (auto *CD = dyn_cast<ConstructorDecl>(AFD)) {
if (isInitializingCtor) {
// initializing constructors of value types always have an implicitly
// inout self.
isMutating = true;
// inout self, as do convenience initializers in Swift 5 and later.
if (!containerTy->hasReferenceSemantics()) {
isMutating = true;
} else if (Ctx.LangOpts.isSwiftVersionAtLeast(5)
&& !CD->isDesignatedInit()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be clearer to say CD->isConvenienceInit()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ever expose factory inits as a user facing feature, they’d have the same behavior, wouldn’t they?

isMutating = true;
}
} else {
// allocating constructors have metatype 'self'.
isStatic = true;
Expand All @@ -3123,8 +3129,9 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD,
isDynamicSelf = true;
}
} else if (isa<DestructorDecl>(AFD)) {
// destructors of value types always have an implicitly inout self.
isMutating = true;
// Destructors only appear on classes today. (If move-only types have
// destructors, they probably would want to consume self.)
assert(containerTy->hasReferenceSemantics());
}

if (isDynamicSelf)
Expand All @@ -3134,10 +3141,6 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD,
if (isStatic)
return AnyFunctionType::Param(MetatypeType::get(selfTy, Ctx));

// Reference types have 'self' of type T.
if (containerTy->hasReferenceSemantics())
return AnyFunctionType::Param(selfTy);

return AnyFunctionType::Param(selfTy, Identifier(),
ParameterTypeFlags().withInOut(isMutating));
}
Expand Down
7 changes: 6 additions & 1 deletion lib/SIL/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,12 @@ class DestructureInputs {

bool isInout = false;
if (auto inoutType = dyn_cast<InOutType>(substType)) {
isInout = true;
// The `self` parameter of convenience initializers is considered 'inout'
// for semantic purposes in the AST, but at the ABI level is always
// passed by value in and returned out.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd only end up here when computing the lowered initializing entry point type right? In which case this check should not matter since we only do this for designated inits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convenience inits still have initializing entry points for ObjC interop. This is necessary so that the lowering for the ObjC thunk still works.

if (!forSelf || rep != SILFunctionTypeRepresentation::ObjCMethod) {
isInout = true;
}
substType = inoutType.getObjectType();
origType = origType.getWithoutSpecifierType();
}
Expand Down
13 changes: 11 additions & 2 deletions lib/SILOptimizer/Mandatory/DIMemoryUseCollectorOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1540,8 +1540,17 @@ collectDelegatingInitUses(const DIMemoryObjectInfo &TheMemory,
// for the dynamic type of that uninitialized object.
if (isa<LoadInst>(User)) {
auto UserVal = cast<SingleValueInstruction>(User);
if (UserVal->hasOneUse()
&& isa<ValueMetatypeInst>(UserVal->getSingleUse()->get())) {
bool onlyUseIsValueMetatype = true;
for (auto use : UserVal->getUses()) {
// A taking or copying load is supposed to destroy the value after
// use, which we can turn into a no-op.
if (isa<ValueMetatypeInst>(use->getUser())
|| isa<DestroyValueInst>(use->getUser()))
continue;
onlyUseIsValueMetatype = false;
break;
}
if (onlyUseIsValueMetatype) {
Kind = DIUseKind::LoadForTypeOfSelf;
}
}
Expand Down
12 changes: 11 additions & 1 deletion lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,7 +857,17 @@ void LifetimeChecker::handleLoadForTypeOfSelfUse(const DIMemoryUse &Use) {
break;
}
assert(valueMetatype);
auto metatypeArgument = load->getFunction()->getSelfMetadataArgument();
SILValue metatypeArgument = load->getFunction()->getSelfMetadataArgument();

// Bitcast to the formal metatype of the self argument, which may be
// different in @dynamic_self-ness.
if (metatypeArgument->getType() != valueMetatype->getType()) {
SILBuilderWithScope B(valueMetatype);
metatypeArgument = B.createUncheckedBitCast(valueMetatype->getLoc(),
metatypeArgument,
valueMetatype->getType());
}

replaceAllSimplifiedUsesAndErase(valueMetatype, metatypeArgument,
[](SILInstruction*) { });
}
Expand Down
13 changes: 13 additions & 0 deletions test/SILGen/objc_convenience_init.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// RUN: %target-swift-emit-sil(mock-sdk: %clang-importer-sdk) -swift-version 5 -verify %s

import Foundation

class X: NSObject {
override init() {}

static func foo() -> Self { fatalError() }

@objc convenience init(x: Int) {
self = type(of: self).foo()
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %target-swift-emit-sil -verify %s
// RUN: %target-swift-emit-sil -swift-version 4 -verify %s
// RUN: %target-swift-emit-sil -swift-version 5 -verify %s

// Integration test to ensure that `type(of: self)` keeps working in
// class convenience initializers, even though they are now implemented as
Expand Down
26 changes: 26 additions & 0 deletions test/decl/init/convenience_init_assignment_s4.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %target-typecheck-verify-swift -swift-version 4

final class A {
static let a = A()
convenience init(a: ()) {
self = A.a // expected-error{{immutable}}
}
}

private func _forceCastToSelf<T: AnyObject>(_ object: AnyObject) -> T {
return object as! T
}

class B {
static let b = B()

convenience init(bWrong: ()) {
self = B.b // expected-error{{immutable}}
}

convenience init(b: ()) {
self = _forceCastToSelf(B.b) // expected-error{{immutable}}
}
}

class C: B { }
39 changes: 39 additions & 0 deletions test/decl/init/convenience_init_assignment_s5.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// RUN: %target-typecheck-verify-swift -swift-version 5

final class A {
static let a = A()
convenience init(a: ()) {
self = A.a
}
}

private func _forceCastToSelf<T: AnyObject>(_ object: AnyObject) -> T {
return object as! T
}

protocol P {}

extension P {
static var selfProperty: Self { fatalError() }
static func selfReturningMethod() -> Self { fatalError() }
static func selfLaunderingMethod(_: Self) -> Self { fatalError() }

init(protocolExt: ()) { fatalError() }
}

class B: P {
static let b = B()

convenience init(otherInstance: B) {
self = B.b // expected-error{{cannot assign value of type 'B' to type 'Self'}}
self = otherInstance // expected-error{{cannot assign value of type 'B' to type 'Self'}}
self = _forceCastToSelf(B.b)
self = type(of: self).selfProperty
self = type(of: self).selfReturningMethod()
self = type(of: self).selfLaunderingMethod(otherInstance) // expected-error{{cannot assign value of type 'B' to type 'Self'}}
self = type(of: self).selfLaunderingMethod(type(of: self).selfReturningMethod())
self = type(of: self).init(protocolExt: ())
}
}

class C: B { }