diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 0ae3f7691af8a..a9e908a4bf890 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -1643,6 +1643,19 @@ DeclName ClangImporter::Implementation::importFullName( if (auto *nameAttr = D->getAttr()) { if (hasCustomName) *hasCustomName = true; + + // If we have an Objective-C method that is being mapped to an + // initializer (e.g., a factory method whose name doesn't fit the + // convention for factory methods), make sure that it can be + // imported as an initializer. + if (auto method = dyn_cast(D)) { + unsigned initPrefixLength; + CtorInitializerKind kind; + if (nameAttr->getName().startswith("init(") && + !shouldImportAsInitializer(method, initPrefixLength, kind)) + return { }; + } + return parseDeclName(SwiftContext, nameAttr->getName()); } @@ -1656,6 +1669,8 @@ DeclName ClangImporter::Implementation::importFullName( /// Whether the result is a function name. bool isFunction = false; bool isInitializer = false; + unsigned initializerPrefixLen; + CtorInitializerKind initKind; StringRef baseName; SmallVector argumentNames; SmallString<16> selectorSplitScratch; @@ -1678,7 +1693,8 @@ DeclName ClangImporter::Implementation::importFullName( case clang::DeclarationName::ObjCOneArgSelector: case clang::DeclarationName::ObjCZeroArgSelector: { auto objcMethod = cast(D); - isInitializer = isInitMethod(objcMethod); + isInitializer = shouldImportAsInitializer(objcMethod, initializerPrefixLen, + initKind); // Map the Objective-C selector directly. auto selector = D->getDeclName().getObjCSelector(); @@ -1697,8 +1713,8 @@ DeclName ClangImporter::Implementation::importFullName( // For initializers, compute the first argument name. if (isInitializer) { - // Skip over the 'init'. - auto argName = selector.getNameForSlot(0).substr(4); + // Skip over the prefix. + auto argName = selector.getNameForSlot(0).substr(initializerPrefixLen); // Drop "With" if present after the "init". bool droppedWith = false; @@ -1714,7 +1730,8 @@ DeclName ClangImporter::Implementation::importFullName( // put "with" back. if (droppedWith && isSwiftReservedName(argName)) { selectorSplitScratch = "with"; - selectorSplitScratch += selector.getNameForSlot(0).substr(8); + selectorSplitScratch += selector.getNameForSlot(0).substr( + initializerPrefixLen + 4); argName = selectorSplitScratch; } @@ -2625,6 +2642,75 @@ bool ClangImporter::Implementation::isInitMethod( return camel_case::getFirstWord(selector.getNameForSlot(0)) == "init"; } +bool ClangImporter::Implementation::shouldImportAsInitializer( + const clang::ObjCMethodDecl *method, + unsigned &prefixLength, + CtorInitializerKind &kind) { + /// Is this an initializer? + if (isInitMethod(method)) { + prefixLength = 4; + kind = CtorInitializerKind::Designated; + return true; + } + + // It must be a class method. + if (!method->isClassMethod()) return false; + + // Said class methods must be in an actual class. + auto objcClass = method->getClassInterface(); + if (!objcClass) return false; + + // Check whether we should try to import this factory method as an + // initializer. + switch (getFactoryAsInit(objcClass, method)) { + case FactoryAsInitKind::AsInitializer: + // Okay; check for the correct result type below. + prefixLength = 0; + break; + + case FactoryAsInitKind::Infer: { + // See if we can match the class name to the beginning of the first + // selector piece. + auto firstPiece = method->getSelector().getNameForSlot(0); + StringRef firstArgLabel = matchLeadingTypeName(firstPiece, + objcClass->getName()); + if (firstArgLabel.size() == firstPiece.size()) + return false; + + // Store the prefix length. + prefixLength = firstPiece.size() - firstArgLabel.size(); + + // Continue checking the result type, below. + break; + } + + case FactoryAsInitKind::AsClassMethod: + return false; + } + + // Determine whether we have a suitable return type. + if (method->hasRelatedResultType()) { + // When the factory method has an "instancetype" result type, we + // can import it as a convenience factory method. + kind = CtorInitializerKind::ConvenienceFactory; + } else if (auto objcPtr = method->getReturnType() + ->getAs()) { + if (objcPtr->getInterfaceDecl() != objcClass) { + // FIXME: Could allow a subclass here, but the rest of the compiler + // isn't prepared for that yet. + return false; + } + + // Factory initializer. + kind = CtorInitializerKind::Factory; + } else { + // Not imported as an initializer. + return false; + } + + return true; +} + #pragma mark Name lookup void ClangImporter::lookupValue(Identifier name, VisibleDeclConsumer &consumer){ auto &pp = Impl.Instance->getPreprocessor(); diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index 38ab4b8848f5a..0f3a58e103f66 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -430,6 +430,20 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation /// that will be imported as a Swift initializer. bool isInitMethod(const clang::ObjCMethodDecl *method); + /// Determine whether this Objective-C method should be imported as + /// an initializer. + /// + /// \param prefixLength Will be set to the length of the prefix that + /// should be stripped from the first selector piece, e.g., "init" + /// or the restated name of the class in a factory method. + /// + /// \param kind Will be set to the kind of initializer being + /// imported. Note that this does not distinguish designated + /// vs. convenience; both will be classified as "designated". + bool shouldImportAsInitializer(const clang::ObjCMethodDecl *method, + unsigned &prefixLength, + CtorInitializerKind &kind); + private: /// \brief Generation number that is used for crude versioning. /// diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index b44271c7d94b3..5256df494fed7 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -19,6 +19,8 @@ SWIFT_NAME(SomeClass) - (instancetype)initWithDefault; - (void)instanceMethodWithX:(float)x y:(float)y z:(float)z; + (instancetype)someClassWithDouble:(double)d; ++ (instancetype)someClassWithTry:(BOOL)shouldTry; ++ (NSObject *)buildWithObject:(NSObject *)object SWIFT_NAME(init(object:)); @property (readonly,nonatomic) float floatProperty; @property (readwrite,nonatomic) double doubleProperty; diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index dae80d819677f..c633983bf50b8 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -14,11 +14,10 @@ // CHECK-NEXT: doubleProperty --> doubleProperty{{$}} // CHECK-NEXT: extensionMethodWithX --> extensionMethodWithX(_:y:) // CHECK-NEXT: floatProperty --> floatProperty{{$}} -// CHECK-NEXT: init --> init(float:), init(withDefault:) +// CHECK-NEXT: init --> init(float:), init(withDefault:), init(double:), init(withTry:){{$}} // CHECK-NEXT: instanceMethodWithX --> instanceMethodWithX(_:y:z:) // CHECK-NEXT: protoInstanceMethodWithX --> protoInstanceMethodWithX(_:y:) // CHECK-NEXT: setAccessibilityFloat --> setAccessibilityFloat(_:) -// CHECK-NEXT: someClassWithDouble --> someClassWithDouble(_:) // CHECK: Full name -> entry mappings: // CHECK-NEXT: NSAccessibility: @@ -43,15 +42,17 @@ // CHECK-NEXT: SNSomeClass: -[SNSomeClass extensionMethodWithX:y:] // CHECK-NEXT: floatProperty: // CHECK-NEXT: SNSomeClass: SNSomeClass.floatProperty +// CHECK-NEXT: init(double:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] // CHECK-NEXT: init(float:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithFloat:] // CHECK-NEXT: init(withDefault:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass initWithDefault] +// CHECK-NEXT: init(withTry:): +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithTry:] // CHECK-NEXT: instanceMethodWithX(_:y:z:): // CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:y:z:] // CHECK-NEXT: protoInstanceMethodWithX(_:y:): // CHECK-NEXT: SNSomeProtocol: -[SNSomeProtocol protoInstanceMethodWithX:y:] // CHECK-NEXT: setAccessibilityFloat(_:): // CHECK-NEXT: NSAccessibility: -[NSAccessibility setAccessibilityFloat:] -// CHECK-NEXT: someClassWithDouble(_:): -// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:]