[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:
Dave Abrahams
2016-07-19 18:55:43 -07:00
parent eb5d8ecf14
commit c2642e65e7
2 changed files with 229 additions and 49 deletions

View File

@@ -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> {

View 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()