Skip to content

Commit c019093

Browse files
authored
Merge pull request #75893 from artemcm/SwiftAPINotesVersionedProto
[APINotes] Add support for handling Clang modules carrying all versions of APINotes
2 parents c447bf5 + b60bc39 commit c019093

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)