Skip to content

Commit 3c01677

Browse files
committed
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 (cherry picked from commit 357c7d6)
1 parent edddb75 commit 3c01677

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
@@ -831,6 +831,35 @@ deriveBodyHashable_compat_hashInto(AbstractFunctionDecl *hashIntoDecl) {
831831
hashIntoDecl->setBody(body);
832832
}
833833

834+
/// Derive the body for the 'hash(into:)' method for an enum by using its raw
835+
/// value.
836+
static void
837+
deriveBodyHashable_enum_rawValue_hashInto(
838+
AbstractFunctionDecl *hashIntoDecl
839+
) {
840+
// enum SomeEnum: Int {
841+
// case A, B, C
842+
// @derived func hash(into hasher: inout Hasher) {
843+
// hasher.combine(self.rawValue)
844+
// }
845+
// }
846+
ASTContext &C = hashIntoDecl->getASTContext();
847+
848+
// generate: self.rawValue
849+
auto *selfRef = DerivedConformance::createSelfDeclRef(hashIntoDecl);
850+
auto *rawValueRef = new (C) UnresolvedDotExpr(selfRef, SourceLoc(),
851+
C.Id_rawValue, DeclNameLoc(),
852+
/*Implicit=*/true);
853+
854+
// generate: hasher.combine(discriminator)
855+
auto hasherParam = hashIntoDecl->getParameterList(1)->get(0);
856+
ASTNode combineStmt = createHasherCombineCall(C, hasherParam, rawValueRef);
857+
858+
auto body = BraceStmt::create(C, SourceLoc(), combineStmt, SourceLoc(),
859+
/*implicit*/ true);
860+
hashIntoDecl->setBody(body);
861+
}
862+
834863
/// Derive the body for the 'hash(into:)' method for an enum without associated
835864
/// values.
836865
static void
@@ -1203,10 +1232,13 @@ ValueDecl *DerivedConformance::deriveHashable(ValueDecl *requirement) {
12031232
return nullptr;
12041233

12051234
if (auto ED = dyn_cast<EnumDecl>(Nominal)) {
1206-
auto bodySynthesizer =
1207-
!ED->hasOnlyCasesWithoutAssociatedValues()
1208-
? &deriveBodyHashable_enum_hasAssociatedValues_hashInto
1209-
: &deriveBodyHashable_enum_noAssociatedValues_hashInto;
1235+
void (*bodySynthesizer)(AbstractFunctionDecl *);
1236+
if (ED->isObjC())
1237+
bodySynthesizer = deriveBodyHashable_enum_rawValue_hashInto;
1238+
else if (ED->hasOnlyCasesWithoutAssociatedValues())
1239+
bodySynthesizer = deriveBodyHashable_enum_noAssociatedValues_hashInto;
1240+
else
1241+
bodySynthesizer=deriveBodyHashable_enum_hasAssociatedValues_hashInto;
12101242
return deriveHashable_hashInto(*this, bodySynthesizer);
12111243
} else if (isa<StructDecl>(Nominal))
12121244
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
@objc enum SwiftEnum : Int32 {
177201
case A, B, C
@@ -264,6 +288,18 @@ EnumTestSuite.test("UnexpectedOkayNested2/SwiftExhaustive") {
264288
expectTrue(gotCorrectValue)
265289
}
266290

291+
EnumTestSuite.test("Equatable/SwiftExhaustive") {
292+
expectEqual(SwiftEnum.getExpectedValue(), .B)
293+
expectNotEqual(SwiftEnum.getUnexpectedValue(), .B)
294+
expectNotEqual(SwiftEnum.getExpectedValue(), SwiftEnum.getUnexpectedValue())
295+
expectEqual(SwiftEnum.getUnexpectedValue(), SwiftEnum.getUnexpectedValue())
296+
}
297+
298+
EnumTestSuite.test("Hashable/SwiftExhaustive") {
299+
expectEqual(SwiftEnum.getExpectedValue().hashValue, SwiftEnum.B.hashValue)
300+
expectNotEqual(SwiftEnum.getUnexpectedValue().hashValue, SwiftEnum.B.hashValue)
301+
}
302+
267303
@inline(never)
268304
func switchOnTwoThings<T>(_ a: T, _ b: SwiftEnum) {
269305
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-frontend -emit-silgen -enable-sil-ownership %s | %FileCheck %s
2-
// RUN: %target-swift-frontend -emit-silgen -enable-sil-ownership -enable-resilience %s | %FileCheck -check-prefix=CHECK-RESILIENT %s
1+
// RUN: %target-swift-frontend -emit-silgen -enable-sil-ownership -emit-sorted-sil %s | %FileCheck %s
2+
// RUN: %target-swift-frontend -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-frontend -emit-silgen -enable-sil-ownership -emit-sorted-sil -enable-objc-interop -disable-objc-attr-requires-foundation-module %s | %FileCheck %s
2+
// RUN: %target-swift-frontend -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)