Skip to content

[APINotes] Add support for handling Clang modules carrying all versions of APINotes #75893

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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 @@ -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 {
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 @@ -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 ">;

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
159 changes: 155 additions & 4 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<clang::Attr*> &discardVersionedAttrSet) {
// 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))
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.
Expand Down Expand Up @@ -9369,17 +9434,40 @@ void ClangImporter::Implementation::importAttributes(
if (auto func = dyn_cast<AbstractFunctionDecl>(MappedDecl))
isAsync = func->hasAsync();

// Determine which versioned attributes are applicable
std::unordered_set<clang::Attr*> 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<clang::SwiftVersionedAdditionAttr>(consideringAttr)) {
// "type" and "nullability" attributes are handled earlier since they change
// the decl's type.
auto additionalAttr = versionedAttr->getAdditionalAttr();
if (isa<clang::SwiftTypeAttr>(additionalAttr) ||
isa<clang::SwiftNullabilityAttr>(additionalAttr))
continue;

if (discardVersionedAttrSet.count(versionedAttr))
continue;

consideringAttr = additionalAttr;
}
}

//
// __attribute__((unavailable))
//
// Mapping: @available(*,unavailable)
//
if (auto unavailable = dyn_cast<clang::UnavailableAttr>(*AI)) {
if (auto unavailable = dyn_cast<clang::UnavailableAttr>(consideringAttr)) {
auto Message = unavailable->getMessage();
auto attr = AvailableAttr::createUniversallyUnavailable(C, Message);
MappedDecl->getAttrs().add(attr);
Expand All @@ -9392,7 +9480,7 @@ void ClangImporter::Implementation::importAttributes(
//
// Mapping: @available(*, unavailable)
//
if (auto unavailable_annot = dyn_cast<clang::AnnotateAttr>(*AI))
if (auto unavailable_annot = dyn_cast<clang::AnnotateAttr>(consideringAttr))
if (unavailable_annot->getAnnotation() == "swift1_unavailable") {
auto attr = AvailableAttr::createUnavailableInSwift(C, "", "");
MappedDecl->getAttrs().add(attr);
Expand All @@ -9405,7 +9493,7 @@ void ClangImporter::Implementation::importAttributes(
//
// Mapping: @available(*,deprecated)
//
if (auto deprecated = dyn_cast<clang::DeprecatedAttr>(*AI)) {
if (auto deprecated = dyn_cast<clang::DeprecatedAttr>(consideringAttr)) {
auto Message = deprecated->getMessage();
auto attr = AvailableAttr::createUniversallyDeprecated(C, Message, "");
MappedDecl->getAttrs().add(attr);
Expand All @@ -9414,7 +9502,7 @@ void ClangImporter::Implementation::importAttributes(

// __attribute__((availability))
//
if (auto avail = dyn_cast<clang::AvailabilityAttr>(*AI)) {
if (auto avail = dyn_cast<clang::AvailabilityAttr>(consideringAttr)) {
StringRef Platform = avail->getPlatform()->getName();

// Is this our special "availability(swift, unavailable)" attribute?
Expand Down Expand Up @@ -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<clang::Attr*> 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<clang::SwiftVersionedAdditionAttr>(*AI)) {
if (!isa<clang::SwiftTypeAttr>(versionedAttr->getAdditionalAttr()) &&
!isa<clang::SwiftNullabilityAttr>(versionedAttr->getAdditionalAttr())) {
continue;
}

if (discardVersionedAttrSet.count(versionedAttr))
continue;

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

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

Decl *
ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
ImportNameVersion version,
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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