Skip to content

[SILGen] TypeWrappers: Support default values in user-defined initializers #61479

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
5 changes: 5 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5714,6 +5714,11 @@ class VarDecl : public AbstractStorageDecl {
/// backing property will be treated as the member-initialized property.
bool isMemberwiseInitialized(bool preferDeclaredProperties) const;

/// Check whether this variable presents a local storage synthesized
/// by the compiler in a user-defined designated initializer to
/// support initialization of type wrapper managed properties.
bool isTypeWrapperLocalStorageForInitializer() const;

/// Return the range of semantics attributes attached to this VarDecl.
auto getSemanticsAttrs() const
-> decltype(getAttrs().getAttributes<SemanticsAttr>()) {
Expand Down
8 changes: 8 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6504,6 +6504,14 @@ bool VarDecl::isMemberwiseInitialized(bool preferDeclaredProperties) const {
return true;
}

bool VarDecl::isTypeWrapperLocalStorageForInitializer() const {
if (auto *ctor =
dyn_cast_or_null<ConstructorDecl>(getDeclContext()->getAsDecl())) {
return this == ctor->getLocalTypeWrapperStorageVar();
}
return false;
}

bool VarDecl::isLet() const {
// An awful hack that stabilizes the value of 'isLet' for ParamDecl instances.
//
Expand Down
89 changes: 78 additions & 11 deletions lib/SILGen/SILGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1335,29 +1335,96 @@ void SILGenFunction::emitPatternBinding(PatternBindingDecl *PBD,
auto initialization = emitPatternBindingInitialization(PBD->getPattern(idx),
JumpDest::invalid());

if (auto *Init = PBD->getExecutableInit(idx)) {
auto emitInitializer = [&](Expr *initExpr, VarDecl *var, bool forLocalContext,
InitializationPtr &initialization) {
// If an initial value expression was specified by the decl, emit it into
// the initialization.
FullExpr Scope(Cleanups, CleanupLocation(Init));
FullExpr Scope(Cleanups, CleanupLocation(initExpr));

auto *var = PBD->getSingleVar();
if (var && var->getDeclContext()->isLocalContext()) {
if (forLocalContext) {
if (auto *orig = var->getOriginalWrappedProperty()) {
auto initInfo = orig->getPropertyWrapperInitializerInfo();
if (auto *placeholder = initInfo.getWrappedValuePlaceholder()) {
Init = placeholder->getOriginalWrappedValue();
initExpr = placeholder->getOriginalWrappedValue();

auto value = emitRValue(Init);
emitApplyOfPropertyWrapperBackingInitializer(SILLocation(PBD), orig,
getForwardingSubstitutionMap(),
std::move(value))
.forwardInto(*this, SILLocation(PBD), initialization.get());
auto value = emitRValue(initExpr);
emitApplyOfPropertyWrapperBackingInitializer(
PBD, orig, getForwardingSubstitutionMap(), std::move(value))
.forwardInto(*this, SILLocation(PBD), initialization.get());
return;
}
}
}

emitExprInto(Init, initialization.get(), SILLocation(PBD));
emitExprInto(initExpr, initialization.get(), SILLocation(PBD));
};

auto *singleVar = PBD->getSingleVar();
if (auto *Init = PBD->getExecutableInit(idx)) {
// If an initial value expression was specified by the decl, emit it into
// the initialization.
bool isLocalVar =
singleVar && singleVar->getDeclContext()->isLocalContext();
emitInitializer(Init, singleVar, isLocalVar, initialization);
} else if (singleVar &&
singleVar->isTypeWrapperLocalStorageForInitializer()) {
// If any of the type wrapper managed properties had default initializers
// we need to emit them as assignments to `_storage` elements as part
// of its initialization.

auto storageVarType = singleVar->getType()->castTo<TupleType>();
auto *wrappedDecl = cast<NominalTypeDecl>(
singleVar->getDeclContext()->getInnermostTypeContext());

SmallVector<std::pair<VarDecl *, Expr *>, 2> fieldsToInitialize;
fieldsToInitialize.resize_for_overwrite(storageVarType->getNumElements());

unsigned numInitializable = 0;
for (auto member : wrappedDecl->getMembers()) {
auto *PBD = dyn_cast<PatternBindingDecl>(member);
// Check every member that is managed by the type wrapper.
if (!(PBD && PBD->getSingleVar() &&
PBD->getSingleVar()->isAccessedViaTypeWrapper()))
continue;

auto *field = PBD->getSingleVar();
auto fieldNo = storageVarType->getNamedElementId(field->getName());

if (auto *initExpr = PBD->getInit(/*index=*/0)) {
fieldsToInitialize[fieldNo] = {PBD->getSingleVar(), initExpr};
++numInitializable;
}
}

if (numInitializable == 0) {
initialization->finishUninitialized(*this);
return;
}

// If there are any initializable fields, let's split _storage into
// element initializers and emit initializations for individual fields.

assert(initialization->canSplitIntoTupleElements());

SmallVector<InitializationPtr, 4> scratch;
auto fieldInits = initialization->splitIntoTupleElements(
*this, PBD, storageVarType->getCanonicalType(), scratch);

for (unsigned i : range(fieldInits.size())) {
VarDecl *field;
Expr *initExpr;

std::tie(field, initExpr) = fieldsToInitialize[i];

auto &fieldInit = fieldInits[i];
if (initExpr) {
emitInitializer(initExpr, field, /*forLocalContext=*/true, fieldInit);
} else {
fieldInit->finishUninitialized(*this);
}
}

initialization->finishInitialization(*this);
} else {
// Otherwise, mark it uninitialized for DI to resolve.
initialization->finishUninitialized(*this);
Expand Down
27 changes: 16 additions & 11 deletions lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,25 +468,30 @@ DIMemoryObjectInfo::getPathStringToElement(unsigned Element,

/// If the specified value is a 'let' property in an initializer, return true.
bool DIMemoryObjectInfo::isElementLetProperty(unsigned Element) const {
NullablePtr<NominalTypeDecl> NTD;

// If this is an element of a `_storage` tuple, we need to
// check the `$Storage` to determine whether underlying storage
// backing element is immutable.
if (auto *storageVar = getAsTypeWrapperLocalStorageVar()) {
auto *wrappedType = cast<NominalTypeDecl>(
storageVar->getDeclContext()->getInnermostTypeContext());
assert(wrappedType && "_storage reference without type wrapper");
NTD = wrappedType->getTypeWrapperStorageDecl();
} else {
// If we aren't representing 'self' in a non-delegating initializer, then we
// can't have 'let' properties.
if (!isNonDelegatingInit())
return IsLet;

NTD = MemorySILType.getNominalOrBoundGenericNominal();

auto storageVarType = storageVar->getInterfaceType()->getAs<TupleType>();
assert(Element < storageVarType->getNumElements());
auto propertyName = storageVarType->getElement(Element).getName();

auto *storageDecl = wrappedType->getTypeWrapperStorageDecl();
auto *property = storageDecl->lookupDirect(propertyName).front();
return cast<VarDecl>(property)->isLet();
}

// If we aren't representing 'self' in a non-delegating initializer, then we
// can't have 'let' properties.
if (!isNonDelegatingInit())
return IsLet;

auto NTD = MemorySILType.getNominalOrBoundGenericNominal();

if (!NTD) {
// Otherwise, we miscounted elements?
assert(Element == 0 && "Element count problem");
Expand All @@ -496,7 +501,7 @@ bool DIMemoryObjectInfo::isElementLetProperty(unsigned Element) const {
auto &Module = MemoryInst->getModule();

auto expansionContext = TypeExpansionContext(*MemoryInst->getFunction());
for (auto *VD : NTD.get()->getStoredProperties()) {
for (auto *VD : NTD->getStoredProperties()) {
auto FieldType = MemorySILType.getFieldType(VD, Module, expansionContext);
unsigned NumFieldElements =
getElementCountRec(expansionContext, Module, FieldType, false);
Expand Down
14 changes: 0 additions & 14 deletions lib/Sema/CodeSynthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,20 +213,6 @@ static void maybeAddTypeWrapperDefaultArg(ParamDecl *arg, VarDecl *var,
if (!initExpr)
return;

// Type wrapper variables are never initialized directly,
// initialization expression (if any) becomes an default
// argument of the initializer synthesized by the type wrapper.
{
// Since type wrapper is applied to backing property, that's
// the the initializer it subsumes.
if (var->hasAttachedPropertyWrapper()) {
auto *backingVar = var->getPropertyWrapperBackingProperty();
PBD = backingVar->getParentPatternBinding();
}

PBD->setInitializerSubsumed(/*index=*/0);
}

arg->setDefaultExpr(initExpr, PBD->isInitializerChecked(/*index=*/0));
arg->setDefaultArgumentKind(DefaultArgumentKind::Normal);
}
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/TypeCheckTypeWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ VarDecl *GetTypeWrapperStorageForProperty::evaluate(Evaluator &evaluator,
auto *storage = wrappedType->getTypeWrapperStorageDecl();
assert(storage);

// Type wrapper variables are never initialized directly,
// initialization expression (if any) becomes an default
// argument of the initializer synthesized by the type wrapper.
if (auto *PBD = property->getParentPatternBinding()) {
PBD->setInitializerSubsumed(/*index=*/0);
}

return injectProperty(storage, property->getName(),
property->getValueInterfaceType(),
property->getIntroducer(), AccessLevel::Internal);
Expand Down
34 changes: 33 additions & 1 deletion test/Interpreter/Inputs/type_wrapper_defs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,11 @@ public class UserDefinedInitWithConditionalTest<T> {
@Wrapper
public class ClassWithConvenienceInit<T> {
public var a: T?
public var b: String
public var b: String = ""

public init(aWithoutB: T?) {
self.a = aWithoutB
}

init(a: T?, b: String) {
// Just to test that conditionals work properly
Expand Down Expand Up @@ -338,3 +342,31 @@ public struct TypeWithLetProperties<T> {
}
}
}

@Wrapper
public class TypeWithDefaultedLetProperties<T> {
let a: T? = nil
let b: Int = 0

public init() {
print(self.a)
print(self.b)
}
}

@Wrapper
public class TypeWithSomeDefaultedLetProperties<T> {
let a: T
let b: Int? = 0
@PropWrapper var c: String = "<default>"
@PropWrapperWithoutInit(value: [1, ""]) var d: [Any]

public init(a: T) {
self.a = a
self.c = "a"

print(self.a)
print(self.b)
print(self.c)
}
}
21 changes: 21 additions & 0 deletions test/Interpreter/type_wrappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,9 @@ do {
// CHECK-NEXT: in getter
// CHECK-NEXT: <modified>

let test2 = ClassWithConvenienceInit(aWithoutB: [1, ""])
// CHECK: Wrapper.init($Storage(a: Optional([1, ""]), b: ""))

func test<T>(_ v: T) {
let test1 = ClassWithConvenienceInit<(Int, String, T)>()
test1.a = (-1, "ultimate question", v)
Expand Down Expand Up @@ -622,4 +625,22 @@ do {
// CHECK-NEXT: Optional([1, 2, 3])
// CHECK-NEXT: in read-only getter
// CHECK-NEXT: 0

let test3 = TypeWithDefaultedLetProperties<[String]>()
// CHECK: Wrapper.init($Storage(a: nil, b: 0))
// CHECK-NEXT: in read-only getter
// CHECK-NEXT: nil
// CHECK-NEXT: in read-only getter
// CHECK-NEXT: 0

let test4 = TypeWithSomeDefaultedLetProperties(a: ["", 1.0])
// CHECK: Wrapper.init($Storage(a: ["", 1.0], b: Optional(0), _c: type_wrapper_defs.PropWrapper<Swift.String>(value: "<default>"), _d: type_wrapper_defs.PropWrapperWithoutInit<Swift.Array<Any>>(value: [1, ""])))
// CHECK-NEXT: in getter
// CHECK-NEXT: in setter => PropWrapper<String>(value: "a")
// CHECK-NEXT: in read-only getter
// CHECK-NEXT: ["", 1.0]
// CHECK-NEXT: in read-only getter
// CHECK-NEXT: Optional(0)
// CHECK-NEXT: in getter
// CHECK-NEXT: a
}
15 changes: 15 additions & 0 deletions test/SILOptimizer/type_wrapper_definite_init_diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,19 @@ do {
self.a = 0
}
}

@Wrapper
struct ImmutableReassignmentTest {
let x: Int = 42

init(x: Int) {
self.x = x // expected-error {{immutable value 'self.x' may only be initialized once}}
}

init(optX: Int?) {
if let x = optX {
self.x = x // expected-error {{immutable value 'self.x' may only be initialized once}}
}
}
}
}
31 changes: 31 additions & 0 deletions test/SILOptimizer/type_wrapper_init_transform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,34 @@ struct TypeWithLetProperties<T> {
}
}
}

@Wrapper
struct TypeWithDefaultedProperties<T> {
let a: [String]
let b: T? = nil
var c: Int = 42

// CHECK-LABEL: sil hidden [ossa] @$s4test27TypeWithDefaultedPropertiesV1aACyxGSaySSG_tcfC
// --> Defaults handling
// CHECK: [[LOCAL_STORAGE:%.*]] = alloc_stack [lexical] $(a: Array<String>, b: Optional<T>, c: Int), var, name "_storage", implicit
// CHECK-NEXT: [[A_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE]] : $*(a: Array<String>, b: Optional<T>, c: Int), 0
// CHECK-NEXT: [[B_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE]] : $*(a: Array<String>, b: Optional<T>, c: Int), 1
// CHECK-NEXT: [[C_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE]] : $*(a: Array<String>, b: Optional<T>, c: Int), 2
// CHECK-NEXT: inject_enum_addr [[B_REF]] : $*Optional<T>, #Optional.none!enumelt
// CHECK-NEXT: [[C_DEFAULT_VAL:%.*]] = integer_literal $Builtin.IntLiteral, 42
// CHECK: [[INT_INIT_REF:%.*]] = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int
// CHECK-NEXT: [[C_VAL:%.*]] = apply [[INT_INIT_REF]]([[C_DEFAULT_VAL]], {{.*}}) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int
// CHECK-NEXT: store [[C_VAL:%.*]] to [trivial] [[C_REF]] : $*Int
// --> Assignment to `let a`
// CHECK: [[LOCAL_STORAGE_ACCESS:%.*]] = begin_access [modify] [static] [[LOCAL_STORAGE]] : $*(a: Array<String>, b: Optional<T>, c: Int)
// CHECK-NEXT: [[A_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: Array<String>, b: Optional<T>, c: Int), 0
// CHECK-NEXT: assign [[A_ARG:%.*]] to [init] %18 : $*Array<String>
// CHECK-NEXT: end_access [[LOCAL_STORAGE_ACCESS]]
// --> Assignment to `var c`, note that the assignment is done to a wrapped value
// CHECK: [[C_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE]] : $*(a: Array<String>, b: Optional<T>, c: Int), 2
// CHECK: assign_by_wrapper origin type_wrapper, {{.*}} : $Int to [assign_wrapped_value] [[C_REF]] : $*Int, set {{.*}}
init(a: [String]) {
self.a = a
self.c = 0
}
}