diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index d6a9e36ecbf8b..02e04a430d45f 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -1113,6 +1113,11 @@ namespace swift { /// invocations directly from clang cc1 args. bool ClangImporterDirectCC1Scan = false; + /// Whether the importer should expect all APINotes to be wrapped + /// in versioned attributes, where the importer must select the appropriate + /// ones to apply. + bool LoadVersionIndependentAPINotes = false; + /// Return a hash code of any components from these options that should /// contribute to a Swift Bridging PCH hash. llvm::hash_code getPCHHashComponents() const { diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 7d1cc7aa8dc53..067989bba01a8 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -673,6 +673,9 @@ def emit_pch : Flag<["-"], "emit-pch">, def pch_disable_validation : Flag<["-"], "pch-disable-validation">, HelpText<"Disable validating the persistent PCH">; +def version_independent_apinotes : Flag<["-"], "version-independent-apinotes">, + HelpText<"Input clang modules carry all versioned APINotes">; + def disable_sil_ownership_verifier : Flag<["-"], "disable-sil-ownership-verifier">, HelpText<"Do not verify ownership invariants during SIL Verification ">; diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 629f74e7814f2..3f1a3f42e54e4 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -787,6 +787,10 @@ void importer::getNormalInvocationArguments( invocationArgStrs.push_back("-iapinotes-modules"); invocationArgStrs.push_back(path.str().str()); } + + if (importerOpts.LoadVersionIndependentAPINotes) + invocationArgStrs.insert(invocationArgStrs.end(), + {"-fswift-version-independent-apinotes"}); } static void diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 8d9227cc0f9ab..86491396cdc36 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -69,6 +69,7 @@ #include "clang/AST/Type.h" #include "clang/Basic/Specifiers.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Sema/SemaDiagnostic.h" #include "clang/Lex/Preprocessor.h" #include "clang/Sema/Lookup.h" @@ -173,6 +174,9 @@ bool importer::recordHasReferenceSemantics( !importerImpl->SwiftContext.LangOpts.CForeignReferenceTypes) return false; + // ACTODO: Check versioned attrs in case of + // captureSwiftVersionIndependentAPINotes + // At this point decl might not be fully imported into Swift yet, which // means we might not have asked Clang to generate its implicit members, such // as copy or move constructors. This would cause CxxRecordSemanticsRequest to @@ -9336,6 +9340,67 @@ static bool isUsingMacroName(clang::SourceManager &SM, return content == MacroName; } +static void filterUsableVersionedAttrs(const clang::NamedDecl *clangDecl, + llvm::VersionTuple currentVersion, + std::unordered_set &discardVersionedAttrSet) { + // Scan through Swift-Versioned clang attributes and select which one to apply + // if multiple candidates exist. + SmallVector swiftVersionedAttributes; + for (auto attr : clangDecl->attrs()) + if (auto versionedAttr = dyn_cast(attr)) + swiftVersionedAttributes.push_back(versionedAttr); + + // An attribute version is valid to apply if it is greater than the current version + // or is unversioned + auto applicableVersion = [currentVersion] (clang::VersionTuple attrVersion) -> bool { + return attrVersion.empty() || attrVersion >= currentVersion; + }; + + // We have a better attribute option if there exists another versioned attr + // wrapper for this attribute kind with a valid version that is lower than the + // one of the attribute we are considering + auto haveBetterAttr = [swiftVersionedAttributes, applicableVersion] + (clang::VersionTuple attrVersion, clang::attr::Kind attrKind) -> bool { + for (auto VAI = swiftVersionedAttributes.begin(), + VAE = swiftVersionedAttributes.end(); VAI != VAE; ++VAI) { + auto otherVersionedAttr = *VAI; + auto otherAttrKind = otherVersionedAttr->getAdditionalAttr()->getKind(); + auto otherAttrVersion = otherVersionedAttr->getVersion(); + // Same exact attribute, ignore + if (otherAttrKind == attrKind && otherAttrVersion == attrVersion) + continue; + + // For a matching attribute kind, an un-versioned attribute + // never takes precedence over an exsiting valid versioned one. + if (otherAttrKind == attrKind && !attrVersion.empty() && otherAttrVersion.empty()) + continue; + if (otherAttrKind == attrKind && applicableVersion(otherAttrVersion) && attrVersion.empty()) + return true; + + // For two versioned attributes of the same kind, the one with the lower applicable + // version takes precedence. + if (otherAttrKind == attrKind && + applicableVersion(otherAttrVersion) && + otherAttrVersion < attrVersion) + return true; + } + return false; + }; + + for (auto VAI = swiftVersionedAttributes.begin(), + VAE = swiftVersionedAttributes.end(); VAI != VAE; ++VAI) { + auto versionedAttr = *VAI; + auto attrKind = versionedAttr->getAdditionalAttr()->getKind(); + auto attrVersion = versionedAttr->getVersion(); + if (!applicableVersion(attrVersion)) + discardVersionedAttrSet.insert(versionedAttr); + else if (haveBetterAttr(attrVersion, attrKind)) + discardVersionedAttrSet.insert(versionedAttr); + else + continue; + } +} + void ClangImporter::Implementation::importAttributesFromClangDeclToSynthesizedSwiftDecl(Decl *sourceDecl, Decl* synthesizedDecl) { // sourceDecl->getClangDecl() can be null because some lazily instantiated cases like C++ members that were instantiated from using-shadow-decls have no corresponding Clang decl. @@ -9369,17 +9434,40 @@ void ClangImporter::Implementation::importAttributes( if (auto func = dyn_cast(MappedDecl)) isAsync = func->hasAsync(); + // Determine which versioned attributes are applicable + std::unordered_set discardVersionedAttrSet; + if (SwiftContext.ClangImporterOpts.LoadVersionIndependentAPINotes) + filterUsableVersionedAttrs(ClangDecl, CurrentVersion.asClangVersionTuple(), + discardVersionedAttrSet); + // Scan through Clang attributes and map them onto Swift // equivalents. bool AnyUnavailable = MappedDecl->isUnavailable(); for (clang::NamedDecl::attr_iterator AI = ClangDecl->attr_begin(), AE = ClangDecl->attr_end(); AI != AE; ++AI) { + clang::Attr *consideringAttr = *AI; + if (SwiftContext.ClangImporterOpts.LoadVersionIndependentAPINotes) { + if (auto versionedAttr = dyn_cast(consideringAttr)) { + // "type" and "nullability" attributes are handled earlier since they change + // the decl's type. + auto additionalAttr = versionedAttr->getAdditionalAttr(); + if (isa(additionalAttr) || + isa(additionalAttr)) + continue; + + if (discardVersionedAttrSet.count(versionedAttr)) + continue; + + consideringAttr = additionalAttr; + } + } + // // __attribute__((unavailable)) // // Mapping: @available(*,unavailable) // - if (auto unavailable = dyn_cast(*AI)) { + if (auto unavailable = dyn_cast(consideringAttr)) { auto Message = unavailable->getMessage(); auto attr = AvailableAttr::createUniversallyUnavailable(C, Message); MappedDecl->getAttrs().add(attr); @@ -9392,7 +9480,7 @@ void ClangImporter::Implementation::importAttributes( // // Mapping: @available(*, unavailable) // - if (auto unavailable_annot = dyn_cast(*AI)) + if (auto unavailable_annot = dyn_cast(consideringAttr)) if (unavailable_annot->getAnnotation() == "swift1_unavailable") { auto attr = AvailableAttr::createUnavailableInSwift(C, "", ""); MappedDecl->getAttrs().add(attr); @@ -9405,7 +9493,7 @@ void ClangImporter::Implementation::importAttributes( // // Mapping: @available(*,deprecated) // - if (auto deprecated = dyn_cast(*AI)) { + if (auto deprecated = dyn_cast(consideringAttr)) { auto Message = deprecated->getMessage(); auto attr = AvailableAttr::createUniversallyDeprecated(C, Message, ""); MappedDecl->getAttrs().add(attr); @@ -9414,7 +9502,7 @@ void ClangImporter::Implementation::importAttributes( // __attribute__((availability)) // - if (auto avail = dyn_cast(*AI)) { + if (auto avail = dyn_cast(consideringAttr)) { StringRef Platform = avail->getPlatform()->getName(); // Is this our special "availability(swift, unavailable)" attribute? @@ -9646,6 +9734,62 @@ void ClangImporter::Implementation::importAttributes( } } +static void applyTypeAndNullabilityAPINotes(const clang::NamedDecl *ClangDecl, + clang::Sema &Sema, + const ImportNameVersion CurrentImporterVersion) { + // Determine which versioned attributes are applicable + std::unordered_set discardVersionedAttrSet; + filterUsableVersionedAttrs(ClangDecl, + CurrentImporterVersion.asClangVersionTuple(), + discardVersionedAttrSet); + + // When importing from a module built with version-independent APINotes payload, + // the decl will carry all possible versioned notes, without directly applying any + // of them. For "type" and "nullability" notes, we must apply them first, here, + // since they change the actual type of the decl as seen downstream. + // + // Other kinds of notes will be handled in `importAttributes`. + for (clang::NamedDecl::attr_iterator AI = ClangDecl->attr_begin(), + AE = ClangDecl->attr_end(); AI != AE; ++AI) { + if (auto versionedAttr = dyn_cast(*AI)) { + if (!isa(versionedAttr->getAdditionalAttr()) && + !isa(versionedAttr->getAdditionalAttr())) { + continue; + } + + if (discardVersionedAttrSet.count(versionedAttr)) + continue; + + // Apply Type APINotes + if (auto typeRenameAttr = dyn_cast(versionedAttr->getAdditionalAttr())) { + Sema.ApplyAPINotesType(const_cast(ClangDecl), + typeRenameAttr->getTypeString()); + } + + // Apply Nullability APINotes + if (auto nullabilityAttr = dyn_cast(versionedAttr->getAdditionalAttr())) { + clang::NullabilityKind nullability; + switch (nullabilityAttr->getKind()) { + case clang::SwiftNullabilityAttr::Kind::NonNull: + nullability = clang::NullabilityKind::NonNull; + break; + case clang::SwiftNullabilityAttr::Kind::Nullable: + nullability = clang::NullabilityKind::Nullable; + break; + case clang::SwiftNullabilityAttr::Kind::Unspecified: + nullability = clang::NullabilityKind::Unspecified; + break; + case clang::SwiftNullabilityAttr::Kind::NullableResult: + nullability = clang::NullabilityKind::NullableResult; + break; + } + + Sema.ApplyNullability(const_cast(ClangDecl), nullability); + } + } + } +} + Decl * ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl, ImportNameVersion version, @@ -9657,6 +9801,13 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl, if (ClangDecl->isInvalidDecl()) return nullptr; + // If '-version-independent-apinotes' is used, then "type" and "nullability" + // notes are applied by the client (Importer) instead of the producer of the + // Clang module we are consuming. Do so now, early, since these notes + // affect the decl's type. + if (SwiftContext.ClangImporterOpts.LoadVersionIndependentAPINotes) + applyTypeAndNullabilityAPINotes(ClangDecl, getClangSema(), CurrentVersion); + bool SkippedOverTypedef = false; Decl *Result = nullptr; if (auto *UnderlyingDecl = canSkipOverTypedef(*this, ClangDecl, diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 8726b222ee576..9aef91e22a4ce 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -2169,6 +2169,8 @@ static bool ParseClangImporterArgs(ClangImporterOptions &Opts, ArgList &Args, Opts.PCHDisableValidation |= Args.hasArg(OPT_pch_disable_validation); } + Opts.LoadVersionIndependentAPINotes |= Args.hasArg(OPT_version_independent_apinotes); + if (FrontendOpts.DisableImplicitModules) Opts.DisableImplicitClangModules = true; diff --git a/lib/Frontend/ModuleInterfaceLoader.cpp b/lib/Frontend/ModuleInterfaceLoader.cpp index ad9db89855a90..cb83ea9755a63 100644 --- a/lib/Frontend/ModuleInterfaceLoader.cpp +++ b/lib/Frontend/ModuleInterfaceLoader.cpp @@ -2018,6 +2018,12 @@ InterfaceSubContextDelegateImpl::InterfaceSubContextDelegateImpl( GenericArgs.push_back(blocklist); } + // Inherit APINotes processing method + if (clangImporterOpts.LoadVersionIndependentAPINotes) { + GenericArgs.push_back("-version-independent-apinotes"); + genericSubInvocation.getClangImporterOptions().LoadVersionIndependentAPINotes = true; + } + // Inherit the C++ interoperability mode. if (langOpts.EnableCXXInterop) { // Modelled after a reverse of validateCxxInteropCompatibilityMode diff --git a/test/APINotes/basic_version_independent.swift b/test/APINotes/basic_version_independent.swift new file mode 100644 index 0000000000000..893372e55d7fa --- /dev/null +++ b/test/APINotes/basic_version_independent.swift @@ -0,0 +1,48 @@ +// RUN: %empty-directory(%t) +// RUN: %empty-directory(%t/InputClangModules) +// RUN: split-file %s %t + +// - Fixup the input module file map +// RUN: sed -e "s|INPUTSDIR|%/t/InputClangModules|g" %t/map.json.template > %t/map.json.template1 +// RUN: sed -e "s|STDLIBMOD|%/stdlib_module|g" %t/map.json.template1 > %t/map.json.template2 +// RUN: sed -e "s|ONONEMOD|%/ononesupport_module|g" %t/map.json.template2 > %t/map.json.template3 +// RUN: sed -e "s|CUSTOMFRAMEWORKS|%S/Inputs/custom-frameworks|g" %t/map.json.template3 > %t/map.json.template4 +// RUN: sed -e "s|SWIFTLIBDIR|%swift-lib-dir|g" %t/map.json.template4 > %t/map.json + +// - Set up explicit dependencies for Client +// RUN: %target-swift-emit-pcm -module-name SwiftShims %swift-lib-dir/swift/shims/module.modulemap -o %t/InputClangModules/SwiftShims.pcm -Xcc -fswift-version-independent-apinotes + +// - Build the APINotesFrameworkTest module using verison-independent APINotes +// RUN: %target-swift-emit-pcm -module-name APINotesFrameworkTest %S/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Modules/module.modulemap -o %t/InputClangModules/APINotesFrameworkTest.pcm -Xcc -I -Xcc %S/Inputs/custom-frameworks/APINotesFrameworkTest.framework/Headers -Xcc -F -Xcc %S/Inputs/custom-frameworks -Xcc -fswift-version-independent-apinotes + +// - Build the client +// RUN: %target-swift-frontend -typecheck -verify %t/client.swift -explicit-swift-module-map-file %t/map.json -disable-implicit-swift-modules -disable-implicit-concurrency-module-import -disable-implicit-string-processing-module-import -version-independent-apinotes + +//--- map.json.template +[ + { + "moduleName": "Swift", + "modulePath": "STDLIBMOD", + "isFramework": false + }, + { + "moduleName": "SwiftOnoneSupport", + "modulePath": "ONONEMOD", + "isFramework": false + }, + { + "moduleName": "SwiftShims", + "isFramework": false, + "clangModuleMapPath": "SWIFTLIBDIR/swift/shims/module.modulemap", + "clangModulePath": "INPUTSDIR/SwiftShims.pcm" + }, + { + "moduleName": "APINotesFrameworkTest", + "isFramework": true, + "clangModuleMapPath": "CUSTOMFRAMEWORKS/APINotesFrameworkTest.framework/Modules/module.modulemap", + "clangModulePath": "INPUTSDIR/APINotesFrameworkTest.pcm" + } +] + +//--- client.swift +import APINotesFrameworkTest diff --git a/test/APINotes/version-independent.swift b/test/APINotes/version-independent.swift new file mode 100644 index 0000000000000..0eb1bb28a04f7 --- /dev/null +++ b/test/APINotes/version-independent.swift @@ -0,0 +1,123 @@ +// RUN: %empty-directory(%t) +// RUN: %empty-directory(%t/inputs) +// RUN: %empty-directory(%t/ModuleCache) +// RUN: %empty-directory(%t/Swift) +// RUN: %empty-directory(%t/CFrameworks) +// RUN: %empty-directory(%t/CFrameworks/Bar.framework) +// RUN: %empty-directory(%t/CFrameworks/Bar.framework/Headers) +// RUN: %empty-directory(%t/CFrameworks/Bar.framework/Modules) +// RUN: split-file %s %t + +// 0. Fixup the input module file map +// RUN: sed -e "s|INPUTSDIR|%/t/inputs|g" %t/input_map.json.template > %t/input_map.json.template1 +// RUN: sed -e "s|STDLIBMOD|%/stdlib_module|g" %t/input_map.json.template1 > %t/input_map.json.template2 +// RUN: sed -e "s|ONONEMOD|%/ononesupport_module|g" %t/input_map.json.template2 > %t/input_map.json.template3 +// RUN: sed -e "s|BARMOD|%t/inputs/Bar.pcm|g" %t/input_map.json.template3 > %t/input_map.json.template4 +// RUN: sed -e "s|BARMAP|%t/CFrameworks/Bar.framework/Modules/module.modulemap|g" %t/input_map.json.template4 > %t/input_map.json.template5 +// RUN: sed -e "s|FOOMOD|%/t/inputs/Foo.swiftmodule|g" %t/input_map.json.template5 > %t/input_map.json.template6 +// RUN: sed -e "s|SWIFTLIBDIR|%swift-lib-dir|g" %t/input_map.json.template6 > %t/input_map.json + +// 1. Build `Foo.swiftmodule` and `Foo.swiftinterface` using `-swift-version 5` +// RUN: %target-swift-frontend -emit-module -F%t/CFrameworks %t/Foo.swift -emit-module-path %t/Swift/Foo.swiftmodule/%target-swiftmodule-name -module-name Foo -emit-module-interface-path %t/Swift/Foo.swiftmodule/%target-swiftinterface-name -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import -module-cache-path %t/ModuleCache -enable-library-evolution -swift-version 5 -version-independent-apinotes + +// 2. Prepare for an explicit build of `Foo` and `main` by pre-compiling its dependencies +// RUN: %target-swift-emit-pcm -module-name Bar -o %t/inputs/Bar.pcm %t/CFrameworks/Bar.framework/Modules/module.modulemap -Xcc -fswift-version-independent-apinotes +// RUN: %target-swift-emit-pcm -module-name SwiftShims %swift-lib-dir/swift/shims/module.modulemap -o %t/inputs/SwiftShims.pcm -Xcc -fswift-version-independent-apinotes + +// 3. Build textual `Foo.swiftinterface` into a binary module using its interface-coded `-swift-version 5` +// RUN: rm %t/Swift/Foo.swiftmodule/%target-swiftmodule-name +// RUN: %target-swift-frontend -compile-module-from-interface %t/Swift/Foo.swiftmodule/%target-swiftinterface-name -o %t/inputs/Foo.swiftmodule -module-name Foo -disable-implicit-concurrency-module-import -disable-implicit-string-processing-module-import -disable-implicit-swift-modules -Xcc -fno-implicit-modules -Xcc -fno-implicit-module-maps -explicit-swift-module-map-file %t/input_map.json -swift-version 5 -version-independent-apinotes + +// 4. Build `main.swift` using `-swift-version 4` +// RUN: %target-swift-frontend -emit-module -emit-module-path %t/Main.swiftmodule -disable-implicit-swift-modules -module-cache-path %t.module-cache -explicit-swift-module-map-file %t/input_map.json -swift-version 4 -version-independent-apinotes %t/main.swift -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import + +// Because the `Bar` Clang module gets built with `-fswift-version-independent-apinotes`, it encodes all versioned APINotes into the resulting PCM. This means that `Foo.swiftinterface`, which gets built with Swift version 5, is able to resolve references to `funcBro` in its inlinable code to Bar's `funcBar`, according to version 5 APINotes, and `main.swift` is able to resolve a reference to `funkBus` to Bar's `funcBaz`, according to version 4 APINotes. `main.swift` compilation also gets the APINotes that ensures that `barGlobal`'s type is `unsigned` rather than `int`, as it appears in the source, as per APINotes version 4. + +//--- CFrameworks/Bar.framework/Headers/Bar.h +int barGlobal = 42; +int funcBar(void) { + return 1; +} +int funcBaz(void) { + return 2; +} +int funcNuc(void) { + return 3; +} + +//--- CFrameworks/Bar.framework/Headers/Bar.apinotes +--- +Name: Bar +SwiftVersions: +- Version: 4.0 + Functions: + - Name: funcBar + SwiftName: "funcBoom()" + - Name: funcBaz + SwiftName: "funkBus()" + + Globals: + - Name: barGlobal + Type: "unsigned" + +- Version: 5.0 + Functions: + - Name: funcBar + SwiftName: "funcBro()" + - Name: funcNuc + SwiftName: "funcNot()" + +Functions: + - Name: funcBar + SwiftName: "funcBroom()" + +//--- CFrameworks/Bar.framework/Modules/module.modulemap +framework module Bar [system] { + header "Bar.h" + export * +} + +//--- Foo.swift +import Bar +@inlinable +public func foo() -> Int { + return Int(funcBro()) +} + +//--- main.swift +import Foo +import Bar + +let x: UInt32 = barGlobal +print(41 + foo() + Int(funkBus()) + Int(funcNot())) + +//--- input_map.json.template +[ + { + "moduleName": "Swift", + "modulePath": "STDLIBMOD", + "isFramework": false + }, + { + "moduleName": "SwiftOnoneSupport", + "modulePath": "ONONEMOD", + "isFramework": false + }, + { + "moduleName": "Bar", + "isFramework": true, + "clangModuleMapPath": "BARMAP", + "clangModulePath": "BARMOD" + }, + { + "moduleName": "Foo", + "modulePath": "FOOMOD", + "isFramework": true + }, + { + "moduleName": "SwiftShims", + "isFramework": false, + "clangModuleMapPath": "SWIFTLIBDIR/swift/shims/module.modulemap", + "clangModulePath": "INPUTSDIR/SwiftShims.pcm" + } +]