Skip to content

Begin importing APIs under both Swift 3 and Swift 4 names, to produce errors #6720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2738,17 +2738,51 @@ void ClangImporter::Implementation::lookupValue(
}
}

// If we have a declaration and nothing matched so far, try the Swift 2
// name.
// If we have a declaration and nothing matched so far, try the names used
// in other versions of Swift.
if (!anyMatching) {
if (auto clangDecl = entry.dyn_cast<clang::NamedDecl *>()) {
if (auto swift2Decl = cast_or_null<ValueDecl>(importDeclReal(
clangDecl->getMostRecentDecl(), ImportNameVersion::Swift2))) {
if (swift2Decl->getFullName().matchesRef(name) &&
swift2Decl->getDeclContext()->isModuleScopeContext()) {
consumer.foundDecl(swift2Decl,
const clang::NamedDecl *recentClangDecl =
clangDecl->getMostRecentDecl();
auto tryImport = [&](ImportNameVersion nameVersion) -> bool {
// Check to see if the name and context match what we expect.
ImportedName newName = importFullName(recentClangDecl, nameVersion);
if (!newName.getDeclName().matchesRef(name))
return false;

const clang::DeclContext *clangDC =
newName.getEffectiveContext().getAsDeclContext();
if (!clangDC || !clangDC->isFileContext())
return false;

// Then try to import the decl under the alternate name.
auto alternateNamedDecl =
cast_or_null<ValueDecl>(importDeclReal(recentClangDecl,
nameVersion));
if (!alternateNamedDecl)
return false;
assert(alternateNamedDecl->getFullName().matchesRef(name) &&
"importFullName behaved differently from importDecl");
if (alternateNamedDecl->getDeclContext()->isModuleScopeContext()) {
consumer.foundDecl(alternateNamedDecl,
DeclVisibilityKind::VisibleAtTopLevel);
return true;
}
return false;
};

// Try importing previous versions of the decl first...
ImportNameVersion nameVersion = CurrentVersion;
while (!anyMatching && nameVersion != ImportNameVersion::Raw) {
--nameVersion;
anyMatching = tryImport(nameVersion);
}
// ...then move on to newer versions if none of the old versions
// matched.
nameVersion = CurrentVersion;
while (!anyMatching && nameVersion != ImportNameVersion::LAST_VERSION) {
++nameVersion;
anyMatching = tryImport(nameVersion);
}
}
}
Expand Down
61 changes: 33 additions & 28 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2009,10 +2009,34 @@ namespace {
/*fullyQualified=*/correctSwiftName.importAsMember(), os);
}

auto attr = AvailableAttr::createPlatformAgnostic(
ctx, StringRef(), ctx.AllocateCopy(renamed.str()),
PlatformAgnosticAvailabilityKind::SwiftVersionSpecific,
clang::VersionTuple(3));
unsigned majorVersion = majorVersionNumberForNameVersion(getVersion());
DeclAttribute *attr;
if (isActiveSwiftVersion() || getVersion() == ImportNameVersion::Raw) {
// "Raw" is the Objective-C name, which was never available in Swift.
// Variants within the active version are usually declarations that
// have been superseded, like the accessors of a property.
attr = AvailableAttr::createPlatformAgnostic(
ctx, /*Message*/StringRef(), ctx.AllocateCopy(renamed.str()),
PlatformAgnosticAvailabilityKind::UnavailableInSwift);
} else if (getVersion() < getActiveSwiftVersion()) {
// A Swift 2 name, for example, was obsoleted in Swift 3.
attr = AvailableAttr::createPlatformAgnostic(
ctx, /*Message*/StringRef(), ctx.AllocateCopy(renamed.str()),
PlatformAgnosticAvailabilityKind::SwiftVersionSpecific,
clang::VersionTuple(majorVersion + 1));
} else {
// Future names are introduced in their future version.
assert(getVersion() > getActiveSwiftVersion());
attr = new (ctx) AvailableAttr(
SourceLoc(), SourceRange(), PlatformKind::none,
/*Message*/StringRef(), ctx.AllocateCopy(renamed.str()),
/*Introduced*/clang::VersionTuple(majorVersion), SourceRange(),
/*Deprecated*/clang::VersionTuple(), SourceRange(),
/*Obsoleted*/clang::VersionTuple(), SourceRange(),
PlatformAgnosticAvailabilityKind::SwiftVersionSpecific,
/*Implicit*/true);
}

decl->getAttrs().add(attr);
decl->setImplicit();
}
Expand Down Expand Up @@ -3485,31 +3509,12 @@ namespace {
{decl->param_begin(), decl->param_size()},
decl->isVariadic(), redundant);

// Directly ask the NameImporter for the non-init variant of the Swift 2
// name.
auto rawName = Impl.importFullName(decl, ImportNameVersion::Raw);
if (!rawName)
return result;

auto rawDecl = importNonInitObjCMethodDecl(decl, dc, rawName, selector,
forceClassMethod);
if (!rawDecl)
return result;

// Mark the raw imported class method "unavailable", with a useful error
// message.
llvm::SmallString<64> message;
llvm::raw_svector_ostream os(message);
os << "use object construction '" << decl->getClassInterface()->getName()
<< "(";
for (auto arg : importedName.getDeclName().getArgumentNames()) {
os << arg << ":";
if (auto rawDecl = Impl.importDecl(decl, ImportNameVersion::Raw)) {
// We expect the raw decl to always be a method.
assert(isa<FuncDecl>(rawDecl));
Impl.addAlternateDecl(result, cast<ValueDecl>(rawDecl));
}
os << ")'";
rawDecl->getAttrs().add(AvailableAttr::createPlatformAgnostic(
Impl.SwiftContext, Impl.SwiftContext.AllocateCopy(os.str())));
markAsVariant(rawDecl, importedName);
Impl.addAlternateDecl(result, cast<ValueDecl>(rawDecl));

return result;
}

Expand Down
153 changes: 99 additions & 54 deletions lib/ClangImporter/ImportName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ importer::nameVersionFromOptions(const LangOptions &langOpts) {
}
}

