Skip to content

Commit

Permalink
Clang importer: teach importFullName about factory methods as initial…
Browse files Browse the repository at this point in the history
…izers.
  • Loading branch information
DougGregor committed Dec 3, 2015
1 parent eedf0fc commit ec91244
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 8 deletions.
94 changes: 90 additions & 4 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,19 @@ DeclName ClangImporter::Implementation::importFullName(
if (auto *nameAttr = D->getAttr<clang::SwiftNameAttr>()) {
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<clang::ObjCMethodDecl>(D)) {
unsigned initPrefixLength;
CtorInitializerKind kind;
if (nameAttr->getName().startswith("init(") &&
!shouldImportAsInitializer(method, initPrefixLength, kind))
return { };
}

return parseDeclName(SwiftContext, nameAttr->getName());
}

Expand All @@ -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<StringRef, 4> argumentNames;
SmallString<16> selectorSplitScratch;
Expand All @@ -1678,7 +1693,8 @@ DeclName ClangImporter::Implementation::importFullName(
case clang::DeclarationName::ObjCOneArgSelector:
case clang::DeclarationName::ObjCZeroArgSelector: {
auto objcMethod = cast<clang::ObjCMethodDecl>(D);
isInitializer = isInitMethod(objcMethod);
isInitializer = shouldImportAsInitializer(objcMethod, initializerPrefixLen,
initKind);

// Map the Objective-C selector directly.
auto selector = D->getDeclName().getObjCSelector();
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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<clang::ObjCObjectPointerType>()) {
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();
Expand Down
14 changes: 14 additions & 0 deletions lib/ClangImporter/ImporterImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
2 changes: 2 additions & 0 deletions test/IDE/Inputs/swift_name_objc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 5 additions & 4 deletions test/IDE/dump_swift_lookup_tables_objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:]

0 comments on commit ec91244

Please sign in to comment.