mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
In the code that makes sure to pair up getters/setters only from the same top level module, we were accidentally skipping the final validity checks (e.g. do the number of parameters line up). This fixes that.
864 lines
29 KiB
C++
864 lines
29 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 "CFTypeInfo.h"
|
|
#include "IAMInference.h"
|
|
#include "ImporterImpl.h"
|
|
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/Basic/StringExtras.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/Lex/Preprocessor.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");
|
|
|
|
// Specifically skipped/avoided
|
|
STATISTIC(SkipLeadingUnderscore,
|
|
"# of globals skipped due to leading underscore");
|
|
STATISTIC(SkipCFMemoryManagement,
|
|
"# of CF memory management globals skipped");
|
|
|
|
// 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");
|
|
|
|
// Omit needless words stats
|
|
STATISTIC(OmitNumTimes,
|
|
"# of times omitNeedlessWords was able to fire on an API");
|
|
|
|
using namespace swift;
|
|
using namespace importer;
|
|
|
|
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"),
|
|
}};
|
|
|
|
IAMOptions IAMOptions::getDefault() { return {}; }
|
|
|
|
// As append, but skip a repeated word at the boundary. First-letter-case
|
|
// insensitive.
|
|
// Example: appendUniq("FooBar", "barBaz") ==> "FooBarBaz"
|
|
static 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->equals_lower(lastWord))
|
|
++wI;
|
|
src.append(wI.getRestOfStr());
|
|
}
|
|
|
|
static StringRef skipLeadingUnderscores(StringRef str) {
|
|
while (!str.empty() && str.startswith("_"))
|
|
str = str.drop_front(1);
|
|
return str;
|
|
}
|
|
|
|
// 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() && wI->equals_lower(lastWord))
|
|
++wI;
|
|
|
|
formHumbleCamelName(left, out);
|
|
camel_case::appendSentenceCase(out, wI.getRestOfStr());
|
|
}
|
|
|
|
static bool hasWord(StringRef s, StringRef matchWord) {
|
|
for (auto word : camel_case::getWords(s))
|
|
if (word == matchWord)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// Drops the specified word, and returns the number of times it was dropped.
|
|
// When forming the resultant string, will call appendUniq to skip repeated
|
|
// words at the boundary.
|
|
static unsigned dropWordUniq(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
|
|
appendUniq(out, *wI);
|
|
|
|
return numDropped;
|
|
}
|
|
|
|
static clang::Module *getSubmodule(const clang::NamedDecl *decl, clang::Sema &clangSema) {
|
|
if (auto m = decl->getImportedOwningModule())
|
|
return m;
|
|
if (auto m = decl->getLocalOwningModule())
|
|
return m;
|
|
if (auto m = clangSema.getPreprocessor().getCurrentModule())
|
|
return m;
|
|
if (auto m = clangSema.getPreprocessor().getCurrentSubmodule())
|
|
return m;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static clang::Module *getTopModule(clang::Module *m) {
|
|
while (m->Parent)
|
|
m = m->Parent;
|
|
return m;
|
|
}
|
|
static clang::Module *getTopModule(const clang::NamedDecl *decl, clang::Sema &clangSema) {
|
|
auto m = getSubmodule(decl, clangSema);
|
|
if (!m)
|
|
return nullptr;
|
|
return getTopModule(m);
|
|
}
|
|
|
|
|
|
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 *);
|
|
IAMResult inferVar(const clang::VarDecl *);
|
|
|
|
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;
|
|
StringRef prefix = buf;
|
|
if (name != initSpecifier) {
|
|
assert(name.size() > initSpecifier.size() &&
|
|
"should have more words in it");
|
|
bool didDrop = dropWordUniq(name, initSpecifier, buf);
|
|
(void)didDrop;
|
|
prefix = buf;
|
|
|
|
// Skip "with"
|
|
auto prefixWords = camel_case::getWords(prefix);
|
|
if (prefixWords.begin() != prefixWords.end() &&
|
|
(*prefixWords.begin() == "With" || *prefixWords.begin() == "with")) {
|
|
prefix = prefix.drop_front(4);
|
|
}
|
|
|
|
// Skip "CF" or "NS"
|
|
prefixWords = camel_case::getWords(prefix);
|
|
if (prefixWords.begin() != prefixWords.end() &&
|
|
(*prefixWords.begin() == "CF" || *prefixWords.begin() == "NS")) {
|
|
prefix = prefix.drop_front(2);
|
|
}
|
|
|
|
assert(didDrop != 0 && "specifier not present?");
|
|
}
|
|
return {formDeclName("init", params, prefix), 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};
|
|
}
|
|
|
|
Identifier getIdentifier(StringRef str) {
|
|
if (str == "")
|
|
return Identifier();
|
|
return context.getIdentifier(str);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
EffectiveClangContext 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 getEffectiveDC(clang::QualType(tyDecl->getTypeForDecl(), 0));
|
|
}
|
|
if (auto tyDecl = clangLookupTypeDecl(prefix)) {
|
|
outStr.append(workingName.drop_front(prefix.size()));
|
|
return getEffectiveDC(clang::QualType(tyDecl->getTypeForDecl(), 0));
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
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 getIdentifier(buf);
|
|
}
|
|
|
|
DeclName formDeclName(StringRef baseName) {
|
|
return {getHumbleIdentifier(baseName)};
|
|
}
|
|
|
|
DeclName formDeclName(StringRef baseName,
|
|
ArrayRef<const clang::ParmVarDecl *> params,
|
|
StringRef firstPrefix = "") {
|
|
|
|
// TODO: redesign from a SmallString to a StringScratchBuffer design for all
|
|
// of this name mangling, since we have to use one for omit needless words
|
|
// anyways
|
|
|
|
if (params.empty() && firstPrefix != "") {
|
|
// We need to form an argument label, despite there being no argument
|
|
NameBuffer paramName;
|
|
formHumbleCamelName(firstPrefix, paramName);
|
|
return {context, getHumbleIdentifier(baseName),
|
|
getIdentifier(paramName)};
|
|
}
|
|
|
|
StringScratchSpace scratch;
|
|
SmallVector<StringRef, 8> argStrs;
|
|
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);
|
|
}
|
|
|
|
argStrs.push_back(scratch.copyString(paramName));
|
|
}
|
|
|
|
DeclName beforeOmit;
|
|
(void)beforeOmit;
|
|
{
|
|
SmallVector<Identifier, 8> argLabels;
|
|
for (auto str : argStrs)
|
|
argLabels.push_back(getIdentifier(str));
|
|
DEBUG((beforeOmit = {context, getHumbleIdentifier(baseName), argLabels}));
|
|
}
|
|
|
|
SmallVector<OmissionTypeName, 8> paramTypeNames;
|
|
for (auto param : params) {
|
|
paramTypeNames.push_back(
|
|
ClangImporter::Implementation::getClangTypeNameForOmission(
|
|
clangSema.getASTContext(), param->getType()));
|
|
}
|
|
|
|
auto humbleBaseName = getHumbleIdentifier(baseName);
|
|
baseName = humbleBaseName.str();
|
|
bool didOmit =
|
|
omitNeedlessWords(baseName, argStrs, "", "", "", paramTypeNames, false,
|
|
false, nullptr, scratch);
|
|
SmallVector<Identifier, 8> argLabels;
|
|
for (auto str : argStrs)
|
|
argLabels.push_back(getIdentifier(str));
|
|
|
|
DeclName ret = {context, getHumbleIdentifier(baseName), argLabels};
|
|
|
|
if (didOmit) {
|
|
++OmitNumTimes;
|
|
DEBUG(llvm::dbgs() << "omission detected: " << beforeOmit << " ==> "
|
|
<< ret << "\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool matchTypeName(StringRef str, clang::QualType qt, NameBuffer &outStr);
|
|
bool match(StringRef str, StringRef toMatch, NameBuffer &outStr);
|
|
|
|
EffectiveClangContext getEffectiveDC(clang::QualType qt) {
|
|
// Read through some attributes
|
|
while (qt.getTypePtrOrNull() && isa<clang::AttributedType>(qt.getTypePtr()))
|
|
qt = qt.getSingleStepDesugaredType(clangSema.getASTContext());
|
|
|
|
// Read through typedefs until we get to a CF typedef or a non-typedef-ed
|
|
// type
|
|
while (qt.getTypePtrOrNull() && isa<clang::TypedefType>(qt.getTypePtr())) {
|
|
auto typedefType = cast<clang::TypedefType>(qt.getTypePtr());
|
|
if (auto pointeeInfo = CFPointeeInfo::classifyTypedef(
|
|
typedefType->getDecl()->getCanonicalDecl())) {
|
|
if (pointeeInfo.isRecord() || pointeeInfo.isTypedef())
|
|
return {typedefType->getDecl()->getCanonicalDecl()};
|
|
assert(pointeeInfo.isVoid() && "no other type");
|
|
return {};
|
|
}
|
|
qt = qt.getSingleStepDesugaredType(clangSema.getASTContext());
|
|
}
|
|
|
|
auto pointeeQT = qt.getTypePtr()->getPointeeType();
|
|
if (pointeeQT != clang::QualType())
|
|
// Retry on the pointee
|
|
return getEffectiveDC(pointeeQT);
|
|
|
|
if (auto tagDecl = qt.getTypePtr()->getAsTagDecl()) {
|
|
auto canon = tagDecl->getCanonicalDecl();
|
|
if (canon->getDefinition())
|
|
return {canon};
|
|
|
|
// TODO: Once the importer learns how to import un-defined structs, then
|
|
// we will be able to infer them. Until that point, we have to bail
|
|
// because ImportDecl won't be able to re-map this.
|
|
return {};
|
|
}
|
|
|
|
// Failed to find a type we can extend
|
|
return {};
|
|
}
|
|
};
|
|
}
|
|
|
|
static StringRef getTypeName(clang::QualType qt) {
|
|
if (auto typedefTy = qt->getAs<clang::TypedefType>()) {
|
|
// Check for a CF type name (drop the "Ref")
|
|
auto cfName = ClangImporter::Implementation::getCFTypeName(
|
|
typedefTy->getDecl()->getCanonicalDecl());
|
|
if (cfName != StringRef())
|
|
return cfName;
|
|
}
|
|
|
|
auto identInfo = qt.getBaseTypeIdentifier();
|
|
if (identInfo)
|
|
return identInfo->getName();
|
|
|
|
// Otherwise, no name
|
|
return {};
|
|
}
|
|
|
|
bool IAMInference::matchTypeName(StringRef str, clang::QualType qt,
|
|
NameBuffer &outStr) {
|
|
StringRef typeName = getTypeName(qt);
|
|
if (typeName == "")
|
|
return false;
|
|
|
|
// Special case: Mutable can appear in both and may screw up word order. Or,
|
|
// Mutable can occur in the type name only. Either way, we want to have the
|
|
// potential of successfully matching the type.
|
|
NameBuffer nonMutableStr;
|
|
NameBuffer nonMutableTypeName;
|
|
if (hasWord(typeName, "Mutable")) {
|
|
dropWordUniq(typeName, "Mutable", nonMutableTypeName);
|
|
typeName = nonMutableTypeName;
|
|
if (hasWord(str, "Mutable")) {
|
|
dropWordUniq(str, "Mutable", nonMutableStr);
|
|
str = nonMutableStr;
|
|
}
|
|
}
|
|
|
|
return match(str, typeName, outStr);
|
|
}
|
|
|
|
bool IAMInference::match(StringRef str, StringRef toMatch, NameBuffer &outStr) {
|
|
// TODO: let options dictate fuzzy matching...
|
|
|
|
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 roughlyEqual(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();
|
|
}
|
|
|
|
static bool
|
|
isValidAsStaticProperty(const clang::FunctionDecl *getterDecl,
|
|
const clang::FunctionDecl *setterDecl = nullptr) {
|
|
// Getter has none, setter has one arg
|
|
if (getterDecl->getNumParams() != 0 ||
|
|
(setterDecl && setterDecl->getNumParams() != 1)) {
|
|
++InvalidPropertyStaticNumParams;
|
|
return false;
|
|
}
|
|
|
|
// Setter's arg type should be same as getter's return type
|
|
auto getterTy = getterDecl->getReturnType();
|
|
if (setterDecl &&
|
|
!roughlyEqual(getterTy, setterDecl->getParamDecl(0)->getType())) {
|
|
++InvalidPropertyStaticGetterSetterType;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
isValidAsInstanceProperty(const clang::FunctionDecl *getterDecl,
|
|
const clang::FunctionDecl *setterDecl = nullptr) {
|
|
// Instance property, look beyond self
|
|
if (getterDecl->getNumParams() != 1 ||
|
|
(setterDecl && setterDecl->getNumParams() != 2)) {
|
|
++InvalidPropertyInstanceNumParams;
|
|
return false;
|
|
}
|
|
|
|
if (!setterDecl)
|
|
return true;
|
|
|
|
// Make sure they pair up
|
|
auto getterTy = getterDecl->getReturnType();
|
|
auto selfTy = getterDecl->getParamDecl(0)->getType();
|
|
|
|
clang::QualType setterTy = {};
|
|
auto setterParam0Ty = setterDecl->getParamDecl(0)->getType();
|
|
auto setterParam1Ty = setterDecl->getParamDecl(1)->getType();
|
|
|
|
if (roughlyEqual(setterParam0Ty, selfTy)) {
|
|
setterTy = setterParam1Ty;
|
|
} else if (roughlyEqual(setterParam1Ty, selfTy)) {
|
|
setterTy = setterParam0Ty;
|
|
} else {
|
|
++InvalidPropertyInstanceNoSelf;
|
|
return false;
|
|
}
|
|
|
|
if (!roughlyEqual(setterTy, getterTy)) {
|
|
++InvalidPropertyInstanceGetterSetterType;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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) {
|
|
if (!isGet)
|
|
return false;
|
|
if (!selfIndex)
|
|
return isValidAsStaticProperty(originalDecl);
|
|
return isValidAsInstanceProperty(originalDecl);
|
|
}
|
|
|
|
auto getterDecl = isGet ? originalDecl : pairedAccessor;
|
|
auto setterDecl = isGet ? pairedAccessor : originalDecl;
|
|
|
|
if (getTopModule(getterDecl, clangSema) !=
|
|
getTopModule(setterDecl, clangSema)) {
|
|
// We paired up decls from two different modules, so either we still infer
|
|
// as a getter with no setter, or we cannot be a property
|
|
if (isGet) {
|
|
pairedAccessor = nullptr;
|
|
setterDecl = nullptr;
|
|
} else {
|
|
// This is set-only as far as we're concerned
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!selfIndex)
|
|
return isValidAsStaticProperty(getterDecl, setterDecl);
|
|
|
|
return isValidAsInstanceProperty(getterDecl, setterDecl);
|
|
}
|
|
|
|
IAMResult IAMInference::inferVar(const clang::VarDecl *varDecl) {
|
|
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;
|
|
if (auto effectiveDC = findTypeAndMatch(workingName, remainingName))
|
|
|
|
return importAsStaticProperty(remainingName, effectiveDC);
|
|
|
|
return fail();
|
|
}
|
|
|
|
IAMResult IAMInference::infer(const clang::NamedDecl *clangDecl) {
|
|
if (clangDecl->getName().startswith("_")) {
|
|
++SkipLeadingUnderscore;
|
|
return {};
|
|
}
|
|
|
|
// Try to infer a member variable
|
|
if (auto varDecl = dyn_cast<clang::VarDecl>(clangDecl))
|
|
return inferVar(varDecl);
|
|
|
|
// 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
|
|
//
|
|
StringRef getTypeID = "GetTypeID";
|
|
StringRef cfSpecials[] = {"Release", "Retain", "Autorelease"};
|
|
// *GetTypeID
|
|
if (numParams == 0 && workingName.endswith(getTypeID)) {
|
|
NameBuffer remainingName;
|
|
if (auto effectiveDC = 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, effectiveDC);
|
|
}
|
|
}
|
|
// *Release/*Retain/*Autorelease
|
|
} else if (numParams == 1 &&
|
|
std::any_of(std::begin(cfSpecials), std::end(cfSpecials),
|
|
[workingName](StringRef suffix) {
|
|
return workingName.endswith(suffix);
|
|
})) {
|
|
if (auto type =
|
|
funcDecl->getParamDecl(0)->getType()->getAs<clang::TypedefType>()) {
|
|
if (CFPointeeInfo::classifyTypedef(type->getDecl())) {
|
|
++SkipCFMemoryManagement;
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
if (matchTypeName(workingName, retTy, remainingName))
|
|
for (auto initSpec : InitSpecifiers)
|
|
if (hasWord(remainingName, initSpec))
|
|
if (auto effectiveDC = getEffectiveDC(retTy))
|
|
return importAsConstructor(
|
|
remainingName, initSpec,
|
|
{funcDecl->param_begin(), funcDecl->param_end()}, effectiveDC);
|
|
}
|
|
|
|
// 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;
|
|
if (matchTypeName(workingName, param->getType(), remainingName)) {
|
|
auto effectiveDC = getEffectiveDC(param->getType());
|
|
if (!effectiveDC)
|
|
continue;
|
|
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 (auto effectiveDC = findTypeAndMatch(workingName, remainingName)) {
|
|
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);
|
|
}
|
|
}
|
|
StringRef methodName =
|
|
remainingName == "" ? workingName : StringRef(remainingName);
|
|
return importAsStaticMethod(methodName, 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);
|
|
}
|