From a5da848064c657e6908bd48415f9314301217bb3 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 22 Dec 2016 17:22:18 -0800 Subject: [PATCH 1/4] [ClangImporter] Put all versions of names into the lookup table. The compiler uses the non-active names to import unavailable declarations telling the user the correct name. Right now it's still only importing "Swift 2" and "current", but that should change. For reference, the best example of a declaration that has four names is a factory method with a custom NS_SWIFT_NAME annotation: - Its raw name is the plain Objective-C name, written as a method: 'fooByFrobnicatingBar(_:)'. - Its "Swift 2" name is the Swift-2-style translation of that to an initializer: 'init(byFrobnicatingBar:)' - Its "Swift 3" name applies the "omit needless words" transformation: 'init(frobnicating:)'. - Its "Swift 4" name uses the name specified by NS_SWIFT_NAME. --- lib/ClangImporter/ImportName.h | 6 ++++ lib/ClangImporter/SwiftLookupTable.cpp | 29 +++++++++++++------- test/IDE/dump_swift_lookup_tables_objc.swift | 20 ++++++++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/ClangImporter/ImportName.h b/lib/ClangImporter/ImportName.h index 9bedf0272be07..fcfe4a9860b7d 100644 --- a/lib/ClangImporter/ImportName.h +++ b/lib/ClangImporter/ImportName.h @@ -58,6 +58,12 @@ enum class ImportNameVersion : unsigned { }; enum { NumImportNameVersions = 4 }; +static inline void +forEachImportNameVersion(llvm::function_ref action) { + for (unsigned raw = 0; raw < NumImportNameVersions; ++raw) + action(static_cast(raw)); +} + /// Map a language version into an import name version ImportNameVersion nameVersionFromOptions(const LangOptions &langOpts); diff --git a/lib/ClangImporter/SwiftLookupTable.cpp b/lib/ClangImporter/SwiftLookupTable.cpp index c16878c25e245..34fd61bc0d724 100644 --- a/lib/ClangImporter/SwiftLookupTable.cpp +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -1541,8 +1541,12 @@ void importer::addEntryToLookupTable(SwiftLookupTable &table, } // If we have a name to import as, add this entry to the table. - if (auto importedName = - nameImporter.importName(named, ImportNameVersion::Swift3)) { + // FIXME: It doesn't actually matter which version we use here, but it should + // probably follow the ASTContext anyway. + ImportNameVersion currentVersion = ImportNameVersion::Swift3; + if (auto importedName = nameImporter.importName(named, currentVersion)) { + SmallPtrSet distinctNames; + distinctNames.insert(importedName.getDeclName()); table.addEntry(importedName.getDeclName(), named, importedName.getEffectiveContext()); @@ -1553,14 +1557,19 @@ void importer::addEntryToLookupTable(SwiftLookupTable &table, ArrayRef()), named, importedName.getEffectiveContext()); - // Import the Swift 2 name of this entity, and record it as well if it is - // different. - if (auto swift2Name = - nameImporter.importName(named, ImportNameVersion::Swift2)) { - if (swift2Name.getDeclName() != importedName.getDeclName()) - table.addEntry(swift2Name.getDeclName(), named, - swift2Name.getEffectiveContext()); - } + forEachImportNameVersion([&] (ImportNameVersion alternateVersion) { + if (alternateVersion == currentVersion) + return; + auto alternateName = nameImporter.importName(named, alternateVersion); + if (!alternateName) + return; + // FIXME: What if the DeclNames are the same but the contexts are + // different? + if (distinctNames.insert(alternateName.getDeclName()).second) { + table.addEntry(alternateName.getDeclName(), named, + alternateName.getEffectiveContext()); + } + }); } else if (auto category = dyn_cast(named)) { // If the category is invalid, don't add it. if (category->isInvalidDecl()) diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index 0428976a32264..1bfe6dc0308a1 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -37,6 +37,10 @@ // CHECK-NEXT: TU: SNCollision{{$}} // CHECK-NEXT: SNCollisionProtocol: // CHECK-NEXT: TU: SNCollision{{$}} +// CHECK-NEXT: SNSomeClass: +// CHECK-NEXT: TU: SNSomeClass +// CHECK-NEXT: SNSomeProtocol: +// CHECK-NEXT: TU: SNSomeProtocol // CHECK-NEXT: SomeClass: // CHECK-NEXT: TU: SNSomeClass // CHECK-NEXT: SomeProtocol: @@ -51,12 +55,18 @@ // CHECK-NEXT: NSErrorImports: -[NSErrorImports badPointerMethodAndReturnError:] // CHECK-NEXT: blockMethod: // CHECK-NEXT: NSErrorImports: -[NSErrorImports blockMethodAndReturnError:] +// CHECK-NEXT: buildWithUnsignedChar: +// CHECK-NEXT: SNSomeClass: +[SNSomeClass buildWithUnsignedChar:] // CHECK-NEXT: categoryMethodWith: // CHECK-NEXT: SNSomeClass: -[SNSomeClass categoryMethodWithX:y:], -[SNSomeClass categoryMethodWithX:y:z:] +// CHECK-NEXT: categoryMethodWithX: +// CHECK-NEXT: SNSomeClass: -[SNSomeClass categoryMethodWithX:y:], -[SNSomeClass categoryMethodWithX:y:z:] // CHECK: doubleProperty: // CHECK-NEXT: SNSomeClass: SNSomeClass.doubleProperty // CHECK-NEXT: extensionMethodWith: // CHECK-NEXT: SNSomeClass: -[SNSomeClass extensionMethodWithX:y:] +// CHECK-NEXT: extensionMethodWithX: +// CHECK-NEXT: SNSomeClass: -[SNSomeClass extensionMethodWithX:y:] // CHECK: floatProperty: // CHECK-NEXT: SNSomeClass: SNSomeClass.floatProperty // CHECK-NEXT: functionPointerMethod: @@ -67,8 +77,12 @@ // CHECK-NEXT: NSErrorImports: -[NSErrorImports initAndReturnError:], -[NSErrorImports initWithFloat:error:] // CHECK-NEXT: instanceMethodWith: // CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:Y:Z:] +// CHECK-NEXT: instanceMethodWithX: +// CHECK-NEXT: SNSomeClass: -[SNSomeClass instanceMethodWithX:Y:Z:] // CHECK: method: // CHECK-NEXT: NSErrorImports: -[NSErrorImports methodAndReturnError:], -[NSErrorImports methodWithFloat:error:] +// CHECK: methodWithFloat: +// CHECK-NEXT: NSErrorImports: -[NSErrorImports methodWithFloat:error:] // CHECK: objectAtIndexedSubscript: // CHECK-NEXT: SNSomeClass: -[SNSomeClass objectAtIndexedSubscript:] // CHECK-NEXT: optSetter: @@ -77,12 +91,18 @@ // CHECK-NEXT: NSErrorImports: -[NSErrorImports pointerMethodAndReturnError:] // CHECK-NEXT: protoInstanceMethodWith: // CHECK-NEXT: SNSomeProtocol: -[SNSomeProtocol protoInstanceMethodWithX:y:] +// CHECK-NEXT: protoInstanceMethodWithX: +// CHECK-NEXT: SNSomeProtocol: -[SNSomeProtocol protoInstanceMethodWithX:y:] // CHECK: reqSetter: // CHECK-NEXT: SNCollision: SNCollision.reqSetter // CHECK-NEXT: selectorMethod: // CHECK-NEXT: NSErrorImports: -[NSErrorImports selectorMethodAndReturnError:] // CHECK-NEXT: setAccessibilityFloat: // CHECK-NEXT: NSAccessibility: -[NSAccessibility setAccessibilityFloat:] +// CHECK-NEXT: someClassWithDouble: +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithDouble:] +// CHECK-NEXT: someClassWithTry: +// CHECK-NEXT: SNSomeClass: +[SNSomeClass someClassWithTry:] // CHECK-NEXT: subscript: // CHECK-NEXT: SNSomeClass: -[SNSomeClass objectAtIndexedSubscript:] From ce810efe7576bcfa26bf8c87e97df7848ea23cfb Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 22 Dec 2016 14:30:10 -0800 Subject: [PATCH 2/4] [ClangImporter] Route getFactoryAsInit through findSwiftNameAttr. The next commit will make findSwiftNameAttr handle Swift 3 / Swift 4 API notes, so it's important that everything is consistently using it. (The other place that isn't updated yet is enum info; conceivably, the prefix for enum constants might be different based on which SwiftNameAttrs are in play. --- lib/ClangImporter/ImportName.cpp | 108 +++++++++++++++---------------- lib/ClangImporter/ImportName.h | 5 ++ 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/lib/ClangImporter/ImportName.cpp b/lib/ClangImporter/ImportName.cpp index 8a3f2c8d0daa8..986325cd7d7c8 100644 --- a/lib/ClangImporter/ImportName.cpp +++ b/lib/ClangImporter/ImportName.cpp @@ -560,12 +560,59 @@ determineCtorInitializerKind(const clang::ObjCMethodDecl *method) { return None; } +const clang::SwiftNameAttr * +importer::findSwiftNameAttr(const clang::Decl *decl, + ImportNameVersion version) { + // Find the attribute. + auto attr = decl->getAttr(); + 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(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(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()) { + const clang::ObjCMethodDecl *method, + ImportNameVersion version) { + if (auto *customNameAttr = findSwiftNameAttr(method, version)) { if (customNameAttr->getName().startswith("init(")) return FactoryAsInitKind::AsInitializer; else @@ -586,6 +633,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? @@ -604,7 +652,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; @@ -634,55 +682,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(); - 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(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(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 &argumentNames, @@ -1179,7 +1178,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(); @@ -1354,7 +1353,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 diff --git a/lib/ClangImporter/ImportName.h b/lib/ClangImporter/ImportName.h index fcfe4a9860b7d..bc3a995434799 100644 --- a/lib/ClangImporter/ImportName.h +++ b/lib/ClangImporter/ImportName.h @@ -227,6 +227,11 @@ class ImportedName { /// in "Notification", or it there would be nothing left. StringRef stripNotification(StringRef name); +/// Find the swift_name attribute associated with this declaration, if any, +/// appropriate for \p version. +const clang::SwiftNameAttr *findSwiftNameAttr(const clang::Decl *decl, + ImportNameVersion version); + /// Class to determine the Swift name of foreign entities. Currently fairly /// stateless and borrows from the ClangImporter::Implementation, but in the /// future will be more self-contained and encapsulated. From 84ca8ece00b6147175c457715c23de9cc538be8d Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 22 Dec 2016 17:44:37 -0800 Subject: [PATCH 3/4] [test] Fix bogus redeclaration of NSError. --- test/IDE/Inputs/swift_name_objc.h | 3 --- test/IDE/dump_swift_lookup_tables_objc.swift | 2 -- 2 files changed, 5 deletions(-) diff --git a/test/IDE/Inputs/swift_name_objc.h b/test/IDE/Inputs/swift_name_objc.h index aaf133a396244..33c6476d5e38b 100644 --- a/test/IDE/Inputs/swift_name_objc.h +++ b/test/IDE/Inputs/swift_name_objc.h @@ -61,9 +61,6 @@ SWIFT_NAME(SomeProtocol) -(instancetype)initWithTitle:(const char *)title delegate:(id)delegate cancelButtonTitle:(const char *)cancelButtonTitle destructiveButtonTitle:(const char *)destructiveButtonTitle otherButtonTitles:(const char *)otherButtonTitles, ...; @end -@interface NSError : NSObject -@end - @interface NSErrorImports : NSObject - (nullable NSObject *)methodAndReturnError:(NSError **)error; - (BOOL)methodWithFloat:(float)value error:(NSError **)error; diff --git a/test/IDE/dump_swift_lookup_tables_objc.swift b/test/IDE/dump_swift_lookup_tables_objc.swift index 1bfe6dc0308a1..cff7a367cffa9 100644 --- a/test/IDE/dump_swift_lookup_tables_objc.swift +++ b/test/IDE/dump_swift_lookup_tables_objc.swift @@ -29,8 +29,6 @@ // CHECK-NEXT: TU: CFTypeRef // CHECK-NEXT: NSAccessibility: // CHECK-NEXT: TU: NSAccessibility{{$}} -// CHECK-NEXT: NSError: -// CHECK-NEXT: TU: NSError // CHECK-NEXT: NSErrorImports: // CHECK-NEXT: TU: NSErrorImports // CHECK-NEXT: SNCollision: From c9124d989dee00c991782f3e5684ed257172b682 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Jan 2017 16:10:51 -0800 Subject: [PATCH 4/4] [ClangImporter] Import Swift 3 versions of top-level decls in Swift 4. ...and Swift 4 versions in Swift 3, and Swift 2 and "raw" versions in both. This allows the compiler to produce sensible errors and fix-its when someone uses the "wrong" name for an API. The diagnostics certainly have room to improve, but at least the essentials are there. Note that this commit only addresses /top-level/ decls, i.e. those found by lookup into a module. We're still limited to producing all members of a nominal type up front, so that'll require a slightly different approach. Part of rdar://problem/29170671 --- lib/ClangImporter/ClangImporter.cpp | 48 ++++++++++++--- lib/ClangImporter/ImportDecl.cpp | 61 ++++++++++--------- lib/ClangImporter/ImportName.cpp | 55 +++++++++++++++-- lib/ClangImporter/ImportName.h | 27 ++++++-- lib/ClangImporter/SwiftLookupTable.cpp | 5 +- test/APINotes/versioned.swift | 25 ++++++++ .../ClangImporter/Inputs/SwiftPrivateAttr.txt | 6 ++ test/ClangImporter/attr-swift_private.swift | 4 +- test/ClangImporter/objc_factory_method.swift | 10 +-- test/ClangImporter/objc_implicit_with.swift | 2 +- test/IDE/print_clang_swift_name.swift | 29 ++++++++- 11 files changed, 214 insertions(+), 58 deletions(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 7716f53bd93da..09b43dc9bc740 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -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()) { - if (auto swift2Decl = cast_or_null(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(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); } } } diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 0b102b55d2eb5..bcacb55497a90 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -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(); } @@ -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(rawDecl)); + Impl.addAlternateDecl(result, cast(rawDecl)); } - os << ")'"; - rawDecl->getAttrs().add(AvailableAttr::createPlatformAgnostic( - Impl.SwiftContext, Impl.SwiftContext.AllocateCopy(os.str()))); - markAsVariant(rawDecl, importedName); - Impl.addAlternateDecl(result, cast(rawDecl)); + return result; } diff --git a/lib/ClangImporter/ImportName.cpp b/lib/ClangImporter/ImportName.cpp index 986325cd7d7c8..b00f41f201c7d 100644 --- a/lib/ClangImporter/ImportName.cpp +++ b/lib/ClangImporter/ImportName.cpp @@ -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, @@ -560,17 +574,48 @@ determineCtorInitializerKind(const clang::ObjCMethodDecl *method) { return None; } +template +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) { - // Find the attribute. + 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(attr)) { + if (!matchesVersion(versionedAttr, version)) + continue; + if (auto *added = + dyn_cast(versionedAttr->getAttrToAdd())) { + return added; + } + } + + if (auto *removeAttr = dyn_cast(attr)) { + if (!matchesVersion(removeAttr, version)) + continue; + if (removeAttr->getAttrKindToRemove() == clang::attr::SwiftName) + return nullptr; + } + } + + return decl->getAttr(); + } + + // The remainder of this function emulates the limited form of swift_name + // supported in Swift 2. auto attr = decl->getAttr(); 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; diff --git a/lib/ClangImporter/ImportName.h b/lib/ClangImporter/ImportName.h index bc3a995434799..fa622eeaa6f9c 100644 --- a/lib/ClangImporter/ImportName.h +++ b/lib/ClangImporter/ImportName.h @@ -25,9 +25,6 @@ #include "swift/AST/ForeignErrorConvention.h" #include "clang/Sema/Sema.h" -// TODO: remove when we drop import name options -#include "clang/AST/Decl.h" - namespace swift { namespace importer { struct PlatformAvailability; @@ -55,18 +52,36 @@ enum class ImportNameVersion : unsigned { /// Names as they appeared in Swift 4 family Swift4, + + /// A placeholder for the latest version, to be used in loops and such. + LAST_VERSION = Swift4 }; -enum { NumImportNameVersions = 4 }; static inline void forEachImportNameVersion(llvm::function_ref action) { - for (unsigned raw = 0; raw < NumImportNameVersions; ++raw) + auto limit = static_cast(ImportNameVersion::LAST_VERSION); + for (unsigned raw = 0; raw <= limit; ++raw) action(static_cast(raw)); } -/// Map a language version into an import name version +static inline ImportNameVersion &operator++(ImportNameVersion &value) { + assert(value != ImportNameVersion::LAST_VERSION); + value = static_cast(static_cast(value) + 1); + return value; +} + +static inline ImportNameVersion &operator--(ImportNameVersion &value) { + assert(value != ImportNameVersion::Raw); + value = static_cast(static_cast(value) - 1); + return value; +} + +/// Map a language version into an import name version. ImportNameVersion nameVersionFromOptions(const LangOptions &langOpts); +/// Map an import name version into a language version. +unsigned majorVersionNumberForNameVersion(ImportNameVersion version); + /// Describes a name that was imported from Clang. class ImportedName { friend class NameImporter; diff --git a/lib/ClangImporter/SwiftLookupTable.cpp b/lib/ClangImporter/SwiftLookupTable.cpp index 34fd61bc0d724..5b4f30fd6ca58 100644 --- a/lib/ClangImporter/SwiftLookupTable.cpp +++ b/lib/ClangImporter/SwiftLookupTable.cpp @@ -1541,9 +1541,8 @@ void importer::addEntryToLookupTable(SwiftLookupTable &table, } // If we have a name to import as, add this entry to the table. - // FIXME: It doesn't actually matter which version we use here, but it should - // probably follow the ASTContext anyway. - ImportNameVersion currentVersion = ImportNameVersion::Swift3; + ImportNameVersion currentVersion = + nameVersionFromOptions(nameImporter.getLangOpts()); if (auto importedName = nameImporter.importName(named, currentVersion)) { SmallPtrSet distinctNames; distinctNames.insert(importedName.getDeclName()); diff --git a/test/APINotes/versioned.swift b/test/APINotes/versioned.swift index 98d8b85e67b31..85dd3e2b4ef70 100644 --- a/test/APINotes/versioned.swift +++ b/test/APINotes/versioned.swift @@ -9,3 +9,28 @@ // CHECK-SWIFT-4: func accept(_ ptr: UnsafeMutablePointer) // CHECK-SWIFT-3: func acceptPointer(_ ptr: UnsafeMutablePointer?) + +// RUN: not %target-swift-frontend -typecheck -F %S/Inputs/custom-frameworks -swift-version 4 %s 2>&1 | %FileCheck -check-prefix=CHECK-DIAGS -check-prefix=CHECK-DIAGS-4 %s +// RUN: not %target-swift-frontend -typecheck -F %S/Inputs/custom-frameworks -swift-version 3 %s 2>&1 | %FileCheck -check-prefix=CHECK-DIAGS -check-prefix=CHECK-DIAGS-3 %s + +import APINotesFrameworkTest + +func testRenamedTopLevel() { + var value = 0.0 + + // CHECK-DIAGS-4-NOT: versioned.swift:[[@LINE+1]] + accept(&value) + // CHECK-DIAGS-3: versioned.swift:[[@LINE-1]]:3: error: 'accept' has been renamed to 'acceptPointer(_:)' + // CHECK-DIAGS-3: note: 'accept' was introduced in Swift 4 + + // CHECK-DIAGS-4-NOT: versioned.swift:[[@LINE+1]] + acceptPointer(&value) + // CHECK-DIAGS-4: versioned.swift:[[@LINE-1]]:3: error: 'acceptPointer' has been renamed to 'accept(_:)' + // CHECK-DIAGS-4: note: 'acceptPointer' was obsoleted in Swift 4 + + acceptDoublePointer(&value) + // CHECK-DIAGS: versioned.swift:[[@LINE-1]]:3: error: 'acceptDoublePointer' has been renamed to + // CHECK-DIAGS-4: 'accept(_:)' + // CHECK-DIAGS-3: 'acceptPointer(_:)' + // CHECK-DIAGS: note: 'acceptDoublePointer' was obsoleted in Swift 3 +} diff --git a/test/ClangImporter/Inputs/SwiftPrivateAttr.txt b/test/ClangImporter/Inputs/SwiftPrivateAttr.txt index b1e788e0f5d85..ae6f5db0d9354 100644 --- a/test/ClangImporter/Inputs/SwiftPrivateAttr.txt +++ b/test/ClangImporter/Inputs/SwiftPrivateAttr.txt @@ -9,8 +9,14 @@ class Foo : NSObject, __PrivProto { func __oneArg(_ arg: Int32) func __twoArgs(_ arg: Int32, other arg2: Int32) class func __withNoArgs() -> Self! + @available(*, unavailable, renamed: "init(__oneArg:)", message: "Not available in Swift") + class func __fooWithOneArg(_ arg: Int32) -> Self! convenience init!(__oneArg arg: Int32) + @available(*, unavailable, renamed: "init(__twoArgs:other:)", message: "Not available in Swift") + class func __fooWithTwoArgs(_ arg: Int32, other arg2: Int32) -> Self! convenience init!(__twoArgs arg: Int32, other arg2: Int32) + @available(*, unavailable, renamed: "init(__:)", message: "Not available in Swift") + class func __foo(_ arg: Int32) -> Self! convenience init!(__ arg: Int32) func objectForKeyedSubscript(_ index: Any!) -> Any! func __setObject(_ object: Any!, forKeyedSubscript index: Any!) diff --git a/test/ClangImporter/attr-swift_private.swift b/test/ClangImporter/attr-swift_private.swift index 0bd1079d588a2..b914821b189b1 100644 --- a/test/ClangImporter/attr-swift_private.swift +++ b/test/ClangImporter/attr-swift_private.swift @@ -135,8 +135,8 @@ _ = 1 as __PrivInt #if !IRGEN func testRawNames() { - let _ = Foo.__fooWithOneArg(0) // expected-error {{'__fooWithOneArg' is unavailable: use object construction 'Foo(__oneArg:)'}} - let _ = Foo.__foo // expected-error{{'__foo' is unavailable: use object construction 'Foo(__:)'}} + let _ = Foo.__fooWithOneArg(0) // expected-error {{'__fooWithOneArg' has been replaced by 'init(__oneArg:)'}} + let _ = Foo.__foo // expected-error{{'__foo' has been replaced by 'init(__:)'}} } #endif diff --git a/test/ClangImporter/objc_factory_method.swift b/test/ClangImporter/objc_factory_method.swift index 9b7a64da3ef9a..b26ff08212548 100644 --- a/test/ClangImporter/objc_factory_method.swift +++ b/test/ClangImporter/objc_factory_method.swift @@ -84,7 +84,7 @@ func testNonInstanceTypeFactoryMethod(_ s: String) { } func testUseOfFactoryMethod(_ queen: Bee) { - _ = Hive.hiveWithQueen(queen) // expected-error{{'hiveWithQueen' is unavailable: use object construction 'Hive(queen:)'}} + _ = Hive.hiveWithQueen(queen) // expected-error{{'hiveWithQueen' has been replaced by 'init(queen:)'}} {{11-25=}} {{26-26=queen: }} } func testNonsplittableFactoryMethod() { @@ -97,18 +97,18 @@ func testFactoryMethodBlacklist() { func test17261609() { _ = NSDecimalNumber(mantissa:1, exponent:1, isNegative:true) - _ = NSDecimalNumber.decimalNumberWithMantissa(1, exponent:1, isNegative:true) // expected-error{{'decimalNumberWithMantissa(_:exponent:isNegative:)' is unavailable: use object construction 'NSDecimalNumber(mantissa:exponent:isNegative:)'}} + _ = NSDecimalNumber.decimalNumberWithMantissa(1, exponent:1, isNegative:true) // expected-error{{'decimalNumberWithMantissa(_:exponent:isNegative:)' has been replaced by 'init(mantissa:exponent:isNegative:)'}} {{22-48=}} {{49-49=mantissa: }} } func testURL() { let url = NSURL(string: "http://www.llvm.org")! - _ = NSURL.URLWithString("http://www.llvm.org") // expected-error{{'URLWithString' is unavailable: use object construction 'NSURL(string:)'}} + _ = NSURL.URLWithString("http://www.llvm.org") // expected-error{{'URLWithString' has been replaced by 'init(string:)'}} {{12-26=}} {{27-27=string: }} NSURLRequest(string: "http://www.llvm.org") // expected-warning{{unused}} NSURLRequest(url: url as URL) // expected-warning{{unused}} - _ = NSURLRequest.requestWithString("http://www.llvm.org") // expected-error{{'requestWithString' is unavailable: use object construction 'NSURLRequest(string:)'}} - _ = NSURLRequest.URLRequestWithURL(url as URL) // expected-error{{'URLRequestWithURL' is unavailable: use object construction 'NSURLRequest(url:)'}} + _ = NSURLRequest.requestWithString("http://www.llvm.org") // expected-error{{'requestWithString' has been replaced by 'init(string:)'}} + _ = NSURLRequest.URLRequestWithURL(url as URL) // expected-error{{'URLRequestWithURL' has been replaced by 'init(url:)'}} } // FIXME: Remove -verify-ignore-unknown. diff --git a/test/ClangImporter/objc_implicit_with.swift b/test/ClangImporter/objc_implicit_with.swift index 7d266897f83f5..1aeebb4a89752 100644 --- a/test/ClangImporter/objc_implicit_with.swift +++ b/test/ClangImporter/objc_implicit_with.swift @@ -50,7 +50,7 @@ func testNonInstanceTypeFactoryMethod(_ s: String) { } func testUseOfFactoryMethod(_ queen: Bee) { - _ = Hive.hiveWithQueen(queen) // expected-error{{'hiveWithQueen' is unavailable: use object construction 'Hive(queen:)'}} + _ = Hive.hiveWithQueen(queen) // expected-error{{'hiveWithQueen' has been replaced by 'init(queen:)'}} {{11-25=}} {{26-26=queen: }} } func testNonsplittableFactoryMethod() { diff --git a/test/IDE/print_clang_swift_name.swift b/test/IDE/print_clang_swift_name.swift index 8f798b2dd0523..9048e5db4c907 100644 --- a/test/IDE/print_clang_swift_name.swift +++ b/test/IDE/print_clang_swift_name.swift @@ -11,15 +11,28 @@ class Test : NSObject { + // "Factory methods" that we'd rather have as initializers. + @available(*, unavailable, renamed: "init()", message: "Not available in Swift") + class func a() -> Self @available(*, unavailable, message: "superseded by import of -[NSObject init]") convenience init() + @available(*, unavailable, renamed: "init(dummyParam:)", message: "Not available in Swift") + class func b() -> Self convenience init(dummyParam: ()) + @available(*, unavailable, renamed: "init(cc:)", message: "Not available in Swift") + class func c(_ x: Any) -> Self convenience init(cc x: Any) + @available(*, unavailable, renamed: "init(_:)", message: "Not available in Swift") + class func d(_ x: Any) -> Self convenience init(_ x: Any) + @available(*, unavailable, renamed: "init(aa:_:cc:)", message: "Not available in Swift") + class func e(_ a: Any, e b: Any, e c: Any) -> Self convenience init(aa a: Any, _ b: Any, cc c: Any) + @available(*, unavailable, renamed: "init(fixedType:)", message: "Not available in Swift") + class func f() -> Test /*not inherited*/ init(fixedType: ()) // Would-be initializers. @@ -31,14 +44,28 @@ class Test : NSObject { } class TestError : NSObject { - + // Factory methods with NSError. + @available(*, unavailable, renamed: "init(error:)", message: "Not available in Swift") + class func err1() throws -> Self convenience init(error: ()) throws + @available(*, unavailable, renamed: "init(aa:error:)", message: "Not available in Swift") + class func err2(_ x: Any?) throws -> Self convenience init(aa x: Any?, error: ()) throws + @available(*, unavailable, renamed: "init(aa:error:block:)", message: "Not available in Swift") + class func err3(_ x: Any?, callback block: @escaping () -> Void) throws -> Self convenience init(aa x: Any?, error: (), block: @escaping () -> Void) throws + @available(*, unavailable, renamed: "init(error:block:)", message: "Not available in Swift") + class func err4(callback block: @escaping () -> Void) throws -> Self convenience init(error: (), block: @escaping () -> Void) throws + @available(*, unavailable, renamed: "init(aa:)", message: "Not available in Swift") + class func err5(_ x: Any?) throws -> Self convenience init(aa x: Any?) throws + @available(*, unavailable, renamed: "init(aa:block:)", message: "Not available in Swift") + class func err6(_ x: Any?, callback block: @escaping () -> Void) throws -> Self convenience init(aa x: Any?, block: @escaping () -> Void) throws + @available(*, unavailable, renamed: "init(block:)", message: "Not available in Swift") + class func err7(callback block: @escaping () -> Void) throws -> Self convenience init(block: @escaping () -> Void) throws // Would-be initializers.