Files
swift-mirror/lib/ClangImporter/ImportEnumInfo.cpp
Michael Ilseman 476f92d2de [Clang Importer] Only strip common prefix for swift_newtype
Rather than do whole-word common-word stripping, we only strip common
prefixes. This is not as powerful as what was originally intended, but
much less magical in the mapping into Swift.

Test case adjusted, and common utility functions exposed.
2016-04-22 16:26:19 -07:00

288 lines
9.4 KiB
C++

//===--- ImportEnumInfo.cpp - Information about importable Clang enums ----===//
//
// 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 provides EnumInfo, which describes a Clang enum ready to be
// imported
//
//===----------------------------------------------------------------------===//
#include "ImportEnumInfo.h"
#include "swift/Basic/StringExtras.h"
#include "swift/Parse/Lexer.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/Lex/MacroInfo.h"
#include "clang/Lex/Preprocessor.h"
using namespace swift;
using namespace importer;
/// Classify the given Clang enumeration to describe how to import it.
void EnumInfo::classifyEnum(const clang::EnumDecl *decl,
clang::Preprocessor &pp) {
// Anonymous enumerations simply get mapped to constants of the
// underlying type of the enum, because there is no way to conjure up a
// name for the Swift type.
if (!decl->hasNameForLinkage()) {
kind = EnumKind::Constants;
return;
}
// First, check for attributes that denote the classification
if (auto domainAttr = decl->getAttr<clang::NSErrorDomainAttr>()) {
kind = EnumKind::Enum;
attribute = domainAttr;
return;
}
// Was the enum declared using *_ENUM or *_OPTIONS?
// FIXME: Use Clang attributes instead of grovelling the macro expansion loc.
auto loc = decl->getLocStart();
if (loc.isMacroID()) {
StringRef MacroName = pp.getImmediateMacroName(loc);
if (MacroName == "CF_ENUM" || MacroName == "__CF_NAMED_ENUM" ||
MacroName == "OBJC_ENUM" || MacroName == "SWIFT_ENUM" ||
MacroName == "SWIFT_ENUM_NAMED") {
kind = EnumKind::Enum;
return;
}
if (MacroName == "CF_OPTIONS" || MacroName == "OBJC_OPTIONS" ||
MacroName == "SWIFT_OPTIONS") {
kind = EnumKind::Options;
return;
}
}
// Hardcode a particular annoying case in the OS X headers.
if (decl->getName() == "DYLD_BOOL") {
kind = EnumKind::Enum;
return;
}
// Fall back to the 'Unknown' path.
kind = EnumKind::Unknown;
}
/// Returns the common prefix of two strings at camel-case word granularity.
///
/// For example, given "NSFooBar" and "NSFooBas", returns "NSFoo"
/// (not "NSFooBa"). The returned StringRef is a slice of the "a" argument.
///
/// If either string has a non-identifier character immediately after the
/// prefix, \p followedByNonIdentifier will be set to \c true. If both strings
/// have identifier characters after the prefix, \p followedByNonIdentifier will
/// be set to \c false. Otherwise, \p followedByNonIdentifier will not be
/// changed from its initial value.
///
/// This is used to derive the common prefix of enum constants so we can elide
/// it from the Swift interface.
StringRef importer::getCommonWordPrefix(StringRef a, StringRef b,
bool &followedByNonIdentifier) {
auto aWords = camel_case::getWords(a), bWords = camel_case::getWords(b);
auto aI = aWords.begin(), aE = aWords.end(), bI = bWords.begin(),
bE = bWords.end();
unsigned prevLength = 0;
unsigned prefixLength = 0;
for (; aI != aE && bI != bE; ++aI, ++bI) {
if (*aI != *bI) {
followedByNonIdentifier = false;
break;
}
prevLength = prefixLength;
prefixLength = aI.getPosition() + aI->size();
}
// Avoid creating a prefix where the rest of the string starts with a number.
if ((aI != aE && !Lexer::isIdentifier(*aI)) ||
(bI != bE && !Lexer::isIdentifier(*bI))) {
followedByNonIdentifier = true;
prefixLength = prevLength;
}
return a.slice(0, prefixLength);
}
/// Returns the common word-prefix of two strings, allowing the second string
/// to be a common English plural form of the first.
///
/// For example, given "NSProperty" and "NSProperties", the full "NSProperty"
/// is returned. Given "NSMagicArmor" and "NSMagicArmory", only
/// "NSMagic" is returned.
///
/// The "-s", "-es", and "-ies" patterns cover every plural NS_OPTIONS name
/// in Cocoa and Cocoa Touch.
///
/// \see getCommonWordPrefix
StringRef importer::getCommonPluralPrefix(StringRef singular,
StringRef plural) {
assert(!plural.empty());
if (singular.empty())
return singular;
bool ignored;
StringRef commonPrefix = getCommonWordPrefix(singular, plural, ignored);
if (commonPrefix.size() == singular.size() || plural.back() != 's')
return commonPrefix;
StringRef leftover = singular.substr(commonPrefix.size());
StringRef firstLeftoverWord = camel_case::getFirstWord(leftover);
StringRef commonPrefixPlusWord =
singular.substr(0, commonPrefix.size() + firstLeftoverWord.size());
// Is the plural string just "[singular]s"?
plural = plural.drop_back();
if (plural.endswith(firstLeftoverWord))
return commonPrefixPlusWord;
if (plural.empty() || plural.back() != 'e')
return commonPrefix;
// Is the plural string "[singular]es"?
plural = plural.drop_back();
if (plural.endswith(firstLeftoverWord))
return commonPrefixPlusWord;
if (plural.empty() || !(plural.back() == 'i' && singular.back() == 'y'))
return commonPrefix;
// Is the plural string "[prefix]ies" and the singular "[prefix]y"?
plural = plural.drop_back();
firstLeftoverWord = firstLeftoverWord.drop_back();
if (plural.endswith(firstLeftoverWord))
return commonPrefixPlusWord;
return commonPrefix;
}
/// Determine the prefix to be stripped from the names of the enum constants
/// within the given enum.
void EnumInfo::determineConstantNamePrefix(ASTContext &ctx,
const clang::EnumDecl *decl) {
switch (getKind()) {
case EnumKind::Enum:
case EnumKind::Options:
// Enums are mapped to Swift enums, Options to Swift option sets, both
// of which attempt prefix-stripping.
break;
case EnumKind::Constants:
case EnumKind::Unknown:
// Nothing to do.
return;
}
// If there are no enumers, there is no prefix to compute.
auto ec = decl->enumerator_begin(), ecEnd = decl->enumerator_end();
if (ec == ecEnd)
return;
// Determine whether the given enumerator is non-deprecated and has no
// specifically-provided name.
auto isNonDeprecatedWithoutCustomName = [](
const clang::EnumConstantDecl *elem) -> bool {
if (elem->hasAttr<clang::SwiftNameAttr>())
return false;
clang::VersionTuple maxVersion{~0U, ~0U, ~0U};
switch (elem->getAvailability(nullptr, maxVersion)) {
case clang::AR_Available:
case clang::AR_NotYetIntroduced:
for (auto attr : elem->attrs()) {
if (auto annotate = dyn_cast<clang::AnnotateAttr>(attr)) {
if (annotate->getAnnotation() == "swift1_unavailable")
return false;
}
if (auto avail = dyn_cast<clang::AvailabilityAttr>(attr)) {
if (avail->getPlatform()->getName() == "swift")
return false;
}
}
return true;
case clang::AR_Deprecated:
case clang::AR_Unavailable:
return false;
}
};
// Move to the first non-deprecated enumerator, or non-swift_name'd
// enumerator, if present.
auto firstNonDeprecated =
std::find_if(ec, ecEnd, isNonDeprecatedWithoutCustomName);
bool hasNonDeprecated = (firstNonDeprecated != ecEnd);
if (hasNonDeprecated) {
ec = firstNonDeprecated;
} else {
// Advance to the first case without a custom name, deprecated or not.
while (ec != ecEnd && (*ec)->hasAttr<clang::SwiftNameAttr>())
++ec;
if (ec == ecEnd) {
return;
}
}
// Compute the common prefix.
StringRef commonPrefix = (*ec)->getName();
bool followedByNonIdentifier = false;
for (++ec; ec != ecEnd; ++ec) {
// Skip deprecated or swift_name'd enumerators.
const clang::EnumConstantDecl *elem = *ec;
if (hasNonDeprecated) {
if (!isNonDeprecatedWithoutCustomName(elem))
continue;
} else {
if (elem->hasAttr<clang::SwiftNameAttr>())
continue;
}
commonPrefix = getCommonWordPrefix(commonPrefix, elem->getName(),
followedByNonIdentifier);
if (commonPrefix.empty())
break;
}
if (!commonPrefix.empty()) {
StringRef checkPrefix = commonPrefix;
// Account for the 'kConstant' naming convention on enumerators.
if (checkPrefix[0] == 'k') {
bool canDropK;
if (checkPrefix.size() >= 2)
canDropK = clang::isUppercase(checkPrefix[1]);
else
canDropK = !followedByNonIdentifier;
if (canDropK)
checkPrefix = checkPrefix.drop_front();
}
// Don't use importFullName() here, we want to ignore the swift_name
// and swift_private attributes.
StringRef enumNameStr = decl->getName();
StringRef commonWithEnum = getCommonPluralPrefix(checkPrefix, enumNameStr);
size_t delta = commonPrefix.size() - checkPrefix.size();
// Account for the 'EnumName_Constant' convention on enumerators.
if (commonWithEnum.size() < checkPrefix.size() &&
checkPrefix[commonWithEnum.size()] == '_' && !followedByNonIdentifier) {
delta += 1;
}
commonPrefix = commonPrefix.slice(0, commonWithEnum.size() + delta);
}
constantNamePrefix = ctx.AllocateCopy(commonPrefix);
}