mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
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:
@@ -48,8 +48,7 @@ public:
|
||||
private:
|
||||
Implementation &Impl;
|
||||
|
||||
ClangImporter(ASTContext &ctx, bool splitPrepositions,
|
||||
bool implicitProperties);
|
||||
ClangImporter(ASTContext &ctx, const ClangImporterOptions &clangImporterOpts);
|
||||
|
||||
public:
|
||||
/// \brief Create a new Clang importer that can import a suitable Clang
|
||||
|
||||
@@ -33,6 +33,9 @@ public:
|
||||
/// If true, matched getter-like and setter-like methods will be imported as
|
||||
/// properties.
|
||||
bool InferImplicitProperties = false;
|
||||
|
||||
/// If true, import factory methods as constructors.
|
||||
bool ImportFactoryMethodsAsConstructors = false;
|
||||
};
|
||||
|
||||
} // end namespace swift
|
||||
|
||||
@@ -123,6 +123,10 @@ def enable_objc_implicit_properties :
|
||||
Flag<["-"], "enable-objc-implicit-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">,
|
||||
HelpText<"Enable importing of Swift source files">;
|
||||
|
||||
|
||||
@@ -100,9 +100,9 @@ namespace {
|
||||
}
|
||||
|
||||
|
||||
ClangImporter::ClangImporter(ASTContext &ctx, bool splitPrepositions,
|
||||
bool implicitProperties)
|
||||
: Impl(*new Implementation(ctx, splitPrepositions, implicitProperties))
|
||||
ClangImporter::ClangImporter(ASTContext &ctx,
|
||||
const ClangImporterOptions &clangImporterOpts)
|
||||
: Impl(*new Implementation(ctx, clangImporterOpts))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -123,8 +123,7 @@ void ClangImporter::clearTypeResolver() {
|
||||
ClangImporter *ClangImporter::create(ASTContext &ctx, StringRef targetTriple,
|
||||
const ClangImporterOptions &clangImporterOpts) {
|
||||
std::unique_ptr<ClangImporter> importer{
|
||||
new ClangImporter(ctx, ctx.LangOpts.SplitPrepositions,
|
||||
clangImporterOpts.InferImplicitProperties)
|
||||
new ClangImporter(ctx, clangImporterOpts)
|
||||
};
|
||||
|
||||
// Get the SearchPathOptions to use when creating the Clang importer.
|
||||
@@ -373,6 +372,15 @@ Module *ClangImporter::loadModule(
|
||||
return result;
|
||||
}
|
||||
|
||||
ClangImporter::Implementation::Implementation(ASTContext &ctx,
|
||||
const ClangImporterOptions &opts)
|
||||
: SwiftContext(ctx),
|
||||
SplitPrepositions(ctx.LangOpts.SplitPrepositions),
|
||||
InferImplicitProperties(opts.InferImplicitProperties),
|
||||
ImportFactoryMethodsAsConstructors(opts.ImportFactoryMethodsAsConstructors)
|
||||
{
|
||||
}
|
||||
|
||||
ClangModuleUnit *
|
||||
ClangImporter::Implementation::getWrapperForModule(ClangImporter &importer,
|
||||
clang::Module *underlying) {
|
||||
@@ -796,6 +804,81 @@ DeclName ClangImporter::Implementation::mapSelectorToDeclName(
|
||||
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() {
|
||||
if (!KnownDesignatedInits.empty())
|
||||
return;
|
||||
|
||||
@@ -40,6 +40,12 @@
|
||||
#define DEBUG_TYPE "Clang module importer"
|
||||
|
||||
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;
|
||||
|
||||
@@ -1735,6 +1741,63 @@ namespace {
|
||||
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,
|
||||
DeclContext *dc,
|
||||
bool forceClassMethod = false) {
|
||||
@@ -1765,6 +1828,10 @@ namespace {
|
||||
if (methodAlreadyImported(selector, isInstance, dc))
|
||||
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,
|
||||
/*isInitializer=*/false);
|
||||
if (!name)
|
||||
@@ -2009,10 +2076,6 @@ namespace {
|
||||
bool implicit,
|
||||
Optional<InitializerKind> kind,
|
||||
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.
|
||||
assert(objcMethod->getMethodFamily() == clang::OMF_init &&
|
||||
"Not an init method");
|
||||
@@ -2028,21 +2091,31 @@ namespace {
|
||||
if (methodAlreadyImported(selector, /*isInstance=*/false, dc))
|
||||
return nullptr;
|
||||
|
||||
// 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();
|
||||
// Map the name and complete the import.
|
||||
auto name = Impl.mapSelectorToDeclName(selector, /*isInitializer=*/true);
|
||||
return importConstructor(objcMethod, dc, implicit, kind, required,
|
||||
selector, name);
|
||||
}
|
||||
|
||||
SourceLoc loc;
|
||||
auto name = Impl.mapSelectorToDeclName(selector, /*isInitializer=*/true);
|
||||
|
||||
/// \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.
|
||||
SmallVector<Pattern *, 4> bodyPatterns;
|
||||
auto selfTy = getSelfTypeForContext(dc);
|
||||
@@ -2063,10 +2136,26 @@ namespace {
|
||||
return nullptr;
|
||||
|
||||
// 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())
|
||||
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'.
|
||||
// This is effectively implementing related-result-type semantics.
|
||||
// FIXME: Perhaps actually check whether the routine has a related result
|
||||
@@ -2083,7 +2172,7 @@ namespace {
|
||||
|
||||
// Create the actual constructor.
|
||||
auto result = new (Impl.SwiftContext)
|
||||
ConstructorDecl(name, loc, selfPat, bodyPatterns.back(),
|
||||
ConstructorDecl(name, SourceLoc(), selfPat, bodyPatterns.back(),
|
||||
/*GenericParams=*/0, dc);
|
||||
result->setClangNode(objcMethod);
|
||||
addObjCAttribute(result, selector);
|
||||
|
||||
@@ -177,10 +177,7 @@ public:
|
||||
Constants
|
||||
};
|
||||
|
||||
Implementation(ASTContext &ctx, bool splitPrepositions,
|
||||
bool implicitProperties)
|
||||
: SwiftContext(ctx), SplitPrepositions(splitPrepositions),
|
||||
InferImplicitProperties(implicitProperties) { }
|
||||
Implementation(ASTContext &ctx, const ClangImporterOptions &opts);
|
||||
|
||||
~Implementation() {
|
||||
assert(NumCurrentImportingEntities == 0);
|
||||
@@ -191,6 +188,7 @@ public:
|
||||
|
||||
const bool SplitPrepositions;
|
||||
const bool InferImplicitProperties;
|
||||
const bool ImportFactoryMethodsAsConstructors;
|
||||
|
||||
private:
|
||||
/// \brief A count of the number of load module operations.
|
||||
@@ -453,6 +451,18 @@ public:
|
||||
/// initializer.
|
||||
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.
|
||||
clang::SourceLocation importSourceLoc(SourceLoc loc);
|
||||
|
||||
|
||||
@@ -566,6 +566,8 @@ static bool ParseClangImporterArgs(ClangImporterOptions &Opts, ArgList &Args,
|
||||
|
||||
Opts.InferImplicitProperties =
|
||||
Args.hasArg(OPT_enable_objc_implicit_properties);
|
||||
Opts.ImportFactoryMethodsAsConstructors =
|
||||
Args.hasArg(OPT_enable_objc_factory_method_constructors);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
23
test/ClangModules/objc_factory_method.swift
Normal file
23
test/ClangModules/objc_factory_method.swift
Normal 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}}
|
||||
}
|
||||
@@ -57,3 +57,11 @@
|
||||
@interface NSTableViewController : NSViewController
|
||||
-(instancetype)initWithInt:(NSInteger)value NS_DESIGNATED_INITIALIZER;
|
||||
@end
|
||||
|
||||
@interface NSObjectFactory : NSObject
|
||||
+(instancetype)objectFactory;
|
||||
+(instancetype)objectFactoryWithInteger:(NSInteger)i;
|
||||
+(instancetype)factoryWithDouble:(double)i;
|
||||
+(id)factoryWithString:(NSString *)s;
|
||||
@end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user