Skip to content

Commit ac1f2ee

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 122afae commit ac1f2ee

File tree

9 files changed

+232
-77
lines changed

9 files changed

+232
-77
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
@@ -3055,6 +3055,26 @@ def Regparm : TypeAttr {
30553055
let ASTNode = 0;
30563056
}
30573057

3058+
def SwiftType : Attr {
3059+
// This attribute has no spellings as it is only ever created implicitly
3060+
// from API notes.
3061+
let Spellings = [];
3062+
let Args = [StringArgument<"TypeString">];
3063+
let SemaHandler = 0;
3064+
let Documentation = [InternalOnly];
3065+
}
3066+
3067+
def SwiftNullability : Attr {
3068+
// This attribute has no spellings as it is only ever created implicitly
3069+
// from API notes.
3070+
let Spellings = [];
3071+
let Args = [EnumArgument<"Kind", "Kind", /*is_string=*/false,
3072+
["non_null", "nullable", "unspecified", "nullable_result"],
3073+
["NonNull", "Nullable", "Unspecified", "NullableResult"]>];
3074+
let SemaHandler = 0;
3075+
let Documentation = [InternalOnly];
3076+
}
3077+
30583078
def SwiftAsyncName : InheritableAttr {
30593079
let Spellings = [GNU<"swift_async_name">];
30603080
let Args = [StringArgument<"Name">];

clang/include/clang/Basic/LangOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, Compatible, "retain documentation
390390

391391
LANGOPT(APINotes, 1, 0, NotCompatible, "use external API notes")
392392
LANGOPT(APINotesModules, 1, 0, NotCompatible, "use module-based external API notes")
393+
LANGOPT(SwiftVersionIndependentAPINotes, 1, 0, NotCompatible, "use external API notes capturing all versions")
393394

394395
LANGOPT(SanitizeAddressFieldPadding, 2, 0, NotCompatible, "controls how aggressive is ASan "
395396
"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
@@ -1888,6 +1888,12 @@ defm apinotes_modules : BoolOption<"f", "apinotes-modules",
18881888
NegFlag<SetFalse, [], [ClangOption], "Disable">,
18891889
BothFlags<[], [ClangOption, CC1Option], " module-based external API notes support">>,
18901890
Group<f_clang_Group>;
1891+
defm swift_version_independent_apinotes : BoolOption<"f", "swift-version-independent-apinotes",
1892+
LangOpts<"SwiftVersionIndependentAPINotes">, DefaultFalse,
1893+
PosFlag<SetTrue, [], [ClangOption], "Enable">,
1894+
NegFlag<SetFalse, [], [ClangOption], "Disable">,
1895+
BothFlags<[], [ClangOption, CC1Option], " version-independent external API notes support">>,
1896+
Group<f_clang_Group>;
18911897
def fapinotes_swift_version : Joined<["-"], "fapinotes-swift-version=">,
18921898
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
18931899
MetaVarName<"<version>">,

clang/include/clang/Sema/Sema.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1614,7 +1614,17 @@ class Sema final : public SemaBase {
16141614
///
16151615
/// Triggered by declaration-attribute processing.
16161616
void ProcessAPINotes(Decl *D);
1617-
1617+
/// Apply the 'Nullability:' annotation to the specified declaration
1618+
void ApplyNullability(Decl *D, NullabilityKind Nullability);
1619+
/// Apply the 'Type:' annotation to the specified declaration
1620+
void ApplyAPINotesType(Decl *D, StringRef TypeString);
1621+
1622+
/// Whether APINotes should be gathered for all applicable Swift language
1623+
/// versions, without being applied. Leaving clients of the current module
1624+
/// to select and apply the correct version.
1625+
bool captureSwiftVersionIndependentAPINotes() {
1626+
return APINotes.captureVersionIndependentSwift();
1627+
}
16181628
///@}
16191629

16201630
//

clang/lib/APINotes/APINotesManager.cpp

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

5151
APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts)
52-
: SM(SM), ImplicitAPINotes(LangOpts.APINotes) {}
52+
: SM(SM), ImplicitAPINotes(LangOpts.APINotes),
53+
VersionIndependentSwift(LangOpts.SwiftVersionIndependentAPINotes) {}
5354

5455
APINotesManager::~APINotesManager() {
5556
// 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
@@ -7074,6 +7074,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
70747074
CmdArgs.push_back("-fapinotes-modules");
70757075
Args.AddLastArg(CmdArgs, options::OPT_fapinotes_swift_version);
70767076

7077+
if (Args.hasFlag(options::OPT_fswift_version_independent_apinotes,
7078+
options::OPT_fno_swift_version_independent_apinotes, false))
7079+
CmdArgs.push_back("-fswift-version-independent-apinotes");
7080+
70777081
// -fblocks=0 is default.
70787082
if (Args.hasFlag(options::OPT_fblocks, options::OPT_fno_blocks,
70797083
TC.IsBlocksDefault()) ||

clang/lib/Sema/SemaAPINotes.cpp

Lines changed: 143 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -52,63 +52,59 @@ static bool isIndirectPointerType(QualType Type) {
5252
Pointee->isMemberPointerType();
5353
}
5454

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

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

87-
// Make it a context-sensitive keyword if we can.
88-
if (!isIndirectPointerType(*Modified))
89-
Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
90-
Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
91-
}
92-
} else if (auto Value = dyn_cast<ValueDecl>(D)) {
93-
if (auto Modified = GetModified(D, Value->getType(), Nullability)) {
94-
Value->setType(*Modified);
61+
// Version-independent APINotes add "type" annotations
62+
// with a versioned attribute for the client to select and apply.
63+
if (S.captureSwiftVersionIndependentAPINotes()) {
64+
auto *typeAttr = SwiftTypeAttr::CreateImplicit(
65+
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+
}
9575

96-
// Make it a context-sensitive keyword if we can.
97-
if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
98-
if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified))
99-
Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
100-
Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
101-
}
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;
10296
}
103-
} else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
104-
if (auto Modified = GetModified(D, Property->getType(), Nullability)) {
105-
Property->setType(*Modified, Property->getTypeSourceInfo());
97+
auto *nullabilityAttr = SwiftNullabilityAttr::CreateImplicit(
98+
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;
106106

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

@@ -361,42 +357,105 @@ static bool checkAPINotesReplacementType(Sema &S, SourceLocation Loc,
361357
return false;
362358
}
363359

364-
/// Process API notes for a variable or property.
365-
static void ProcessAPINotes(Sema &S, Decl *D,
366-
const api_notes::VariableInfo &Info,
367-
VersionedInfoMetadata Metadata) {
368-
// Type override.
369-
if (Metadata.IsActive && !Info.getType().empty() &&
370-
S.ParseTypeFromStringCallback) {
371-
auto ParsedType = S.ParseTypeFromStringCallback(
372-
Info.getType(), "<API Notes>", D->getLocation());
360+
void Sema::ApplyAPINotesType(Decl *D, StringRef TypeString) {
361+
if (!TypeString.empty() &&
362+
ParseTypeFromStringCallback) {
363+
auto ParsedType = ParseTypeFromStringCallback(TypeString,
364+
"<API Notes>",
365+
D->getLocation());
373366
if (ParsedType.isUsable()) {
374367
QualType Type = Sema::GetTypeFromParser(ParsedType.get());
375368
auto TypeInfo =
376-
S.Context.getTrivialTypeSourceInfo(Type, D->getLocation());
377-
369+
Context.getTrivialTypeSourceInfo(Type, D->getLocation());
378370
if (auto Var = dyn_cast<VarDecl>(D)) {
379371
// Make adjustments to parameter types.
380372
if (isa<ParmVarDecl>(Var)) {
381-
Type = S.ObjC().AdjustParameterTypeForObjCAutoRefCount(
382-
Type, D->getLocation(), TypeInfo);
383-
Type = S.Context.getAdjustedParameterType(Type);
373+
Type = ObjC().AdjustParameterTypeForObjCAutoRefCount(Type,
374+
D->getLocation(),
375+
TypeInfo);
376+
Type = Context.getAdjustedParameterType(Type);
384377
}
385378

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

401460
// Nullability.
402461
if (auto Nullability = Info.getNullability())
@@ -814,7 +873,8 @@ static void ProcessVersionedAPINotes(
814873
Sema &S, SpecificDecl *D,
815874
const api_notes::APINotesReader::VersionedInfo<SpecificInfo> Info) {
816875

817-
maybeAttachUnversionedSwiftName(S, D, Info);
876+
if (!S.captureSwiftVersionIndependentAPINotes())
877+
maybeAttachUnversionedSwiftName(S, D, Info);
818878

819879
unsigned Selected = Info.getSelected().value_or(Info.size());
820880

@@ -824,10 +884,18 @@ static void ProcessVersionedAPINotes(
824884
std::tie(Version, InfoSlice) = Info[i];
825885
auto Active = (i == Selected) ? IsActive_t::Active : IsActive_t::Inactive;
826886
auto Replacement = IsSubstitution_t::Original;
827-
if (Active == IsActive_t::Inactive && Version.empty()) {
887+
888+
// When collection all APINotes as version-independent,
889+
// capture all as inactive and defer to the client select the
890+
// right one.
891+
if (S.captureSwiftVersionIndependentAPINotes()) {
892+
Active = IsActive_t::Inactive;
893+
Replacement = IsSubstitution_t::Original;
894+
} else if (Active == IsActive_t::Inactive && Version.empty()) {
828895
Replacement = IsSubstitution_t::Replacement;
829896
Version = Info[Selected].first;
830897
}
898+
831899
ProcessAPINotes(S, D, InfoSlice,
832900
VersionedInfoMetadata(Version, Active, Replacement));
833901
}

0 commit comments

Comments
 (0)