Skip to content

Commit b60bc39

Browse files
committed
[APINotes] Add support for handling Clang modules carrying all versions of APINotes
Controlled from Swift with '-version-independent-apinotes', which, for the underlying Clang invocation enables '-fswift-version-independent-apinotes', results in PCMs which aggregate all versioned APINotes wrapped in a 'SwiftVersionedAttr', with the intent to have the client pick and apply only those that match its current Swift version, discarding the rest. This change introduces the configuration flags for this mode as well as the corresponding logic at the beginning of `importDeclImpl` to canonicalize versioned attributes, i.e. select the appropriate attributes for the current target and discard the rest.
1 parent 328dd1f commit b60bc39

File tree

8 files changed

+345
-0
lines changed

8 files changed

+345
-0
lines changed

include/swift/Basic/LangOptions.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,11 @@ namespace swift {
11271127
/// globals.
11281128
bool EnableConstValueImporting = true;
11291129

1130+
/// Whether the importer should expect all APINotes to be wrapped
1131+
/// in versioned attributes, where the importer must select the appropriate
1132+
/// ones to apply.
1133+
bool LoadVersionIndependentAPINotes = false;
1134+
11301135
/// Return a hash code of any components from these options that should
11311136
/// contribute to a Swift Bridging PCH hash.
11321137
llvm::hash_code getPCHHashComponents() const {

include/swift/Option/FrontendOptions.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,9 @@ def emit_pch : Flag<["-"], "emit-pch">,
685685
def pch_disable_validation : Flag<["-"], "pch-disable-validation">,
686686
HelpText<"Disable validating the persistent PCH">;
687687

688+
def version_independent_apinotes : Flag<["-"], "version-independent-apinotes">,
689+
HelpText<"Input clang modules carry all versioned APINotes">;
690+
688691
def disable_sil_ownership_verifier : Flag<["-"], "disable-sil-ownership-verifier">,
689692
HelpText<"Do not verify ownership invariants during SIL Verification ">;
690693

lib/ClangImporter/ClangImporter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,10 @@ void importer::getNormalInvocationArguments(
787787
invocationArgStrs.push_back("-iapinotes-modules");
788788
invocationArgStrs.push_back(path.str().str());
789789
}
790+
791+
if (importerOpts.LoadVersionIndependentAPINotes)
792+
invocationArgStrs.insert(invocationArgStrs.end(),
793+
{"-fswift-version-independent-apinotes"});
790794
}
791795

792796
static void

lib/ClangImporter/ImportDecl.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#include "clang/AST/Type.h"
7171
#include "clang/Basic/Specifiers.h"
7272
#include "clang/Basic/TargetInfo.h"
73+
#include "clang/Sema/SemaDiagnostic.h"
7374
#include "clang/Lex/Preprocessor.h"
7475
#include "clang/Sema/Lookup.h"
7576

@@ -9481,6 +9482,72 @@ static bool isUsingMacroName(clang::SourceManager &SM,
94819482
return content == MacroName;
94829483
}
94839484

9485+
static void filterUsableVersionedAttrs(
9486+
const clang::NamedDecl *clangDecl, llvm::VersionTuple currentVersion,
9487+
std::set<clang::SwiftVersionedAdditionAttr *> &applicableVersionedAttrSet) {
9488+
// Scan through Swift-Versioned clang attributes and select which one to apply
9489+
// if multiple candidates exist.
9490+
SmallVector<clang::SwiftVersionedAdditionAttr *, 4> swiftVersionedAttributes;
9491+
for (auto attr : clangDecl->attrs())
9492+
if (auto versionedAttr = dyn_cast<clang::SwiftVersionedAdditionAttr>(attr))
9493+
swiftVersionedAttributes.push_back(versionedAttr);
9494+
9495+
// An attribute version is valid to apply if it is greater than the current
9496+
// version or is unversioned
9497+
auto applicableVersion =
9498+
[currentVersion](clang::VersionTuple attrVersion) -> bool {
9499+
return attrVersion.empty() || attrVersion >= currentVersion;
9500+
};
9501+
9502+
// We have a better attribute option if there exists another versioned attr
9503+
// wrapper for this attribute kind with a valid version that is lower than the
9504+
// one of the attribute we are considering
9505+
auto haveBetterAttr = [swiftVersionedAttributes, applicableVersion](
9506+
clang::VersionTuple attrVersion,
9507+
clang::attr::Kind attrKind) -> bool {
9508+
for (auto VAI = swiftVersionedAttributes.begin(),
9509+
VAE = swiftVersionedAttributes.end();
9510+
VAI != VAE; ++VAI) {
9511+
auto otherVersionedAttr = *VAI;
9512+
auto otherAttrKind = otherVersionedAttr->getAdditionalAttr()->getKind();
9513+
auto otherAttrVersion = otherVersionedAttr->getVersion();
9514+
// Same exact attribute, ignore
9515+
if (otherAttrKind == attrKind && otherAttrVersion == attrVersion)
9516+
continue;
9517+
9518+
// For a matching attribute kind, an un-versioned attribute
9519+
// never takes precedence over an exsiting valid versioned one.
9520+
if (otherAttrKind == attrKind && !attrVersion.empty() &&
9521+
otherAttrVersion.empty())
9522+
continue;
9523+
if (otherAttrKind == attrKind && applicableVersion(otherAttrVersion) &&
9524+
attrVersion.empty())
9525+
return true;
9526+
9527+
// For two versioned attributes of the same kind, the one with the lower
9528+
// applicable version takes precedence.
9529+
if (otherAttrKind == attrKind && applicableVersion(otherAttrVersion) &&
9530+
otherAttrVersion < attrVersion)
9531+
return true;
9532+
}
9533+
return false;
9534+
};
9535+
9536+
for (auto VAI = swiftVersionedAttributes.begin(),
9537+
VAE = swiftVersionedAttributes.end();
9538+
VAI != VAE; ++VAI) {
9539+
auto versionedAttr = *VAI;
9540+
auto attrKind = versionedAttr->getAdditionalAttr()->getKind();
9541+
auto attrVersion = versionedAttr->getVersion();
9542+
if (!applicableVersion(attrVersion))
9543+
continue;
9544+
else if (haveBetterAttr(attrVersion, attrKind))
9545+
continue;
9546+
else
9547+
applicableVersionedAttrSet.insert(versionedAttr);
9548+
}
9549+
}
9550+
94849551
void ClangImporter::Implementation::importAttributesFromClangDeclToSynthesizedSwiftDecl(Decl *sourceDecl, Decl* synthesizedDecl)
94859552
{
94869553
// 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.
@@ -9791,6 +9858,76 @@ void ClangImporter::Implementation::importAttributes(
97919858
}
97929859
}
97939860

9861+
static void applyTypeAndNullabilityAPINotes(
9862+
const clang::NamedDecl *ClangDecl, clang::Sema &Sema,
9863+
const ImportNameVersion CurrentImporterVersion) {
9864+
// When importing from a module built with version-independent APINotes
9865+
// payload, the decl will carry all possible versioned notes, without directly
9866+
// applying any of them. For "type" and "nullability" notes, we must apply
9867+
// them first, here, since they change the actual type of the decl as seen
9868+
// downstream.
9869+
//
9870+
// Other kinds of notes will be handled in `importAttributes`.
9871+
for (clang::NamedDecl::attr_iterator AI = ClangDecl->attr_begin(),
9872+
AE = ClangDecl->attr_end();
9873+
AI != AE; ++AI) {
9874+
if (!isa<clang::SwiftTypeAttr>(*AI) &&
9875+
!isa<clang::SwiftNullabilityAttr>(*AI))
9876+
continue;
9877+
9878+
// Apply Type APINotes
9879+
if (auto typeRenameAttr = dyn_cast<clang::SwiftTypeAttr>(*AI)) {
9880+
Sema.ApplyAPINotesType(const_cast<clang::NamedDecl *>(ClangDecl),
9881+
typeRenameAttr->getTypeString());
9882+
}
9883+
9884+
// Apply Nullability APINotes
9885+
if (auto nullabilityAttr = dyn_cast<clang::SwiftNullabilityAttr>(*AI)) {
9886+
clang::NullabilityKind nullability;
9887+
switch (nullabilityAttr->getKind()) {
9888+
case clang::SwiftNullabilityAttr::Kind::NonNull:
9889+
nullability = clang::NullabilityKind::NonNull;
9890+
break;
9891+
case clang::SwiftNullabilityAttr::Kind::Nullable:
9892+
nullability = clang::NullabilityKind::Nullable;
9893+
break;
9894+
case clang::SwiftNullabilityAttr::Kind::Unspecified:
9895+
nullability = clang::NullabilityKind::Unspecified;
9896+
break;
9897+
case clang::SwiftNullabilityAttr::Kind::NullableResult:
9898+
nullability = clang::NullabilityKind::NullableResult;
9899+
break;
9900+
}
9901+
9902+
Sema.ApplyNullability(const_cast<clang::NamedDecl *>(ClangDecl),
9903+
nullability);
9904+
}
9905+
}
9906+
}
9907+
9908+
static void canonicalizeVersionedSwiftAttributes(
9909+
const clang::NamedDecl *ClangDecl,
9910+
const ImportNameVersion CurrentImporterVersion) {
9911+
if (!ClangDecl->hasAttrs())
9912+
return;
9913+
9914+
// Filter out only the versioned attributes which apply to the
9915+
// current compilation's language version
9916+
std::set<clang::SwiftVersionedAdditionAttr *> applicableVersionedAttrSet;
9917+
filterUsableVersionedAttrs(ClangDecl,
9918+
CurrentImporterVersion.asClangVersionTuple(),
9919+
applicableVersionedAttrSet);
9920+
9921+
// Drop all versioned addition attributes and re-add
9922+
// above-filtered out applicable attributes in a non-versioned
9923+
// form in order to ensure all downstream clients
9924+
// get the expected attribute view.
9925+
auto mutableDecl = const_cast<clang::NamedDecl *>(ClangDecl);
9926+
mutableDecl->dropAttrs<clang::SwiftVersionedAdditionAttr>();
9927+
for (const auto &attr : applicableVersionedAttrSet)
9928+
mutableDecl->addAttr(attr->getAdditionalAttr());
9929+
}
9930+
97949931
Decl *
97959932
ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
97969933
ImportNameVersion version,
@@ -9802,6 +9939,21 @@ ClangImporter::Implementation::importDeclImpl(const clang::NamedDecl *ClangDecl,
98029939
if (ClangDecl->isInvalidDecl())
98039940
return nullptr;
98049941

9942+
// If '-version-independent-apinotes' is used, the `ClangDecl`
9943+
// will be carrying various APINotes-sourced attributes wrapped
9944+
// in `SwiftVersionedAdditionAttr`. Filter out which ones are applicable
9945+
// for the current compilation version and rewrite the set of versioned
9946+
// attributes with the corresponding subset of only applicable wrapped
9947+
// attributes.
9948+
if (SwiftContext.ClangImporterOpts.LoadVersionIndependentAPINotes) {
9949+
canonicalizeVersionedSwiftAttributes(ClangDecl, CurrentVersion);
9950+
// When '-version-independent-apinotes' is used, "type" and "nullability"
9951+
// notes are applied by the client (Importer) instead of the producer of the
9952+
// Clang module we are consuming. Do so now, early, since these notes
9953+
// affect the decl's type and require mutation.
9954+
applyTypeAndNullabilityAPINotes(ClangDecl, getClangSema(), CurrentVersion);
9955+
}
9956+
98059957
bool SkippedOverTypedef = false;
98069958
Decl *Result = nullptr;
98079959
if (auto *UnderlyingDecl = canSkipOverTypedef(*this, ClangDecl,

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,6 +2124,8 @@ static bool ParseClangImporterArgs(ClangImporterOptions &Opts, ArgList &Args,
21242124
Opts.PCHDisableValidation |= Args.hasArg(OPT_pch_disable_validation);
21252125
}
21262126

2127+
Opts.LoadVersionIndependentAPINotes |= Args.hasArg(OPT_version_independent_apinotes);
2128+
21272129
if (FrontendOpts.DisableImplicitModules)
21282130
Opts.DisableImplicitClangModules = true;
21292131

lib/Frontend/ModuleInterfaceLoader.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,6 +2013,12 @@ InterfaceSubContextDelegateImpl::InterfaceSubContextDelegateImpl(
20132013
GenericArgs.push_back(blocklist);
20142014
}
20152015

2016+
// Inherit APINotes processing method
2017+
if (clangImporterOpts.LoadVersionIndependentAPINotes) {
2018+
GenericArgs.push_back("-version-independent-apinotes");
2019+
genericSubInvocation.getClangImporterOptions().LoadVersionIndependentAPINotes = true;
2020+
}
2021+
20162022
// Inherit the C++ interoperability mode.
20172023
if (langOpts.EnableCXXInterop) {
20182024
// Modelled after a reverse of validateCxxInteropCompatibilityMode
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// REQUIRES: objc_interop
2+
// RUN: %empty-directory(%t)
3+
// RUN: %empty-directory(%t/InputClangModules)
4+
// RUN: split-file %s %t
5+
6+
// - Fixup the input module file map
7+
// RUN: sed -e "s|INPUTSDIR|%/t/InputClangModules|g" %t/map.json.template > %t/map.json.template1
8+
// RUN: sed -e "s|STDLIBMOD|%/stdlib_module|g" %t/map.json.template1 > %t/map.json.template2
9+
// RUN: sed -e "s|ONONEMOD|%/ononesupport_module|g" %t/map.json.template2 > %t/map.json.template3
10+
// RUN: sed -e "s|CUSTOMFRAMEWORKS|%S/Inputs/custom-frameworks|g" %t/map.json.template3 > %t/map.json.template4
11+
// RUN: sed -e "s|SWIFTLIBDIR|%swift-lib-dir|g" %t/map.json.template4 > %t/map.json
12+
13+
// - Set up explicit dependencies for Client
14+
// 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
15+
16+
// - Build the APINotesFrameworkTest module using verison-independent APINotes
17+
// 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
18+
19+
// - Build the client
20+
// 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
21+
22+
//--- map.json.template
23+
[
24+
{
25+
"moduleName": "Swift",
26+
"modulePath": "STDLIBMOD",
27+
"isFramework": false
28+
},
29+
{
30+
"moduleName": "SwiftOnoneSupport",
31+
"modulePath": "ONONEMOD",
32+
"isFramework": false
33+
},
34+
{
35+
"moduleName": "SwiftShims",
36+
"isFramework": false,
37+
"clangModuleMapPath": "SWIFTLIBDIR/swift/shims/module.modulemap",
38+
"clangModulePath": "INPUTSDIR/SwiftShims.pcm"
39+
},
40+
{
41+
"moduleName": "APINotesFrameworkTest",
42+
"isFramework": true,
43+
"clangModuleMapPath": "CUSTOMFRAMEWORKS/APINotesFrameworkTest.framework/Modules/module.modulemap",
44+
"clangModulePath": "INPUTSDIR/APINotesFrameworkTest.pcm"
45+
}
46+
]
47+
48+
//--- client.swift
49+
import APINotesFrameworkTest

0 commit comments

Comments
 (0)