Skip to content

Commit 357c7d6

Browse files
authored
Handle unexpected raw values for @objc enums in derived conformances (#17836)
Because people put all sorts of nonsense into @objc enums (most reasonably, "private cases", which represent valid values that are not API), the Swift-synthesized implementation of 'hash(into:)' needs to not expect a switch statement to be exhaustive. And since Swift-defined @objc enums are supposed to behave enough like C-defined enums, they should at least handle simple method calls with an invalid raw value, which means that 'rawValue' likewise should not use a switch. This patch provides alternate implementations that look like this: extension ImportedEnum { public var rawValue: Int { return unsafeBitCast(self, to: Int.self) } public func hash(into hasher: inout Hasher) { hasher.combine(self.rawValue) } } rdar://problem/41913284
1 parent 18ec4ad commit 357c7d6

File tree

5 files changed

+122
-8
lines changed

5 files changed

+122
-8
lines changed

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,35 @@ deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) {
815815
hashIntoDecl->setBody(body);
816816
}
817817

818+
/// Derive the body for the 'hash(into:)' method for an enum by using its raw
819+
/// value.
820+
static void
821+
deriveBodyHashable_enum_rawValue_hashInto(
822+
AbstractFunctionDecl *hashIntoDecl
823+
) {
824+
// enum SomeEnum: Int {
825+
// case A, B, C
826+
// @derived func hash(into hasher: inout Hasher) {
827+
// hasher.combine(self.rawValue)
828+
// }
829+
// }
830+
ASTContext &C = hashIntoDecl->getASTContext();
831+
832+
// generate: self.rawValue
833+
auto *selfRef = DerivedConformance::createSelfDeclRef(hashIntoDecl);
834+
auto *rawValueRef = new (C) UnresolvedDotExpr(selfRef, SourceLoc(),
835+
C.Id_rawValue, DeclNameLoc(),
836+
/*Implicit=*/true);
837+
838+
// generate: hasher.combine(discriminator)
839+
auto hasherParam = hashIntoDecl->getParameterList(1)->get(0);
840+
ASTNode combineStmt = createHasherCombineCall(C, hasherParam, rawValueRef);
841+
842+
auto body = BraceStmt::create(C, SourceLoc(), combineStmt, SourceLoc(),
843+
/*implicit*/ true);
844+
hashIntoDecl->setBody(body);
845+
}
846+
818847
/// Derive the body for the 'hash(into:)' method for an enum without associated
819848
/// values.
820849
static void
@@ -1184,10 +1213,13 @@ ValueDecl *DerivedConformance::deriveHashable(ValueDecl *requirement) {
11841213
return nullptr;
11851214

11861215
if (auto ED = dyn_cast<EnumDecl>(Nominal)) {
1187-
auto bodySynthesizer =
1188-
!ED->hasOnlyCasesWithoutAssociatedValues()
1189-
? &deriveBodyHashable_enum_hasAssociatedValues_hashInto
1190-
: &deriveBodyHashable_enum_noAssociatedValues_hashInto;
1216+
void (*bodySynthesizer)(AbstractFunctionDecl *);
1217+
if (ED->isObjC())
1218+
bodySynthesizer = deriveBodyHashable_enum_rawValue_hashInto;
1219+
else if (ED->hasOnlyCasesWithoutAssociatedValues())
1220+
bodySynthesizer = deriveBodyHashable_enum_noAssociatedValues_hashInto;
1221+
else
1222+
bodySynthesizer=deriveBodyHashable_enum_hasAssociatedValues_hashInto;
11911223
return deriveHashable_hashInto(*this, bodySynthesizer);
11921224
} else if (isa<StructDecl>(Nominal))
11931225
return deriveHashable_hashInto(*this,

lib/Sema/DerivedConformanceRawRepresentable.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,27 @@ static void deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl) {
9090
}
9191
#endif
9292

93+
if (enumDecl->isObjC()) {
94+
// Special case: ObjC enums are represented by their raw value, so just use
95+
// a bitcast.
96+
97+
// return unsafeBitCast(self, to: RawType.self)
98+
DeclName name(C, C.getIdentifier("unsafeBitCast"), {Identifier(), C.Id_to});
99+
auto functionRef = new (C) UnresolvedDeclRefExpr(name,
100+
DeclRefKind::Ordinary,
101+
DeclNameLoc());
102+
auto selfRef = DerivedConformance::createSelfDeclRef(toRawDecl);
103+
auto bareTypeExpr = TypeExpr::createImplicit(rawTy, C);
104+
auto typeExpr = new (C) DotSelfExpr(bareTypeExpr, SourceLoc(), SourceLoc());
105+
auto call = CallExpr::createImplicit(C, functionRef, {selfRef, typeExpr},
106+
{Identifier(), C.Id_to});
107+
auto returnStmt = new (C) ReturnStmt(SourceLoc(), call);
108+
auto body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt),
109+
SourceLoc());
110+
toRawDecl->setBody(body);
111+
return;
112+
}
113+
93114
Type enumType = parentDC->getDeclaredTypeInContext();
94115

