Skip to content

Commit 7a78798

Browse files
committed
[APINotes] Add support for capturing all possible versioned APINotes without applying them
Swift-versioned API notes get applied at PCM constrution time relying on '-fapinotes-swift-version=X' argument to pick the appropriate version. This change adds a new APINotes application mode with '-fswift-version-independent-apinotes' which causes *all* versioned API notes to get recorded into the PCM wrapped in 'SwiftVersionedAttr' instances. The expectation in this mode is that the Swift client will perform the required transformations as per the API notes on the client side, when loading the PCM, instead of them getting applied on the producer side. This will allow the same PCM to be usable by Swift clients building with different language versions. In addition to versioned-wrapping the various existing API notes annotations which are carried in declaration attributes, this change adds a new attribute for two annotations which were previously applied directly to the declaration at the PCM producer side: 1) Type and 2) Nullability annotations with 'SwiftTypeAttr' and 'SwiftNullabilityAttr', respectively. The logic to apply these two annotations to a declaration is refactored into API.
1 parent 2f1a5ce commit 7a78798

File tree

9 files changed

+226
-79
lines changed

9 files changed

+226
-79
lines changed

clang/include/clang/APINotes/APINotesManager.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ class APINotesManager {
5050
/// source file from which an entity was declared.
5151
bool ImplicitAPINotes;
5252

53+
/// Whether to apply all APINotes as optionally-applied versioned
54+
/// entities. This means that when building a Clang module,
55+
/// we capture every note on a given decl wrapped in a SwiftVersionedAttr
56+
/// (with an empty version field for unversioned notes), and have the
57+
/// client apply the relevant version's notes.
58+
bool VersionIndependentSwift;
59+
5360
/// The Swift version to use when interpreting versioned API notes.
5461
llvm::VersionTuple SwiftVersion;
5562

@@ -167,6 +174,8 @@ class APINotesManager {
167174

168175
/// Find the API notes readers that correspond to the given source location.
169176
llvm::SmallVector<APINotesReader *, 2> findAPINotes(SourceLocation Loc);
177+
178+
bool captureVersionIndependentSwift() { return VersionIndependentSwift; }
170179
};
171180

172181
} // end namespace api_notes

clang/include/clang/Basic/Attr.td

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2987,6 +2987,26 @@ def Regparm : TypeAttr {
29872987
let ASTNode = 0;
29882988
}
29892989