unsigned importer::majorVersionNumberForNameVersion(ImportNameVersion version) {
switch (version) {
case ImportNameVersion::Raw:
return 0;
case ImportNameVersion::Swift2:
return 2;
case ImportNameVersion::Swift3:
return 3;
case ImportNameVersion::Swift4:
return 4;
}
}


/// Determine whether the given Clang selector matches the given
/// selector pieces.
static bool isNonNullarySelector(clang::Selector selector,
Expand Down Expand Up @@ -560,12 +574,90 @@ determineCtorInitializerKind(const clang::ObjCMethodDecl *method) {
return None;
}

template <typename A>
static bool matchesVersion(A *versionedAttr, ImportNameVersion version) {
clang::VersionTuple attrVersion = versionedAttr->getVersion();
if (attrVersion.empty())
return version == ImportNameVersion::LAST_VERSION;
return attrVersion.getMajor() == majorVersionNumberForNameVersion(version);
}

const clang::SwiftNameAttr *
importer::findSwiftNameAttr(const clang::Decl *decl,
ImportNameVersion version) {
if (version == ImportNameVersion::Raw)
return nullptr;

// Handle versioned API notes for Swift 3 and later. This is the common case.
if (version != ImportNameVersion::Swift2) {
for (auto *attr : decl->attrs()) {
if (auto *versionedAttr = dyn_cast<clang::SwiftVersionedAttr>(attr)) {
if (!matchesVersion(versionedAttr, version))
continue;
if (auto *added =
dyn_cast<clang::SwiftNameAttr>(versionedAttr->getAttrToAdd())) {
return added;
}
}

if (auto *removeAttr = dyn_cast<clang::SwiftVersionedRemovalAttr>(attr)) {
if (!matchesVersion(removeAttr, version))
continue;
if (removeAttr->getAttrKindToRemove() == clang::attr::SwiftName)
return nullptr;
}
}

return decl->getAttr<clang::SwiftNameAttr>();
}

// The remainder of this function emulates the limited form of swift_name
// supported in Swift 2.
auto attr = decl->getAttr<clang::SwiftNameAttr>();
if (!attr) return nullptr;

// API notes produce implicit attributes; ignore them because they weren't
// used for naming in Swift 2.
if (attr->isImplicit()) return nullptr;

// Whitelist certain explicitly-written Swift names that were
// permitted and used in Swift 2. All others are ignored, so that we are
// assuming a more direct translation from the Objective-C APIs into Swift.

if (auto enumerator = dyn_cast<clang::EnumConstantDecl>(decl)) {
// Foundation's NSXMLDTDKind had an explicit swift_name attribute in
// Swift 2. Honor it.
if (enumerator->getName() == "NSXMLDTDKind") return attr;
return nullptr;
}

if (auto method = dyn_cast<clang::ObjCMethodDecl>(decl)) {
// Special case: mapping to an initializer.
if (attr->getName().startswith("init(")) {
// If we have a class method, honor the annotation to turn a class
// method into an initializer.
if (method->isClassMethod()) return attr;

return nullptr;
}

// Special case: preventing a mapping to an initializer.
if (matchFactoryAsInitName(method) && determineCtorInitializerKind(method))
return attr;

return nullptr;
}

return nullptr;
}

/// Determine whether the given class method should be imported as
/// an initializer.
static FactoryAsInitKind
getFactoryAsInit(const clang::ObjCInterfaceDecl *classDecl,
const clang::ObjCMethodDecl *method) {
if (auto *customNameAttr = method->getAttr<clang::SwiftNameAttr>()) {
const clang::ObjCMethodDecl *method,
ImportNameVersion version) {
if (auto *customNameAttr = findSwiftNameAttr(method, version)) {
if (customNameAttr->getName().startswith("init("))
return FactoryAsInitKind::AsInitializer;
else
Expand All @@ -586,6 +678,7 @@ getFactoryAsInit(const clang::ObjCInterfaceDecl *classDecl,
/// imported. Note that this does not distinguish designated
/// vs. convenience; both will be classified as "designated".
static bool shouldImportAsInitializer(const clang::ObjCMethodDecl *method,
ImportNameVersion version,
unsigned &prefixLength,
CtorInitializerKind &kind) {
/// Is this an initializer?
Expand All @@ -604,7 +697,7 @@ static bool shouldImportAsInitializer(const clang::ObjCMethodDecl *method,

// Check whether we should try to import this factory method as an
// initializer.
switch (getFactoryAsInit(objcClass, method)) {
switch (getFactoryAsInit(objcClass, method, version)) {
case FactoryAsInitKind::AsInitializer:
// Okay; check for the correct result type below.
prefixLength = 0;
Expand Down Expand Up @@ -634,55 +727,6 @@ static bool shouldImportAsInitializer(const clang::ObjCMethodDecl *method,
return false;
}

/// Find the swift_name attribute associated with this declaration, if
/// any.
///
/// \param version The version we're importing the name as
static clang::SwiftNameAttr *findSwiftNameAttr(const clang::Decl *decl,
ImportNameVersion version) {
// Find the attribute.
auto attr = decl->getAttr<clang::SwiftNameAttr>();
if (!attr) return nullptr;

// If we're not emulating the Swift 2 behavior, return what we got.
if (version != ImportNameVersion::Swift2)
return attr;

// API notes produce implicit attributes; ignore them because they weren't
// used for naming in Swift 2.
if (attr->isImplicit()) return nullptr;

// Whitelist certain explicitly-written Swift names that were
// permitted and used in Swift 2. All others are ignored, so that we are
// assuming a more direct translation from the Objective-C APIs into Swift.

if (auto enumerator = dyn_cast<clang::EnumConstantDecl>(decl)) {
// Foundation's NSXMLDTDKind had an explicit swift_name attribute in
// Swift 2. Honor it.
if (enumerator->getName() == "NSXMLDTDKind") return attr;
return nullptr;
}

if (auto method = dyn_cast<clang::ObjCMethodDecl>(decl)) {
// Special case: mapping to an initializer.
if (attr->getName().startswith("init(")) {
// If we have a class method, honor the annotation to turn a class
// method into an initializer.
if (method->isClassMethod()) return attr;

return nullptr;
}

// Special case: preventing a mapping to an initializer.
if (matchFactoryAsInitName(method) && determineCtorInitializerKind(method))
return attr;

return nullptr;
}

return nullptr;
}

/// Attempt to omit needless words from the given function name.
static bool omitNeedlessWordsInFunctionName(
StringRef &baseName, SmallVectorImpl<StringRef> &argumentNames,
Expand Down Expand Up @@ -1179,7 +1223,7 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
if (method) {
unsigned initPrefixLength;
if (parsedName.BaseName == "init" && parsedName.IsFunctionName) {
if (!shouldImportAsInitializer(method, initPrefixLength,
if (!shouldImportAsInitializer(method, version, initPrefixLength,
result.info.initKind)) {
// We cannot import this as an initializer anyway.
return ImportedName();
Expand Down Expand Up @@ -1354,7 +1398,8 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
if (baseName.empty())
return ImportedName();

isInitializer = shouldImportAsInitializer(objcMethod, initializerPrefixLen,
isInitializer = shouldImportAsInitializer(objcMethod, version,
initializerPrefixLen,
result.info.initKind);

// If we would import a factory method as an initializer but were
Expand Down
Loading