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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,11 @@ namespace swift {
/// globals.
bool EnableConstValueImporting = true;

/// 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 {
Expand Down
3 changes: 3 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,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 ">;

Expand Down
4 changes: 4 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
152 changes: 152 additions & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,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"

Expand Down Expand Up @@ -9481,6 +9482,72 @@ static bool isUsingMacroName(clang::SourceManager &SM,
return content == MacroName;
}

static void filterUsableVersionedAttrs(
const clang::NamedDecl *clangDecl, llvm::VersionTuple currentVersion,
std::set<clang::SwiftVersionedAdditionAttr *> &applicableVersionedAttrSet) {
// Scan through Swift-Versioned clang attributes and select which one to apply
// if multiple candidates exist.
SmallVector<clang::SwiftVersionedAdditionAttr *, 4> swiftVersionedAttributes;
for (auto attr : clangDecl->attrs())
if (auto versionedAttr = dyn_cast<clang::SwiftVersionedAdditionAttr>(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))
continue;
else if (haveBetterAttr(attrVersion, attrKind))
continue;
else
applicableVersionedAttrSet.insert(versionedAttr);
}
}

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.
Expand Down Expand Up @@ -9791,6 +9858,76 @@ void ClangImporter::Implementation::importAttributes(
}
}

static void applyTypeAndNullabilityAPINotes(
const clang::NamedDecl *ClangDecl, clang::Sema &Sema,
const ImportNameVersion CurrentImporterVersion) {
// 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 (!isa<clang::SwiftTypeAttr>(*AI) &&
!isa<clang::SwiftNullabilityAttr>(*AI))
continue;

// Apply Type APINotes
if (auto typeRenameAttr = dyn_cast<clang::SwiftTypeAttr>(*AI)) {
Sema.ApplyAPINotesType(const_cast<clang::NamedDecl *>(ClangDecl),
typeRenameAttr->getTypeString());
}

// Apply Nullability APINotes
if (auto nullabilityAttr = dyn_cast<clang::SwiftNullabilityAttr>(*AI)) {
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<clang::NamedDecl *>(ClangDecl),
nullability);
}
}
}

static void canonicalizeVersionedSwiftAttributes(
const clang::NamedDecl *ClangDecl,
const ImportNameVersion CurrentImporterVersion) {
if (!ClangDecl->hasAttrs())
return;

// Filter out only the versioned attributes which apply to the
// current compilation's language version
std::set<clang::SwiftVersionedAdditionAttr *> applicableVersionedAttrSet;
filterUsableVersionedAttrs(ClangDecl,
CurrentImporterVersion.asClangVersionTuple(),
applicableVersionedAttrSet);

// Drop all versioned addition attributes and re-add
// above-filtered out applicable attributes in a non-versioned
// form in order to ensure all downstream clients
// get the expected attribute view.
auto mutableDecl = const_cast<clang::NamedDecl *>(ClangDecl);
mutableDecl->dropAttrs<clang::SwiftVersionedAdditionAttr>();
for (const auto &attr : applicableVersionedAttrSet)
mutableDecl->addAttr(attr->getAdditionalAttr());
}

Decl *
ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
ImportNameVersion version,
Expand All @@ -9802,6 +9939,21 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
if (ClangDecl->isInvalidDecl())
return nullptr;

// If '-version-independent-apinotes' is used, the `ClangDecl`
// will be carrying various APINotes-sourced attributes wrapped
// in `SwiftVersionedAdditionAttr`. Filter out which ones are applicable
// for the current compilation version and rewrite the set of versioned
// attributes with the corresponding subset of only applicable wrapped
// attributes.
if (SwiftContext.ClangImporterOpts.LoadVersionIndependentAPINotes) {
canonicalizeVersionedSwiftAttributes(ClangDecl, CurrentVersion);
// When '-version-independent-apinotes' is used, "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 and require mutation.
applyTypeAndNullabilityAPINotes(ClangDecl, getClangSema(), CurrentVersion);
}

bool SkippedOverTypedef = false;
Decl *Result = nullptr;
if (auto *UnderlyingDecl = canSkipOverTypedef(*this, ClangDecl,
Expand Down
2 changes: 2 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2124,6 +2124,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;

Expand Down
6 changes: 6 additions & 0 deletions lib/Frontend/ModuleInterfaceLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,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
Expand Down
49 changes: 49 additions & 0 deletions test/APINotes/basic_version_independent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// REQUIRES: objc_interop
// 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
Loading