Skip to content

Commit 8ea2d0d

Browse files
committed
Sema: Fix conditional downcasts from Swift types to CF types
Conditional and forced downcasts enter a constraint that almost always succeeds; only when applying the solution do we evaluate the feasability of the cast and determine if it always succeeds, always fails, or conditionally succeeds. This changes how the resulting AST is represented and can also emit diagnostics. If the conditional cast is at this stage determined to always succeed, we treat it as an unconditional cast, going through ExprRewriter::coerceToType() to build the AST for the coercion. However conditional cast constraints don't enter the same restrictions into the solution as unconditional casts do, so coerceToType() would fall over if casting a Swift type to a CF type by first bridging the Swift type to Objective-C. Get around this by checking for this case explicitly when lowering a CoerceExpr. It feels like there's a more fundamental issue here with how casts are modeled in the constraint solver, but I'm not going to try understanding that now. Fixes <rdar://problem/32227571>.
1 parent 88a5656 commit 8ea2d0d

File tree

3 files changed

+42
-0
lines changed

3 files changed

+42
-0
lines changed

lib/Sema/CSApply.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5816,6 +5816,8 @@ Expr *ExprRewriter::buildCollectionUpcastExpr(Expr *expr, Type toType,
58165816

58175817
Expr *ExprRewriter::buildObjCBridgeExpr(Expr *expr, Type toType,
58185818
ConstraintLocatorBuilder locator) {
5819+
auto &tc = cs.getTypeChecker();
5820+
58195821
Type fromType = cs.getType(expr);
58205822

58215823
// Bridged collection casts always succeed, so we treat them as
@@ -5838,6 +5840,20 @@ Expr *ExprRewriter::buildObjCBridgeExpr(Expr *expr, Type toType,
58385840
if (!objcExpr)
58395841
return nullptr;
58405842

5843+
// We might have a coercion of a Swift type to a CF type toll-free
5844+
// bridged to Objective-C.
5845+
//
5846+
// FIXME: Ideally we would instead have already recorded a restriction
5847+
// when solving the constraint, and we wouldn't need to duplicate this
5848+
// part of coerceToType() here.
5849+
if (auto foreignClass = toType->getClassOrBoundGenericClass()) {
5850+
if (foreignClass->getForeignClassKind() ==
5851+
ClassDecl::ForeignKind::CFType) {
5852+
return cs.cacheType(
5853+
new (tc.Context) ForeignObjectConversionExpr(objcExpr, toType));
5854+
}
5855+
}
5856+
58415857
return coerceToType(objcExpr, toType, locator);
58425858
}
58435859

test/Inputs/clang-importer-sdk/usr/include/CoreFoundation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ typedef struct __attribute__((objc_bridge(NSString))) __CFString const *CFString
1414
typedef struct __CFTree *CFTreeRef;
1515
typedef const struct __attribute__((objc_bridge(CFURL))) __CFURL * CFURLRef;
1616

17+
typedef struct __attribute__((objc_bridge(NSDictionary))) __CFDictionary const *CFDictionaryRef;
18+
typedef struct __attribute__((objc_bridge(NSArray))) __CFArray const *CFArrayRef;
19+
typedef struct __attribute__((objc_bridge(NSSet))) __CFSet const *CFSetRef;
20+
1721
typedef CFTypeRef CFAliasForTypeRef;
1822

1923

test/expr/cast/cf.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,25 @@ func testCFConvWithIUO(_ x: CFString!, y: NSString!) {
8080
acceptNSString(x)
8181
acceptCFString(y)
8282
}
83+
84+
func testBridgedCFDowncast(array: [Any], dictionary: [AnyHashable : Any], set: Set<AnyHashable>) {
85+
let cfArray = array as CFArray
86+
let cfDictionary = dictionary as CFDictionary
87+
let cfSet = set as CFSet
88+
89+
_ = array as? CFArray // expected-warning {{conditional cast from '[Any]' to 'CFArray' always succeeds}}
90+
_ = dictionary as? CFDictionary // expected-warning {{conditional cast from '[AnyHashable : Any]' to 'CFDictionary' always succeeds}}
91+
_ = set as? CFSet // expected-warning {{conditional cast from 'Set<AnyHashable>' to 'CFSet' always succeeds}}
92+
93+
_ = array as! CFArray // expected-warning {{forced cast from '[Any]' to 'CFArray' always succeeds}}
94+
_ = dictionary as! CFDictionary // expected-warning {{forced cast from '[AnyHashable : Any]' to 'CFDictionary' always succeeds}}
95+
_ = set as! CFSet // expected-warning {{forced cast from 'Set<AnyHashable>' to 'CFSet' always succeeds}}
96+
97+
_ = cfArray as! [Any]
98+
_ = cfDictionary as! [AnyHashable : Any]
99+
_ = cfSet as! Set<AnyHashable>
100+
101+
_ = cfArray as? [Any]
102+
_ = cfDictionary as? [AnyHashable : Any]
103+
_ = cfSet as? Set<AnyHashable>
104+
}

0 commit comments

Comments
 (0)