Skip to content

Commit 927a2b3

Browse files
authored
Merge pull request swiftlang#35694 from tbkka/tbkka/compatibleOptionalAnyHashableCasting54
[5.4] Restore behavior of Optional -> AnyHashable casts
2 parents cbdb032 + 782f693 commit 927a2b3

File tree

2 files changed

+85
-1
lines changed

2 files changed

+85
-1
lines changed

stdlib/public/runtime/DynamicCast.cpp

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,49 @@ tryCastToAnyHashable(
709709
assert(cast<StructMetadata>(destType)->Description
710710
== &STRUCT_TYPE_DESCR_SYM(s11AnyHashable));
711711

712+
switch (srcType->getKind()) {
713+
case MetadataKind::ForeignClass: // CF -> String
714+
case MetadataKind::ObjCClassWrapper: { // Obj-C -> String
715+
#if SWIFT_OBJC_INTEROP
716+
// TODO: Implement a fast path for NSString->AnyHashable casts.
717+
// These are incredibly common because an NSDictionary with
718+
// NSString keys is bridged by default to [AnyHashable:Any].
719+
// Until this is implemented, fall through to the general case
720+
break;
721+
#else
722+
// If no Obj-C interop, just fall through to the general case.
723+
break;
724+
#endif
725+
}
726+
case MetadataKind::Optional: {
727+
// Until SR-9047 fixes the interactions between AnyHashable and Optional, we
728+
// avoid directly injecting Optionals. In particular, this allows
729+
// casts from [String?:String] to [AnyHashable:Any] to work the way people
730+
// expect. Otherwise, without SR-9047, the resulting dictionary can only be
731+
// indexed with an explicit Optional<String>, not a plain String.
732+
// After SR-9047, we can consider dropping this special case entirely.
733+
734+
// !!!! This breaks compatibility with compiler-optimized casts
735+
// (which just inject) and violates the Casting Spec. It just preserves
736+
// the behavior of the older casting code until we can clean things up.
737+
auto srcInnerType = cast<EnumMetadata>(srcType)->getGenericArgs()[0];
738+
unsigned sourceEnumCase = srcInnerType->vw_getEnumTagSinglePayload(
739+
srcValue, /*emptyCases=*/1);
740+
auto nonNil = (sourceEnumCase == 0);
741+
if (nonNil) {
742+
return DynamicCastResult::Failure; // Our caller will unwrap the optional and try again
743+
}
744+
// Else Optional is nil -- the general case below will inject it
745+
break;
746+
}
747+
default:
748+
break;
749+
}
750+
751+
752+
// General case: If it conforms to Hashable, we cast it
712753
auto hashableConformance = reinterpret_cast<const HashableWitnessTable *>(
713-
swift_conformsToProtocol(srcType, &HashableProtocolDescriptor));
754+
swift_conformsToProtocol(srcType, &HashableProtocolDescriptor));
714755
if (hashableConformance) {
715756
_swift_convertToAnyHashableIndirect(srcValue, destLocation,
716757
srcType, hashableConformance);

test/Casting/Casts.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,4 +881,47 @@ CastsTests.test("NSDictionary -> Dictionary casting [SR-12025]") {
881881
}
882882
#endif
883883

884+
// Casting optionals to AnyHashable is a little peculiar
885+
// TODO: It would be nice if AnyHashable(Optional("Foo")) == AnyHashable("Foo")
886+
// (including as dictionary keys). That would make this a lot less confusing.
887+
CastsTests.test("Optional cast to AnyHashable") {
888+
let d: [String?: String] = ["FooKey": "FooValue", nil: "NilValue"]
889+
// In Swift 5.3, this cast DOES unwrap the non-nil key
890+
// We've deliberately tried to preserve that behavior in Swift 5.4
891+
let d2 = d as [AnyHashable: String]
892+
893+
// After SR-9047, all four of the following should work:
894+
let d3 = d2["FooKey" as String? as AnyHashable]
895+
expectNil(d3)
896+
let d4 = d2["FooKey" as String?]
897+
expectNil(d4)
898+
let d5 = d2["FooKey"]
899+
expectNotNil(d5)
900+
let d6 = d2["FooKey" as AnyHashable]
901+
expectNotNil(d6)
902+
903+
// The nil key should be preserved and still function
904+
let d7 = d2[String?.none as AnyHashable]
905+
expectNotNil(d7)
906+
907+
// Direct casts via the runtime unwrap the optional
908+
let a: String = "Foo"
909+
let ah: AnyHashable = a
910+
let b: String? = a
911+
let bh = runtimeCast(b, to: AnyHashable.self)
912+
expectEqual(bh, ah)
913+
914+
// Direct casts that don't go through the runtime don't unwrap the optional
915+
// This is inconsistent with the runtime cast behavior above. We should
916+
// probably change the runtime behavior above to work the same as this,
917+
// but that should wait until SR-9047 lands.
918+
let x: String = "Baz"
919+
let xh = x as AnyHashable
920+
let y: String? = x
921+
let yh = y as AnyHashable // Doesn't unwrap the optional
922+
// xh is AnyHashable("Baz")
923+
// yh is AnyHashable(Optional("Baz"))
924+
expectNotEqual(xh, yh)
925+
}
926+
884927
runAllTests()

0 commit comments

Comments
 (0)