Skip to content

Dynamic casts to AnyObject should succeed for all types via SwiftValue #4416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion stdlib/public/core/ArrayCast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public func _arrayForceCast<SourceElement, TargetElement>(
return Array(_immutableCocoaArray: source._buffer._asCocoaArray())
}
#endif
return _arrayConditionalCast(source)!
return source.map { $0 as! TargetElement }
}

internal struct _UnwrappingFailed : Error {}
Expand Down
14 changes: 2 additions & 12 deletions stdlib/public/core/HashedCollections.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -1278,15 +1278,6 @@ internal func _stdlib_NSSet_allObjects(_ nss: _NSSet) ->

//===--- Compiler conversion/casting entry points for Set<Element> --------===//

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`
Expand All @@ -1295,7 +1286,7 @@ public func _setUpCast<DerivedValue, BaseValue>(_ source: Set<DerivedValue>)
-> Set<BaseValue> {
var builder = _SetBuilder<BaseValue>(count: source.count)
for x in source {
builder.add(member: _unsafeUpcast(x, to: BaseValue.self))
builder.add(member: x as! BaseValue)
}
return builder.take()
}
Expand Down Expand Up @@ -2220,8 +2211,7 @@ public func _dictionaryUpCast<DerivedKey, DerivedValue, BaseKey, BaseValue>(
var result = Dictionary<BaseKey, BaseValue>(minimumCapacity: source.count)

for (k, v) in source {
result[_unsafeUpcast(k, to: BaseKey.self)]
= _unsafeUpcast(v, to: BaseValue.self)
result[k as! BaseKey] = v as! BaseValue
}
return result
}
Expand Down
30 changes: 25 additions & 5 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,26 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
MetadataKind kind =
srcDynamicType ? srcDynamicType->getKind() : MetadataKind::Class;

// A fallback to use if we don't have a more specialized approach
// for a non-class type.
auto fallbackForNonClass = [&] {
#if SWIFT_OBJC_INTEROP
// If the destination type is a set of protocols that SwiftValue
// implements, we're fine.
if (findSwiftValueConformances(targetType->Protocols,
destExistential->getWitnessTables())) {
bool consumeValue = dynamicFlags & DynamicCastFlags::TakeOnSuccess;
destExistential->Value =
bridgeAnythingToSwiftValueObject(srcDynamicValue, srcDynamicType,
consumeValue);
maybeDeallocateSource(true);
return true;
}
#endif

return _fail(src, srcType, targetType, flags);
};

// If the source type is a value type, it cannot possibly conform
// to a class-bounded protocol.
switch (kind) {
Expand All @@ -1068,14 +1088,15 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
}
#endif
// Otherwise, metatypes aren't class objects.
return _fail(src, srcType, targetType, flags);
return fallbackForNonClass();
}

case MetadataKind::Class:
case MetadataKind::ObjCClassWrapper:
case MetadataKind::ForeignClass:
case MetadataKind::Existential:
// Handle these cases below.
// Handle the class cases below. Note that opaque existentials
// shouldn't get here because we should have drilled into them above.
break;

case MetadataKind::Struct:
Expand Down Expand Up @@ -1105,16 +1126,15 @@ static bool _dynamicCastToExistential(OpaqueValue *dest,
return success;
}
#endif
break;
SWIFT_FALLTHROUGH;

case MetadataKind::Function:
case MetadataKind::HeapLocalVariable:
case MetadataKind::HeapGenericLocalVariable:
case MetadataKind::ErrorObject:
case MetadataKind::Opaque:
case MetadataKind::Tuple:
// Will never succeed.
return _fail(src, srcType, targetType, flags);
return fallbackForNonClass();
}

// Check for protocol conformances and fill in the witness tables. If
Expand Down
7 changes: 7 additions & 0 deletions stdlib/public/runtime/SwiftValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#if SWIFT_OBJC_INTEROP
#include <objc/runtime.h>
#include "swift/Runtime/Metadata.h"

// _SwiftValue is an Objective-C class, but we shouldn't interface with it
// directly as such. Keep the type opaque.
Expand Down Expand Up @@ -51,6 +52,12 @@ getValueFromSwiftValue(_SwiftValue *v);
/// or nil if it is not.
_SwiftValue *getAsSwiftValue(id object);

/// Find conformances for SwiftValue to the given list of protocols.
///
/// Returns true if SwiftValue does conform to all the protocols.
bool findSwiftValueConformances(const ProtocolDescriptorList &protocols,
const WitnessTable **tablesBuffer);

} // namespace swift
#endif

Expand Down
43 changes: 43 additions & 0 deletions stdlib/public/runtime/SwiftValue.mm
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,49 @@ static size_t getSwiftValuePayloadAlignMask(const Metadata *type) {
return nil;
}

bool
swift::findSwiftValueConformances(const ProtocolDescriptorList &protocols,
const WitnessTable **tablesBuffer) {
Class cls = nullptr;

// Note that currently we never modify tablesBuffer because
// _SwiftValue doesn't conform to any protocols that need witness tables.

for (size_t i = 0, e = protocols.NumProtocols; i != e; ++i) {
auto protocol = protocols[i];

// _SwiftValue does conform to AnyObject.
switch (protocol->Flags.getSpecialProtocol()) {
case SpecialProtocol::AnyObject:
continue;

case SpecialProtocol::Error:
return false;

case SpecialProtocol::None:
break;
}

// Otherwise, it only conforms to ObjC protocols. We specifically
// don't want to say that _SwiftValue conforms to the Swift protocols
// that NSObject conforms to because that would create a situation
// where arguably an arbitrary type would conform to those protocols
// by way of coercion through _SwiftValue. Eventually we want to
// change _SwiftValue to not be an NSObject subclass at all.

if (protocol->Flags.getDispatchStrategy() != ProtocolDispatchStrategy::ObjC)
return false;

if (!cls) cls = _getSwiftValueClass();

// Check whether the class conforms to the protocol.
if (![cls conformsToProtocol: (Protocol*) protocol])
return false;
}

return true;
}

@implementation _SwiftValue

+ (instancetype)allocWithZone:(NSZone *)zone {
Expand Down
9 changes: 8 additions & 1 deletion test/Interpreter/generic_casts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,15 @@ print(allToAll(type(of: C()), AnyObject.self)) // CHECK-NEXT: true
// Bridging
print(allToAll(0, AnyObject.self)) // CHECK-NEXT: true

// This will get bridged using _SwiftValue.
struct NotBridged { var x: Int }
print(allToAll(NotBridged(x: 0), AnyObject.self)) // CHECK-NEXT: false
print(allToAll(NotBridged(x: 0), AnyObject.self)) // CHECK-NEXT: true
print(allToAll(NotBridged(x: 0), NSCopying.self)) // CHECK-NEXT: true

// These casts fail (intentionally) even though _SwiftValue does
// technically conform to these protocols through NSObject.
print(allToAll(NotBridged(x: 0), CustomStringConvertible.self)) // CHECK-NEXT: false
print(allToAll(NotBridged(x: 0), (AnyObject & CustomStringConvertible).self)) // CHECK-NEXT: false

//
// rdar://problem/19482567
Expand Down