Skip to content

Commit cfa9cd9

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 c9bad5e commit cfa9cd9

File tree

10 files changed

+365
-16
lines changed

10 files changed

+365
-16
lines changed

lib/AST/ASTContext.cpp

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

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

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

lib/AST/Builtins.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@ static ValueDecl *getIsOptionalOperation(ASTContext &Context, Identifier Id) {
701701
return builder.build(Id);
702702
}
703703

704+
704705
static ValueDecl *getAllocOperation(ASTContext &Context, Identifier Id) {
705706
Type PtrSizeTy = BuiltinIntegerType::getWordType(Context);
706707
Type ResultTy = Context.TheRawPointerType;

lib/SILGen/SILGenBridging.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ 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+
if (witnessFnTy.castTo<SILFunctionType>()->getParameters()[0].isIndirect()
76+
&& !swiftValue.getType().isAddress()) {
77+
auto tmp = gen.emitTemporaryAllocation(loc, swiftValue.getType());
78+
gen.B.createStore(loc, swiftValue.getValue(), tmp);
79+
swiftValue = ManagedValue::forUnmanaged(tmp);
80+
}
81+
7382
// Call the witness.
7483
SILType resultTy = gen.getLoweredType(objcType);
7584
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
@@ -1664,6 +1664,11 @@ optimizeBridgedSwiftToObjCCast(SILInstruction *Inst,
16641664

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

16681673
// Get substitutions, if source is a bound generic type.
16691674
ArrayRef<Substitution> Subs =
@@ -1730,9 +1735,9 @@ optimizeBridgedSwiftToObjCCast(SILInstruction *Inst,
17301735
case ParameterConvention::Direct_Unowned:
17311736
break;
17321737
case ParameterConvention::Indirect_In:
1738+
case ParameterConvention::Indirect_In_Guaranteed:
17331739
case ParameterConvention::Indirect_Inout:
17341740
case ParameterConvention::Indirect_InoutAliasable:
1735-
case ParameterConvention::Indirect_In_Guaranteed:
17361741
case ParameterConvention::Direct_Deallocating:
17371742
llvm_unreachable("unsupported convention for bridging conversion");
17381743
}

stdlib/public/core/Optional.swift

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,3 +487,78 @@ 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(_ source: AnyObject,
513+
result: inout Optional<Wrapped>?) {
514+
// Map the nil sentinel back to .none.
515+
// NB that the signature of _forceBridgeFromObjectiveC adds another level
516+
// of optionality, so we need to wrap the immediate result of the conversion
517+
// in `.some`.
518+
if source === _nilSentinel {
519+
result = .some(.none)
520+
return
521+
}
522+
// Otherwise, force-bridge the underlying value.
523+
let unwrappedResult = source as! Wrapped
524+
result = .some(.some(unwrappedResult))
525+
}
526+
527+
public static func _conditionallyBridgeFromObjectiveC(_ source: AnyObject,
528+
result: inout Optional<Wrapped>?)
529+
-> Bool {
530+
// Map the nil sentinel back to .none.
531+
// NB that the signature of _forceBridgeFromObjectiveC adds another level
532+
// of optionality, so we need to wrap the immediate result of the conversion
533+
// in `.some` to indicate success of the bridging operation, with a nil
534+
// result.
535+
if source === _nilSentinel {
536+
result = .some(.none)
537+
return true
538+
}
539+
// Otherwise, try to bridge the underlying value.
540+
if let unwrappedResult = source as? Wrapped {
541+
result = .some(.some(unwrappedResult))
542+
return true
543+
} else {
544+
result = .none
545+
return false
546+
}
547+
}
548+
549+
public static func _unconditionallyBridgeFromObjectiveC(_ source: AnyObject?)
550+
-> Optional<Wrapped> {
551+
if let nonnullSource = source {
552+
// Map the nil sentinel back to none.
553+
if nonnullSource === _nilSentinel {
554+
return .none
555+
} else {
556+
return .some(nonnullSource as! Wrapped)
557+
}
558+
} else {
559+
// If we unexpectedly got nil, just map it to `none` too.
560+
return .none
561+
}
562+
}
563+
}
564+
#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: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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 <Foundation/Foundation.h>
14+
#include "swift/Basic/Lazy.h"
15+
#include "swift/Basic/LLVM.h"
16+
#include "swift/Runtime/Metadata.h"
17+
#include "swift/Runtime/Mutex.h"
18+
#include "swift/Runtime/ObjCBridge.h"
19+
#include <vector>
20+
21+
using namespace swift;
22+
23+
/// Class of sentinel objects used to represent the `nil` value of nested
24+
/// optionals.
25+
@interface _SwiftNull : NSObject {
26+
@public
27+
unsigned depth;
28+
}
29+
@end
30+
31+
@implementation _SwiftNull : NSObject
32+
33+
- (NSString*)description {
34+
return [NSString stringWithFormat:@"<_SwiftNull %u>", self->depth];
35+
}
36+
37+
- (void)dealloc {
38+
[super dealloc];
39+
}
40+
41+
@end
42+
43+
namespace {
44+
45+
struct SwiftNullSentinelCache {
46+
std::vector<id> Cache;
47+
StaticReadWriteLock Lock;
48+
};
49+
50+
static Lazy<SwiftNullSentinelCache> Sentinels;
51+
52+
static id getSentinelForDepth(unsigned depth) {
53+
// For unnested optionals, use NSNull.
54+
if (depth == 1)
55+
return SWIFT_LAZY_CONSTANT([NSNull null]);
56+
// Otherwise, make up our own sentinel.
57+
// See if we created one for this depth.
58+
auto &theSentinels = Sentinels.get();
59+
unsigned depthIndex = depth - 2;
60+
{
61+
StaticScopedReadLock lock(theSentinels.Lock);
62+
const auto &cache = theSentinels.Cache;
63+
if (depthIndex < cache.size()) {
64+
id cached = cache[depthIndex];
65+
if (cached)
66+
return cached;
67+
}
68+
}
69+
// Make one if we need to.
70+
{
71+
StaticScopedWriteLock lock(theSentinels.Lock);
72+
if (depthIndex >= theSentinels.Cache.size())
73+
theSentinels.Cache.resize(depthIndex + 1);
74+
auto &cached = theSentinels.Cache[depthIndex];
75+
// Make sure another writer didn't sneak in.
76+
if (!cached) {
77+
auto sentinel = [[_SwiftNull alloc] init];
78+
sentinel->depth = depth;
79+
cached = sentinel;
80+
}
81+
return cached;
82+
}
83+
}
84+
85+
}
86+
87+
/// Return the sentinel object to use to represent `nil` for a given Optional
88+
/// type.
89+
SWIFT_RUNTIME_STDLIB_INTERFACE SWIFT_CC(swift)
90+
extern "C"
91+
id _swift_Foundation_getOptionalNilSentinelObject(const Metadata *Wrapped) {
92+
// Figure out the depth of optionality we're working with.
93+
unsigned depth = 1;
94+
while (Wrapped->getKind() == MetadataKind::Optional) {
95+
++depth;
96+
Wrapped = cast<EnumMetadata>(Wrapped)->getGenericArgs()[0];
97+
}
98+
99+
return objc_retain(getSentinelForDepth(depth));
100+
}

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)