Start importing factory methods as initializers.

When an Objective-C class method follows the naming convention of a
factory method, i.e., its starting words match the ending words of the
class name, import it as a convenience initializer when it also:
  - Returns instancetype (i.e., dynamic Self in Swift parlance)
  - Has no NSError** parameters, which indicate the potential for failures

This is under a new flag (-enable-objc-factory-method-constructors)
because it is not generally functional. However, this is a step toward
<rdar://problem/16509024>.

Swift SVN r16479
This commit is contained in:
Doug Gregor
2014-04-17 23:34:00 +00:00
parent 318aba81ef
commit f56c68386e
9 changed files with 252 additions and 31 deletions

View File

@@ -48,8 +48,7 @@ public:
private: private:
Implementation &Impl; Implementation &Impl;
ClangImporter(ASTContext &ctx, bool splitPrepositions, ClangImporter(ASTContext &ctx, const ClangImporterOptions &clangImporterOpts);
bool implicitProperties);
public: public:
/// \brief Create a new Clang importer that can import a suitable Clang /// \brief Create a new Clang importer that can import a suitable Clang

View File

@@ -33,6 +33,9 @@ public:
/// If true, matched getter-like and setter-like methods will be imported as /// If true, matched getter-like and setter-like methods will be imported as
/// properties. /// properties.
bool InferImplicitProperties = false; bool InferImplicitProperties = false;
/// If true, import factory methods as constructors.
bool ImportFactoryMethodsAsConstructors = false;
}; };
} // end namespace swift } // end namespace swift

View File

@@ -122,7 +122,11 @@ def enable_experimental_patterns : Flag<["-"], "enable-experimental-patterns">,
def enable_objc_implicit_properties : def enable_objc_implicit_properties :
Flag<["-"], "enable-objc-implicit-properties">, Flag<["-"], "enable-objc-implicit-properties">,
HelpText<"Import Objective-C \"implicit properties\" as properties">; HelpText<"Import Objective-C \"implicit properties\" as properties">;
def enable_objc_factory_method_constructors :
Flag<["-"], "enable-objc-factory-method-constructors">,
HelpText<"Import Objective-C factory methods as constructors">;
def enable_source_import : Flag<["-"], "enable-source-import">, def enable_source_import : Flag<["-"], "enable-source-import">,
HelpText<"Enable importing of Swift source files">; HelpText<"Enable importing of Swift source files">;

View File