95116
SmallVector<ASTNode, 4> cases;

test/Interpreter/enum-nonexhaustivity.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ EnumTestSuite.test("UnexpectedOkayNested2/NonExhaustive") {
9191
expectTrue(gotCorrectValue)
9292
}
9393

94+
EnumTestSuite.test("Equatable/NonExhaustive") {
95+
expectEqual(getExpectedValue(), .B)
96+
expectNotEqual(getUnexpectedValue(), .B)
97+
expectNotEqual(getExpectedValue(), getUnexpectedValue())
98+
expectEqual(getUnexpectedValue(), getUnexpectedValue())
99+
}
100+
101+
EnumTestSuite.test("Hashable/NonExhaustive") {
102+
expectEqual(getExpectedValue().hashValue, NonExhaustiveEnum.B.hashValue)
103+
expectNotEqual(getUnexpectedValue().hashValue, NonExhaustiveEnum.B.hashValue)
104+
}
105+
94106

95107
EnumTestSuite.test("PlainOldSwitch/LyingExhaustive") {
96108
var gotCorrectValue = false
@@ -172,6 +184,18 @@ EnumTestSuite.test("UnexpectedOkayNested2/LyingExhaustive") {
172184
expectTrue(gotCorrectValue)
173185
}
174186

187+
EnumTestSuite.test("Equatable/LyingExhaustive") {
188+
expectEqual(getExpectedLiarValue(), .B)
189+
expectNotEqual(getUnexpectedLiarValue(), .B)
190+
expectNotEqual(getExpectedLiarValue(), getUnexpectedLiarValue())
191+
expectEqual(getUnexpectedLiarValue(), getUnexpectedLiarValue())
192+
}
193+
194+
EnumTestSuite.test("Hashable/LyingExhaustive") {
195+
expectEqual(getExpectedLiarValue().hashValue, LyingExhaustiveEnum.B.hashValue)
196+
expectNotEqual(getUnexpectedLiarValue().hashValue, LyingExhaustiveEnum.B.hashValue)
197+
}
198+
175199

176200
#if _runtime(_ObjC)
177201
@objc enum SwiftEnum : Int32 {
@@ -265,6 +289,18 @@ EnumTestSuite.test("UnexpectedOkayNested2/SwiftExhaustive") {
265289
expectTrue(gotCorrectValue)
266290
}
267291

292+
EnumTestSuite.test("Equatable/SwiftExhaustive") {
293+
expectEqual(SwiftEnum.getExpectedValue(), .B)
294+
expectNotEqual(SwiftEnum.getUnexpectedValue(), .B)
295+
expectNotEqual(SwiftEnum.getExpectedValue(), SwiftEnum.getUnexpectedValue())
296+
expectEqual(SwiftEnum.getUnexpectedValue(), SwiftEnum.getUnexpectedValue())
297+
}
298+
299+
EnumTestSuite.test("Hashable/SwiftExhaustive") {
300+
expectEqual(SwiftEnum.getExpectedValue().hashValue, SwiftEnum.B.hashValue)
301+
expectNotEqual(SwiftEnum.getUnexpectedValue().hashValue, SwiftEnum.B.hashValue)
302+
}
303+
268304
@inline(never)
269305
func switchOnTwoThings<T>(_ a: T, _ b: SwiftEnum) {
270306
switch (a, b) {
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
// RUN: %target-swift-emit-silgen -enable-sil-ownership %s | %FileCheck %s
2-
// RUN: %target-swift-emit-silgen -enable-sil-ownership -enable-resilience %s | %FileCheck -check-prefix=CHECK-RESILIENT %s
1+
// RUN: %target-swift-emit-silgen -enable-sil-ownership -emit-sorted-sil %s | %FileCheck %s
2+
// RUN: %target-swift-emit-silgen -enable-sil-ownership -emit-sorted-sil -enable-resilience %s | %FileCheck -check-prefix=CHECK-RESILIENT %s
33

44
public enum E: Int {
55
case a, b, c
66
}
77

8-
// CHECK-DAG: sil [serialized] @$S22enum_raw_representable1EO0B5ValueACSgSi_tcfC
9-
// CHECK-DAG: sil [serialized] @$S22enum_raw_representable1EO0B5ValueSivg
8+
// CHECK-LABEL: sil [serialized] @$S22enum_raw_representable1EO0B5ValueACSgSi_tcfC
9+
10+
// CHECK-LABEL: sil [serialized] @$S22enum_raw_representable1EO0B5ValueSivg
11+
// CHECK: switch_enum %0 : $E
12+
// CHECK: end sil function '$S22enum_raw_representable1EO0B5ValueSivg'
13+
1014

1115
// CHECK-RESILIENT-DAG: sil @$S22enum_raw_representable1EO0B5ValueACSgSi_tcfC
1216
// CHECK-RESILIENT-DAG: sil @$S22enum_raw_representable1EO0B5ValueSivg
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-swift-emit-silgen -enable-sil-ownership -emit-sorted-sil -enable-objc-interop -disable-objc-attr-requires-foundation-module %s | %FileCheck %s
2+
// RUN: %target-swift-emit-silgen -enable-sil-ownership -emit-sorted-sil -enable-objc-interop -disable-objc-attr-requires-foundation-module -enable-resilience %s | %FileCheck -check-prefix=CHECK-RESILIENT %s
3+
4+
@objc public enum CLike: Int {
5+
case a, b, c
6+
}
7+
8+
// CHECK-LABEL: sil [serialized] @$S27enum_raw_representable_objc5CLikeO0B5ValueACSgSi_tcfC
9+
10+
// CHECK-LABEL: sil [serialized] @$S27enum_raw_representable_objc5CLikeO0B5ValueSivg
11+
// CHECK-DAG: [[RESULT_BOX:%.+]] = alloc_stack $Int
12+
// CHECK-DAG: [[INPUT_BOX:%.+]] = alloc_stack $CLike
13+
// CHECK: [[RAW_TYPE:%.+]] = metatype $@thick Int.Type
14+
// CHECK: [[CAST_FUNC:%.+]] = function_ref @$Ss13unsafeBitCast_2toq_x_q_mtr0_lF
15+
// CHECK: = apply [[CAST_FUNC]]<CLike, Int>([[RESULT_BOX]], [[INPUT_BOX]], [[RAW_TYPE]])
16+
// CHECK: [[RESULT:%.+]] = load [trivial] [[RESULT_BOX]]
17+
// CHECK: return [[RESULT]]
18+
// CHECK: end sil function '$S27enum_raw_representable_objc5CLikeO0B5ValueSivg'
19+
20+
// CHECK-RESILIENT-DAG: sil @$S27enum_raw_representable_objc5CLikeO0B5ValueSivg
21+
// CHECK-RESILIENT-DAG: sil @$S27enum_raw_representable_objc5CLikeO0B5ValueACSgSi_tcfC

0 commit comments

Comments
 (0)