Skip to content

Commit 7bcd8ff

Browse files
committed
[Sema] TypeWrappers: Replace self. with _storage. for immutable properties
Immutable properties cannot be re-assigned and don't have setters which means that we cannot use `assign_by_wrapper` instruction to handle `let` properties, but we can use a direct reference to `_storage` property instead when immutable property appears as an assignment destination and let read references go through a getter still.
1 parent 644a7f8 commit 7bcd8ff

File tree

5 files changed

+218
-5
lines changed

5 files changed

+218
-5
lines changed

lib/Sema/CSApply.cpp

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,6 +1651,56 @@ namespace {
16511651

16521652
// For properties, build member references.
16531653
if (auto *varDecl = dyn_cast<VarDecl>(member)) {
1654+
// \returns result of the given function type
1655+
auto resultType = [](Type fnTy) -> Type {
1656+
return fnTy->castTo<FunctionType>()->getResult();
1657+
};
1658+
1659+
auto isAssignmentDestination = [&](ConstraintLocatorBuilder locator) {
1660+
if (auto *anchor = getAsExpr(locator.getAnchor())) {
1661+
if (auto *assignment =
1662+
getAsExpr<AssignExpr>(cs.getParentExpr(anchor)))
1663+
return assignment->getDest() == anchor;
1664+
}
1665+
return false;
1666+
};
1667+
1668+
// If this is a reference to an immutable type wrapper
1669+
// managed property used as an assignment destination
1670+
// i.e. `self.<name> = ...` in an initializer context,
1671+
// let's rewrite member access from `self` to `_storage`
1672+
// injected by the compiler to support type wrapper
1673+
// initialization.
1674+
//
1675+
// Note that this is safe to do only for immutable
1676+
// properties because they do no support re-assignment.
1677+
if (isa<ConstructorDecl>(cs.DC) && varDecl->isLet() &&
1678+
varDecl->isAccessedViaTypeWrapper() &&
1679+
isAssignmentDestination(memberLocator)) {
1680+
auto *ctor = cast<ConstructorDecl>(cs.DC);
1681+
auto *storageVar = ctor->getLocalTypeWrapperStorageVar();
1682+
auto *storageVarTy =
1683+
storageVar->getInterfaceType()->castTo<TupleType>();
1684+
1685+
base =
1686+
new (context) DeclRefExpr(storageVar, DeclNameLoc(base->getLoc()),
1687+
/*implicit=*/true);
1688+
base->setType(
1689+
LValueType::get(ctor->mapTypeIntoContext(storageVarTy)));
1690+
1691+
cs.cacheType(base);
1692+
1693+
Expr *memberRefExpr = new (context) TupleElementExpr(
1694+
base, /*DotLoc=*/SourceLoc(),
1695+
storageVarTy->getNamedElementId(varDecl->getName()),
1696+
memberLoc.getBaseNameLoc(), resultType(refTy));
1697+
memberRefExpr->setImplicit();
1698+
1699+
cs.cacheType(memberRefExpr);
1700+
1701+
return forceUnwrapIfExpected(memberRefExpr, memberLocator);
1702+
}
1703+
16541704
if (isUnboundInstanceMember) {
16551705
assert(memberLocator.getBaseLocator() &&
16561706
cs.UnevaluatedRootExprs.count(
@@ -1677,11 +1727,6 @@ namespace {
16771727
containerTy, 1);
16781728
}
16791729

1680-
// \returns result of the given function type
1681-
auto resultType = [](Type fnTy) -> Type {
1682-
return fnTy->castTo<FunctionType>()->getResult();
1683-
};
1684-
16851730
cs.setType(memberRefExpr, resultType(refTy));
16861731

16871732
Expr *result = memberRefExpr;

test/Interpreter/Inputs/type_wrapper_defs.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,31 @@ public class ClassWithConvenienceInit<T> {
310310
print(self.b)
311311
}
312312
}
313+
314+
@Wrapper
315+
public struct TypeWithLetProperties<T> {
316+
let a: T
317+
let b: Int
318+
319+
public init(a: T, b: Int? = nil, onSet: (() -> Void)? = nil) {
320+
self.a = a
321+
if let b {
322+
self.b = b
323+
} else {
324+
self.b = 0
325+
}
326+
327+
print("--Before onSet--")
328+
329+
print(self.a)
330+
print(self.b)
331+
332+
if let onSet {
333+
onSet()
334+
335+
print("--After onSet--")
336+
print(self.a)
337+
print(self.b)
338+
}
339+
}
340+
}

test/Interpreter/type_wrappers.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,3 +588,38 @@ do {
588588
// CHECK-NEXT: in getter
589589
// CHECK-NEXT: Optional((-1, "ultimate question", (a: 1, b: 2.0, c: 3)))
590590
}
591+
592+
do {
593+
class X : CustomStringConvertible {
594+
var x: [Int] = []
595+
596+
var description: String {
597+
"X(x: \(x))"
598+
}
599+
}
600+
601+
var arg = X()
602+
603+
let test1 = TypeWithLetProperties(a: arg, b: 42) {
604+
arg.x.append(1)
605+
}
606+
// CHECK: Wrapper.init($Storage(a: X(x: []), b: 42))
607+
// CHECK-NEXT: --Before onSet--
608+
// CHECK-NEXT: in read-only getter
609+
// CHECK-NEXT: X(x: [])
610+
// CHECK-NEXT: in read-only getter
611+
// CHECK-NEXT: 42
612+
// CHECK-NEXT: --After onSet--
613+
// CHECK-NEXT: in read-only getter
614+
// CHECK-NEXT: X(x: [1])
615+
// CHECK-NEXT: in read-only getter
616+
// CHECK-nEXT: 42
617+
618+
let test2 = TypeWithLetProperties(a: Optional.some([1, 2, 3]))
619+
// CHECK: Wrapper.init($Storage(a: Optional([1, 2, 3]), b: 0))
620+
// CHECK-NEXT: --Before onSet--
621+
// CHECK-NEXT: in read-only getter
622+
// CHECK-NEXT: Optional([1, 2, 3])
623+
// CHECK-NEXT: in read-only getter
624+
// CHECK-NEXT: 0
625+
}

test/SILOptimizer/type_wrapper_init_transform.swift

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ public struct Wrapper<S> {
1111
self.underlying = memberwise
1212
}
1313

14+
public subscript<V>(storageKeyPath path: KeyPath<S, V>) -> V {
15+
get { underlying[keyPath: path] }
16+
}
17+
1418
public subscript<V>(storageKeyPath path: WritableKeyPath<S, V>) -> V {
1519
get { underlying[keyPath: path] }
1620
set { underlying[keyPath: path] = newValue }
@@ -332,3 +336,81 @@ class ClassWithConvenienceInit<T> {
332336
}
333337
}
334338
}
339+
340+
@Wrapper
341+
struct TypeWithLetProperties<T> {
342+
let a: T
343+
let b: Int
344+
345+
// CHECK-LABEL: sil hidden [ossa] @$s4test21TypeWithLetPropertiesV1a1b5onSetACyxGx_SiSgyycSgtcfC
346+
// CHECK: [[LOCAL_STORAGE:%.*]] = alloc_stack [lexical] $(a: T, b: Int), var, name "_storage", implicit
347+
public init(a: T, b: Int? = nil, onSet: (() -> Void)? = nil) {
348+
// CHECK: [[LOCAL_STORAGE_ACCESS:%.*]] = begin_access [modify] [static] [[LOCAL_STORAGE]] : $*(a: T, b: Int)
349+
// CHECK-NEXT: [[A_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int), 0
350+
// CHECK-NEXT: copy_addr [take] %11 to [initialization] [[A_REF]] : $*T
351+
// CHECK-NOT: {{.*}} = assign_by_wrapper {{.*}}
352+
// CHECK-NEXT: end_access [[LOCAL_STORAGE_ACCESS]]
353+
self.a = a
354+
if let b {
355+
// CHECK: [[LOCAL_STORAGE_ACCESS:%.*]] = begin_access [modify] [static] [[LOCAL_STORAGE]] : $*(a: T, b: Int)
356+
// CHECK-NEXT: [[B_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int), 1
357+
// CHECK-NEXT: assign {{.*}} to [init] [[B_REF]] : $*Int
358+
// CHECK-NOT: {{.*}} = assign_by_wrapper {{.*}}
359+
// CHECK-NEXT: end_access [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int)
360+
361+
// CHECK: [[STORAGE:%.*]] = alloc_stack $TypeWithLetProperties<T>.$Storage
362+
// CHECK: [[STORAGE_INIT_REF:%.*]] = function_ref @$s4test21TypeWithLetPropertiesV8$StorageV1a1bAEyx_Gx_SitcfC
363+
364+
// CHECK: [[LOCAL_STORAGE_ACCESS:%.*]] = begin_access [read] [unsafe] [[LOCAL_STORAGE]] : $*(a: T, b: Int)
365+
// CHECK-NEXT: [[A_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int), 0
366+
// CHECK-NEXT: [[T:%.*]] = alloc_stack $T
367+
// CHECK-NEXT: copy_addr [[A_REF]] to [initialization] [[T]] : $*T
368+
// CHECK-NEXT: [[B_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int), 1
369+
// CHECK-NEXT: [[B_VAL:%.*]] = load [trivial] [[B_REF]] : $*Int
370+
// CHECK-NEXT: end_access [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int)
371+
372+
// CHECK-NEXT: [[STORAGE_METATYPE:%.*]] = metatype $@thin TypeWithLetProperties<T>.$Storage.Type
373+
// CHECK-NEXT: {{.*}} = apply [[STORAGE_INIT_REF]]<T>([[STORAGE]], [[T]], [[B_VAL]], [[STORAGE_METATYPE]])
374+
375+
// CHECK: [[WRAPPER_INIT_REF:%.*]] = function_ref @$s4test7WrapperV10memberwiseACyxGx_tcfC
376+
// CHECK: [[STORAGE_PROP:%.*]] = struct_element_addr [[SELF_ACCESS:%.*]] : $*TypeWithLetProperties<T>, #TypeWithLetProperties.$_storage
377+
// CHECK: [[WRAPPER_INST:%.*]] = alloc_stack $Wrapper<TypeWithLetProperties<T>.$Storage>
378+
// CHECK-NEXT: {{.*}} = apply [[WRAPPER_INIT_REF]]<TypeWithLetProperties<T>.$Storage>([[WRAPPER_INST]], [[STORAGE]], [[WRAPPER_METATYPE:%.*]])
379+
380+
// CHECK: [[STORAGE_PROP_ACCESS:%.*]] = begin_access [modify] [dynamic] [[STORAGE_PROP]] : $*Wrapper<TypeWithLetProperties<T>.$Storage>
381+
// CHECK-NEXT: copy_addr [take] [[WRAPPER_INST]] to [initialization] [[STORAGE_PROP_ACCESS]] : $*Wrapper<TypeWithLetProperties<T>.$Storage>
382+
// CHECK-NEXT: end_access [[STORAGE_PROP_ACCESS]]
383+
self.b = b
384+
} else {
385+
// CHECK: [[LOCAL_STORAGE_ACCESS:%.*]] = begin_access [modify] [static] [[LOCAL_STORAGE]] : $*(a: T, b: Int)
386+
// CHECK-NEXT: [[B_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int), 1
387+
// CHECK-NEXT: assign {{.*}} to [init] [[B_REF]] : $*Int
388+
// CHECK-NOT: {{.*}} = assign_by_wrapper {{.*}}
389+
// CHECK-NEXT: end_access [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int)
390+
391+
// CHECK: [[STORAGE:%.*]] = alloc_stack $TypeWithLetProperties<T>.$Storage
392+
// CHECK: [[STORAGE_INIT_REF:%.*]] = function_ref @$s4test21TypeWithLetPropertiesV8$StorageV1a1bAEyx_Gx_SitcfC
393+
394+
// CHECK: [[LOCAL_STORAGE_ACCESS:%.*]] = begin_access [read] [unsafe] [[LOCAL_STORAGE]] : $*(a: T, b: Int)
395+
// CHECK-NEXT: [[A_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int), 0
396+
// CHECK-NEXT: [[T:%.*]] = alloc_stack $T
397+
// CHECK-NEXT: copy_addr [[A_REF]] to [initialization] [[T]] : $*T
398+
// CHECK-NEXT: [[B_REF:%.*]] = tuple_element_addr [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int), 1
399+
// CHECK-NEXT: [[B_VAL:%.*]] = load [trivial] [[B_REF]] : $*Int
400+
// CHECK-NEXT: end_access [[LOCAL_STORAGE_ACCESS]] : $*(a: T, b: Int)
401+
402+
// CHECK-NEXT: [[STORAGE_METATYPE:%.*]] = metatype $@thin TypeWithLetProperties<T>.$Storage.Type
403+
// CHECK-NEXT: {{.*}} = apply [[STORAGE_INIT_REF]]<T>([[STORAGE]], [[T]], [[B_VAL]], [[STORAGE_METATYPE]])
404+
405+
// CHECK: [[WRAPPER_INIT_REF:%.*]] = function_ref @$s4test7WrapperV10memberwiseACyxGx_tcfC
406+
// CHECK: [[STORAGE_PROP:%.*]] = struct_element_addr [[SELF_ACCESS:%.*]] : $*TypeWithLetProperties<T>, #TypeWithLetProperties.$_storage
407+
// CHECK: [[WRAPPER_INST:%.*]] = alloc_stack $Wrapper<TypeWithLetProperties<T>.$Storage>
408+
// CHECK-NEXT: {{.*}} = apply [[WRAPPER_INIT_REF]]<TypeWithLetProperties<T>.$Storage>([[WRAPPER_INST]], [[STORAGE]], [[WRAPPER_METATYPE:%.*]])
409+
410+
// CHECK: [[STORAGE_PROP_ACCESS:%.*]] = begin_access [modify] [dynamic] [[STORAGE_PROP]] : $*Wrapper<TypeWithLetProperties<T>.$Storage>
411+
// CHECK-NEXT: copy_addr [take] [[WRAPPER_INST]] to [initialization] [[STORAGE_PROP_ACCESS]] : $*Wrapper<TypeWithLetProperties<T>.$Storage>
412+
// CHECK-NEXT: end_access [[STORAGE_PROP_ACCESS]]
413+
self.b = 0
414+
}
415+
}
416+
}

test/type/type_wrapper.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,3 +480,26 @@ func testMissingReadOnlyAndWritableSubscriptsAreDiagnosed() {
480480
}
481481
}
482482
}
483+
484+
func testIncorrectUsesOfImmutableProperties() {
485+
class X<T> {
486+
var storage: [T]
487+
488+
init(storage: [T]) {
489+
self.storage = storage
490+
}
491+
}
492+
493+
@NoopWrapper
494+
struct Test<T> {
495+
let x: T? // expected-note {{change 'let' to 'var' to make it mutable}}
496+
497+
init(x: T) {
498+
self.x = x
499+
}
500+
}
501+
502+
let test = Test(x: X(storage: [1, 2, 3]))
503+
test.x = X(storage: [0]) // expected-error {{cannot assign to property: 'x' is a 'let' constant}}
504+
test.x?.storage.append(0) // Ok
505+
}

0 commit comments

Comments
 (0)