From 17716524c5cd4362af7c3001b690cf74e62563da Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 21 Aug 2014 21:36:05 +0000 Subject: [PATCH] Handle CF <-> Objective-C toll-free-bridged conversions in the type checker. Introduce an attribute that describes when a given CF type is toll-free-bridged to an Objective-C class, and which class that is. Use that information in the type checker to provide the CF <-> Objective-C toll-free-bridged conversions directly, rather than using the user-defined conversion machinery. Swift SVN r21376 --- include/swift/AST/Attr.def | 2 + include/swift/AST/Attr.h | 26 ++++++ include/swift/AST/Expr.h | 11 +++ include/swift/AST/ExprNodes.def | 3 +- include/swift/Serialization/ModuleFormat.h | 5 +- lib/AST/ASTDumper.cpp | 5 + lib/AST/Attr.cpp | 7 ++ lib/ClangImporter/ImportDecl.cpp | 92 ++++--------------- lib/Parse/ParseDecl.cpp | 1 + lib/SILGen/SILGenExpr.cpp | 14 +++ lib/Sema/CSApply.cpp | 20 ++++ lib/Sema/CSSimplify.cpp | 52 +++++++++++ lib/Sema/Constraint.cpp | 4 + lib/Sema/Constraint.h | 6 ++ lib/Sema/TypeCheckAttr.cpp | 2 + lib/Sema/TypeCheckDecl.cpp | 1 + lib/Serialization/Serialization.cpp | 1 + .../usr/include/CoreFoundation.h | 4 +- .../usr/include/Foundation.h | 3 + test/conv/CoreFoundation.swift | 27 ++++++ 20 files changed, 205 insertions(+), 81 deletions(-) create mode 100644 test/conv/CoreFoundation.swift diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 4f51b140735be..29994e56011c2 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -192,6 +192,8 @@ DECL_ATTR(effects, Effects, OnFunc, 50) DECL_ATTR_ALIAS(unowned, Ownership) +DECL_ATTR(__objc_bridged, ObjCBridged, OnClass | NotSerialized, + /* Not serialized */51) // Reordering these platforms will break serialization. AVAILABILITY_PLATFORM(iOS, "iOS") diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 66d47fd75adc5..08336973c46dc 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -31,6 +31,7 @@ class ASTPrinter; class ASTContext; struct PrintOptions; class Decl; +class ClassDecl; /// The associativity of a binary operator. enum class Associativity { @@ -945,6 +946,31 @@ class RawDocCommentAttr : public DeclAttribute { } }; +/// An attribute applied to a CoreFoundation class that is toll-free bridged to +/// an Objective-C class. +/// +/// This attribute is introduced by the Clang importer, and is therefore always +/// implicit. +class ObjCBridgedAttr : public DeclAttribute { + ClassDecl *ObjCClass; + +public: + ObjCBridgedAttr(ClassDecl *ObjCClass) + : DeclAttribute(DAK_ObjCBridged, SourceLoc(), SourceRange(), + /*Implicit=*/true), + ObjCClass(ObjCClass) + { + } + + /// Retrieve the Objective-C class to which this foreign class is toll-free + /// bridged. + ClassDecl *getObjCClass() const { return ObjCClass; } + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DAK_ObjCBridged; + } +}; + /// \brief Attributes that may be applied to declarations. class DeclAttributes { /// Linked list of declaration attributes. diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 7cea0c04e5566..5ef576a5221d1 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -2022,7 +2022,18 @@ class PointerToPointerExpr : public ImplicitConversionExpr { return E->getKind() == ExprKind::PointerToPointer; } }; + +/// Convert between a foreign object and its corresponding Objective-C object. +class ForeignObjectConversionExpr : public ImplicitConversionExpr { +public: + ForeignObjectConversionExpr(Expr *subExpr, Type ty) + : ImplicitConversionExpr(ExprKind::ForeignObjectConversion, subExpr, ty) {} + static bool classof(const Expr *E) { + return E->getKind() == ExprKind::ForeignObjectConversion; + } +}; + /// TupleShuffleExpr - This represents a permutation of a tuple value to a new /// tuple type. The expression's type is known to be a tuple type and the /// subexpression is known to have a tuple type as well. diff --git a/include/swift/AST/ExprNodes.def b/include/swift/AST/ExprNodes.def index 1c7c76627ba80..fcf143ef7d9e3 100644 --- a/include/swift/AST/ExprNodes.def +++ b/include/swift/AST/ExprNodes.def @@ -137,7 +137,8 @@ ABSTRACT_EXPR(ImplicitConversion, Expr) EXPR(StringToPointer, ImplicitConversionExpr) EXPR(PointerToPointer, ImplicitConversionExpr) EXPR(LValueToPointer, ImplicitConversionExpr) - EXPR_RANGE(ImplicitConversion, Load, LValueToPointer) + EXPR(ForeignObjectConversion, ImplicitConversionExpr) + EXPR_RANGE(ImplicitConversion, Load, ForeignObjectConversion) ABSTRACT_EXPR(ExplicitCast, Expr) ABSTRACT_EXPR(CheckedCast, ExplicitCastExpr) UNCHECKED_EXPR(UnresolvedCheckedCast, CheckedCastExpr) diff --git a/include/swift/Serialization/ModuleFormat.h b/include/swift/Serialization/ModuleFormat.h index b8ea143b031ca..a691faf8b570f 100644 --- a/include/swift/Serialization/ModuleFormat.h +++ b/include/swift/Serialization/ModuleFormat.h @@ -1058,10 +1058,11 @@ namespace decls_block { using AccessibilityDeclAttrLayout = BCRecordLayout; using SetterAccessibilityDeclAttrLayout = BCRecordLayout; + using ObjCBridgedDeclAttrLayout = BCRecordLayout; using InlineDeclAttrLayout = BCRecordLayout< - Inline_DECL_ATTR, - BCFixed<2> // inline value + Inline_DECL_ATTR, + BCFixed<2> // inline value >; // Encodes a VersionTuple: diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 8c4a9048ff304..467e64934d9f4 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -1526,6 +1526,11 @@ class PrintExpr : public ExprVisitor { printRec(E->getSubExpr()); OS << ')'; } + void visitForeignObjectConversionExpr(ForeignObjectConversionExpr *E) { + printCommon(E, "foreign_object_conversion") << '\n'; + printRec(E->getSubExpr()); + OS << ')'; + } void visitInOutExpr(InOutExpr *E) { printCommon(E, "inout_expr") << '\n'; diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 72def70aebf3d..03a3f9fa9fdd4 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -198,6 +198,11 @@ void DeclAttribute::print(ASTPrinter &Printer, case DAK_RawDocComment: // Not printed. return; + + case DAK_ObjCBridged: + // Not printed. + return; + case DAK_Count: llvm_unreachable("exceed declaration attribute kinds"); } @@ -277,6 +282,8 @@ StringRef DeclAttribute::getAttrName() const { } case DAK_RawDocComment: return "<>"; + case DAK_ObjCBridged: + return "<>"; } } diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 50f5f4e12a4f3..aa6deeae4ae00 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -781,66 +781,6 @@ static ConstructorDecl *makeOptionSetDefaultConstructor(StructDecl *optionSetDec return ctorDecl; } -/// Create a call to a builtin name. -static Expr *emitBuiltinCast(ASTContext &C, Expr *op, StringRef builtinName) { - SmallVector results; - C.TheBuiltinModule->lookupValue({}, C.getIdentifier(builtinName), - NLKind::QualifiedLookup, results); - assert(results.size() == 1 && "lookup for builtin didn't find anything?"); - auto builtin = new (C) DeclRefExpr(results[0], SourceLoc(), /*implicit*/true); - return new (C) CallExpr(builtin, op, /*implicit*/ true); -} - -// Build the implicit conversion from a CF type to an NS type or vice versa. -// -// extension FromType : ToType { -// var value: RawType -// func __conversion() -> Bool { -// return self.value != 0 -// } -// } -static FuncDecl *makeClassToClassImplicitConversion(ClassDecl *fromDecl, - ClassDecl *toDecl) { - ASTContext &C = fromDecl->getASTContext(); - - auto resultType = toDecl->getDeclaredType(); - - VarDecl *selfDecl = createSelfDecl(fromDecl, /*isStaticMethod*/false); - Pattern *selfParam = createTypedNamedPattern(selfDecl); - Pattern *methodParam = TuplePattern::create(C, SourceLoc(),{},SourceLoc()); - methodParam->setType(TupleType::getEmpty(C)); - Pattern *params[] = {selfParam, methodParam}; - - DeclName name(C, C.Id_Conversion, { }); - FuncDecl *conversionDecl = FuncDecl::create( - C, SourceLoc(), StaticSpellingKind::None, SourceLoc(), - name, SourceLoc(), nullptr, Type(), - params, TypeLoc::withoutLoc(resultType), fromDecl); - conversionDecl->setImplicit(); - conversionDecl->getAttrs().add(new (C) FinalAttr(/*implicit*/ true)); - conversionDecl->setAccessibility(Accessibility::Public); - - auto argType = TupleType::getEmpty(C); - Type fnType = FunctionType::get(argType, resultType); - fnType = FunctionType::get(fromDecl->getDeclaredTypeInContext(), fnType); - conversionDecl->setType(fnType); - conversionDecl->setBodyResultType(resultType); - - Expr *op = new (C) DeclRefExpr(selfDecl, SourceLoc(), /*implicit*/ true); - op = emitBuiltinCast(C, op, "castToNativeObject"); - op = emitBuiltinCast(C, op, "castFromNativeObject"); - auto ret = new (C) ReturnStmt(SourceLoc(), op); - - auto body = BraceStmt::create(C, SourceLoc(), ASTNode(ret), - SourceLoc(), - /*implicit*/ true); - conversionDecl->setBody(body); - - // Add as an external definition. - C.addedExternalDecl(conversionDecl); - - return conversionDecl; -} namespace { class CFPointeeInfo { @@ -1083,20 +1023,6 @@ namespace { return Type(); } - void addClassToClassConversions(ClassDecl *fromClass, - const clang::IdentifierInfo *toClassName) { - // Just bail out if we can't find a class by that name. - ClassDecl *toClass = dyn_cast_or_null( - Impl.importDeclByName(toClassName->getName())); - if (!toClass) return; - - auto toConversion = makeClassToClassImplicitConversion(fromClass, toClass); - fromClass->addMember(toConversion); - - auto fromConversion = makeClassToClassImplicitConversion(toClass, fromClass); - toClass->addMember(fromConversion); - } - Type importCFClassType(const clang::TypedefNameDecl *decl, CFPointeeInfo info) { // Name the class 'CFString', not 'CFStringRef'. @@ -1135,11 +1061,25 @@ namespace { auto record = info.getRecord()->getMostRecentDecl(); if (info.isConst()) { if (auto attr = record->getAttr()) { - addClassToClassConversions(theClass, attr->getBridgedType()); + // Record the Objective-C class to which this CF type is toll-free + // bridged. + if (ClassDecl *objcClass = dyn_cast_or_null( + Impl.importDeclByName( + attr->getBridgedType()->getName()))) { + theClass->getAttrs().add( + new (Impl.SwiftContext) ObjCBridgedAttr(objcClass)); + } } } else { if (auto attr = record->getAttr()) { - addClassToClassConversions(theClass, attr->getBridgedType()); + // Record the Objective-C class to which this CF type is toll-free + // bridged. + if (ClassDecl *objcClass = dyn_cast_or_null( + Impl.importDeclByName( + attr->getBridgedType()->getName()))) { + theClass->getAttrs().add( + new (Impl.SwiftContext) ObjCBridgedAttr(objcClass)); + } } } diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 17bcb233a9796..f7e9cc3d3c5c1 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -333,6 +333,7 @@ bool Parser::parseNewDeclAttribute(DeclAttributes &Attributes, SourceLoc AtLoc, llvm_unreachable("DAK_Count should not appear in parsing switch"); case DAK_RawDocComment: + case DAK_ObjCBridged: llvm_unreachable("virtual attributes should not be parsed " "by attribute parsing code"); case DAK_SetterAccessibility: diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index 485b558f9d7b1..70c5373777cc4 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -238,6 +238,8 @@ namespace { RValue visitArrayToPointerExpr(ArrayToPointerExpr *E, SGFContext C); RValue visitStringToPointerExpr(StringToPointerExpr *E, SGFContext C); RValue visitPointerToPointerExpr(PointerToPointerExpr *E, SGFContext C); + RValue visitForeignObjectConversionExpr(ForeignObjectConversionExpr *E, + SGFContext C); }; } @@ -5659,6 +5661,18 @@ RValue RValueEmitter::visitPointerToPointerExpr(PointerToPointerExpr *E, return RValue(SGF, E, result); } +RValue RValueEmitter::visitForeignObjectConversionExpr( + ForeignObjectConversionExpr *E, + SGFContext C) { + // Get the original value. + ManagedValue orig = SGF.emitRValueAsSingleValue(E->getSubExpr()); + ManagedValue result(SGF.B.createUncheckedRefCast( + E, orig.getValue(), + SGF.getLoweredType(E->getType())), + orig.getCleanup()); + return RValue(SGF, E, E->getType()->getCanonicalType(), result); +} + RValue SILGenFunction::emitRValue(Expr *E, SGFContext C) { return RValueEmitter(*this).visit(E, C); } diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 30a99a09dbdcb..1bf27ad679afe 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -4038,6 +4038,26 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType, case ConversionRestrictionKind::BridgeFromObjC: return forceBridgeFromObjectiveC(expr, toType); + + case ConversionRestrictionKind::CFTollFreeBridgeToObjC: { + auto foreignClass = expr->getType()->getClassOrBoundGenericClass(); + auto objcType = foreignClass->getAttrs().getAttribute() + ->getObjCClass()->getDeclaredInterfaceType(); + auto asObjCClass = new (tc.Context) ForeignObjectConversionExpr(expr, + objcType); + return coerceToType(asObjCClass, toType, locator); + } + + case ConversionRestrictionKind::ObjCTollFreeBridgeToCF: { + auto foreignClass = toType->getClassOrBoundGenericClass(); + auto objcType = foreignClass->getAttrs().getAttribute() + ->getObjCClass()->getDeclaredInterfaceType(); + Expr *result = coerceToType(expr, objcType, locator); + if (!result) + return nullptr; + + return new (tc.Context) ForeignObjectConversionExpr(result, toType); + } } } diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 06965ae1f2c03..78ec412131eda 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -1513,6 +1513,27 @@ ConstraintSystem::matchTypes(Type type1, Type type2, TypeMatchKind kind, if (nominal1->getDecl() == nominal2->getDecl()) { conversionsOrFixes.push_back(ConversionRestrictionKind::DeepEquality); } + + // Check for CF <-> ObjectiveC bridging. + if (desugar1->getKind() == TypeKind::Class) { + auto class1 = cast(nominal1->getDecl()); + auto class2 = cast(nominal2->getDecl()); + + // CF -> Objective-C via toll-free bridging. + if (class1->isForeign() && !class2->isForeign() && + class1->getAttrs().hasAttribute()) { + conversionsOrFixes.push_back( + ConversionRestrictionKind::CFTollFreeBridgeToObjC); + } + + // Objective-C -> CF via toll-free bridging. + if (class2->isForeign() && !class1->isForeign() && + class2->getAttrs().hasAttribute()) { + conversionsOrFixes.push_back( + ConversionRestrictionKind::ObjCTollFreeBridgeToCF); + } + } + break; } @@ -3803,6 +3824,37 @@ ConstraintSystem::simplifyRestrictedConstraint(ConversionRestrictionKind restric locator); } + // T' < U and T a toll-free-bridged to T' ===> T' getClassOrBoundGenericClass(); + auto bridgedObjCClass + = nativeClass->getAttrs().getAttribute()->getObjCClass(); + + return matchTypes(bridgedObjCClass->getDeclaredInterfaceType(), + type2, TypeMatchKind::Subtype, subFlags, locator); + } + + // T < U' and U a toll-free-bridged to U' ===> T getClassOrBoundGenericClass(); + auto bridgedObjCClass + = nativeClass->getAttrs().getAttribute()->getObjCClass(); + + return matchTypes(type1, + bridgedObjCClass->getDeclaredInterfaceType(), + TypeMatchKind::Subtype, subFlags, locator); + } + // T' < U, hasMember(T, conversion, T -> T') ===> T { IGNORED_ATTR(ObjC) IGNORED_ATTR(Optional) IGNORED_ATTR(RawDocComment) + IGNORED_ATTR(ObjCBridged) IGNORED_ATTR(Required) IGNORED_ATTR(Convenience) IGNORED_ATTR(Semantics) @@ -517,6 +518,7 @@ class AttributeChecker : public AttributeVisitor { IGNORED_ATTR(NoReturn) IGNORED_ATTR(NSManaged) // checked early. IGNORED_ATTR(ObjC) + IGNORED_ATTR(ObjCBridged) IGNORED_ATTR(Optional) IGNORED_ATTR(Ownership) IGNORED_ATTR(Override) diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index aaaff8b73d487..99c1960aab095 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -5131,6 +5131,7 @@ class DeclChecker : public DeclVisitor { UNINTERESTING_ATTR(NonMutating) UNINTERESTING_ATTR(NSCopying) UNINTERESTING_ATTR(NSManaged) + UNINTERESTING_ATTR(ObjCBridged) UNINTERESTING_ATTR(Optional) UNINTERESTING_ATTR(Override) UNINTERESTING_ATTR(RawDocComment) diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 15908f7d12332..b1bcb6d5438ac 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -1315,6 +1315,7 @@ void Serializer::writeDeclAttribute(const DeclAttribute *DA) { case DAK_Ownership: // Serialized as part of the type. case DAK_Accessibility: case DAK_SetterAccessibility: + case DAK_ObjCBridged: case DAK_Count: llvm_unreachable("cannot serialize DAK_Count"); return; diff --git a/test/Inputs/clang-importer-sdk/usr/include/CoreFoundation.h b/test/Inputs/clang-importer-sdk/usr/include/CoreFoundation.h index 816057f4f3c55..78252bcebc8d4 100644 --- a/test/Inputs/clang-importer-sdk/usr/include/CoreFoundation.h +++ b/test/Inputs/clang-importer-sdk/usr/include/CoreFoundation.h @@ -1,6 +1,6 @@ typedef const void *CFTypeRef; -typedef struct __CFString *CFMutableStringRef; -typedef struct __CFString const *CFStringRef; +typedef struct __attribute__((objc_bridge_mutable(NSMutableString))) __CFString *CFMutableStringRef; +typedef struct __attribute__((objc_bridge(NSString))) __CFString const *CFStringRef; typedef struct __CFTree *CFTreeRef; typedef signed long CFIndex; diff --git a/test/Inputs/clang-importer-sdk/usr/include/Foundation.h b/test/Inputs/clang-importer-sdk/usr/include/Foundation.h index 65baaa2a6d287..5a82c6d747f97 100644 --- a/test/Inputs/clang-importer-sdk/usr/include/Foundation.h +++ b/test/Inputs/clang-importer-sdk/usr/include/Foundation.h @@ -108,6 +108,9 @@ NSString *NSStringToNSString(NSString *str); - (instancetype)visit; @end +@interface NSMutableString : NSString +@end + BOOL BOOLtoBOOL(BOOL b); typedef CGPoint NSPoint; diff --git a/test/conv/CoreFoundation.swift b/test/conv/CoreFoundation.swift new file mode 100644 index 0000000000000..70f59f7a5073e --- /dev/null +++ b/test/conv/CoreFoundation.swift @@ -0,0 +1,27 @@ +// RUN: rm -rf %t/clang-module-cache +// RUN: %swift %clang-importer-sdk -parse -module-cache-path %t/clang-module-cache %s -verify + +import CoreFoundation +import Foundation + +func testCFToObjC(let cfStr: CFString, cfMutableStr: CFMutableString) { + var nsStr: NSString = cfStr + nsStr = cfMutableStr + + var nsMutableStr: NSMutableString = cfMutableStr + nsMutableStr = cfStr // expected-error{{'NSString' is not a subtype of 'NSMutableString'}} + + // sanity check + nsStr = nsMutableStr +} + +func testObjCToCF(let nsStr: NSString, nsMutableStr: NSMutableString) { + var cfStr: CFString = nsStr + cfStr = nsMutableStr + + var cfMutableStr: CFMutableString = nsMutableStr + cfMutableStr = cfStr // expected-error{{'CFString' is not convertible to 'CFMutableString'}} + + // sanity check + cfStr = cfMutableStr +}