mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[stdlib] id-as-Any: hashed collection casting
Relax some preconditions in the cast machinery and write a comprehensive test suite. FIXMEs in test/1_stdlib/HashedCollectionCasts.swift.gyb show where the typechecker doesn't seem to quite work, or the frontend might be generating the wrong runtime calls. TODO: Add tests for failing downcasts
This commit is contained in:
@@ -1255,23 +1255,30 @@ internal func _stdlib_NSSet_allObjects(_ nss: _NSSet) ->
|
||||
|
||||
//===--- Compiler conversion/casting entry points for Set<Element> --------===//
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
func _impossible<T>(_:T.Type) -> T {
|
||||
Builtin.unreachable()
|
||||
}
|
||||
|
||||
func _unsafeUpcast<T, U>(_ x: T, to: U.Type) -> U {
|
||||
_sanityCheck(x is U)
|
||||
return x as? U ?? _impossible(U.self)
|
||||
}
|
||||
|
||||
/// Perform a non-bridged upcast that always succeeds.
|
||||
///
|
||||
/// - Precondition: `BaseValue` is a base class or base `@objc`
|
||||
/// protocol (such as `AnyObject`) of `DerivedValue`.
|
||||
public func _setUpCast<DerivedValue, BaseValue>(_ source: Set<DerivedValue>)
|
||||
-> Set<BaseValue> {
|
||||
_sanityCheck(_isClassOrObjCExistential(BaseValue.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(DerivedValue.self))
|
||||
|
||||
var builder = _SetBuilder<BaseValue>(count: source.count)
|
||||
for member in source {
|
||||
builder.add(member: unsafeBitCast(member, to: BaseValue.self))
|
||||
for x in source {
|
||||
builder.add(member: _unsafeUpcast(x, to: BaseValue.self))
|
||||
}
|
||||
return builder.take()
|
||||
}
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
|
||||
/// Implements an unconditional upcast that involves bridging.
|
||||
///
|
||||
/// The cast can fail if bridging fails.
|
||||
@@ -1288,6 +1295,7 @@ public func _setBridgeToObjectiveC<SwiftValue, ObjCValue>(
|
||||
let valueBridgesDirectly =
|
||||
_isBridgedVerbatimToObjectiveC(SwiftValue.self) ==
|
||||
_isBridgedVerbatimToObjectiveC(ObjCValue.self)
|
||||
|
||||
for member in source {
|
||||
var bridgedMember: ObjCValue
|
||||
if valueBridgesDirectly {
|
||||
@@ -1303,6 +1311,8 @@ public func _setBridgeToObjectiveC<SwiftValue, ObjCValue>(
|
||||
return result
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// Implements a forced downcast. This operation should have O(1) complexity.
|
||||
///
|
||||
/// The cast can fail if bridging fails. The actual checks and bridging can be
|
||||
@@ -1353,6 +1363,8 @@ public func _setDownCastConditional<BaseValue, DerivedValue>(
|
||||
return result
|
||||
}
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
|
||||
/// Implements an unconditional downcast that involves bridging.
|
||||
///
|
||||
/// - Precondition: At least one of `SwiftValue` is a bridged value
|
||||
@@ -1406,23 +1418,6 @@ public func _setBridgeFromObjectiveCConditional<
|
||||
}
|
||||
return result
|
||||
}
|
||||
#else
|
||||
|
||||
public func _setUpCast<DerivedValue, BaseValue>(_ source: Set<DerivedValue>)
|
||||
-> Set<BaseValue> {
|
||||
fatalError("_setUpCast is unimplemented")
|
||||
}
|
||||
|
||||
public func _setDownCast<BaseValue, DerivedValue>(_ source: Set<BaseValue>)
|
||||
-> Set<DerivedValue> {
|
||||
fatalError("_setDownCast is unimplemented")
|
||||
}
|
||||
|
||||
public func _setDownCastConditional<BaseValue, DerivedValue>(
|
||||
_ source: Set<BaseValue>
|
||||
) -> Set<DerivedValue>? {
|
||||
fatalError("_setDownCastConditional is unimplemented")
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2179,7 +2174,6 @@ internal func _stdlib_NSDictionary_allKeys(_ nsd: _NSDictionary)
|
||||
|
||||
//===--- Compiler conversion/casting entry points for Dictionary<K, V> ----===//
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
/// Perform a non-bridged upcast that always succeeds.
|
||||
///
|
||||
/// - Precondition: `BaseKey` and `BaseValue` are base classes or base `@objc`
|
||||
@@ -2188,22 +2182,17 @@ internal func _stdlib_NSDictionary_allKeys(_ nsd: _NSDictionary)
|
||||
public func _dictionaryUpCast<DerivedKey, DerivedValue, BaseKey, BaseValue>(
|
||||
_ source: Dictionary<DerivedKey, DerivedValue>
|
||||
) -> Dictionary<BaseKey, BaseValue> {
|
||||
// FIXME: This crappy implementation is O(n) because it copies the
|
||||
// data; a proper implementation would be O(1).
|
||||
|
||||
_sanityCheck(_isClassOrObjCExistential(BaseKey.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(BaseValue.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(DerivedKey.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(DerivedValue.self))
|
||||
|
||||
var result = Dictionary<BaseKey, BaseValue>(minimumCapacity: source.count)
|
||||
|
||||
for (k, v) in source {
|
||||
result[unsafeBitCast(k, to: BaseKey.self)] =
|
||||
unsafeBitCast(v, to: BaseValue.self)
|
||||
result[_unsafeUpcast(k, to: BaseKey.self)]
|
||||
= _unsafeUpcast(v, to: BaseValue.self)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
#if _runtime(_ObjC)
|
||||
|
||||
/// Implements an unconditional upcast that involves bridging.
|
||||
///
|
||||
/// The cast can fail if bridging fails.
|
||||
@@ -2274,10 +2263,6 @@ public func _dictionaryBridgeToObjectiveC<
|
||||
public func _dictionaryDownCast<BaseKey, BaseValue, DerivedKey, DerivedValue>(
|
||||
_ source: Dictionary<BaseKey, BaseValue>
|
||||
) -> Dictionary<DerivedKey, DerivedValue> {
|
||||
_sanityCheck(_isClassOrObjCExistential(BaseKey.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(BaseValue.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(DerivedKey.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(DerivedValue.self))
|
||||
|
||||
switch source._variantStorage {
|
||||
case .native(let nativeOwner):
|
||||
@@ -2315,11 +2300,7 @@ public func _dictionaryDownCastConditional<
|
||||
>(
|
||||
_ source: Dictionary<BaseKey, BaseValue>
|
||||
) -> Dictionary<DerivedKey, DerivedValue>? {
|
||||
_sanityCheck(_isClassOrObjCExistential(BaseKey.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(BaseValue.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(DerivedKey.self))
|
||||
_sanityCheck(_isClassOrObjCExistential(DerivedValue.self))
|
||||
|
||||
|
||||
var result = Dictionary<DerivedKey, DerivedValue>()
|
||||
for (key, value) in source {
|
||||
if let derivedKey = key as? DerivedKey {
|
||||
@@ -2419,12 +2400,6 @@ public func _dictionaryBridgeFromObjectiveCConditional<
|
||||
}
|
||||
#else
|
||||
|
||||
public func _dictionaryUpCast<DerivedKey, DerivedValue, BaseKey, BaseValue>(
|
||||
_ source: Dictionary<DerivedKey, DerivedValue>
|
||||
) -> Dictionary<BaseKey, BaseValue> {
|
||||
fatalError("_dictionaryUpCast is unimplemented")
|
||||
}
|
||||
|
||||
public func _dictionaryDownCast<BaseKey, BaseValue, DerivedKey, DerivedValue>(
|
||||
_ source: Dictionary<BaseKey, BaseValue>
|
||||
) -> Dictionary<DerivedKey, DerivedValue> {
|
||||
|
||||
205
test/1_stdlib/HashedCollectionCasts.swift.gyb
Normal file
205
test/1_stdlib/HashedCollectionCasts.swift.gyb
Normal file
@@ -0,0 +1,205 @@
|
||||
//===--- HashedCollectionCasts.swift.gyb ----------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See http://swift.org/LICENSE.txt for license information
|
||||
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// RUN: rm -rf %t
|
||||
// RUN: mkdir -p %t
|
||||
// RUN: %gyb %s -o %t/HashedCollectionCasts.swift
|
||||
// RUN: %line-directive %t/HashedCollectionCasts.swift -- %target-build-swift -Xfrontend -enable-experimental-collection-casts %t/HashedCollectionCasts.swift -o %t/a.out
|
||||
// RUN: %line-directive %t/HashedCollectionCasts.swift -- %target-run %t/a.out 2>&1
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: objc_interop
|
||||
|
||||
import StdlibUnittest
|
||||
|
||||
class Base : Hashable {
|
||||
init(_ value: Int) {
|
||||
self.value = value
|
||||
}
|
||||
var value: Int
|
||||
var hashValue : Int {
|
||||
return value.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
func == (lhs: Base, rhs: Base) -> Bool {
|
||||
return lhs.value == rhs.value
|
||||
}
|
||||
|
||||
class Derived : Base {}
|
||||
|
||||
protocol AnyFoo {
|
||||
init(_ value: Int)
|
||||
}
|
||||
extension Int : AnyFoo { }
|
||||
|
||||
var tests = TestSuite("HashedCollectionCasts")
|
||||
|
||||
// A wrapper for an as? cast that suppresses warnings when the cast always
|
||||
// succeeds.
|
||||
func cast<T, U>(_ x: T, to result: U.Type) -> U? {
|
||||
return x as? U
|
||||
}
|
||||
|
||||
%{
|
||||
from gyb_stdlib_unittest_support import TRACE, stackTrace, trace
|
||||
classKeys = ('Derived', 'Base')
|
||||
classValues = classKeys + ('AnyObject', 'Any')
|
||||
nonClassKeys = ('Int',)
|
||||
nonClassValues = ('Int', 'AnyFoo', 'Any')
|
||||
|
||||
testingWithExperimentalCollectionCasts = True
|
||||
|
||||
# FIXME: depending on how we do the tests, some direct casts fail. This
|
||||
# function describes those cases.
|
||||
|
||||
def indirectCastFIXME(Key0, Value0, Key1, Value1):
|
||||
if testingWithExperimentalCollectionCasts:
|
||||
# Typechecker asserts per https://github.com/apple/swift/commit/f4d7ce84d88d8132a4e46cc9644b9d8a52a00e71#commitcomment-18346931
|
||||
return (Key0 == Key1) != (Value0 == Value1)
|
||||
else:
|
||||
# Typechecker rejects the cast
|
||||
return '[%s:%s]=>[%s:%s]' % (Key0,Value0,Key1,Value1) in (
|
||||
'[Derived:Any]=>[Base:Any]', '[Int:AnyFoo]=>[Int:Any]')
|
||||
}%
|
||||
|
||||
% for keys, values in [ (classKeys, classValues), (nonClassKeys, nonClassValues) ]:
|
||||
|
||||
% for ki0, Key0 in enumerate(keys):
|
||||
% DynamicKey = keys[0] if Key0.startswith('Any') else Key0
|
||||
% for Key1 in keys[ki0:]:
|
||||
|
||||
tests.test("Set/Up/${Key0}=>${Key1}") {
|
||||
|
||||
let source : Set = [
|
||||
${DynamicKey}(42) as ${Key0},
|
||||
${DynamicKey}(17) as ${Key0}]
|
||||
|
||||
let upcasted = source as Set<${Key1}>
|
||||
|
||||
expectEqual(source.count, upcasted.count)
|
||||
for x in source {
|
||||
expectTrue(upcasted.contains(x))
|
||||
}
|
||||
}
|
||||
|
||||
% if Key1 != Key0:
|
||||
tests.test("Set/Down/${Key1}=>${Key0}") {
|
||||
|
||||
let source : Set = [
|
||||
${DynamicKey}(42) as ${Key1},
|
||||
${DynamicKey}(17) as ${Key1}]
|
||||
|
||||
// FIXME Set<Base> => Set<Derived> fails dynamically, and never even reaches
|
||||
// one of my runtime entry points.
|
||||
guard let downcasted = expectNotEmpty(cast(source, to: Set<${Key0}>.self))
|
||||
else { return }
|
||||
|
||||
expectEqual(source.count, downcasted.count)
|
||||
for x in downcasted {
|
||||
expectTrue(source.contains(x))
|
||||
}
|
||||
}
|
||||
% end
|
||||
|
||||
% for vi0, Value0 in enumerate(values):
|
||||
% DynamicValue = values[0] if Value0.startswith('Any') else Value0
|
||||
% def makeValue(i): return '%s(%s) as %s' % (DynamicValue, i, Value0)
|
||||
% for Value1 in values[vi0:]:
|
||||
|
||||
tests.test(
|
||||
"Dictionary/Up/[${Key0}:${Value0}]=>[${Key1}:${Value1}]") {
|
||||
|
||||
// Check that the cast type-checks
|
||||
let source = [
|
||||
${DynamicKey}(42) as ${Key0} : ${DynamicValue}(42) as ${Value0},
|
||||
${DynamicKey}(17) as ${Key0} : ${DynamicValue}(17) as ${Value0}]
|
||||
|
||||
|
||||
% if indirectCastFIXME(Key0,Value0,Key1,Value1):
|
||||
// let upcasted_ = source as Any as? [${Key1}:${Value1}]
|
||||
let upcasted_ = cast(source, to: [${Key1}:${Value1}].self)
|
||||
|
||||
// FIXME: Conditional casting fails to dynamically type-check, and never even
|
||||
// reaches one of my runtime entry points in any of these cases.
|
||||
guard let upcasted = expectNotEmpty(upcasted_) else { return }
|
||||
% else:
|
||||
let upcasted = source as [${Key1}:${Value1}]
|
||||
% end
|
||||
|
||||
expectEqual(source.count, upcasted.count)
|
||||
|
||||
for (k0, v0) in source {
|
||||
guard let v1 = expectNotEmpty(upcasted[k0]) else { continue }
|
||||
|
||||
guard let dv0 = expectNotEmpty(
|
||||
cast(v0, to: ${DynamicValue}.self)) else { continue }
|
||||
|
||||
guard let dv1 = expectNotEmpty(
|
||||
cast(v1, to: ${DynamicValue}.self)) else { continue }
|
||||
|
||||
expectEqual(dv0, dv1)
|
||||
}
|
||||
}
|
||||
|
||||
% if Key1 != Key0 or Value1 != Value0:
|
||||
|
||||
tests.test(
|
||||
"Dictionary/Down/[${Key1}:${Value1}]=>[${Key0}:${Value0}]") {
|
||||
|
||||
// Check that the cast type-checks
|
||||
let source = [
|
||||
${DynamicKey}(42) as ${Key1} : ${DynamicValue}(42) as ${Value1},
|
||||
${DynamicKey}(17) as ${Key1} : ${DynamicValue}(17) as ${Value1}]
|
||||
|
||||
// FIXME: The following are routed through
|
||||
// _dictionaryBridgeFromObjectiveCConditional, causing the test to crash, but
|
||||
// there's no reason for bridging in these cases:
|
||||
//
|
||||
// [Derived:Any] => [Derived:AnyObject]
|
||||
// [Derived:Any] => [Derived:Base]
|
||||
// [Base:Any] => [Derived:Derived]
|
||||
// [Base:Any] => [Derived:Base]
|
||||
// [Base:Any] => [Derived:AnyObject]
|
||||
// [Base:Any] => [Base:Derived]
|
||||
// [Base:Any] => [Base:Base]
|
||||
// [Base:Any] => [Base:AnyObject]
|
||||
// [Int:AnyFoo] => [Int:Int]
|
||||
// [Int:Any] => [Int:Int]
|
||||
// [Int:Any] => [Int:AnyFoo]
|
||||
guard let downcasted = expectNotEmpty(source as? [${Key0}:${Value0}])
|
||||
else { return }
|
||||
|
||||
expectEqual(source.count, downcasted.count)
|
||||
|
||||
for (k0, v0) in downcasted {
|
||||
guard let v1 = expectNotEmpty(source[k0]) else { continue }
|
||||
|
||||
guard let dv0 = expectNotEmpty(
|
||||
cast(v0, to: ${DynamicValue}.self)) else { continue }
|
||||
|
||||
guard let dv1 = expectNotEmpty(
|
||||
cast(v1, to: ${DynamicValue}.self)) else { continue }
|
||||
|
||||
expectEqual(dv0, dv1)
|
||||
}
|
||||
}
|
||||
|
||||
% end
|
||||
|
||||
% end
|
||||
% end
|
||||
% end
|
||||
% end
|
||||
% end
|
||||
|
||||
|
||||
runAllTests()
|
||||
|
||||
Reference in New Issue
Block a user