Skip to content

Commit 086dc14

Browse files
committed
Don't link-check the Optional/AnyHashable workaround
This restores the earlier behavior of Optionals cast to AnyHashable, so that [String?:Any] dictionaries cast to [AnyHashable:Any] can be indexed by plain String keys. This is a little problematic because it's not consistent with the compiler-optimized casts. But the ability to index such dictionaries by plain String keys seems important to preserve. SR-9047 will expand Optional/AnyHashable interoperability so that such indexing works without this special case. An earlier proposal would link-check this, changing the behavior depending on the caller. But it's not really workable to change the behavior seen by intermediate frameworks depending on the app they're being called by.
1 parent 067beb1 commit 086dc14

File tree

2 files changed

+32
-37
lines changed

2 files changed

+32
-37
lines changed

stdlib/public/runtime/DynamicCast.cpp

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -712,14 +712,6 @@ struct ObjCBridgeMemo {
712712
};
713713
#endif
714714

715-
static bool avoidOptionalsInAnyHashable() {
716-
// Unexpected nulls are fatal in Swift 5.4 and not before
717-
// AnyHashable avoids optionals before Swift 5.4
718-
// TODO: This is admittedly awkward, but I need to get this changed quickly
719-
// I'll come back and clean this up later.
720-
return !unexpectedNullIsFatal();
721-
}
722-
723715
static DynamicCastResult
724716
tryCastToAnyHashable(
725717
OpaqueValue *destLocation, const Metadata *destType,
@@ -747,22 +739,24 @@ tryCastToAnyHashable(
747739
#endif
748740
}
749741
case MetadataKind::Optional: {
750-
if (avoidOptionalsInAnyHashable()) {
751-
// Mimic the Swift 5.3 behavior when running in older contexts
752-
// This behavior is consistent with Swift 5.3 runtime, but inconsistent
753-
// with how other casting paths handle this case (which is why it's being
754-
// changed in Swift 5.4).
755-
// Old behavior: "Foo" as! String? as! AnyHashable => AnyHashable("Foo")
756-
// New behavior: "Foo" as! String? as! AnyHashable => AnyHashable(Optional("Foo"))
757-
auto srcInnerType = cast<EnumMetadata>(srcType)->getGenericArgs()[0];
758-
unsigned sourceEnumCase = srcInnerType->vw_getEnumTagSinglePayload(
759-
srcValue, /*emptyCases=*/1);
760-
auto nonNil = (sourceEnumCase == 0);
761-
if (nonNil) {
762-
return DynamicCastResult::Failure; // Our caller will unwrap the optional and try again
763-
}
764-
// If it is nil, fall through to the general case to just wrap the nil
742+
// Until SR-9047 fixes the interactions between AnyHashable and Optional, we
743+
// avoid directly injecting Optionals. In particular, this allows
744+
// casts from [String?:String] to [AnyHashable:Any] to work the way people
745+
// expect. Otherwise, without SR-9047, the resulting dictionary can only be
746+
// indexed with an explicit Optional<String>, not a plain String.
747+
// After SR-9047, we can consider dropping this special case entirely.
748+
749+
// !!!! This breaks compatibility with compiler-optimized casts
750+
// (which just inject) and violates the Casting Spec. It just preserves
751+
// the behavior of the older casting code until we can clean things up.
752+
auto srcInnerType = cast<EnumMetadata>(srcType)->getGenericArgs()[0];
753+
unsigned sourceEnumCase = srcInnerType->vw_getEnumTagSinglePayload(
754+
srcValue, /*emptyCases=*/1);
755+
auto nonNil = (sourceEnumCase == 0);
756+
if (nonNil) {
757+
return DynamicCastResult::Failure; // Our caller will unwrap the optional and try again
765758
}
759+
// Else Optional is nil -- the general case below will inject it
766760
break;
767761
}
768762
default:

test/Casting/Casts.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -887,39 +887,40 @@ CastsTests.test("NSDictionary -> Dictionary casting [SR-12025]") {
887887
CastsTests.test("Optional cast to AnyHashable") {
888888
let d: [String?: String] = ["FooKey": "FooValue", nil: "NilValue"]
889889
// In Swift 5.3, this cast DOES unwrap the non-nil key
890-
// In Swift 5.4, the cast does NOT unwrap the non-nil key
890+
// We've deliberately tried to preserve that behavior in Swift 5.4
891891
let d2 = d as [AnyHashable: String]
892+
893+
// After SR-9047, all four of the following should work:
892894
let d3 = d2["FooKey" as String? as AnyHashable]
893-
expectNotNil(d3)
895+
expectNil(d3)
894896
let d4 = d2["FooKey" as String?]
895-
expectNotNil(d4)
896-
// In Swift 5.4, AnyHashable(String?) is never equal to AnyHashable(String)
897-
// This is expected to change...
897+
expectNil(d4)
898898
let d5 = d2["FooKey"]
899-
expectNil(d5)
899+
expectNotNil(d5)
900900
let d6 = d2["FooKey" as AnyHashable]
901-
expectNil(d6)
901+
expectNotNil(d6)
902902

903-
// In both Swift 5.3 and 5.4, the nil key should be preserved and still function
903+
// The nil key should be preserved and still function
904904
let d7 = d2[String?.none as AnyHashable]
905905
expectNotNil(d7)
906906

907-
// Direct casts via the runtime unwrap the optional in 5.3 but not 5.4.
907+
// Direct casts via the runtime unwrap the optional
908908
let a: String = "Foo"
909909
let ah: AnyHashable = a
910910
let b: String? = a
911911
let bh = runtimeCast(b, to: AnyHashable.self)
912-
// bh is an AnyHashable(Optional("Foo")) in Swift 5.4 but not earlier
913-
// ah is an AnyHashable("Foo")
914-
expectNotEqual(bh, ah)
912+
expectEqual(bh, ah)
915913

916-
// In both Swift 5.3 and 5.4, direct casts that don't go through the runtime don't unwrap the optional
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.
917918
let x: String = "Baz"
918919
let xh = x as AnyHashable
919920
let y: String? = x
920921
let yh = y as AnyHashable // Doesn't unwrap the optional
921922
// xh is AnyHashable("Baz")
922-
// yh is AnyHashable(Optional("Baz")) in Swift 5.4 and earlier
923+
// yh is AnyHashable(Optional("Baz"))
923924
expectNotEqual(xh, yh)
924925
}
925926

0 commit comments

Comments
 (0)