2990+
def SwiftType : Attr {
2991+
// This attribute has no spellings as it is only ever created implicitly
2992+
// from API notes.
2993+
let Spellings = [];
2994+
let Args = [StringArgument<"TypeString">];
2995+
let SemaHandler = 0;
2996+
let Documentation = [InternalOnly];
2997+
}
2998+
2999+
def SwiftNullability : Attr {
3000+
// This attribute has no spellings as it is only ever created implicitly
3001+
// from API notes.
3002+
let Spellings = [];
3003+
let Args = [EnumArgument<"Kind", "Kind", /*is_string=*/false,
3004+
["non_null", "nullable", "unspecified", "nullable_result"],
3005+
["NonNull", "Nullable", "Unspecified", "NullableResult"]>];
3006+
let SemaHandler = 0;
3007+
let Documentation = [InternalOnly];
3008+
}
3009+
29903010
def SwiftAsyncName : InheritableAttr {
29913011
let Spellings = [GNU<"swift_async_name">];
29923012
let Args = [StringArgument<"Name">];

clang/include/clang/Basic/LangOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, "retain documentation comments fr
438438
LANGOPT(APINotes, 1, 0, "use external API notes")
439439
LANGOPT(APINotesModules, 1, 0, "use module-based external API notes")
440440
LANGOPT(NeededByPCHOrCompilationUsesPCH, 1, 0, "compilation involves pch")
441+
LANGOPT(SwiftVersionIndependentAPINotes, 1, 0, "use external API notes capturing all versions")
441442

442443
LANGOPT(SanitizeAddressFieldPadding, 2, 0, "controls how aggressive is ASan "
443444
"field padding (0: none, 1:least "

clang/include/clang/Driver/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,12 @@ defm apinotes_modules : BoolOption<"f", "apinotes-modules",
19341934
NegFlag<SetFalse, [], [ClangOption], "Disable">,
19351935
BothFlags<[], [ClangOption, CC1Option], " module-based external API notes support">>,
19361936
Group<f_clang_Group>;
1937+
defm swift_version_independent_apinotes : BoolOption<"f", "swift-version-independent-apinotes",
1938+
LangOpts<"SwiftVersionIndependentAPINotes">, DefaultFalse,
1939+
PosFlag<SetTrue, [], [ClangOption], "Enable">,
1940+
NegFlag<SetFalse, [], [ClangOption], "Disable">,
1941+
BothFlags<[], [ClangOption, CC1Option], " version-independent external API notes support">>,
1942+
Group<f_clang_Group>;
19371943
def fapinotes_swift_version : Joined<["-"], "fapinotes-swift-version=">,
19381944
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
19391945
MetaVarName<"<version>">,

clang/include/clang/Sema/Sema.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,7 +1416,6 @@ class Sema final : public SemaBase {
14161416
// -------------------------------------------------------------------------
14171417
//
14181418
//
1419-
14201419
/// \name C++ Access Control
14211420
/// Implementations are in SemaAccess.cpp
14221421
///@{
@@ -15543,7 +15542,17 @@ class Sema final : public SemaBase {
1554315542
///
1554415543
/// Triggered by declaration-attribute processing.
1554515544
void ProcessAPINotes(Decl *D);
15546-
15545+
/// Apply the 'Nullability:' annotation to the specified declaration
15546+
void ApplyNullability(Decl *D, NullabilityKind Nullability);
15547+
/// Apply the 'Type:' annotation to the specified declaration
15548+
void ApplyAPINotesType(Decl *D, StringRef TypeString);
15549+
15550+
/// Whether APINotes should be gathered for all applicable Swift language
15551+
/// versions, without being applied. Leaving clients of the current module
15552+
/// to select and apply the correct version.
15553+
bool captureSwiftVersionIndependentAPINotes() {
15554+
return APINotes.captureVersionIndependentSwift();
15555+
}
1554715556
///@}
1554815557

1554915558
//

clang/lib/APINotes/APINotesManager.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry {
5151
} // namespace
5252

5353
APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts)
54-
: SM(SM), ImplicitAPINotes(LangOpts.APINotes) {}
54+
: SM(SM), ImplicitAPINotes(LangOpts.APINotes),
55+
VersionIndependentSwift(LangOpts.SwiftVersionIndependentAPINotes) {}
5556

5657
APINotesManager::~APINotesManager() {
5758
// Free the API notes readers.

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7214,6 +7214,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &Job,
72147214
Args.AddLastArg(CmdArgs, options::OPT_fapinotes_swift_version);
72157215
}
72167216

7217+
if (Args.hasFlag(options::OPT_fswift_version_independent_apinotes,
7218+
options::OPT_fno_swift_version_independent_apinotes, false))
7219+
CmdArgs.push_back("-fswift-version-independent-apinotes");
7220+
72177221
// -fblocks=0 is default.
72187222
if (Args.hasFlag(options::OPT_fblocks, options::OPT_fno_blocks,
72197223
TC.IsBlocksDefault()) ||

clang/lib/Sema/SemaAPINotes.cpp

Lines changed: 137 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -53,63 +53,58 @@ static bool isIndirectPointerType(QualType Type) {
5353
Pointee->isMemberPointerType();
5454
}
5555

56-
/// Apply nullability to the given declaration.
57-
static void applyNullability(Sema &S, Decl *D, NullabilityKind Nullability,
58-
VersionedInfoMetadata Metadata) {
59-
if (!Metadata.IsActive)
60-
return;
61-
62-
auto GetModified =
63-
[&](Decl *D, QualType QT,
64-
NullabilityKind Nullability) -> std::optional<QualType> {
65-
QualType Original = QT;
66-
S.CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(),
67-
isa<ParmVarDecl>(D),
68-
/*OverrideExisting=*/true);
69-
return (QT.getTypePtr() != Original.getTypePtr()) ? std::optional(QT)
70-
: std::nullopt;
71-
};
56+
static void applyAPINotesType(Sema &S, Decl *decl, StringRef typeString,
57+
VersionedInfoMetadata metadata) {
58+
if (typeString.empty())
7259

73-
if (auto Function = dyn_cast<FunctionDecl>(D)) {
74-
if (auto Modified =
75-
GetModified(D, Function->getReturnType(), Nullability)) {
76-
const FunctionType *FnType = Function->getType()->castAs<FunctionType>();
77-
if (const FunctionProtoType *proto = dyn_cast<FunctionProtoType>(FnType))
78-
Function->setType(S.Context.getFunctionType(
79-
*Modified, proto->getParamTypes(), proto->getExtProtoInfo()));
80-
else
81-
Function->setType(
82-
S.Context.getFunctionNoProtoType(*Modified, FnType->getExtInfo()));
83-
}
84-
} else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
85-
if (auto Modified = GetModified(D, Method->getReturnType(), Nullability)) {
86-
Method->setReturnType(*Modified);
60+
return;
8761

88-
// Make it a context-sensitive keyword if we can.
89-
if (!isIndirectPointerType(*Modified))
90-
Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
91-
Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
92-
}
93-
} else if (auto Value = dyn_cast<ValueDecl>(D)) {
94-
if (auto Modified = GetModified(D, Value->getType(), Nullability)) {
95-
Value->setType(*Modified);
62+
// Version-independent APINotes add "type" annotations
63+
// with a versioned attribute for the client to select and apply.
64+
if (S.captureSwiftVersionIndependentAPINotes()) {
65+
auto *typeAttr = SwiftTypeAttr::CreateImplicit(S.Context, typeString);
66+
auto *versioned = SwiftVersionedAdditionAttr::CreateImplicit(
67+
S.Context, metadata.Version, typeAttr, metadata.IsReplacement);
68+
decl->addAttr(versioned);
69+
} else {
70+
if (!metadata.IsActive)
71+
return;
72+
S.ApplyAPINotesType(decl, typeString);
73+
}
74+
}
9675

97-
// Make it a context-sensitive keyword if we can.
98-
if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
99-
if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified))
100-
Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
101-
Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
102-
}
76+
/// Apply nullability to the given declaration.
77+
static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability,
78+
VersionedInfoMetadata metadata) {
79+
// Version-independent APINotes add "nullability" annotations
80+
// with a versioned attribute for the client to select and apply.
81+
if (S.captureSwiftVersionIndependentAPINotes()) {
82+
SwiftNullabilityAttr::Kind attrNullabilityKind;
83+
switch (nullability) {
84+
case NullabilityKind::NonNull:
85+
attrNullabilityKind = SwiftNullabilityAttr::Kind::NonNull;
86+
break;
87+
case NullabilityKind::Nullable:
88+
attrNullabilityKind = SwiftNullabilityAttr::Kind::Nullable;
89+
break;
90+
case NullabilityKind::Unspecified:
91+
attrNullabilityKind = SwiftNullabilityAttr::Kind::Unspecified;
92+
break;
93+
case NullabilityKind::NullableResult:
94+
attrNullabilityKind = SwiftNullabilityAttr::Kind::NullableResult;
95+
break;
10396
}
104-
} else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
105-
if (auto Modified = GetModified(D, Property->getType(), Nullability)) {
106-
Property->setType(*Modified, Property->getTypeSourceInfo());
97+
auto *nullabilityAttr =
98+
SwiftNullabilityAttr::CreateImplicit(S.Context, attrNullabilityKind);
99+
auto *versioned = SwiftVersionedAdditionAttr::CreateImplicit(
100+
S.Context, metadata.Version, nullabilityAttr, metadata.IsReplacement);
101+
decl->addAttr(versioned);
102+
return;
103+
} else {
104+
if (!metadata.IsActive)
105+
return;
107106

108-
// Make it a property attribute if we can.
109-
if (!isIndirectPointerType(*Modified))
110-
Property->setPropertyAttributes(
111-
ObjCPropertyAttribute::kind_null_resettable);
112-
}
107+
S.ApplyNullability(decl, nullability);
113108
}
114109
}
115110

