Skip to content

Commit 1a52e3f

Browse files
committed
SR-0140: Bridge Optionals to nonnull ObjC objects by bridging their payload, or using a sentinel.
id-as-Any lets you pass Optional to an ObjC API that takes `nonnull id`, and also lets you bridge containers of `Optional` to `NSArray` etc. When this occurs, we can unwrap the value and bridge it so that inhabited optionals still pass into ObjC in the expected way, but we need something to represent `none` other than the `nil` pointer. Cocoa provides `NSNull` as the canonical "null for containers" object, which is the least bad of many possible answers. If we happen to have the rare nested optional `T??`, there is no precedented analog for these in Cocoa, so just generate a unique sentinel object to preserve the `nil`-ness depth so we at least don't lose information round-tripping across the ObjC-Swift bridge. Making Optional conform to _ObjectiveCBridgeable is more or less enough to make this all work, though there are a few additional edge case things that need to be fixed up. We don't want to accept `AnyObject??` as an @objc-compatible type, so special-case Optional in `getForeignRepresentable`. Implements SR-0140 (rdar://problem/27905315).
1 parent 6132819 commit 1a52e3f

File tree

9 files changed

+410
-16
lines changed

9 files changed

+410
-16
lines changed

lib/AST/ASTContext.cpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3879,18 +3879,22 @@ ASTContext::getForeignRepresentationInfo(NominalTypeDecl *nominal,
38793879
known->second.getGeneration() < CurrentGeneration)) {
38803880
Optional<ForeignRepresentationInfo> result;
38813881

3882-
// Look for a conformance to _ObjectiveCBridgeable.
3882+
// Look for a conformance to _ObjectiveCBridgeable (other than Optional's--
3883+
// we don't want to allow exposing APIs with double-optional types like
3884+
// NSObject??, even though Optional is bridged to its underlying type).
38833885
//
38843886
// FIXME: We're implicitly depending on the fact that lookupConformance
38853887
// is global, ignoring the module we provide for it.
3886-
if (auto objcBridgeable
3887-
= getProtocol(KnownProtocolKind::ObjectiveCBridgeable)) {
3888-
if (auto conformance
3889-
= dc->getParentModule()->lookupConformance(
3890-
nominal->getDeclaredType(), objcBridgeable,
3891-
getLazyResolver())) {
3892-
result =
3893-
ForeignRepresentationInfo::forBridged(conformance->getConcrete());
3888+
if (nominal != dc->getASTContext().getOptionalDecl()) {
3889+
if (auto objcBridgeable
3890+
= getProtocol(KnownProtocolKind::ObjectiveCBridgeable)) {
3891+
if (auto conformance
3892+
= dc->getParentModule()->lookupConformance(
3893+
nominal->getDeclaredType(), objcBridgeable,
3894+
getLazyResolver())) {
3895+
result =
3896+
ForeignRepresentationInfo::forBridged(conformance->getConcrete());
3897+
}
38943898
}
38953899
}
38963900

@@ -3986,6 +3990,11 @@ Type ASTContext::getBridgedToObjC(const DeclContext *dc, Type type,
39863990
// Try to find a conformance that will enable bridging.
39873991
auto findConformance =
39883992
[&](KnownProtocolKind known) -> Optional<ProtocolConformanceRef> {
3993+
// Don't ascribe any behavior to Optional other than what we explicitly
3994+
// give it. We don't want things like AnyObject?? to work.
3995+
if (type->getAnyNominal() == getOptionalDecl())
3996+
return None;
3997+
39893998
// Find the protocol.
39903999
auto proto = getProtocol(known);
39914000
if (!proto) return None;

lib/SILGen/SILGenBridging.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ emitBridgeNativeToObjectiveC(SILGenFunction &gen,
7070
witnessFnTy = witnessFnTy.substGenericArgs(gen.SGM.M, substitutions);
7171
}
7272

73+
// The witness may be more abstract than the concrete value we're bridging,
74+
// for instance, if the value is a concrete instantiation of a generic type.
75+
//
76+
// Note that we assume that we don't ever have to reabstract the parameter.
77+
// This is safe for now, since only nominal types currently can conform to
78+
// protocols.
79+
if (witnessFnTy.castTo<SILFunctionType>()->getParameters()[0].isIndirect()
80+
&& !swiftValue.getType().isAddress()) {
81+
auto tmp = gen.emitTemporaryAllocation(loc, swiftValue.getType());
82+
gen.B.createStore(loc, swiftValue.getValue(), tmp);
83+
swiftValue = ManagedValue::forUnmanaged(tmp);
84+
}
85+
7386
// Call the witness.
7487
SILType resultTy = gen.getLoweredType(objcType);
7588
SILValue bridgedValue = gen.B.createApply(loc, witnessRef, witnessFnTy,

lib/SILOptimizer/Utils/Local.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1662,6 +1662,11 @@ optimizeBridgedSwiftToObjCCast(SILInstruction *Inst,
16621662

16631663
auto SILFnTy = SILType::getPrimitiveObjectType(
16641664
M.Types.getConstantFunctionType(BridgeFuncDeclRef));
1665+
1666+
// TODO: Handle indirect argument to or return from witness function.
1667+
if (ParamTypes[0].isIndirect()
1668+
|| BridgedFunc->getLoweredFunctionType()->getSingleResult().isIndirect())
1669+
return nullptr;
16651670

16661671
// Get substitutions, if source is a bound generic type.
16671672
ArrayRef<Substitution> Subs =
@@ -1728,9 +1733,9 @@ optimizeBridgedSwiftToObjCCast(SILInstruction *Inst,
17281733
case ParameterConvention::Direct_Unowned:
17291734
break;
17301735
case ParameterConvention::Indirect_In:
1736+
case ParameterConvention::Indirect_In_Guaranteed:
17311737
case ParameterConvention::Indirect_Inout:
17321738
case ParameterConvention::Indirect_InoutAliasable:
1733-
case ParameterConvention::Indirect_In_Guaranteed:
17341739
case ParameterConvention::Direct_Deallocating:
17351740
llvm_unreachable("unsupported convention for bridging conversion");
17361741
}

stdlib/public/core/Optional.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,3 +487,81 @@ extension Optional {
487487
}
488488

489489
}
490+
491+
//===----------------------------------------------------------------------===//
492+
// Bridging
493+
//===----------------------------------------------------------------------===//
494+
495+
#if _runtime(_ObjC)
496+
extension Optional : _ObjectiveCBridgeable {
497+
// The object that represents `none` for an Optional of this type.
498+
internal static var _nilSentinel : AnyObject {
499+
@_silgen_name("_swift_Foundation_getOptionalNilSentinelObject")
500+
get
501+
}
502+
503+
public func _bridgeToObjectiveC() -> AnyObject {
504+
// Bridge a wrapped value by unwrapping.
505+
if let value = self {
506+
return _bridgeAnythingToObjectiveC(value)
507+
}
508+
// Bridge nil using a sentinel.
509+
return type(of: self)._nilSentinel
510+
}
511+
512+
public static func _forceBridgeFromObjectiveC(
513+
_ source: AnyObject,
514+
result: inout Optional<Wrapped>?
515+
) {
516+
// Map the nil sentinel back to .none.
517+
// NB that the signature of _forceBridgeFromObjectiveC adds another level
518+
// of optionality, so we need to wrap the immediate result of the conversion
519+
// in `.some`.
520+
if source === _nilSentinel {
521+
result = .some(.none)
522+
return
523+
}
524+
// Otherwise, force-bridge the underlying value.
525+
let unwrappedResult = source as! Wrapped
526+
result = .some(.some(unwrappedResult))
527+
}
528+
529+
public static func _conditionallyBridgeFromObjectiveC(
530+
_ source: AnyObject,
531+
result: inout Optional<Wrapped>?
532+
) -> Bool {
533+
// Map the nil sentinel back to .none.
534+
// NB that the signature of _forceBridgeFromObjectiveC adds another level
535+
// of optionality, so we need to wrap the immediate result of the conversion
536+
// in `.some` to indicate success of the bridging operation, with a nil
537+
// result.
538+
if source === _nilSentinel {
539+
result = .some(.none)
540+
return true
541+
}
542+
// Otherwise, try to bridge the underlying value.
543+
if let unwrappedResult = source as? Wrapped {
544+
result = .some(.some(unwrappedResult))
545+
return true
546+
} else {
547+
result = .none
548+
return false
549+
}
550+
}
551+
552+
public static func _unconditionallyBridgeFromObjectiveC(_ source: AnyObject?)
553+
-> Optional<Wrapped> {
554+
if let nonnullSource = source {
555+
// Map the nil sentinel back to none.
556+
if nonnullSource === _nilSentinel {
557+
return .none
558+
} else {
559+
return .some(nonnullSource as! Wrapped)
560+
}
561+
} else {
562+
// If we unexpectedly got nil, just map it to `none` too.
563+
return .none
564+
}
565+
}
566+
}
567+
#endif

stdlib/public/runtime/Casting.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2513,6 +2513,18 @@ bool swift::swift_dynamicCast(OpaqueValue *dest,
25132513
// unwrapping the target. This handles an optional source wrapped within an
25142514
// existential that Optional conforms to (Any).
25152515
if (auto srcExistentialType = dyn_cast<ExistentialTypeMetadata>(srcType)) {
2516+
#if SWIFT_OBJC_INTEROP
2517+
// If coming from AnyObject, we may want to bridge.
2518+
if (srcExistentialType->Flags.getSpecialProtocol()
2519+
== SpecialProtocol::AnyObject) {
2520+
if (auto targetBridgeWitness = findBridgeWitness(targetType)) {
2521+
return _dynamicCastClassToValueViaObjCBridgeable(dest, src, srcType,
2522+
targetType,
2523+
targetBridgeWitness,
2524+
flags);
2525+
}
2526+
}
2527+
#endif
25162528
return _dynamicCastFromExistential(dest, src, srcExistentialType,
25172529
targetType, flags);
25182530
}

stdlib/public/stubs/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ if(SWIFT_HOST_VARIANT MATCHES "${SWIFT_DARWIN_VARIANTS}")
66
Availability.mm
77
DispatchShims.mm
88
FoundationHelpers.mm
9+
OptionalBridgingHelper.mm
910
Reflection.mm
1011
SwiftNativeNSXXXBase.mm.gyb)
1112
set(LLVM_OPTIONAL_SOURCES
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/Basic/Lazy.h"
14+
#include "swift/Basic/LLVM.h"
15+
#include "swift/Runtime/Metadata.h"
16+
#include "swift/Runtime/Mutex.h"
17+
#include "swift/Runtime/ObjCBridge.h"
18+
#include <vector>
19+
#import <Foundation/Foundation.h>
20+
#import <CoreFoundation/CoreFoundation.h>
21+
22+
using namespace swift;
23+
24+
/// Class of sentinel objects used to represent the `nil` value of nested
25+
/// optionals.
26+
@interface _SwiftNull : NSObject {
27+
@public
28+
unsigned depth;
29+
}
30+
@end
31+
32+
@implementation _SwiftNull : NSObject
33+
34+
- (NSString*)description {
35+
return [NSString stringWithFormat:@"<%@ %p depth = %u>", [self class],
36+
self,
37+
self->depth];
38+
}
39+
40+
@end
41+
42+
namespace {
43+
44+
struct SwiftNullSentinelCache {
45+
std::vector<id> Cache;
46+
StaticReadWriteLock Lock;
47+
};
48+
49+
static Lazy<SwiftNullSentinelCache> Sentinels;
50+
51+
static id getSentinelForDepth(unsigned depth) {
52+
// For unnested optionals, use NSNull.
53+
if (depth == 1)
54+
return (id)kCFNull;
55+
// Otherwise, make up our own sentinel.
56+
// See if we created one for this depth.
57+
auto &theSentinels = Sentinels.get();
58+
unsigned depthIndex = depth - 2;
59+
{
60+
StaticScopedReadLock lock(theSentinels.Lock);
61+
const auto &cache = theSentinels.Cache;
62+
if (depthIndex < cache.size()) {
63+
id cached = cache[depthIndex];
64+
if (cached)
65+
return cached;
66+
}
67+
}
68+
// Make one if we need to.
69+
{
70+
StaticScopedWriteLock lock(theSentinels.Lock);
71+
if (depthIndex >= theSentinels.Cache.size())
72+
theSentinels.Cache.resize(depthIndex + 1);
73+
auto &cached = theSentinels.Cache[depthIndex];
74+
// Make sure another writer didn't sneak in.
75+
if (!cached) {
76+
auto sentinel = [[_SwiftNull alloc] init];
77+
sentinel->depth = depth;
78+
cached = sentinel;
79+
}
80+
return cached;
81+
}
82+
}
83+
84+
}
85+
86+
/// Return the sentinel object to use to represent `nil` for a given Optional
87+
/// type.
88+
SWIFT_RUNTIME_STDLIB_INTERFACE SWIFT_CC(swift)
89+
extern "C"
90+
id _swift_Foundation_getOptionalNilSentinelObject(const Metadata *Wrapped) {
91+
// Figure out the depth of optionality we're working with.
92+
unsigned depth = 1;
93+
while (Wrapped->getKind() == MetadataKind::Optional) {
94+
++depth;
95+
Wrapped = cast<EnumMetadata>(Wrapped)->getGenericArgs()[0];
96+
}
97+
98+
return objc_retain(getSentinelForDepth(depth));
99+
}

test/SILGen/objc_bridging_any.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,27 +126,29 @@ func passingToId<T: CP, U>(receiver: NSIdLover,
126126
// CHECK: apply [[METHOD]]([[ANYOBJECT]], [[SELF]])
127127
receiver.takesId(knownUnbridged)
128128

129+
// These cases bridge using Optional's _ObjectiveCBridgeable conformance.
130+
129131
// CHECK: [[METHOD:%.*]] = class_method [volatile] [[SELF]] : $NSIdLover,
132+
// CHECK: [[BRIDGE_OPTIONAL:%.*]] = function_ref @_TFSq19_bridgeToObjectiveCfT_Ps9AnyObject_
130133
// CHECK: [[TMP:%.*]] = alloc_stack $Optional<String>
131134
// CHECK: store [[OPT_STRING]] to [[TMP]]
132-
// CHECK: [[BRIDGE_ANYTHING:%.*]] = function_ref @_TFs27_bridgeAnythingToObjectiveC
133-
// CHECK: [[ANYOBJECT:%.*]] = apply [[BRIDGE_ANYTHING]]<Optional<String>>([[TMP]])
135+
// CHECK: [[ANYOBJECT:%.*]] = apply [[BRIDGE_OPTIONAL]]<String>([[TMP]])
134136
// CHECK: apply [[METHOD]]([[ANYOBJECT]], [[SELF]])
135137
receiver.takesId(optionalA)
136138

137139
// CHECK: [[METHOD:%.*]] = class_method [volatile] [[SELF]] : $NSIdLover,
140+
// CHECK: [[BRIDGE_OPTIONAL:%.*]] = function_ref @_TFSq19_bridgeToObjectiveCfT_Ps9AnyObject_
138141
// CHECK: [[TMP:%.*]] = alloc_stack $Optional<NSString>
139142
// CHECK: store [[OPT_NSSTRING]] to [[TMP]]
140-
// CHECK: [[BRIDGE_ANYTHING:%.*]] = function_ref @_TFs27_bridgeAnythingToObjectiveC
141-
// CHECK: [[ANYOBJECT:%.*]] = apply [[BRIDGE_ANYTHING]]<Optional<NSString>>([[TMP]])
143+
// CHECK: [[ANYOBJECT:%.*]] = apply [[BRIDGE_OPTIONAL]]<NSString>([[TMP]])
142144
// CHECK: apply [[METHOD]]([[ANYOBJECT]], [[SELF]])
143145
receiver.takesId(optionalB)
144146

145147
// CHECK: [[METHOD:%.*]] = class_method [volatile] [[SELF]] : $NSIdLover,
146148
// CHECK: [[TMP:%.*]] = alloc_stack $Optional<Any>
147149
// CHECK: copy_addr [[OPT_ANY]] to [initialization] [[TMP]]
148-
// CHECK: [[BRIDGE_ANYTHING:%.*]] = function_ref @_TFs27_bridgeAnythingToObjectiveC
149-
// CHECK: [[ANYOBJECT:%.*]] = apply [[BRIDGE_ANYTHING]]<Optional<Any>>([[TMP]])
150+
// CHECK: [[BRIDGE_OPTIONAL:%.*]] = function_ref @_TFSq19_bridgeToObjectiveCfT_Ps9AnyObject_
151+
// CHECK: [[ANYOBJECT:%.*]] = apply [[BRIDGE_OPTIONAL]]<Any>([[TMP]])
150152
// CHECK: apply [[METHOD]]([[ANYOBJECT]], [[SELF]])
151153
receiver.takesId(optionalC)
152154

0 commit comments

Comments
 (0)