Skip to content

Commit 0c29431

Browse files
committed
Sema: Diagnose invalid initializers on resilient types
Value type initializers must initialize stored properties directly if they do not delegate to another initializer via self.init(). Since direct stored property access is not permitted for resilient value types from outside their resilience domain, this means that such initializers are prohibited in two cases: - If the initializer is defined in an extension from outside the value type's resilience domain - If the initializer is public and @_inlineable, since it might get inlined outside the value type's resilience domain Right now, such initializers cannot *assign* to self either; I filed <https://bugs.swift.org/browse/SR-3686> to track the issue.
1 parent 6a540fe commit 0c29431

File tree

7 files changed

+132
-9
lines changed

7 files changed

+132
-9
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3356,10 +3356,19 @@ ERROR(resilience_decl_unavailable,
33563356
"cannot be referenced from " FRAGILE_FUNC_KIND "3",
33573357
(DescriptiveDeclKind, DeclName, Accessibility, unsigned))
33583358

3359+
#undef FRAGILE_FUNC_KIND
3360+
33593361
NOTE(resilience_decl_declared_here,
33603362
none, "%0 %1 is not '@_versioned' or public", (DescriptiveDeclKind, DeclName))
33613363

3362-
#undef FRAGILE_FUNC_KIND
3364+
ERROR(designated_init_in_extension_resilient,none,
3365+
"initializer declared in an extension of "
3366+
"non-'@_fixed_layout' type %0 must delegate to another initializer", (Type))
3367+
3368+
ERROR(designated_init_inlineable_resilient,none,
3369+
"initializer of non-'@_fixed_layout' type %0 is "
3370+
"'%select{@_transparent|@inline(__always)|@_inlineable|%error}1' and must "
3371+
"delegate to another initializer", (Type, unsigned))
33633372

33643373
//------------------------------------------------------------------------------
33653374
// @_specialize diagnostics

lib/Sema/CSApply.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ static bool shouldAccessStorageDirectly(Expr *base, VarDecl *member,
210210
member->getDeclContext()->getDeclaredInterfaceType()))
211211
return false;
212212

213+
// If the storage is resilient, we cannot access it directly at all.
214+
if (!member->hasFixedLayout(DC->getParentModule(),
215+
DC->getResilienceExpansion()))
216+
return false;
217+
213218
return true;
214219
}
215220

