From a5da848064c657e6908bd48415f9314301217bb3 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Thu, 22 Dec 2016 17:22:18 -0800 Subject: [PATCH 1/9] [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/9] [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/9] [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/9] [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. From 7196c76eac16bb4c6faabd1d7e4fda6a39091c40 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Jan 2017 16:11:07 -0800 Subject: [PATCH 5/9] [ClangImporter] Use the current ImportNameVersion for param names. This shouldn't have any effect since we've never customized how parameter names are imported (body names, not argument labels), but it removes special cases. --- lib/ClangImporter/ImportType.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 841c171a98bb7..36e4dfa5d9196 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -1626,7 +1626,7 @@ ParameterList *ClangImporter::Implementation::importFunctionParameterList( } // Figure out the name for this parameter. - Identifier bodyName = importFullName(param, ImportNameVersion::Swift3) + Identifier bodyName = importFullName(param, CurrentVersion) .getDeclName() .getBaseName(); @@ -2037,7 +2037,7 @@ Type ClangImporter::Implementation::importMethodType( } // Figure out the name for this parameter. - Identifier bodyName = importFullName(param, ImportNameVersion::Swift3) + Identifier bodyName = importFullName(param, CurrentVersion) .getDeclName() .getBaseName(); @@ -2194,7 +2194,7 @@ Type ClangImporter::Implementation::importAccessorMethodType( } else { const clang::ParmVarDecl *param = clangDecl->parameters().front(); - ImportedName fullBodyName = importFullName(param,ImportNameVersion::Swift3); + ImportedName fullBodyName = importFullName(param, CurrentVersion); Identifier bodyName = fullBodyName.getDeclName().getBaseName(); SourceLoc nameLoc = importSourceLoc(param->getLocation()); Identifier argLabel = functionName.getDeclName().getArgumentNames().front(); From c297e643798321611ef7868c83884faf2f87ced1 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Jan 2017 16:39:45 -0800 Subject: [PATCH 6/9] [ClangImporter] Fix marking of protocols with missing requirements. This doesn't actually have any effect yet, but if we start importing both Swift 3 and Swift 4 versions of protocol requirements and the non-active one is unavailable, we might mistakenly mark the protocol un-implementable even when the requirements that are needed are all there. (Hopefully we would never make a protocol /less/ available in a newer release, of course.) The test case is designed to catch that. --- lib/ClangImporter/ImportDecl.cpp | 2 +- .../Headers/APINotesFrameworkTest.apinotes | 6 ++++++ .../Headers/APINotesFrameworkTest.h | 2 ++ .../Headers/Protocols.h | 9 +++++++++ test/APINotes/versioned.swift | 10 ++++++++-- 5 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Protocols.h diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index bcacb55497a90..75b30eda3e1e3 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -6758,7 +6758,7 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl, Result = converter.Visit(ClangDecl); HadForwardDeclaration = converter.hadForwardDeclaration(); } - if (!Result && version > ImportNameVersion::Swift2) { + if (!Result && version == CurrentVersion) { // If we couldn't import this Objective-C entity, determine // whether it was a required member of a protocol. bool hasMissingRequiredMember = false; diff --git a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes index 747e0bb9ed663..c0a811f0cec32 100644 --- a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes +++ b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes @@ -79,6 +79,12 @@ SwiftVersions: - Name: accessorsOnlyRenamedRetypedClass PropertyKind: Class SwiftImportAsAccessors: true + Protocols: + - Name: ProtoWithVersionedUnavailableMember + Methods: + - Selector: requirement + MethodKind: Instance + ResultType: 'ForwardClass * _Nullable' Functions: - Name: acceptDoublePointer SwiftName: 'acceptPointer(_:)' diff --git a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h index b0a6023e0c4b9..d67caf145abd8 100644 --- a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h +++ b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h @@ -14,4 +14,6 @@ __attribute__((objc_root_class)) @end #import +#import + #endif diff --git a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Protocols.h b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Protocols.h new file mode 100644 index 0000000000000..f68392d942280 --- /dev/null +++ b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Protocols.h @@ -0,0 +1,9 @@ +#pragma clang assume_nonnull begin + +@class ForwardClass; // used by API notes + +@protocol ProtoWithVersionedUnavailableMember +- (nullable id)requirement; +@end + +#pragma clang assume_nonnull end diff --git a/test/APINotes/versioned.swift b/test/APINotes/versioned.swift index 85dd3e2b4ef70..947182d175bea 100644 --- a/test/APINotes/versioned.swift +++ b/test/APINotes/versioned.swift @@ -15,15 +15,21 @@ import APINotesFrameworkTest +// CHECK-DIAGS-4-NOT: versioned.swift:[[@LINE-1]]: +class ProtoWithVersionedUnavailableMemberImpl: ProtoWithVersionedUnavailableMember { + // CHECK-DIAGS-3: versioned.swift:[[@LINE-1]]:7: error: type 'ProtoWithVersionedUnavailableMemberImpl' cannot conform to protocol 'ProtoWithVersionedUnavailableMember' because it has requirements that cannot be satisfied + func requirement() -> Any? { return nil } +} + func testRenamedTopLevel() { var value = 0.0 - // CHECK-DIAGS-4-NOT: versioned.swift:[[@LINE+1]] + // 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]] + // 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 From 83fd92b1edbc12bf7f9ebd4ffdc0a8469806c00a Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Jan 2017 16:54:40 -0800 Subject: [PATCH 7/9] [ClangImporter] Use the correct name for replacement decls. When a C declaration is marked unavailable with a replacement, we look for the replacement to see how it would be imported into Swift. Make sure we do that with respect to the active language version. --- lib/ClangImporter/ImportDecl.cpp | 2 +- .../Headers/APINotesFrameworkTest.h | 2 ++ test/APINotes/versioned.swift | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 75b30eda3e1e3..0bf220675eaeb 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -6516,7 +6516,7 @@ getSwiftNameFromClangName(StringRef replacement) { if (!clangDecl) return ""; - auto importedName = importFullName(clangDecl, ImportNameVersion::Swift3); + auto importedName = importFullName(clangDecl, CurrentVersion); if (!importedName) return ""; diff --git a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h index d67caf145abd8..1288ed9571915 100644 --- a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h +++ b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h @@ -2,6 +2,8 @@ void jumpToLocation(double x, double y, double z); void acceptDoublePointer(double* _Nonnull ptr) __attribute__((swift_name("accept(_:)"))); +void oldAcceptDoublePointer(double* _Nonnull ptr) __attribute__((availability(swift, unavailable, replacement="acceptDoublePointer"))); + #ifdef __OBJC__ __attribute__((objc_root_class)) diff --git a/test/APINotes/versioned.swift b/test/APINotes/versioned.swift index 947182d175bea..fb19eec5603f3 100644 --- a/test/APINotes/versioned.swift +++ b/test/APINotes/versioned.swift @@ -39,4 +39,10 @@ func testRenamedTopLevel() { // CHECK-DIAGS-4: 'accept(_:)' // CHECK-DIAGS-3: 'acceptPointer(_:)' // CHECK-DIAGS: note: 'acceptDoublePointer' was obsoleted in Swift 3 + + oldAcceptDoublePointer(&value) + // CHECK-DIAGS: versioned.swift:[[@LINE-1]]:3: error: 'oldAcceptDoublePointer' has been renamed to + // CHECK-DIAGS-4: 'accept(_:)' + // CHECK-DIAGS-3: 'acceptPointer(_:)' + // CHECK-DIAGS: note: 'oldAcceptDoublePointer' has been explicitly marked unavailable here } From 9849ab94e5bdbac3d4cfa55b6388d60f6175f639 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Jan 2017 17:43:07 -0800 Subject: [PATCH 8/9] [ClangImporter] Give compatibility typealiases the correct version. Unlike values, we can't import multiple copies of types under different names and get good results. Instead, we make a typealias that points back to the original type. Make sure this typealias is flagged with whatever version is appropriate, rather than always using "Swift 2". --- lib/ClangImporter/ImportDecl.cpp | 46 ++++++++++--------- .../Headers/APINotesFrameworkTest.apinotes | 3 ++ .../Headers/APINotesFrameworkTest.h | 5 +- .../Headers/Types.h | 7 +++ test/APINotes/versioned.swift | 26 +++++++++-- 5 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Types.h diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 0bf220675eaeb..85e3b7216b5ea 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2041,10 +2041,11 @@ namespace { decl->setImplicit(); } - /// Create a typealias for the Swift 2 name of a Clang type declaration. - Decl *importSwift2TypeAlias(const clang::NamedDecl *decl, - ImportedName swift2Name, - ImportedName correctSwiftName); + /// Create a typealias for the name of a Clang type declaration in an + /// alternate version of Swift. + Decl *importCompatibilityTypeAlias(const clang::NamedDecl *decl, + ImportedName compatibilityName, + ImportedName correctSwiftName); /// Create a swift_newtype struct corresponding to a typedef. Returns /// nullptr if unable. @@ -2062,7 +2063,8 @@ namespace { // If we've been asked to produce a Swift 2 stub, handle it via a // typealias. if (correctSwiftName) - return importSwift2TypeAlias(Decl, importedName, *correctSwiftName); + return importCompatibilityTypeAlias(Decl, importedName, + *correctSwiftName); Type SwiftType; if (Decl->getDeclContext()->getRedeclContext()->isTranslationUnit()) { @@ -2277,7 +2279,8 @@ namespace { // If we've been asked to produce a Swift 2 stub, handle it via a // typealias. if (correctSwiftName) - return importSwift2TypeAlias(decl, importedName, *correctSwiftName); + return importCompatibilityTypeAlias(decl, importedName, + *correctSwiftName); auto dc = Impl.importDeclContextOf(decl, importedName.getEffectiveContext()); @@ -2693,7 +2696,8 @@ namespace { // If we've been asked to produce a Swift 2 stub, handle it via a // typealias. if (correctSwiftName) - return importSwift2TypeAlias(decl, importedName, *correctSwiftName); + return importCompatibilityTypeAlias(decl, importedName, + *correctSwiftName); auto dc = Impl.importDeclContextOf(decl, importedName.getEffectiveContext()); @@ -3957,7 +3961,8 @@ namespace { // If we've been asked to produce a Swift 2 stub, handle it via a // typealias. if (correctSwiftName) - return importSwift2TypeAlias(decl, importedName, *correctSwiftName); + return importCompatibilityTypeAlias(decl, importedName, + *correctSwiftName); Identifier name = importedName.getDeclName().getBaseName(); @@ -4092,7 +4097,8 @@ namespace { // If we've been asked to produce a Swift 2 stub, handle it via a // typealias. if (correctSwiftName) - return importSwift2TypeAlias(decl, importedName, *correctSwiftName); + return importCompatibilityTypeAlias(decl, importedName, + *correctSwiftName); auto name = importedName.getDeclName().getBaseName(); @@ -4630,9 +4636,10 @@ SwiftDeclConverter::importCFClassType(const clang::TypedefNameDecl *decl, return theClass; } -Decl *SwiftDeclConverter::importSwift2TypeAlias(const clang::NamedDecl *decl, - ImportedName swift2Name, - ImportedName correctSwiftName) { +Decl *SwiftDeclConverter::importCompatibilityTypeAlias( + const clang::NamedDecl *decl, + ImportedName compatibilityName, + ImportedName correctSwiftName) { // Import the referenced declaration. If it doesn't come in as a type, // we don't care. auto importedDecl = Impl.importDecl(decl, getActiveSwiftVersion()); @@ -4665,19 +4672,14 @@ Decl *SwiftDeclConverter::importSwift2TypeAlias(const clang::NamedDecl *decl, // Create the type alias. auto alias = Impl.createDeclWithClangNode( - decl, - Accessibility::Public, Impl.importSourceLoc(decl->getLocStart()), - SourceLoc(), swift2Name.getDeclName().getBaseName(), - Impl.importSourceLoc(decl->getLocation()), - genericParams, dc); + decl, Accessibility::Public, Impl.importSourceLoc(decl->getLocStart()), + SourceLoc(), compatibilityName.getDeclName().getBaseName(), + Impl.importSourceLoc(decl->getLocation()), genericParams, dc); alias->setUnderlyingType(underlyingType); alias->setGenericEnvironment(genericEnv); - // Record that this is the Swift 2 version of this declaration. - Impl.ImportedDecls[{decl->getCanonicalDecl(), ImportNameVersion::Swift2}] = - alias; - - // Mark it as the Swift 2 variant. + // Record that this is the official version of this declaration. + Impl.ImportedDecls[{decl->getCanonicalDecl(), getVersion()}] = alias; markAsVariant(alias, correctSwiftName); return alias; } diff --git a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes index c0a811f0cec32..3cff8ce99f7bd 100644 --- a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes +++ b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.apinotes @@ -89,3 +89,6 @@ SwiftVersions: - Name: acceptDoublePointer SwiftName: 'acceptPointer(_:)' Nullability: [ O ] + Tags: + - Name: SomeCStruct + SwiftName: ImportantCStruct diff --git a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h index 1288ed9571915..92858ee3a8735 100644 --- a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h +++ b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/APINotesFrameworkTest.h @@ -4,6 +4,9 @@ void acceptDoublePointer(double* _Nonnull ptr) __attribute__((swift_name("accept void oldAcceptDoublePointer(double* _Nonnull ptr) __attribute__((availability(swift, unavailable, replacement="acceptDoublePointer"))); + +#import + #ifdef __OBJC__ __attribute__((objc_root_class)) @@ -18,4 +21,4 @@ __attribute__((objc_root_class)) #import #import -#endif +#endif // __OBJC__ diff --git a/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Types.h b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Types.h new file mode 100644 index 0000000000000..c44439fd74fd3 --- /dev/null +++ b/test/APINotes/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers/Types.h @@ -0,0 +1,7 @@ +#pragma clang assume_nonnull begin + +struct __attribute__((swift_name("VeryImportantCStruct"))) SomeCStruct { + int field; +}; + +#pragma clang assume_nonnull end diff --git a/test/APINotes/versioned.swift b/test/APINotes/versioned.swift index fb19eec5603f3..c5f1712a2d1e6 100644 --- a/test/APINotes/versioned.swift +++ b/test/APINotes/versioned.swift @@ -29,20 +29,36 @@ func testRenamedTopLevel() { // 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]]: + // CHECK-DIAGS-3-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-4-SAME: 'accept(_:)' + // CHECK-DIAGS-3-SAME: 'acceptPointer(_:)' // CHECK-DIAGS: note: 'acceptDoublePointer' was obsoleted in Swift 3 oldAcceptDoublePointer(&value) // CHECK-DIAGS: versioned.swift:[[@LINE-1]]:3: error: 'oldAcceptDoublePointer' has been renamed to - // CHECK-DIAGS-4: 'accept(_:)' - // CHECK-DIAGS-3: 'acceptPointer(_:)' + // CHECK-DIAGS-4-SAME: 'accept(_:)' + // CHECK-DIAGS-3-SAME: 'acceptPointer(_:)' // CHECK-DIAGS: note: 'oldAcceptDoublePointer' has been explicitly marked unavailable here + + _ = SomeCStruct() + // CHECK-DIAGS: versioned.swift:[[@LINE-1]]:7: error: 'SomeCStruct' has been renamed to + // CHECK-DIAGS-4-SAME: 'VeryImportantCStruct' + // CHECK-DIAGS-3-SAME: 'ImportantCStruct' + // CHECK-DIAGS: note: 'SomeCStruct' was obsoleted in Swift 3 + + // CHECK-DIAGS-3-NOT: versioned.swift:[[@LINE+1]]: + _ = ImportantCStruct() + // CHECK-DIAGS-4: versioned.swift:[[@LINE-1]]:7: error: 'ImportantCStruct' has been renamed to 'VeryImportantCStruct' + // CHECK-DIAGS-4: note: 'ImportantCStruct' was obsoleted in Swift 4 + + // CHECK-DIAGS-4-NOT: versioned.swift:[[@LINE+1]]: + _ = VeryImportantCStruct() + // CHECK-DIAGS-3: versioned.swift:[[@LINE-1]]:7: error: 'VeryImportantCStruct' has been renamed to 'ImportantCStruct' + // CHECK-DIAGS-3: note: 'VeryImportantCStruct' was introduced in Swift 4 } From 7560dfc0ab9ec41001b63947e69a95024e6518f2 Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Wed, 11 Jan 2017 17:56:15 -0800 Subject: [PATCH 9/9] [ClangImporter] Use the current version to vend enum constant names. The compiler itself no longer uses this API but the debugger does, in order to pretty-print option sets. The normal way to test this would be to add an LLDB-side test that uses a framework with versioned API notes. Unfortunately I can't think of a straightforward way to test it Swift-side. --- include/swift/ClangImporter/ClangImporter.h | 3 +-- lib/ClangImporter/ImportDecl.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/swift/ClangImporter/ClangImporter.h b/include/swift/ClangImporter/ClangImporter.h index 242f0750d432a..c0881c9c9294f 100644 --- a/include/swift/ClangImporter/ClangImporter.h +++ b/include/swift/ClangImporter/ClangImporter.h @@ -260,8 +260,7 @@ class ClangImporter final : public ClangModuleLoader { /// The return value may be an empty identifier, in which case the enum would /// not be imported. /// - /// This is mostly an implementation detail of the importer, but is also - /// used by the debugger. + /// This is not used by the importer itself, but is used by the debugger. Identifier getEnumConstantName(const clang::EnumConstantDecl *enumConstant); /// Writes the mangled name of \p clangDecl to \p os. diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 85e3b7216b5ea..01fa9e0ab4a61 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -7570,7 +7570,7 @@ ClangImporter::Implementation::getSpecialTypedefKind(clang::TypedefNameDecl *dec Identifier ClangImporter::getEnumConstantName(const clang::EnumConstantDecl *enumConstant){ - return Impl.importFullName(enumConstant, ImportNameVersion::Swift3) + return Impl.importFullName(enumConstant, Impl.CurrentVersion) .getDeclName() .getBaseName(); }