@@ -100,9 +100,9 @@ namespace {
} }
ClangImporter::ClangImporter(ASTContext &ctx, bool splitPrepositions, ClangImporter::ClangImporter(ASTContext &ctx,
bool implicitProperties) const ClangImporterOptions &clangImporterOpts)
: Impl(*new Implementation(ctx, splitPrepositions, implicitProperties)) : Impl(*new Implementation(ctx, clangImporterOpts))
{ {
} }
@@ -123,8 +123,7 @@ void ClangImporter::clearTypeResolver() {
ClangImporter *ClangImporter::create(ASTContext &ctx, StringRef targetTriple, ClangImporter *ClangImporter::create(ASTContext &ctx, StringRef targetTriple,
const ClangImporterOptions &clangImporterOpts) { const ClangImporterOptions &clangImporterOpts) {
std::unique_ptr<ClangImporter> importer{ std::unique_ptr<ClangImporter> importer{
new ClangImporter(ctx, ctx.LangOpts.SplitPrepositions, new ClangImporter(ctx, clangImporterOpts)
clangImporterOpts.InferImplicitProperties)
}; };
// Get the SearchPathOptions to use when creating the Clang importer. // Get the SearchPathOptions to use when creating the Clang importer.
@@ -373,6 +372,15 @@ Module *ClangImporter::loadModule(
return result; return result;
} }
ClangImporter::Implementation::Implementation(ASTContext &ctx,
const ClangImporterOptions &opts)
: SwiftContext(ctx),
SplitPrepositions(ctx.LangOpts.SplitPrepositions),
InferImplicitProperties(opts.InferImplicitProperties),
ImportFactoryMethodsAsConstructors(opts.ImportFactoryMethodsAsConstructors)
{
}
ClangModuleUnit * ClangModuleUnit *
ClangImporter::Implementation::getWrapperForModule(ClangImporter &importer, ClangImporter::Implementation::getWrapperForModule(ClangImporter &importer,
clang::Module *underlying) { clang::Module *underlying) {
@@ -796,6 +804,81 @@ DeclName ClangImporter::Implementation::mapSelectorToDeclName(
return result; return result;
} }
DeclName ClangImporter::Implementation::mapFactorySelectorToInitializerName(
ObjCSelector selector,
StringRef className) {
auto firstPiece = selector.getSelectorPieces()[0];
if (firstPiece.empty())
return DeclName();
// Match the camelCase beginning of the first selector piece to the
// ending of the class name.
auto firstPieceStr = firstPiece.str();
auto methodWords = camel_case::getWords(firstPieceStr);
auto classWords = camel_case::getWords(className);
auto methodWordIter = methodWords.begin(),
methodWordIterEnd = methodWords.end();
auto classWordRevIter = classWords.rbegin(),
classWordRevIterEnd = classWords.rend();
// Find the last instance of the first word in the method's name within
// the words in the class name.
while (classWordRevIter != classWordRevIterEnd &&
!camel_case::sameWordIgnoreFirstCase(*methodWordIter,
*classWordRevIter)) {
++classWordRevIter;
}
// If we didn't find the first word in the method's name at all, we're
// done.
if (classWordRevIter == classWordRevIterEnd)
return DeclName();
// Now, match from the first word up until the end of the class.
auto classWordIter = classWordRevIter.base(),
classWordIterEnd = classWords.end();
++methodWordIter;
while (classWordIter != classWordIterEnd &&
methodWordIter != methodWordIterEnd &&
camel_case::sameWordIgnoreFirstCase(*classWordIter,
*methodWordIter)) {
++classWordIter;
++methodWordIter;
}
// If we didn't reach the end of the class name, don't match.
if (classWordIter != classWordIterEnd)
return DeclName();
// We found the chopping point. Form the first argument name.
llvm::SmallString<32> scratch;
SmallVector<Identifier, 4> argumentNames;
argumentNames.push_back(
importArgName(SwiftContext,
splitSelectorPieceAt(firstPieceStr,
methodWordIter.getPosition(),
scratch).second));
// Handle nullary factory methods.
if (selector.getNumArgs() == 0) {
if (argumentNames[0].empty())
return DeclName(SwiftContext, SwiftContext.Id_init, { });
// FIXME: Fake up an empty tuple here?
return DeclName();
}
// Map the remaining selector pieces.
for (auto piece : selector.getSelectorPieces().slice(1)) {
if (piece.empty())
argumentNames.push_back(piece);
else
argumentNames.push_back(importArgName(SwiftContext, piece.str()));
}
return DeclName(SwiftContext, SwiftContext.Id_init, argumentNames);
}
void ClangImporter::Implementation::populateKnownDesignatedInits() { void ClangImporter::Implementation::populateKnownDesignatedInits() {
if (!KnownDesignatedInits.empty()) if (!KnownDesignatedInits.empty())
return; return;

View File

@@ -40,6 +40,12 @@
#define DEBUG_TYPE "Clang module importer" #define DEBUG_TYPE "Clang module importer"
STATISTIC(NumTotalImportedEntities, "# of imported clang entities"); STATISTIC(NumTotalImportedEntities, "# of imported clang entities");
STATISTIC(NumFactoryMethodsNSError,
"# of factory methods not mapped due to NSError");
STATISTIC(NumFactoryMethodsWrongResult,
"# of factory methods not mapped due to an incorrect result type");
STATISTIC(NumFactoryMethodsAsInitializers,
"# of factory methods mapped to initializers");
using namespace swift; using namespace swift;
@@ -1735,6 +1741,63 @@ namespace {
return false; return false;
} }
/// If the given method is a factory method, import it as a constructor
Optional<ConstructorDecl *>
importFactoryMethodAsConstructor(const clang::ObjCMethodDecl *decl,
ObjCSelector selector,
DeclContext *dc) {
if (!Impl.ImportFactoryMethodsAsConstructors)
return Nothing;
// Only class methods can be mapped to constructors.
if (!decl->isClassMethod())
return Nothing;
// Said class methods must be in an actual class.
auto objcClass = decl->getClassInterface();
if (!objcClass)
return Nothing;
// Check whether the name fits the pattern.
DeclName initName
= Impl.mapFactorySelectorToInitializerName(selector,
objcClass->getName());
if (!initName)
return Nothing;
// Check whether the result is instancetype.
if (!decl->hasRelatedResultType()) {
++NumFactoryMethodsWrongResult;
return Nothing;
}
// Check whether there is an NSError parameter, which implies failability.
// FIXME: Revisit once we have failing initializers.
for (auto param : decl->parameters()) {
if (auto ptr = param->getType()->getAs<clang::PointerType>()) {
if (auto classPtr = ptr->getPointeeType()
->getAs<clang::ObjCObjectPointerType>()) {
if (auto classDecl = classPtr->getInterfaceDecl()) {
if (classDecl->getName() == "NSError") {
++NumFactoryMethodsNSError;
return Nothing;
}
}
}
}
}
// FIXME: Check for redundant initializers. It's very, very hard to
// avoid order dependencies here. Perhaps we should just deal with the
// ambiguity later?
auto result = importConstructor(decl, dc, false,
InitializerKind::Convenience,
/*required=*/false, selector, initName);
if (result)
++NumFactoryMethodsAsInitializers;
return result;
}
Decl *VisitObjCMethodDecl(const clang::ObjCMethodDecl *decl, Decl *VisitObjCMethodDecl(const clang::ObjCMethodDecl *decl,
DeclContext *dc, DeclContext *dc,
bool forceClassMethod = false) { bool forceClassMethod = false) {
@@ -1765,6 +1828,10 @@ namespace {
if (methodAlreadyImported(selector, isInstance, dc)) if (methodAlreadyImported(selector, isInstance, dc))
return nullptr; return nullptr;
// If this is a factory method, try to import it as a constructor.
if (auto factory = importFactoryMethodAsConstructor(decl, selector, dc))
return *factory;
DeclName name = Impl.mapSelectorToDeclName(selector, DeclName name = Impl.mapSelectorToDeclName(selector,
/*isInitializer=*/false); /*isInitializer=*/false);
if (!name) if (!name)
@@ -2009,10 +2076,6 @@ namespace {
bool implicit, bool implicit,
Optional<InitializerKind> kind, Optional<InitializerKind> kind,
bool required) { bool required) {
// Figure out the type of the container.
auto containerTy = dc->getDeclaredTypeOfContext();
assert(containerTy && "Method in non-type context?");
// Only methods in the 'init' family can become constructors. // Only methods in the 'init' family can become constructors.
assert(objcMethod->getMethodFamily() == clang::OMF_init && assert(objcMethod->getMethodFamily() == clang::OMF_init &&
"Not an init method"); "Not an init method");
@@ -2028,21 +2091,31 @@ namespace {
if (methodAlreadyImported(selector, /*isInstance=*/false, dc)) if (methodAlreadyImported(selector, /*isInstance=*/false, dc))
return nullptr; return nullptr;
// Find the interface, if we can. // Map the name and complete the import.
const clang::ObjCInterfaceDecl *interface = nullptr;
if (isa<clang::ObjCProtocolDecl>(objcMethod->getDeclContext())) {
// FIXME: Part of the mirroring hack.
if (auto classDecl = containerTy->getClassOrBoundGenericClass())
interface = dyn_cast_or_null<clang::ObjCInterfaceDecl>(
classDecl->getClangDecl());
} else {
// For non-protocol methods, just look for the interface.
interface = objcMethod->getClassInterface();
}
SourceLoc loc;
auto name = Impl.mapSelectorToDeclName(selector, /*isInitializer=*/true); auto name = Impl.mapSelectorToDeclName(selector, /*isInitializer=*/true);
return importConstructor(objcMethod, dc, implicit, kind, required,
selector, name);
}
/// \brief Given an imported method, try to import it as a constructor.
///
/// Objective-C methods in the 'init' family are imported as
/// constructors in Swift, enabling object construction syntax, e.g.,
///
/// \code
/// // in objc: [[NSArray alloc] initWithCapacity:1024]
/// NSArray(withCapacity: 1024)
/// \endcode
///
/// This variant of the function is responsible for actually binding the
/// constructor declaration appropriately.
ConstructorDecl *importConstructor(const clang::ObjCMethodDecl *objcMethod,
DeclContext *dc,
bool implicit,
Optional<InitializerKind> kind,
bool required,
ObjCSelector selector,
DeclName name) {
// Add the implicit 'self' parameter patterns. // Add the implicit 'self' parameter patterns.
SmallVector<Pattern *, 4> bodyPatterns; SmallVector<Pattern *, 4> bodyPatterns;
auto selfTy = getSelfTypeForContext(dc); auto selfTy = getSelfTypeForContext(dc);
@@ -2063,10 +2136,26 @@ namespace {
return nullptr; return nullptr;
// Check whether we've already created the constructor. // Check whether we've already created the constructor.
known = Impl.Constructors.find({objcMethod, dc}); auto known = Impl.Constructors.find({objcMethod, dc});
if (known != Impl.Constructors.end()) if (known != Impl.Constructors.end())
return known->second; return known->second;
// Figure out the type of the container.
auto containerTy = dc->getDeclaredTypeOfContext();
assert(containerTy && "Method in non-type context?");
// Find the interface, if we can.
const clang::ObjCInterfaceDecl *interface = nullptr;
if (isa<clang::ObjCProtocolDecl>(objcMethod->getDeclContext())) {
// FIXME: Part of the mirroring hack.
if (auto classDecl = containerTy->getClassOrBoundGenericClass())
interface = dyn_cast_or_null<clang::ObjCInterfaceDecl>(
classDecl->getClangDecl());
} else {
// For non-protocol methods, just look for the interface.
interface = objcMethod->getClassInterface();
}
// A constructor returns an object of the type, not 'id'. // A constructor returns an object of the type, not 'id'.
// This is effectively implementing related-result-type semantics. // This is effectively implementing related-result-type semantics.
// FIXME: Perhaps actually check whether the routine has a related result // FIXME: Perhaps actually check whether the routine has a related result
@@ -2083,7 +2172,7 @@ namespace {
// Create the actual constructor. // Create the actual constructor.
auto result = new (Impl.SwiftContext) auto result = new (Impl.SwiftContext)
ConstructorDecl(name, loc, selfPat, bodyPatterns.back(), ConstructorDecl(name, SourceLoc(), selfPat, bodyPatterns.back(),
/*GenericParams=*/0, dc); /*GenericParams=*/0, dc);
result->setClangNode(objcMethod); result->setClangNode(objcMethod);
addObjCAttribute(result, selector); addObjCAttribute(result, selector);

View File

@@ -177,10 +177,7 @@ public:
Constants Constants
}; };
Implementation(ASTContext &ctx, bool splitPrepositions, Implementation(ASTContext &ctx, const ClangImporterOptions &opts);
bool implicitProperties)
: SwiftContext(ctx), SplitPrepositions(splitPrepositions),
InferImplicitProperties(implicitProperties) { }
~Implementation() { ~Implementation() {
assert(NumCurrentImportingEntities == 0); assert(NumCurrentImportingEntities == 0);
@@ -191,6 +188,7 @@ public:
const bool SplitPrepositions; const bool SplitPrepositions;
const bool InferImplicitProperties; const bool InferImplicitProperties;
const bool ImportFactoryMethodsAsConstructors;
private: private:
/// \brief A count of the number of load module operations. /// \brief A count of the number of load module operations.
@@ -453,6 +451,18 @@ public:
/// initializer. /// initializer.
DeclName mapSelectorToDeclName(ObjCSelector selector, bool isInitializer); DeclName mapSelectorToDeclName(ObjCSelector selector, bool isInitializer);
/// Try to map the given selector, which may be the name of a factory method,
/// to the name of an initializer.
///
/// \param selector The selector to map.
///
/// \param className The name of the class in which the method occurs.
///
/// \returns the initializer name for this factory method, or an empty
/// name if this selector does not fit the pattern.
DeclName mapFactorySelectorToInitializerName(ObjCSelector selector,
StringRef className);
/// \brief Import the given Swift source location into Clang. /// \brief Import the given Swift source location into Clang.
clang::SourceLocation importSourceLoc(SourceLoc loc); clang::SourceLocation importSourceLoc(SourceLoc loc);

View File

@@ -566,6 +566,8 @@ static bool ParseClangImporterArgs(ClangImporterOptions &Opts, ArgList &Args,
Opts.InferImplicitProperties = Opts.InferImplicitProperties =
Args.hasArg(OPT_enable_objc_implicit_properties); Args.hasArg(OPT_enable_objc_implicit_properties);
Opts.ImportFactoryMethodsAsConstructors =
Args.hasArg(OPT_enable_objc_factory_method_constructors);
return false; return false;
} }

View File

@@ -0,0 +1,23 @@
// RUN: rm -rf %t/clang-module-cache
// RUN: %swift %clang-importer-sdk -parse -module-cache-path %t/clang-module-cache -enable-objc-factory-method-constructors %s -verify
import AppKit
func testInstanceTypeFactoryMethod(queen: B) {
var hive1 = Hive(withQueen: queen)
// FIXME: Need to suppress the class method when there is a corresponding
// initializer with the same name.
var of1 = NSObjectFactory() // FIXME: expected-error{{does not type-check}}
var of2 = NSObjectFactory(withInteger: 1)
var of3 = NSObjectFactory(withDouble: 314159)
}
func testNSErrorFactoryMethod(path: String) {
var error: NSError?
var s1 = NSString(withContentsOfFile: path, error: &error) // expected-error{{does not type-check}}
}
func testNonInstanceTypeFactoryMethod(s: String) {
var of1 = NSObjectFactory(withString: s) // expected-error{{does not type-check}}
}

View File

@@ -57,3 +57,11 @@
@interface NSTableViewController : NSViewController @interface NSTableViewController : NSViewController
-(instancetype)initWithInt:(NSInteger)value NS_DESIGNATED_INITIALIZER; -(instancetype)initWithInt:(NSInteger)value NS_DESIGNATED_INITIALIZER;
@end @end
@interface NSObjectFactory : NSObject
+(instancetype)objectFactory;
+(instancetype)objectFactoryWithInteger:(NSInteger)i;
+(instancetype)factoryWithDouble:(double)i;
+(id)factoryWithString:(NSString *)s;
@end