@@ -363,42 +358,99 @@ static bool checkAPINotesReplacementType(Sema &S, SourceLocation Loc,
363358
return false;
364359
}
365360

366-
/// Process API notes for a variable or property.
367-
static void ProcessAPINotes(Sema &S, Decl *D,
368-
const api_notes::VariableInfo &Info,
369-
VersionedInfoMetadata Metadata) {
370-
// Type override.
371-
if (Metadata.IsActive && !Info.getType().empty() &&
372-
S.ParseTypeFromStringCallback) {
373-
auto ParsedType = S.ParseTypeFromStringCallback(
374-
Info.getType(), "<API Notes>", D->getLocation());
361+
void Sema::ApplyAPINotesType(Decl *D, StringRef TypeString) {
362+
if (!TypeString.empty() && ParseTypeFromStringCallback) {
363+
auto ParsedType = ParseTypeFromStringCallback(TypeString, "<API Notes>",
364+
D->getLocation());
375365
if (ParsedType.isUsable()) {
376366
QualType Type = Sema::GetTypeFromParser(ParsedType.get());
377-
auto TypeInfo =
378-
S.Context.getTrivialTypeSourceInfo(Type, D->getLocation());
379-
367+
auto TypeInfo = Context.getTrivialTypeSourceInfo(Type, D->getLocation());
380368
if (auto Var = dyn_cast<VarDecl>(D)) {
381369
// Make adjustments to parameter types.
382370
if (isa<ParmVarDecl>(Var)) {
383-
Type = S.ObjC().AdjustParameterTypeForObjCAutoRefCount(
371+
Type = ObjC().AdjustParameterTypeForObjCAutoRefCount(
384372
Type, D->getLocation(), TypeInfo);
385-
Type = S.Context.getAdjustedParameterType(Type);
373+
Type = Context.getAdjustedParameterType(Type);
386374
}
387375

388-
if (!checkAPINotesReplacementType(S, Var->getLocation(), Var->getType(),
389-
Type)) {
376+
if (!checkAPINotesReplacementType(*this, Var->getLocation(),
377+
Var->getType(), Type)) {
390378
Var->setType(Type);
391379
Var->setTypeSourceInfo(TypeInfo);
392380
}
393-
} else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
394-
if (!checkAPINotesReplacementType(S, Property->getLocation(),
395-
Property->getType(), Type))
396-
Property->setType(Type, TypeInfo);
397-
398-
} else
381+
} else if (auto property = dyn_cast<ObjCPropertyDecl>(D)) {
382+
if (!checkAPINotesReplacementType(*this, property->getLocation(),
383+
property->getType(), Type)) {
384+
property->setType(Type, TypeInfo);
385+
}
386+
} else {
399387
llvm_unreachable("API notes allowed a type on an unknown declaration");
388+
}
389+
}
390+
}
391+
}
392+
393+
void Sema::ApplyNullability(Decl *D, NullabilityKind Nullability) {
394+
auto GetModified =
395+
[&](class Decl *D, QualType QT,
396+
NullabilityKind Nullability) -> std::optional<QualType> {
397+
QualType Original = QT;
398+
CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(),
399+
isa<ParmVarDecl>(D),
400+
/*OverrideExisting=*/true);
401+
return (QT.getTypePtr() != Original.getTypePtr()) ? std::optional(QT)
402+
: std::nullopt;
403+
};
404+
405+
if (auto Function = dyn_cast<FunctionDecl>(D)) {
406+
if (auto Modified =
407+
GetModified(D, Function->getReturnType(), Nullability)) {
408+
const FunctionType *FnType = Function->getType()->castAs<FunctionType>();
409+
if (const FunctionProtoType *proto = dyn_cast<FunctionProtoType>(FnType))
410+
Function->setType(Context.getFunctionType(
411+
*Modified, proto->getParamTypes(), proto->getExtProtoInfo()));
412+
else
413+
Function->setType(
414+
Context.getFunctionNoProtoType(*Modified, FnType->getExtInfo()));
415+
}
416+
} else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
417+
if (auto Modified = GetModified(D, Method->getReturnType(), Nullability)) {
418+
Method->setReturnType(*Modified);
419+
420+
// Make it a context-sensitive keyword if we can.
421+
if (!isIndirectPointerType(*Modified))
422+
Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
423+
Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
424+
}
425+
} else if (auto Value = dyn_cast<ValueDecl>(D)) {
426+
if (auto Modified = GetModified(D, Value->getType(), Nullability)) {
427+
Value->setType(*Modified);
428+
429+
// Make it a context-sensitive keyword if we can.
430+
if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
431+
if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified))
432+
Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
433+
Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
434+
}
435+
}
436+
} else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
437+
if (auto Modified = GetModified(D, Property->getType(), Nullability)) {
438+
Property->setType(*Modified, Property->getTypeSourceInfo());
439+
440+
// Make it a property attribute if we can.
441+
if (!isIndirectPointerType(*Modified))
442+
Property->setPropertyAttributes(
443+
ObjCPropertyAttribute::kind_null_resettable);
400444
}
401445
}
446+
}
447+
448+
/// Process API notes for a variable or property.
449+
static void ProcessAPINotes(Sema &S, Decl *D,
450+
const api_notes::VariableInfo &Info,
451+
VersionedInfoMetadata Metadata) {
452+
// Type override.
453+
applyAPINotesType(S, D, Info.getType(), Metadata);
402454

403455
// Nullability.
404456
if (auto Nullability = Info.getNullability())
@@ -892,7 +944,8 @@ static void ProcessVersionedAPINotes(
892944
Sema &S, SpecificDecl *D,
893945
const api_notes::APINotesReader::VersionedInfo<SpecificInfo> Info) {
894946

895-
maybeAttachUnversionedSwiftName(S, D, Info);
947+
if (!S.captureSwiftVersionIndependentAPINotes())
948+
maybeAttachUnversionedSwiftName(S, D, Info);
896949

897950
unsigned Selected = Info.getSelected().value_or(Info.size());
898951

@@ -902,10 +955,18 @@ static void ProcessVersionedAPINotes(
902955
std::tie(Version, InfoSlice) = Info[i];
903956
auto Active = (i == Selected) ? IsActive_t::Active : IsActive_t::Inactive;
904957
auto Replacement = IsSubstitution_t::Original;
905-
if (Active == IsActive_t::Inactive && Version.empty()) {
958+
959+
// When collection all APINotes as version-independent,
960+
// capture all as inactive and defer to the client select the
961+
// right one.
962+
if (S.captureSwiftVersionIndependentAPINotes()) {
963+
Active = IsActive_t::Inactive;
964+
Replacement = IsSubstitution_t::Original;
965+
} else if (Active == IsActive_t::Inactive && Version.empty()) {
906966
Replacement = IsSubstitution_t::Replacement;
907967
Version = Info[Selected].first;
908968
}
969+
909970
ProcessAPINotes(S, D, InfoSlice,
910971
VersionedInfoMetadata(Version, Active, Replacement));
911972
}

0 commit comments

Comments
 (0)