@@ -222,12 +227,6 @@ getImplicitMemberReferenceAccessSemantics(Expr *base, VarDecl *member,
222227
// accessors. However, in the init and destructor methods for the type
223228
// immediately containing the property, accesses are done direct.
224229
if (shouldAccessStorageDirectly(base, member, DC)) {
225-
// The storage better not be resilient.
226-
assert(member->hasFixedLayout(DC->getParentModule(),
227-
DC->getResilienceExpansion()) &&
228-
"Designated initializers and destructors of resilient types "
229-
"cannot be @_transparent or defined in extensions");
230-
231230
// Access this directly instead of going through (e.g.) observing or
232231
// trivial accessors.
233232
return AccessSemantics::DirectToStorage;

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,33 @@ bool TypeChecker::diagnoseInlineableDeclRef(SourceLoc loc,
9999

100100
return false;
101101
}
102+
103+
void TypeChecker::diagnoseResilientValueConstructor(ConstructorDecl *ctor) {
104+
auto nominalDecl = ctor->getDeclContext()
105+
->getAsNominalTypeOrNominalTypeExtensionContext();
106+
107+
bool isDelegating =
108+
(ctor->getDelegatingOrChainedInitKind(&Diags) ==
109+
ConstructorDecl::BodyInitKind::Delegating);
110+
111+
if (!isDelegating &&
112+
!nominalDecl->hasFixedLayout(ctor->getParentModule(),
113+
ctor->getResilienceExpansion())) {
114+
if (ctor->getResilienceExpansion() == ResilienceExpansion::Minimal) {
115+
// An @_inlineable designated initializer defined in a resilient type
116+
// cannot initialize stored properties directly, and must chain to
117+
// another initializer.
118+
diagnose(ctor->getLoc(),
119+
diag::designated_init_inlineable_resilient,
120+
ctor->getDeclContext()->getDeclaredInterfaceType(),
121+
getFragileFunctionKind(ctor));
122+
} else {
123+
// A designated initializer defined on an extension of a resilient
124+
// type from a different resilience domain cannot initialize stored
125+
// properties directly, and must chain to another initializer.
126+
diagnose(ctor->getLoc(),
127+
diag::designated_init_in_extension_resilient,
128+
ctor->getDeclContext()->getDeclaredInterfaceType());
129+
}
130+
}
131+
}

lib/Sema/TypeCheckDecl.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6416,6 +6416,13 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
64166416
} else if (auto extType = CD->getDeclContext()->getDeclaredInterfaceType()) {
64176417
// A designated initializer for a class must be written within the class
64186418
// itself.
6419+
//
6420+
// This is because designated initializers of classes get a vtable entry,
6421+
// and extensions cannot add vtable entries to the extended type.
6422+
//
6423+
// If we implement the ability for extensions defined in the same module
6424+
// (or the same file) to add vtable entries, we can re-evaluate this
6425+
// restriction.
64196426
if (extType->getClassOrBoundGenericClass() &&
64206427
isa<ExtensionDecl>(CD->getDeclContext())) {
64216428
TC.diagnose(CD->getLoc(), diag::designated_init_in_extension, extType)

lib/Sema/TypeCheckStmt.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,7 +1489,12 @@ bool TypeChecker::typeCheckConstructorBodyUntil(ConstructorDecl *ctor,
14891489
// Determine whether we need to introduce a super.init call.
14901490
auto nominalDecl = ctor->getDeclContext()
14911491
->getAsNominalTypeOrNominalTypeExtensionContext();
1492-
ClassDecl *ClassD = dyn_cast_or_null<ClassDecl>(nominalDecl);
1492+
1493+
// Error case.
1494+
if (nominalDecl == nullptr)
1495+
return HadError;
1496+
1497+
ClassDecl *ClassD = dyn_cast<ClassDecl>(nominalDecl);
14931498
bool wantSuperInitCall = false;
14941499
if (ClassD) {
14951500
bool isDelegating = false;
@@ -1521,18 +1526,22 @@ bool TypeChecker::typeCheckConstructorBodyUntil(ConstructorDecl *ctor,
15211526
}
15221527

15231528
// A class designated initializer must never be delegating.
1524-
if (ctor->isDesignatedInit() && ClassD && isDelegating) {
1529+
if (ctor->isDesignatedInit() && isDelegating) {
15251530
diagnose(ctor->getLoc(),
15261531
diag::delegating_designated_init,
15271532
ctor->getDeclContext()->getDeclaredInterfaceType())
15281533
.fixItInsert(ctor->getLoc(), "convenience ");
15291534
diagnose(initExpr->getLoc(), diag::delegation_here);
15301535
ctor->setInitKind(CtorInitializerKind::Convenience);
15311536
}
1537+
} else {
1538+
diagnoseResilientValueConstructor(ctor);
15321539
}
15331540

15341541
// If we want a super.init call...
15351542
if (wantSuperInitCall) {
1543+
assert(ClassD != nullptr);
1544+
15361545
// Find a default initializer in the superclass.
15371546
if (Expr *SuperInitCall = constructCallToSuperInit(ctor, ClassD)) {
15381547
// If the initializer we found is a designated initializer, we're okay.

lib/Sema/TypeChecker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,8 @@ class TypeChecker final : public LazyResolver {
19431943
bool diagnoseInlineableDeclRef(SourceLoc loc, const ValueDecl *D,
19441944
const DeclContext *DC);
19451945

1946+
void diagnoseResilientValueConstructor(ConstructorDecl *ctor);
1947+
19461948
/// \name Availability checking
19471949
///
19481950
/// Routines that perform API availability checking and type checking of

test/decl/init/resilience.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// RUN: rm -rf %t && mkdir %t
2+
// RUN: %target-swift-frontend -emit-module -enable-resilience -emit-module-path=%t/resilient_struct.swiftmodule -module-name=resilient_struct %S/../../Inputs/resilient_struct.swift
3+
// RUN: %target-swift-frontend -typecheck -verify -enable-resilience -I %t %s
4+
5+
import resilient_struct
6+
7+
// Point is @_fixed_layout -- this is OK
8+
extension Point {
9+
init(xx: Int, yy: Int) {
10+
self.x = xx
11+
self.y = yy
12+
}
13+
}
14+
15+
// Size is not @_fixed_layout, so we cannot define a new designated initializer
16+
extension Size {
17+
init(ww: Int, hh: Int) {
18+
// expected-error@-1 {{initializer declared in an extension of non-'@_fixed_layout' type 'Size' must delegate to another initializer}}
19+
self.w = ww
20+
self.h = hh
21+
}
22+
23+
// This is OK
24+
init(www: Int, hhh: Int) {
25+
self.init(w: www, h: hhh)
26+
}
27+
28+
// FIXME: This should be allowed, but Sema doesn't distinguish this
29+
// case from memberwise initialization, and DI explodes the value type
30+
init(other: Size) {
31+
// expected-error@-1 {{initializer declared in an extension of non-'@_fixed_layout' type 'Size' must delegate to another initializer}}
32+
self = other
33+
}
34+
}
35+
36+
// Animal is not @_fixed_layout, so we cannot define an @_inlineable
37+
// designated initializer
38+
public struct Animal {
39+
public let name: String
40+
41+
@_inlineable public init(name: String) {
42+
// expected-error@-1 {{initializer of non-'@_fixed_layout' type 'Animal' is '@_inlineable' and must delegate to another initializer}}
43+
self.name = name
44+
}
45+
46+
@inline(__always) public init(dog: String) {
47+
// expected-error@-1 {{initializer of non-'@_fixed_layout' type 'Animal' is '@inline(__always)' and must delegate to another initializer}}
48+
self.name = dog
49+
}
50+
51+
@_transparent public init(cat: String) {
52+
// expected-error@-1 {{initializer of non-'@_fixed_layout' type 'Animal' is '@_transparent' and must delegate to another initializer}}
53+
self.name = cat
54+
}
55+
56+
// This is OK
57+
@_inlineable public init(cow: String) {
58+
self.init(name: cow)
59+
}
60+
61+
// FIXME: This should be allowed, but Sema doesn't distinguish this
62+
// case from memberwise initialization, and DI explodes the value type
63+
@_inlineable public init(other: Animal) {
64+
// expected-error@-1 {{initializer of non-'@_fixed_layout' type 'Animal' is '@_inlineable' and must delegate to another initializer}}
65+
self = other
66+
}
67+
}

0 commit comments

Comments
 (0)