Files
swift-mirror/lib/ClangImporter/IAMInference.cpp
Michael Ilseman 3c690d422d [Import as method] Create an empty tuple first parameter
For function that we are importing as init, if there's dangling words
after the "Create" or "Make", we move them onto the first argument
label. This introduces the creation of an empty tuple parameter when
that init has no arguments.
2016-03-09 15:20:04 -08:00

700 lines
23 KiB
C++

//===--- IAMInference.cpp - Import as member inference system -------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements support for inferring when globals can be imported as
// members
//
//===----------------------------------------------------------------------===//
#include "IAMInference.h"
#include "swift/AST/ASTContext.h"
#include "swift/Basic/StringExtras.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/Lookup.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Debug.h"
#include <array>
#include <tuple>
#define DEBUG_TYPE "Infer import as member"
// Statistics for failure to infer
STATISTIC(FailInferVar, "# of variables unable to infer");
STATISTIC(FailInferFunction, "# of functions unable to infer");
// Success statistics
STATISTIC(SuccessImportAsTypeID, "# imported as 'typeID'");
STATISTIC(SuccessImportAsConstructor, "# imported as 'init'");
STATISTIC(SuccessImportAsInstanceComputedProperty,
"# imported as instance computed property");
STATISTIC(SuccessImportAsStaticProperty,
"# imported as static (stored) property");
STATISTIC(SuccessImportAsStaticComputedProperty,
"# imported as static computed property");
STATISTIC(SuccessImportAsStaticMethod, "# imported as static method");
STATISTIC(SuccessImportAsInstanceMethod, "# imported as instance method");
// Statistics for why we couldn't infer in more specific ways, and fell back to
// methods
STATISTIC(InvalidPropertyStaticNumParams,
"couldn't infer as static property: invalid number of parameters");
STATISTIC(InvalidPropertyInstanceNumParams,
"couldn't infer as instance property: invalid number of parameters");
STATISTIC(InvalidPropertyStaticGetterSetterType,
"couldn't infer as static property: getter/setter type mismatch");
STATISTIC(InvalidPropertyInstanceGetterSetterType,
"couldn't infer as instance property: getter/setter type mismatch");
STATISTIC(InvalidPropertyInstanceNoSelf,
"couldn't infer as instance property: couldn't find self");
using namespace swift;
using NameBuffer = SmallString<32>;
static const std::array<StringRef, 2> InitSpecifiers{{
StringRef("Create"), StringRef("Make"),
}};
static const std::array<StringRef, 2> PropertySpecifiers{{
StringRef("Get"), StringRef("Set"),
}};
namespace {
enum class CFNameKind : unsigned {
None,
CFType,
NonCFType,
};
}
static CFNameKind getCFNameKind(StringRef name) {
#define CF_TYPE(TYPE) \
if (name == #TYPE) \
return CFNameKind::CFType;
#define NON_CF_TYPE(TYPE) \
if (name == #TYPE) \
return CFNameKind::NonCFType;
#include "CFDatabase.def"
return CFNameKind::None;
}
static bool isCFTypeName(StringRef name) {
return getCFNameKind(name) == CFNameKind::CFType;
}
// static bool isExplicitNonCFTypeName(StringRef name) {
// return getCFNameKind(name) == CFNameKind::NonCFType;
// }
IAMOptions IAMOptions::getDefault() { return {}; }
// As append, but skip a repeated word at the boundary. Case sensitive.
// Example: appendUniq("FooBar", "BarBaz") ==> "FooBarBaz"
void appendUniq(NameBuffer &src, StringRef toAppend) {
if (src.empty()) {
src = toAppend;
return;
}
auto appendWords = camel_case::getWords(toAppend);
StringRef lastWord = *camel_case::getWords(src).rbegin();
auto wI = appendWords.begin();
while (wI != appendWords.end() && *wI == lastWord)
++wI;
src.append(wI.getRestOfStr());
}
StringRef skipLeadingUnderscores(StringRef str) {
unsigned numToDrop = 0;
for (/*empty*/; numToDrop < str.size(); ++numToDrop)
if (str[numToDrop] != '_')
break;
return str.drop_front(numToDrop);
}
// Form a humble camel name from a string. Skips leading underscores.
static void formHumbleCamelName(StringRef str, NameBuffer &out) {
str = skipLeadingUnderscores(str);
auto newStr = camel_case::toLowercaseInitialisms(str, out);
if (newStr == str)
out = newStr;
}
// Form a humble camel by appending the two strings and adjusting case as
// needed. Skips leading underscores in either name, and skips a repeated word
// at the boundary. Example: formHumbleCamelName("__FooBar", "barBaz") ==>
// "fooBarBaz".
static void formHumbleCamelName(StringRef left, StringRef right,
NameBuffer &out) {
left = skipLeadingUnderscores(left);
if (left == "") {
formHumbleCamelName(right, out);
return;
}
right = skipLeadingUnderscores(right);
if (right == "") {
formHumbleCamelName(left, out);
return;
}
StringRef lastWord = *camel_case::getWords(left).rbegin();
auto rightWords = camel_case::getWords(right);
auto wI = rightWords.begin();
while (wI != rightWords.end() &&
camel_case::sameWordIgnoreFirstCase(*wI, lastWord))
++wI;
formHumbleCamelName(left, out);
camel_case::appendSentenceCase(out, wI.getRestOfStr());
}
static unsigned dropWord(StringRef str, StringRef word, NameBuffer &out) {
unsigned numDropped = 0;
auto words = camel_case::getWords(str);
for (auto wI = words.begin(), wE = words.end(); wI != wE; ++wI)
if (*wI == word)
++numDropped;
else
out.append(*wI);
return numDropped;
}
namespace {
class IAMInference {
ASTContext &context;
clang::Sema &clangSema;
IAMOptions options;
public:
IAMInference(ASTContext &ctx, clang::Sema &sema, IAMOptions opts)
: context(ctx), clangSema(sema), options(opts) {
(void)options;
}
IAMResult infer(const clang::NamedDecl *);
private:
// typeID
IAMResult importAsTypeID(const clang::QualType typeIDTy,
EffectiveClangContext effectiveDC) {
++SuccessImportAsTypeID;
return {formDeclName("typeID"), IAMAccessorKind::Getter, effectiveDC};
}
// Init
IAMResult importAsConstructor(StringRef name, StringRef initSpecifier,
ArrayRef<const clang::ParmVarDecl *> params,
EffectiveClangContext effectiveDC) {
++SuccessImportAsConstructor;
NameBuffer buf;
if (name != initSpecifier) {
assert(name.size() > initSpecifier.size() &&
"should have more words in it");
auto didDrop = dropWord(name, initSpecifier, buf);
(void)didDrop;
assert(didDrop != 0 && "specifier not present?");
}
return {formDeclName("init", params, buf), effectiveDC};
}
// Instance computed property
IAMResult
importAsInstanceProperty(StringRef name, StringRef propSpec, unsigned selfIdx,
ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
const clang::FunctionDecl *pairedAccessor,
EffectiveClangContext effectiveDC) {
++SuccessImportAsInstanceComputedProperty;
IAMAccessorKind kind =
propSpec == "Get" ? IAMAccessorKind::Getter : IAMAccessorKind::Setter;
assert(kind == IAMAccessorKind::Getter || pairedAccessor && "no set-only");
return {formDeclName(name), kind, selfIdx, effectiveDC};
}
// Instance method
IAMResult
importAsInstanceMethod(StringRef name, unsigned selfIdx,
ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
EffectiveClangContext effectiveDC) {
++SuccessImportAsInstanceMethod;
return {formDeclName(name, nonSelfParams), selfIdx, effectiveDC};
}
// Static stored property
IAMResult importAsStaticProperty(StringRef name,
EffectiveClangContext effectiveDC) {
++SuccessImportAsStaticProperty;
return {formDeclName(name), effectiveDC};
}
// Static computed property
IAMResult
importAsStaticProperty(StringRef name, StringRef propSpec,
ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
const clang::FunctionDecl *pairedAccessor,
EffectiveClangContext effectiveDC) {
++SuccessImportAsStaticComputedProperty;
IAMAccessorKind kind =
propSpec == "Get" ? IAMAccessorKind::Getter : IAMAccessorKind::Setter;
assert(kind == IAMAccessorKind::Getter || pairedAccessor && "no set-only");
return {formDeclName(name), kind, effectiveDC};
}
// Static method
IAMResult
importAsStaticMethod(StringRef name,
ArrayRef<const clang::ParmVarDecl *> nonSelfParams,
EffectiveClangContext effectiveDC) {
++SuccessImportAsStaticMethod;
return {formDeclName(name, nonSelfParams), effectiveDC};
}
template <typename DeclType>
inline DeclType *clangLookup(StringRef name,
clang::Sema::LookupNameKind kind);
clang::TypeDecl *clangLookupTypeDecl(StringRef name) {
if (auto ty = clangLookup<clang::TypedefNameDecl>(
name, clang::Sema::LookupNameKind::LookupOrdinaryName))
return ty;
return clangLookup<clang::TagDecl>(
name, clang::Sema::LookupNameKind::LookupTagName);
}
clang::FunctionDecl *clangLookupFunction(StringRef name) {
return clangLookup<clang::FunctionDecl>(
name, clang::Sema::LookupNameKind::LookupOrdinaryName);
}
const clang::TypeDecl *findTypeAndMatch(StringRef workingName,
NameBuffer &outStr) {
// FIXME: drop mutable...
// TODO: should we try some form of fuzzy or fuzzier matching?
// Longest-prefix matching, alternate with checking for a trailing "Ref"
// suffix and the prefix itself. We iterate from the back to the beginning.
auto words = camel_case::getWords(workingName);
for (auto rWordsIter = words.rbegin(), rWordsEnd = words.rend();
rWordsIter != rWordsEnd; ++rWordsIter) {
NameBuffer nameAttempt;
nameAttempt.append(rWordsIter.base().getPriorStr());
StringRef prefix = nameAttempt;
nameAttempt.append("Ref");
StringRef prefixWithRef = nameAttempt;
if (auto tyDecl = clangLookupTypeDecl(prefixWithRef)) {
outStr.append(workingName.drop_front(prefix.size()));
return tyDecl;
}
if (auto tyDecl = clangLookupTypeDecl(prefix)) {
outStr.append(workingName.drop_front(prefix.size()));
return tyDecl;
}
}
return nullptr;
}
bool validToImportAsProperty(const clang::FunctionDecl *originalDecl,
StringRef propSpec, Optional<unsigned> selfIndex,
const clang::FunctionDecl *&pairedAccessor);
const clang::FunctionDecl *findPairedAccessor(StringRef name,
StringRef propSpec) {
NameBuffer pairName;
auto words = camel_case::getWords(name);
for (auto word : words) {
if (word == propSpec) {
if (propSpec == "Get") {
pairName.append("Set");
} else {
assert(propSpec == "Set");
pairName.append("Get");
}
} else {
pairName.append(word);
}
}
return clangLookupFunction(pairName);
}
Identifier getHumbleIdentifier(StringRef name) {
// Lower-camel-case the incoming name
NameBuffer buf;
formHumbleCamelName(name, buf);
return {context.getIdentifier(buf)};
}
DeclName formDeclName(StringRef baseName) {
return {getHumbleIdentifier(baseName)};
}
DeclName formDeclName(StringRef baseName,
ArrayRef<const clang::ParmVarDecl *> params,
StringRef firstPrefix = "") {
SmallVector<Identifier, 8> argLabels;
if (params.empty() && firstPrefix != "") {
// We need to form an argument label, despite there being no argument
NameBuffer paramName;
formHumbleCamelName(firstPrefix, paramName);
argLabels.push_back(context.getIdentifier(paramName));
}
for (unsigned i = 0; i < params.size(); ++i) {
NameBuffer paramName;
if (i == 0 && firstPrefix != "") {
formHumbleCamelName(firstPrefix, params[i]->getName(), paramName);
} else {
// TODO: strip leading underscores
formHumbleCamelName(params[i]->getName(), paramName);
}
argLabels.push_back(context.getIdentifier(paramName));
}
return {context, getHumbleIdentifier(baseName), argLabels};
}
bool match(StringRef str, StringRef toMatch, NameBuffer &outStr) {
// TODO: let options dictate fuzzy matching...
// FIXME: drop mutable...
auto strWords = camel_case::getWords(str);
auto matchWords = camel_case::getWords(toMatch);
auto strIter = strWords.begin();
auto matchIter = matchWords.begin();
// Match in order, but allowing interjected words
while (strIter != strWords.end()) {
if (matchIter == matchWords.end()) {
// We matched them all!
appendUniq(outStr, strIter.getRestOfStr());
return true;
}
if (*strIter == *matchIter) {
// It's a match!
++strIter;
++matchIter;
continue;
}
// Move on to the next one
appendUniq(outStr, *strIter);
++strIter;
}
return false;
}
};
}
// A loose type equality check that disregards all sugar, qualification, looks
// through pointers, etc.
static bool rouglyEqual(clang::QualType left, clang::QualType right) {
auto leftPointee = left->getPointeeType();
if (leftPointee != clang::QualType())
left = leftPointee;
auto rightPointee = right->getPointeeType();
if (rightPointee != clang::QualType())
right = rightPointee;
return left->getUnqualifiedDesugaredType() ==
right->getUnqualifiedDesugaredType();
}
bool IAMInference::validToImportAsProperty(
const clang::FunctionDecl *originalDecl, StringRef propSpec,
Optional<unsigned> selfIndex, const clang::FunctionDecl *&pairedAccessor) {
bool isGet = propSpec == "Get";
pairedAccessor = findPairedAccessor(originalDecl->getName(), propSpec);
if (!pairedAccessor)
return isGet;
auto getterDecl = isGet ? originalDecl : pairedAccessor;
auto setterDecl = isGet ? pairedAccessor : originalDecl;
auto getterTy = getterDecl->getReturnType();
// See if this is a static property
if (!selfIndex) {
// Getter has none, setter has one arg
if (getterDecl->getNumParams() != 0 || setterDecl->getNumParams() != 1) {
++InvalidPropertyStaticNumParams;
return false;
}
// Setter's arg type should be same as getter's return type
if (!rouglyEqual(getterTy, setterDecl->getParamDecl(0)->getType())) {
++InvalidPropertyStaticGetterSetterType;
return false;
}
return true;
}
// Instance property, look beyond self
if (getterDecl->getNumParams() != 1 || setterDecl->getNumParams() != 2) {
++InvalidPropertyInstanceNumParams;
return false;
}
auto selfTy = getterDecl->getParamDecl(0)->getType();
clang::QualType setterTy = {};
auto setterParam0Ty = setterDecl->getParamDecl(0)->getType();
auto setterParam1Ty = setterDecl->getParamDecl(1)->getType();
if (rouglyEqual(setterParam0Ty, selfTy)) {
setterTy = setterParam1Ty;
} else if (rouglyEqual(setterParam1Ty, selfTy)) {
setterTy = setterParam0Ty;
} else {
++InvalidPropertyInstanceNoSelf;
return false;
}
if (!rouglyEqual(setterTy, getterTy)) {
++InvalidPropertyInstanceGetterSetterType;
return false;
}
return true;
}
static StringRef getTypeName(clang::QualType qt) {
if (auto typedefTy = qt->getAs<clang::TypedefType>()) {
// Use the sugar-ed, but un-ref-ed name
auto name = typedefTy->getDecl()->getName();
if (name.endswith("Ref") &&
(isCFTypeName(name) ||
typedefTy->getDecl()->hasAttr<clang::ObjCBridgeAttr>()))
return name.drop_back(3);
}
auto identInfo = qt.getBaseTypeIdentifier();
if (identInfo)
return identInfo->getName();
// Otherwise, no name
return {};
}
// Get the canonical, but still sugar-ed, effective decl context
static EffectiveClangContext getEffectiveDC(clang::QualType qt) {
auto ty = qt.getTypePtr();
if (auto typedefType = ty->getAs<clang::TypedefType>())
return {typedefType->getDecl()->getCanonicalDecl()};
auto pointeeQT = ty->getPointeeType();
if (pointeeQT != clang::QualType())
ty = pointeeQT.getTypePtr();
if (auto typedefType = ty->getAs<clang::TypedefType>()) {
return {typedefType->getDecl()->getCanonicalDecl()};
}
if (const clang::RecordType *recTy = ty->getAsStructureType())
return {recTy->getDecl()->getCanonicalDecl()};
assert(0 && "not a valid Clang context");
return {};
}
static EffectiveClangContext getEffectiveDC(const clang::TypeDecl *tyDecl) {
// FIXME: ewwe
auto typeDecl = const_cast<clang::TypeDecl *>(tyDecl);
if (auto typedefName = dyn_cast<clang::TypedefNameDecl>(typeDecl))
return {typedefName->getCanonicalDecl()};
if (auto tagDecl = dyn_cast<clang::TagDecl>(typeDecl))
return {tagDecl->getCanonicalDecl()};
assert("expected valid Clang context");
return {};
}
static bool hasWord(StringRef s, StringRef matchWord) {
for (auto word : camel_case::getWords(s))
if (word == matchWord)
return true;
return false;
}
IAMResult IAMInference::infer(const clang::NamedDecl *clangDecl) {
if (auto varDecl = dyn_cast<clang::VarDecl>(clangDecl)) {
auto fail = [varDecl]() -> IAMResult {
DEBUG(llvm::dbgs() << "failed to infer variable: ");
DEBUG(varDecl->print(llvm::dbgs()));
DEBUG(llvm::dbgs() << "\n");
++FailInferVar;
return {};
};
// Try to find a type to add this as a static property to
StringRef workingName = varDecl->getName();
if (workingName.empty())
return fail();
// Special pattern: constants of the form "kFooBarBaz", extend "FooBar" with
// property "Baz"
if (*camel_case::getWords(workingName).begin() == "k") {
workingName = workingName.drop_front(1);
}
NameBuffer remainingName;
const clang::TypeDecl *tyDecl =
findTypeAndMatch(workingName, remainingName);
if (tyDecl)
return importAsStaticProperty(remainingName, getEffectiveDC(tyDecl));
return fail();
}
// Try to infer a member function
auto funcDecl = dyn_cast<clang::FunctionDecl>(clangDecl);
if (!funcDecl) {
// TODO: Do we want to collects stats here? Should it be assert?
return {};
}
auto fail = [funcDecl]() -> IAMResult {
DEBUG(llvm::dbgs() << "failed to infer function: ");
DEBUG(funcDecl->print(llvm::dbgs()));
DEBUG(llvm::dbgs() << "\n");
++FailInferFunction;
return {};
};
// Can't really import variadics well
if (funcDecl->isVariadic())
return fail();
// FIXME: drop "Mutable"...
StringRef workingName = funcDecl->getName();
auto retTy = funcDecl->getReturnType();
unsigned numParams = funcDecl->getNumParams();
// 0) Special cases are specially handled: *GetTypeID()
//
StringRef getTypeID = "GetTypeID";
if (numParams == 0 && workingName.endswith(getTypeID)) {
NameBuffer remainingName;
if (auto tyDecl = findTypeAndMatch(workingName.drop_back(getTypeID.size()),
remainingName)) {
// We shouldn't have anything else left in our name for typeID
if (remainingName.empty())
return importAsTypeID(retTy, getEffectiveDC(tyDecl));
}
}
// 1) If we find an init specifier and our name matches the return type, we
// import as some kind of constructor
//
if (!retTy->isVoidType()) {
NameBuffer remainingName;
StringRef typeName = getTypeName(retTy);
if (typeName != StringRef())
if (match(workingName, typeName, remainingName))
for (auto initSpec : InitSpecifiers)
if (hasWord(remainingName, initSpec))
return importAsConstructor(
remainingName, initSpec,
{funcDecl->param_begin(), funcDecl->param_end()},
getEffectiveDC(retTy));
}
// 2) If we find a likely self reference in the parameters, make an instance
// member (method or property)
//
SmallVector<const clang::ParmVarDecl *, 8> nonSelfParams;
unsigned selfIdx = 0;
for (auto paramI = funcDecl->param_begin(), paramE = funcDecl->param_end();
paramI != paramE; ++paramI, ++selfIdx) {
auto param = *paramI;
NameBuffer remainingName;
StringRef typeName = getTypeName(param->getType());
if (typeName != StringRef()) {
if (match(workingName, typeName, remainingName)) {
auto effectiveDC = getEffectiveDC(param->getType());
nonSelfParams.append(funcDecl->param_begin(), paramI);
nonSelfParams.append(++paramI, paramE);
// See if it's a property
for (auto propSpec : PropertySpecifiers) {
NameBuffer propName;
if (match(remainingName, propSpec, propName)) {
const clang::FunctionDecl *pairedAccessor;
if (validToImportAsProperty(funcDecl, propSpec, selfIdx,
pairedAccessor))
return importAsInstanceProperty(propName, propSpec, selfIdx,
nonSelfParams, pairedAccessor,
effectiveDC);
}
}
return importAsInstanceMethod(remainingName, selfIdx, nonSelfParams,
effectiveDC);
}
}
}
// No self, must be static
nonSelfParams = {funcDecl->param_begin(), funcDecl->param_end()};
// 3) Finally, try to find a class to put this on as a static function
NameBuffer remainingName;
if (const clang::TypeDecl *tyDecl =
findTypeAndMatch(workingName, remainingName)) {
auto effectiveDC = getEffectiveDC(tyDecl);
ArrayRef<const clang::ParmVarDecl *> params = {funcDecl->param_begin(),
funcDecl->param_end()};
// See if it's a property
for (auto propSpec : PropertySpecifiers) {
NameBuffer propName;
if (match(remainingName, propSpec, propName)) {
const clang::FunctionDecl *pairedAccessor;
if (validToImportAsProperty(funcDecl, propSpec, None, pairedAccessor))
return importAsStaticProperty(propName, propSpec, nonSelfParams,
pairedAccessor, effectiveDC);
}
}
return importAsStaticMethod(remainingName, nonSelfParams, effectiveDC);
}
return fail();
}
template <typename DeclType>
DeclType *IAMInference::clangLookup(StringRef name,
clang::Sema::LookupNameKind kind) {
clang::IdentifierInfo *nameII = &clangSema.getASTContext().Idents.get(name);
clang::LookupResult lookupResult(clangSema, clang::DeclarationName(nameII),
clang::SourceLocation(), kind);
if (!clangSema.LookupName(lookupResult, clangSema.TUScope))
return nullptr;
auto res = lookupResult.getAsSingle<DeclType>();
if (!res)
return nullptr;
return res->getCanonicalDecl();
}
IAMResult IAMResult::infer(ASTContext &ctx, clang::Sema &clangSema,
const clang::NamedDecl *decl, IAMOptions opts) {
IAMInference inference(ctx, clangSema, opts);
return inference.infer(decl);
}