[clang] [APINotes] Prepare Sema for consumer-side versioned note collapse and capture only in module/PCH builds (PR #232743)
Artem Chikin via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 20 06:51:16 PDT 2026
https://github.com/artemcm updated https://github.com/llvm/llvm-project/pull/232743
>From e7b3b8755d5608146f9373aef770b58e6e959788 Mon Sep 17 00:00:00 2001
From: Artem Chikin <achikin at apple.com>
Date: Fri, 25 Sep 2026 11:38:57 +0100
Subject: [PATCH 1/4] [APINotes] Don't give a parameter the API notes of a
same-named global
Sema processes a parameter's attributes, API notes included, while the parameter's `DeclContext` is still the scope around its function. For a function at file scope, that scope is the translation unit. This means `Sema::ProcessAPINotes` looked up the parameter as a global variable, and gave it the notes of any API-noted global with the same name.
A parameter has no API notes entry of its own. Its notes come from its function's entry, and `ProcessAPINotes` applies them with the function. So skip parameters altogether.
---
clang/lib/Sema/SemaAPINotes.cpp | 3 ++
clang/lib/Sema/SemaDeclObjC.cpp | 1 -
.../APINotes/parameter-named-like-global.c | 35 +++++++++++++++++++
3 files changed, 38 insertions(+), 1 deletion(-)
create mode 100644 clang/test/APINotes/parameter-named-like-global.c
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index c934aa2fee04b..0e38d01dbbcdc 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1166,6 +1166,9 @@ static void processExactAPINotes(
void Sema::ProcessAPINotes(Decl *D) {
if (!D)
return;
+ // A parameter's notes come from its function's entry.
+ if (isa<ParmVarDecl>(D))
+ return;
if (!APINotes.hasAPINotes())
return;
auto Readers = APINotes.findAPINotes(D->getLocation());
diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp
index 4a6fc658575d9..38c68b5c5b624 100644
--- a/clang/lib/Sema/SemaDeclObjC.cpp
+++ b/clang/lib/Sema/SemaDeclObjC.cpp
@@ -4832,7 +4832,6 @@ Decl *SemaObjC::ActOnMethodDeclaration(
for (unsigned I = 0; I < Sel.getNumArgs(); ++I) {
ParmVarDecl *Param = ArgInfo[I];
Param->setDeclContext(ObjCMethod);
- SemaRef.ProcessAPINotes(Param);
Params.push_back(Param);
}
diff --git a/clang/test/APINotes/parameter-named-like-global.c b/clang/test/APINotes/parameter-named-like-global.c
new file mode 100644
index 0000000000000..c62dc1617199c
--- /dev/null
+++ b/clang/test/APINotes/parameter-named-like-global.c
@@ -0,0 +1,35 @@
+// A parameter takes API notes from its function's entry, never from a lookup
+// of its own. While a parameter's attributes are processed its DeclContext is
+// still the translation unit, so a lookup would find a global variable of the
+// same name and give the parameter that global's notes.
+
+// RUN: rm -rf %t && split-file %s %t
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -fdisable-module-hash -fapinotes-modules -I %t %t/use.c -ast-dump -ast-dump-filter takesShadow -x c | FileCheck --check-prefix=PARAM %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -fdisable-module-hash -fapinotes-modules -I %t %t/use.c -ast-dump -ast-dump-filter shadow -x c | FileCheck --check-prefix=GLOBAL %s
+
+// PARAM: Dumping takesShadow:
+// PARAM-NEXT: FunctionDecl {{.*}} takesShadow 'void (int *)'
+// PARAM-NEXT: ParmVarDecl {{.*}} shadow 'int *'
+// PARAM-EMPTY:
+
+// The global keeps its own notes.
+// GLOBAL: Dumping shadow:
+// GLOBAL-NEXT: VarDecl {{.*}} shadow 'int *'
+// GLOBAL-NEXT: SwiftPrivateAttr
+
+//--- module.modulemap
+module Shadow { header "Shadow.h" export * }
+
+//--- Shadow.apinotes
+Name: Shadow
+Globals:
+ - Name: shadow
+ SwiftPrivate: true
+
+//--- Shadow.h
+extern int *shadow;
+void takesShadow(int *shadow);
+
+//--- use.c
+#include "Shadow.h"
+void caller(void) { takesShadow(shadow); }
>From 3bae4397c0748fa882f21038b0dc94fa4cfd7883 Mon Sep 17 00:00:00 2001
From: Artem Chikin <achikin at apple.com>
Date: Wed, 23 Sep 2026 11:00:37 +0100
Subject: [PATCH 2/4] [APINotes][NFC] Factor out the kind-erased core of
handleAPINotedAttribute
`handleAPINotedAttribute<A>` uses the attribute's static type in three places:
- The kind that a removal records
- The type that `CreateAttr` returns
= The default displacement check.
Move its body into `handleAPINotedAttributeImpl`, which takes the kind as a value, returns `Attr *`, and needs only the `ASTContext`. The existing template forwards to it. A caller that knows the kind only at run time can then apply an attribute with the default mode's rules, even without `Sema`.
The main complexity is in which existing key a given attribute may displace. Most keys displace the first attribute of the same kind. Three keys
displace something else:
- `UnavailableInSwift`
- `SwiftSafety`
- Retain-count conventions
Each of them is the only key that produces its attribute kinds, so the kind selects the displacement rule. `isDisplacedBy` encodes the rule for each kind, and `findAttrDisplacedBy` replaces the predicate that each call site passed.
---
clang/lib/Sema/SemaAPINotes.cpp | 168 ++++++++++++++++++--------------
1 file changed, 96 insertions(+), 72 deletions(-)
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 0e38d01dbbcdc..e9a2c7d4987c9 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -138,24 +138,84 @@ template <typename A> struct AttrKindFor {};
static const attr::Kind value = attr::X; \
};
#include "clang/Basic/AttrList.inc"
+} // namespace
+
+//------------------------------------------------------------------------------
+// Displacement predicates
+//
+// Most API notes keys displace "the first attribute of the same kind".
+// The following three do not: isDisplacedBy, which every application goes
+// through, dispatches to them by kind.
+//------------------------------------------------------------------------------
+
+/// A Swift-platform availability attribute, which `UnavailableInSwift`
+/// displaces. A non-Swift availability attribute is untouched.
+static bool isSwiftAvailabilityAttr(const Attr *A) {
+ if (const auto *AA = dyn_cast<AvailabilityAttr>(A))
+ if (const auto *II = AA->getPlatform())
+ return II->isStr("swift");
+ return false;
+}
+
+/// A `swift_attr` carrying a safety verdict, which `SwiftSafety` displaces.
+static bool isSwiftSafetyAttr(const Attr *A) {
+ if (const auto *SA = dyn_cast<SwiftAttrAttr>(A))
+ return SA->getAttribute() == "safe" || SA->getAttribute() == "unsafe";
+ return false;
+}
+
+/// Any retain-count convention attribute. The conventions are mutually
+/// exclusive, so setting one displaces whichever of the six is present.
+static bool isRetainCountConventionAttr(const Attr *A) {
+ return isa<CFUnknownTransferAttr, CFReturnsRetainedAttr,
+ CFReturnsNotRetainedAttr, NSReturnsRetainedAttr,
+ NSReturnsNotRetainedAttr, CFAuditedTransferAttr>(A);
+}
+
+/// Whether an API note of kind \p Kind displaces the existing attribute \p A.
+static bool isDisplacedBy(const Attr *A, attr::Kind Kind) {
+ switch (Kind) {
+ case attr::Availability:
+ return isSwiftAvailabilityAttr(A);
+ case attr::SwiftAttr:
+ return isSwiftSafetyAttr(A);
+ case attr::CFUnknownTransfer:
+ case attr::CFReturnsRetained:
+ case attr::CFReturnsNotRetained:
+ case attr::NSReturnsRetained:
+ case attr::NSReturnsNotRetained:
+ return isRetainCountConventionAttr(A);
+ default:
+ return A->getKind() == Kind;
+ }
+}
+
+/// The existing attribute an API note of kind \p Kind displaces, which is the
+/// first one it displaces.
+static Decl::attr_iterator findAttrDisplacedBy(const Decl *D, attr::Kind Kind) {
+ return llvm::find_if(D->attrs(), [Kind](const Attr *Next) {
+ return isDisplacedBy(Next, Kind);
+ });
+}
/// Handle an attribute introduced by API notes.
///
/// \param IsAddition Whether we should add a new attribute
/// (otherwise, we might remove an existing attribute).
+/// \param Kind The kind being added or removed. A removal records it, and it
+/// decides which existing attribute an active slice displaces.
/// \param CreateAttr Create the new attribute to be added.
-template <typename A>
-void handleAPINotedAttribute(
- Sema &S, Decl *D, bool IsAddition, VersionedInfoMetadata Metadata,
- llvm::function_ref<A *()> CreateAttr,
- llvm::function_ref<Decl::attr_iterator(const Decl *)> GetExistingAttr) {
+static void
+handleAPINotedAttributeImpl(ASTContext &Ctx, Decl *D, bool IsAddition,
+ VersionedInfoMetadata Metadata, attr::Kind Kind,
+ llvm::function_ref<Attr *()> CreateAttr) {
if (Metadata.IsActive) {
- auto Existing = GetExistingAttr(D);
+ const auto *Existing = findAttrDisplacedBy(D, Kind);
if (Existing != D->attr_end()) {
// Remove the existing attribute, and treat it as a superseded
// non-versioned attribute.
auto *Versioned = SwiftVersionedAdditionAttr::CreateImplicit(
- S.Context, Metadata.Version, *Existing, /*IsReplacedByActive*/ true,
+ Ctx, Metadata.Version, *Existing, /*IsReplacedByActive*/ true,
Metadata.SliceGroup);
D->getAttrs().erase(Existing);
@@ -164,16 +224,16 @@ void handleAPINotedAttribute(
// If we're supposed to add a new attribute, do so.
if (IsAddition) {
- if (auto Attr = CreateAttr())
+ if (auto *Attr = CreateAttr())
D->addAttr(Attr);
}
return;
}
if (IsAddition) {
- if (auto Attr = CreateAttr()) {
+ if (auto *Attr = CreateAttr()) {
auto *Versioned = SwiftVersionedAdditionAttr::CreateImplicit(
- S.Context, Metadata.Version, Attr,
+ Ctx, Metadata.Version, Attr,
/*IsReplacedByActive*/ Metadata.IsReplacement, Metadata.SliceGroup);
D->addAttr(Versioned);
}
@@ -182,23 +242,20 @@ void handleAPINotedAttribute(
// availability, where we're trying to remove a /specific/ kind of
// attribute.
auto *Versioned = SwiftVersionedRemovalAttr::CreateImplicit(
- S.Context, Metadata.Version, AttrKindFor<A>::value,
+ Ctx, Metadata.Version, Kind,
/*IsReplacedByActive*/ Metadata.IsReplacement, Metadata.SliceGroup);
D->addAttr(Versioned);
}
}
+/// Handle an attribute introduced by API notes, naming it statically.
template <typename A>
-void handleAPINotedAttribute(Sema &S, Decl *D, bool ShouldAddAttribute,
- VersionedInfoMetadata Metadata,
- llvm::function_ref<A *()> CreateAttr) {
- handleAPINotedAttribute<A>(
- S, D, ShouldAddAttribute, Metadata, CreateAttr, [](const Decl *D) {
- return llvm::find_if(D->attrs(),
- [](const Attr *Next) { return isa<A>(Next); });
- });
+static void handleAPINotedAttribute(Sema &S, Decl *D, bool IsAddition,
+ VersionedInfoMetadata Metadata,
+ llvm::function_ref<A *()> CreateAttr) {
+ handleAPINotedAttributeImpl(S.Context, D, IsAddition, Metadata,
+ AttrKindFor<A>::value, CreateAttr);
}
-} // namespace
template <typename A>
static void handleAPINotedRetainCountAttribute(Sema &S, Decl *D,
@@ -206,18 +263,9 @@ static void handleAPINotedRetainCountAttribute(Sema &S, Decl *D,
VersionedInfoMetadata Metadata) {
// The template argument has a default to make the "removal" case more
// concise; it doesn't matter /which/ attribute is being removed.
- handleAPINotedAttribute<A>(
- S, D, ShouldAddAttribute, Metadata,
- [&] { return new (S.Context) A(S.Context, getPlaceholderAttrInfo()); },
- [](const Decl *D) -> Decl::attr_iterator {
- return llvm::find_if(D->attrs(), [](const Attr *Next) -> bool {
- return isa<CFReturnsRetainedAttr>(Next) ||
- isa<CFReturnsNotRetainedAttr>(Next) ||
- isa<NSReturnsRetainedAttr>(Next) ||
- isa<NSReturnsNotRetainedAttr>(Next) ||
- isa<CFAuditedTransferAttr>(Next);
- });
- });
+ handleAPINotedAttribute<A>(S, D, ShouldAddAttribute, Metadata, [&] {
+ return new (S.Context) A(S.Context, getPlaceholderAttrInfo());
+ });
}
static void handleAPINotedRetainCountConvention(
@@ -276,28 +324,17 @@ static void ProcessAPINotes(Sema &S, Decl *D,
}
if (Info.UnavailableInSwift) {
- handleAPINotedAttribute<AvailabilityAttr>(
- S, D, true, Metadata,
- [&] {
- return new (S.Context) AvailabilityAttr(
- S.Context, getPlaceholderAttrInfo(),
- &S.Context.Idents.get("swift"), VersionTuple(), VersionTuple(),
- VersionTuple(),
- /*Unavailable=*/true,
- ASTAllocateString(S.Context, Info.UnavailableMsg),
- /*Strict=*/false,
- /*Replacement=*/StringRef(),
- /*Priority=*/Sema::AP_Explicit,
- /*Environment=*/nullptr);
- },
- [](const Decl *D) {
- return llvm::find_if(D->attrs(), [](const Attr *next) -> bool {
- if (const auto *AA = dyn_cast<AvailabilityAttr>(next))
- if (const auto *II = AA->getPlatform())
- return II->isStr("swift");
- return false;
- });
- });
+ handleAPINotedAttribute<AvailabilityAttr>(S, D, true, Metadata, [&] {
+ return new (S.Context) AvailabilityAttr(
+ S.Context, getPlaceholderAttrInfo(), &S.Context.Idents.get("swift"),
+ VersionTuple(), VersionTuple(), VersionTuple(),
+ /*Unavailable=*/true,
+ ASTAllocateString(S.Context, Info.UnavailableMsg),
+ /*Strict=*/false,
+ /*Replacement=*/StringRef(),
+ /*Priority=*/Sema::AP_Explicit,
+ /*Environment=*/nullptr);
+ });
}
// swift_private
@@ -312,24 +349,11 @@ static void ProcessAPINotes(Sema &S, Decl *D,
// swift_safety
if (auto SafetyKind = Info.getSwiftSafety()) {
bool Addition = *SafetyKind != api_notes::SwiftSafetyKind::Unspecified;
- handleAPINotedAttribute<SwiftAttrAttr>(
- S, D, Addition, Metadata,
- [&] {
- return SwiftAttrAttr::Create(
- S.Context, *SafetyKind == api_notes::SwiftSafetyKind::Safe
- ? "safe"
- : "unsafe");
- },
- [](const Decl *D) {
- return llvm::find_if(D->attrs(), [](const Attr *attr) {
- if (const auto *swiftAttr = dyn_cast<SwiftAttrAttr>(attr)) {
- if (swiftAttr->getAttribute() == "safe" ||
- swiftAttr->getAttribute() == "unsafe")
- return true;
- }
- return false;
- });
- });
+ handleAPINotedAttribute<SwiftAttrAttr>(S, D, Addition, Metadata, [&] {
+ return SwiftAttrAttr::Create(
+ S.Context,
+ *SafetyKind == api_notes::SwiftSafetyKind::Safe ? "safe" : "unsafe");
+ });
}
// swift_name
>From 2beb32e72af35e52b1c29afd72df2c4bbe9fa214 Mon Sep 17 00:00:00 2001
From: Artem Chikin <achikin at apple.com>
Date: Fri, 25 Sep 2026 11:45:46 +0100
Subject: [PATCH 3/4] [APINotes][NFC] Factor Sema-free cores out of type and
nullability notes
To apply a `Type:` or `ResultType:` note, Sema parses the type, adjusts and checks it against the declaration, and installs it. Only the parse needs `Sema`.
Previously, SemaAPINotes had to separate code-paths to do this:
1. `Sema::ApplyAPINotesType`, which handled `Type:` notes on `VarDecl`, `ObjCPropertyDecl`, and `FieldDecl. It took care of calling into the type-parsing code (`ParseTypeFromStringCallback`), and ultimately mutating the decl.
2. In `ProcessAPINotes` inline code handled `ResultType:` notes on `FunctionDecl`sand `ObjCMethodDecl`s, similarly calling into type-parsing code and rebuilding the resulting mutated decl.
This change splits this into 3 different parts:
1. A new `parseAPINotesType` (requires Sema), which handles type parsing for all declaration kinds and builds a type info and returns a `ParsedAPINotesType{Type, TypeInfo}.
2. `applyTypeToDecl` (does not require Sema, requires ASTContext), which installs `{Type, TypeInfo}` onto all relevant declaration kinds.
3. `rebuildFunctionType` (does not require Sema, requires ASTContext), which rebuilds `FD->getType()` and replaces three different places in SemaAPINotes that previously handled `FunctionProtoType`/`FunctionNoProtoType` rebuild.
Nullability annotations, similarly to type annotations directly mutated the decl, when applied.
Previously, SemaAPINotes had:
1. `Sema::ApplyNullability` modified a decl's type by calling `CheckImplicitNullabilityTypeSpecifier`.
2. `CheckImplicitNullabilityTypeSpecifier` forwarded to an overlaod of `CheckNullabilityTypeSpecifier` with `State = nullptr` and `OverrideExisting = true`.
a. This configuration skipped all Sema operations.
This change simplifies it into:
1. `Sema::OverrideImplicitNullability`, which replaces `CheckImplicitNullabilityTypeSpecifier` with a `static` method which takes an `ASTContext` instead of `Sema`.
2. `applyNullabilityToDecl`, which contains the body of `Sema::ApplyNullability`, calling `Sema::OverrideImplicitNullability` and `rebuildFunctionType` using only `ASTContext`.
---
clang/include/clang/Sema/Sema.h | 36 ++----
clang/lib/Sema/SemaAPINotes.cpp | 207 ++++++++++++++++++--------------
clang/lib/Sema/SemaType.cpp | 93 ++++++--------
3 files changed, 164 insertions(+), 172 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 865defa98a1b3..0fcf58add4264 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1693,12 +1693,10 @@ class Sema final : public SemaBase {
/// declaration processed in this translation unit.
void DiagnoseUnusedAPINotesSelectors();
- /// Whether APINotes should be gathered for all applicable Swift language
- /// versions, without being applied. Leaving clients of the current module
- /// to select and apply the correct version.
- bool captureSwiftVersionIndependentAPINotes() {
- return APINotes.captureVersionIndependentSwift();
- }
+ /// Whether API notes should be gathered for all applicable Swift language
+ /// versions, without being applied, leaving clients of the current module to
+ /// select and apply the correct version.
+ bool captureSwiftVersionIndependentAPINotes();
///@}
//
@@ -15546,28 +15544,14 @@ class Sema final : public SemaBase {
// one that came from a typedef.
bool hasExplicitCallingConv(QualType T);
- /// Check whether a nullability type specifier can be added to the given
- /// type through some means not written in source (e.g. API notes).
- ///
- /// \param Type The type to which the nullability specifier will be
- /// added. On success, this type will be updated appropriately.
- ///
- /// \param Nullability The nullability specifier to add.
- ///
- /// \param DiagLoc The location to use for diagnostics.
- ///
- /// \param AllowArrayTypes Whether to accept nullability specifiers on an
- /// array type (e.g., because it will decay to a pointer).
- ///
- /// \param OverrideExisting Whether to override an existing, locally-specified
- /// nullability specifier rather than complaining about the conflict.
+ /// Add \p Nullability to \p Type, replacing any nullability specifier
+ /// \p Type carries locally. Emits no diagnostics, so it needs no Sema.
+ /// Also sued by the collapse of captured version-independent API notes.
///
/// \returns true if nullability cannot be applied, false otherwise.
- bool CheckImplicitNullabilityTypeSpecifier(QualType &Type,
- NullabilityKind Nullability,
- SourceLocation DiagLoc,
- bool AllowArrayTypes,
- bool OverrideExisting);
+ static bool OverrideImplicitNullability(ASTContext &Context, QualType &Type,
+ NullabilityKind Nullability,
+ bool AllowArrayTypes);
/// Check whether the given variable declaration has a size that fits within
/// the address space it is declared in. This issues a diagnostic if not.
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index e9a2c7d4987c9..802cb8894d715 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -58,6 +58,14 @@ static bool isIndirectPointerType(QualType Type) {
Pointee->isMemberPointerType();
}
+namespace {
+/// A replacement type parsed and validated from an API note.
+struct ParsedAPINotesType {
+ QualType Type;
+ TypeSourceInfo *TypeInfo;
+};
+} // namespace
+
static void applyAPINotesType(Sema &S, Decl *decl, StringRef typeString,
VersionedInfoMetadata metadata) {
if (typeString.empty())
@@ -424,69 +432,105 @@ static bool checkAPINotesReplacementType(Sema &S, SourceLocation Loc,
return false;
}
-void Sema::ApplyAPINotesType(Decl *D, StringRef TypeString) {
- if (!TypeString.empty() && ParseTypeFromStringCallback) {
- auto ParsedType = ParseTypeFromStringCallback(TypeString, "<API Notes>",
- D->getLocation());
- if (ParsedType.isUsable()) {
- QualType Type = Sema::GetTypeFromParser(ParsedType.get());
- auto TypeInfo = Context.getTrivialTypeSourceInfo(Type, D->getLocation());
- if (auto Var = dyn_cast<VarDecl>(D)) {
- // Make adjustments to parameter types.
- if (isa<ParmVarDecl>(Var)) {
- Type = ObjC().AdjustParameterTypeForObjCAutoRefCount(
- Type, D->getLocation(), TypeInfo);
- Type = Context.getAdjustedParameterType(Type);
- }
+/// Rebuild \p FD's type from its parameters' types, and from \p ResultType if
+/// given, as ProcessAPINotes does once API notes changed either. An
+/// unprototyped function takes no parameter types, so it is rebuilt only for a
+/// new result type.
+static void rebuildFunctionType(ASTContext &Ctx, FunctionDecl *FD,
+ std::optional<QualType> ResultType) {
+ if (const auto *Proto = FD->getType()->getAs<FunctionProtoType>()) {
+ SmallVector<QualType, 4> ParamTypes;
+ for (const ParmVarDecl *Param : FD->parameters())
+ ParamTypes.push_back(Param->getType());
+ FD->setType(Ctx.getFunctionType(ResultType.value_or(Proto->getReturnType()),
+ ParamTypes, Proto->getExtProtoInfo()));
+ } else if (ResultType) {
+ FD->setType(Ctx.getFunctionNoProtoType(
+ *ResultType,
+ FD->getType()->castAs<FunctionNoProtoType>()->getExtInfo()));
+ }
+}
- if (!checkAPINotesReplacementType(*this, Var->getLocation(),
- Var->getType(), Type)) {
- Var->setType(Type);
- Var->setTypeSourceInfo(TypeInfo);
- }
- } else if (auto property = dyn_cast<ObjCPropertyDecl>(D)) {
- if (!checkAPINotesReplacementType(*this, property->getLocation(),
- property->getType(), Type)) {
- property->setType(Type, TypeInfo);
- }
- } else if (auto field = dyn_cast<FieldDecl>(D)) {
- if (!checkAPINotesReplacementType(*this, field->getLocation(),
- field->getType(), Type)) {
- field->setType(Type);
- field->setTypeSourceInfo(TypeInfo);
- }
- } else {
- llvm_unreachable("API notes allowed a type on an unknown declaration");
- }
+/// Install a replacement type on \p D, as ProcessAPINotes does once it has
+/// parsed, adjusted and checked it; for a function or method, its result type.
+/// Needs no Sema.
+static void applyTypeToDecl(ASTContext &Ctx, Decl *D, QualType Type,
+ TypeSourceInfo *TypeInfo) {
+ if (auto *Var = dyn_cast<VarDecl>(D)) {
+ Var->setType(Type);
+ Var->setTypeSourceInfo(TypeInfo);
+ } else if (auto *Property = dyn_cast<ObjCPropertyDecl>(D)) {
+ Property->setType(Type, TypeInfo);
+ } else if (auto *Field = dyn_cast<FieldDecl>(D)) {
+ Field->setType(Type);
+ Field->setTypeSourceInfo(TypeInfo);
+ } else if (auto *Method = dyn_cast<ObjCMethodDecl>(D)) {
+ Method->setReturnType(Type);
+ Method->setReturnTypeSourceInfo(TypeInfo);
+ } else if (auto *Function = dyn_cast<FunctionDecl>(D)) {
+ rebuildFunctionType(Ctx, Function, Type);
+ } else {
+ llvm_unreachable("API notes allowed a type on an unknown declaration");
+ }
+}
+
+static std::optional<ParsedAPINotesType>
+parseAPINotesType(Sema &S, Decl *D, StringRef TypeString) {
+ if (TypeString.empty() || !S.ParseTypeFromStringCallback)
+ return std::nullopt;
+ auto ParsedType = S.ParseTypeFromStringCallback(TypeString, "<API Notes>",
+ D->getLocation());
+ if (!ParsedType.isUsable())
+ return std::nullopt;
+ QualType Type = Sema::GetTypeFromParser(ParsedType.get());
+ auto *TypeInfo = S.Context.getTrivialTypeSourceInfo(Type, D->getLocation());
+ QualType OrigType;
+ if (auto *Var = dyn_cast<VarDecl>(D)) {
+ // Make adjustments to parameter types.
+ if (isa<ParmVarDecl>(Var)) {
+ Type = S.ObjC().AdjustParameterTypeForObjCAutoRefCount(
+ Type, D->getLocation(), TypeInfo);
+ Type = S.Context.getAdjustedParameterType(Type);
}
+ OrigType = Var->getType();
+ } else if (auto *Property = dyn_cast<ObjCPropertyDecl>(D)) {
+ OrigType = Property->getType();
+ } else if (auto *Field = dyn_cast<FieldDecl>(D)) {
+ OrigType = Field->getType();
+ } else if (auto *Method = dyn_cast<ObjCMethodDecl>(D)) {
+ OrigType = Method->getReturnType();
+ } else if (auto *Function = dyn_cast<FunctionDecl>(D)) {
+ OrigType = Function->getReturnType();
+ } else {
+ llvm_unreachable("API notes allowed a type on an unknown declaration");
}
+ if (checkAPINotesReplacementType(S, D->getLocation(), OrigType, Type))
+ return std::nullopt;
+ return ParsedAPINotesType{Type, TypeInfo};
}
-void Sema::ApplyNullability(Decl *D, NullabilityKind Nullability) {
- auto GetModified =
- [&](class Decl *D, QualType QT,
- NullabilityKind Nullability) -> std::optional<QualType> {
+void Sema::ApplyAPINotesType(Decl *D, StringRef TypeString) {
+ if (auto Parsed = parseAPINotesType(*this, D, TypeString))
+ applyTypeToDecl(Context, D, Parsed->Type, Parsed->TypeInfo);
+}
+
+/// Apply 'Nullability:' to \p D, as Sema::ApplyNullability does. Needs no
+/// Sema.
+static void applyNullabilityToDecl(ASTContext &Context, Decl *D,
+ NullabilityKind Nullability) {
+ auto GetModified = [&](QualType QT) -> std::optional<QualType> {
QualType Original = QT;
- CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(),
- isa<ParmVarDecl>(D),
- /*OverrideExisting=*/true);
+ Sema::OverrideImplicitNullability(Context, QT, Nullability,
+ isa<ParmVarDecl>(D));
return (QT.getTypePtr() != Original.getTypePtr()) ? std::optional(QT)
: std::nullopt;
};
- if (auto Function = dyn_cast<FunctionDecl>(D)) {
- if (auto Modified =
- GetModified(D, Function->getReturnType(), Nullability)) {
- const FunctionType *FnType = Function->getType()->castAs<FunctionType>();
- if (const FunctionProtoType *proto = dyn_cast<FunctionProtoType>(FnType))
- Function->setType(Context.getFunctionType(
- *Modified, proto->getParamTypes(), proto->getExtProtoInfo()));
- else
- Function->setType(
- Context.getFunctionNoProtoType(*Modified, FnType->getExtInfo()));
- }
- } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
- if (auto Modified = GetModified(D, Method->getReturnType(), Nullability)) {
+ if (auto *Function = dyn_cast<FunctionDecl>(D)) {
+ if (auto Modified = GetModified(Function->getReturnType()))
+ rebuildFunctionType(Context, Function, *Modified);
+ } else if (auto *Method = dyn_cast<ObjCMethodDecl>(D)) {
+ if (auto Modified = GetModified(Method->getReturnType())) {
Method->setReturnType(*Modified);
// Make it a context-sensitive keyword if we can.
@@ -494,19 +538,19 @@ void Sema::ApplyNullability(Decl *D, NullabilityKind Nullability) {
Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
}
- } else if (auto Value = dyn_cast<ValueDecl>(D)) {
- if (auto Modified = GetModified(D, Value->getType(), Nullability)) {
+ } else if (auto *Value = dyn_cast<ValueDecl>(D)) {
+ if (auto Modified = GetModified(Value->getType())) {
Value->setType(*Modified);
// Make it a context-sensitive keyword if we can.
- if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
+ if (auto *Parm = dyn_cast<ParmVarDecl>(D)) {
if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified))
Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
}
}
- } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
- if (auto Modified = GetModified(D, Property->getType(), Nullability)) {
+ } else if (auto *Property = dyn_cast<ObjCPropertyDecl>(D)) {
+ if (auto Modified = GetModified(Property->getType())) {
Property->setType(*Modified, Property->getTypeSourceInfo());
// Make it a property attribute if we can.
@@ -517,6 +561,10 @@ void Sema::ApplyNullability(Decl *D, NullabilityKind Nullability) {
}
}
+void Sema::ApplyNullability(Decl *D, NullabilityKind Nullability) {
+ applyNullabilityToDecl(Context, D, Nullability);
+}
+
/// Process API notes for a variable or property.
static void ProcessAPINotes(Sema &S, Decl *D,
const api_notes::VariableInfo &Info,
@@ -649,25 +697,13 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc,
addSwiftAttrIfAbsent(S, D, "returns_" + Info.SwiftReturnOwnership);
// Result type override.
- QualType OverriddenResultType;
- if (Metadata.IsActive && !Info.ResultType.empty() &&
- S.ParseTypeFromStringCallback) {
- auto ParsedType = S.ParseTypeFromStringCallback(
- Info.ResultType, "<API Notes>", D->getLocation());
- if (ParsedType.isUsable()) {
- QualType ResultType = Sema::GetTypeFromParser(ParsedType.get());
-
+ std::optional<QualType> OverriddenResultType;
+ if (Metadata.IsActive) {
+ if (auto Parsed = parseAPINotesType(S, D, Info.ResultType)) {
if (MD) {
- if (!checkAPINotesReplacementType(S, D->getLocation(),
- MD->getReturnType(), ResultType)) {
- auto ResultTypeInfo =
- S.Context.getTrivialTypeSourceInfo(ResultType, D->getLocation());
- MD->setReturnType(ResultType);
- MD->setReturnTypeSourceInfo(ResultTypeInfo);
- }
- } else if (!checkAPINotesReplacementType(
- S, FD->getLocation(), FD->getReturnType(), ResultType)) {
- OverriddenResultType = ResultType;
+ applyTypeToDecl(S.Context, MD, Parsed->Type, Parsed->TypeInfo);
+ } else {
+ OverriddenResultType = Parsed->Type;
AnyTypeChanged = true;
}
}
@@ -675,23 +711,8 @@ static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc,
// If the result type or any of the parameter types changed for a function
// declaration, we have to rebuild the type.
- if (FD && AnyTypeChanged) {
- if (const auto *fnProtoType = FD->getType()->getAs<FunctionProtoType>()) {
- if (OverriddenResultType.isNull())
- OverriddenResultType = fnProtoType->getReturnType();
-
- SmallVector<QualType, 4> ParamTypes;
- for (auto Param : FD->parameters())
- ParamTypes.push_back(Param->getType());
-
- FD->setType(S.Context.getFunctionType(OverriddenResultType, ParamTypes,
- fnProtoType->getExtProtoInfo()));
- } else if (!OverriddenResultType.isNull()) {
- const auto *FnNoProtoType = FD->getType()->castAs<FunctionNoProtoType>();
- FD->setType(S.Context.getFunctionNoProtoType(
- OverriddenResultType, FnNoProtoType->getExtInfo()));
- }
- }
+ if (FD && AnyTypeChanged)
+ rebuildFunctionType(S.Context, FD, OverriddenResultType);
// Retain count convention
handleAPINotedRetainCountConvention(S, D, Metadata,
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 3d444bfabc8c3..19e67c1110cdd 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -7512,13 +7512,15 @@ static NullabilityKind mapNullabilityAttrKind(ParsedAttr::Kind kind) {
}
}
-static bool CheckNullabilityTypeSpecifier(
- Sema &S, TypeProcessingState *State, ParsedAttr *PAttr, QualType &QT,
- NullabilityKind Nullability, SourceLocation NullabilityLoc,
- bool IsContextSensitive, bool AllowOnArrayType, bool OverrideExisting) {
- bool Implicit = (State == nullptr);
- if (!Implicit)
- recordNullabilitySeen(S, NullabilityLoc);
+static bool CheckNullabilityTypeSpecifier(TypeProcessingState &State,
+ QualType &QT, ParsedAttr &PAttr,
+ bool AllowOnArrayType) {
+ NullabilityKind Nullability = mapNullabilityAttrKind(PAttr.getKind());
+ SourceLocation NullabilityLoc = PAttr.getLoc();
+ bool IsContextSensitive = PAttr.isContextSensitiveKeywordAttribute();
+
+ Sema &S = State.getSema();
+ recordNullabilitySeen(S, NullabilityLoc);
// Check for existing nullability attributes on the type.
QualType Desugared = QT;
@@ -7527,9 +7529,6 @@ static bool CheckNullabilityTypeSpecifier(
if (auto ExistingNullability = Attributed->getImmediateNullability()) {
// Duplicated nullability.
if (Nullability == *ExistingNullability) {
- if (Implicit)
- break;
-
S.Diag(NullabilityLoc, diag::warn_nullability_duplicate)
<< DiagNullabilityKind(Nullability, IsContextSensitive)
<< FixItHint::CreateRemoval(NullabilityLoc);
@@ -7537,16 +7536,11 @@ static bool CheckNullabilityTypeSpecifier(
break;
}
- if (!OverrideExisting) {
- // Conflicting nullability.
- S.Diag(NullabilityLoc, diag::err_nullability_conflicting)
- << DiagNullabilityKind(Nullability, IsContextSensitive)
- << DiagNullabilityKind(*ExistingNullability, false);
- return true;
- }
-
- // Rebuild the attributed type, dropping the existing nullability.
- QT = rebuildAttributedTypeWithoutNullability(S.Context, QT);
+ // Conflicting nullability.
+ S.Diag(NullabilityLoc, diag::err_nullability_conflicting)
+ << DiagNullabilityKind(Nullability, IsContextSensitive)
+ << DiagNullabilityKind(*ExistingNullability, false);
+ return true;
}
Desugared = Attributed->getModifiedType();
@@ -7557,13 +7551,13 @@ static bool CheckNullabilityTypeSpecifier(
// have nullability specifiers on them, which means we cannot
// provide a useful Fix-It.
if (auto ExistingNullability = Desugared->getNullability()) {
- if (Nullability != *ExistingNullability && !Implicit) {
+ if (Nullability != *ExistingNullability) {
S.Diag(NullabilityLoc, diag::err_nullability_conflicting)
<< DiagNullabilityKind(Nullability, IsContextSensitive)
<< DiagNullabilityKind(*ExistingNullability, false);
// Try to find the typedef with the existing nullability specifier.
- if (auto TT = Desugared->getAs<TypedefType>()) {
+ if (const auto *TT = Desugared->getAs<TypedefType>()) {
TypedefNameDecl *typedefDecl = TT->getDecl();
QualType underlyingType = typedefDecl->getUnderlyingType();
if (auto typedefNullability =
@@ -7582,9 +7576,8 @@ static bool CheckNullabilityTypeSpecifier(
// If this definitely isn't a pointer type, reject the specifier.
if (!Desugared->canHaveNullability() &&
!(AllowOnArrayType && Desugared->isArrayType())) {
- if (!Implicit)
- S.Diag(NullabilityLoc, diag::err_nullability_nonpointer)
- << DiagNullabilityKind(Nullability, IsContextSensitive) << QT;
+ S.Diag(NullabilityLoc, diag::err_nullability_nonpointer)
+ << DiagNullabilityKind(Nullability, IsContextSensitive) << QT;
return true;
}
@@ -7613,37 +7606,31 @@ static bool CheckNullabilityTypeSpecifier(
}
// Form the attributed type.
- if (State) {
- assert(PAttr);
- Attr *A = createNullabilityAttr(S.Context, *PAttr, Nullability);
- QT = State->getAttributedType(A, QT, QT);
- } else {
- QT = S.Context.getAttributedType(Nullability, QT, QT);
- }
+ Attr *A = createNullabilityAttr(S.Context, PAttr, Nullability);
+ QT = State.getAttributedType(A, QT, QT);
return false;
}
-static bool CheckNullabilityTypeSpecifier(TypeProcessingState &State,
- QualType &Type, ParsedAttr &Attr,
- bool AllowOnArrayType) {
- NullabilityKind Nullability = mapNullabilityAttrKind(Attr.getKind());
- SourceLocation NullabilityLoc = Attr.getLoc();
- bool IsContextSensitive = Attr.isContextSensitiveKeywordAttribute();
-
- return CheckNullabilityTypeSpecifier(State.getSema(), &State, &Attr, Type,
- Nullability, NullabilityLoc,
- IsContextSensitive, AllowOnArrayType,
- /*overrideExisting*/ false);
-}
-
-bool Sema::CheckImplicitNullabilityTypeSpecifier(QualType &Type,
- NullabilityKind Nullability,
- SourceLocation DiagLoc,
- bool AllowArrayTypes,
- bool OverrideExisting) {
- return CheckNullabilityTypeSpecifier(
- *this, nullptr, nullptr, Type, Nullability, DiagLoc,
- /*isContextSensitive*/ false, AllowArrayTypes, OverrideExisting);
+bool Sema::OverrideImplicitNullability(ASTContext &Context, QualType &Type,
+ NullabilityKind Nullability,
+ bool AllowArrayTypes) {
+ // Drop the nullability the type carries locally, unless it is this one.
+ QualType Desugared = Type;
+ while (auto *Attributed = dyn_cast<AttributedType>(Desugared.getTypePtr())) {
+ if (auto ExistingNullability = Attributed->getImmediateNullability()) {
+ if (Nullability == *ExistingNullability)
+ return false;
+ Type = rebuildAttributedTypeWithoutNullability(Context, Type);
+ }
+ Desugared = Attributed->getModifiedType();
+ }
+
+ if (!Desugared->canHaveNullability() &&
+ !(AllowArrayTypes && Desugared->isArrayType()))
+ return true;
+
+ Type = Context.getAttributedType(Nullability, Type, Type);
+ return false;
}
bool Sema::CheckVarDeclSizeAddressSpace(const VarDecl *VD, LangAS AS) {
>From 3d60c44f7bddf8c7e46041cbff6956c86f20899d Mon Sep 17 00:00:00 2001
From: Artem Chikin <achikin at apple.com>
Date: Tue, 20 Oct 2026 14:46:41 +0100
Subject: [PATCH 4/4] [APINotes] Capture version-independent API notes only in
module and PCH builds
`-fswift-version-independent-apinotes` exists so that one module file can serve Swift clients at every language version. Only module and PCH builds produce such files.
Add `LangOptions::capturesVersionIndependentAPINotes`, and capture only when it is true. Every other compilation applies API notes as the default mode does.
A later change collapses captured notes as a compilation reads them. It asks the same predicate, so one flag covers both roles. An implicit module build needs this, because one invocation both builds modules and reads them.
---
.../include/clang/APINotes/APINotesManager.h | 9 ----
clang/include/clang/Basic/LangOptions.h | 8 +++
clang/lib/APINotes/APINotesManager.cpp | 3 +-
clang/lib/Sema/SemaAPINotes.cpp | 7 +++
.../version-independent-translation-unit.c | 52 +++++++++++++++++++
5 files changed, 68 insertions(+), 11 deletions(-)
create mode 100644 clang/test/APINotes/version-independent-translation-unit.c
diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h
index aaf48706fb26b..fbadeb9991eba 100644
--- a/clang/include/clang/APINotes/APINotesManager.h
+++ b/clang/include/clang/APINotes/APINotesManager.h
@@ -55,13 +55,6 @@ class APINotesManager {
/// a compilation, so it can be tested per-declaration without recomputing.
bool HasAPINotes;
- /// Whether to apply all APINotes as optionally-applied versioned
- /// entities. This means that when building a Clang module,
- /// we capture every note on a given decl wrapped in a SwiftVersionedAttr
- /// (with an empty version field for unversioned notes), and have the
- /// client apply the relevant version's notes.
- bool VersionIndependentSwift;
-
/// The Swift version to use when interpreting versioned API notes.
llvm::VersionTuple SwiftVersion;
@@ -181,8 +174,6 @@ class APINotesManager {
/// Find the API notes readers that correspond to the given source location.
llvm::SmallVector<APINotesReader *, 2> findAPINotes(SourceLocation Loc);
-
- bool captureVersionIndependentSwift() { return VersionIndependentSwift; }
};
} // end namespace api_notes
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 160c53fed4935..b9a8151c4f98b 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -668,6 +668,14 @@ class LangOptions : public LangOptionsBase {
return !isCompilingModule() && !ModuleName.empty();
}
+ /// Does this compilation capture API notes for every Swift version, leaving
+ /// its importers to select and apply them.
+ /// Only module or PCH build under -fswift-version-independent-apinotes does.
+ bool capturesVersionIndependentAPINotes(bool GeneratingPreamble) const {
+ return SwiftVersionIndependentAPINotes &&
+ (isCompilingModule() || (CompilingPCH && !GeneratingPreamble));
+ }
+
/// Do we need to track the owning module for a local declaration?
bool trackLocalOwningModule() const {
return isCompilingModule() || ModulesLocalVisibility;
diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp
index 5c1bb41d804e9..1ce51b6f741c1 100644
--- a/clang/lib/APINotes/APINotesManager.cpp
+++ b/clang/lib/APINotes/APINotesManager.cpp
@@ -50,8 +50,7 @@ class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry {
APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts)
: SM(SM), ImplicitAPINotes(LangOpts.APINotes),
- HasAPINotes(LangOpts.APINotes),
- VersionIndependentSwift(LangOpts.SwiftVersionIndependentAPINotes) {}
+ HasAPINotes(LangOpts.APINotes) {}
APINotesManager::~APINotesManager() {
// Free the API notes readers.
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 802cb8894d715..d9d529991422b 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -21,6 +21,8 @@
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Lexer.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Sema/SemaObjC.h"
#include "clang/Sema/SemaSwift.h"
#include <stack>
@@ -1560,3 +1562,8 @@ void Sema::DiagnoseUnusedAPINotesSelectors() {
APINotesSelectorDiagnostics->diagnoseUnused(*this);
APINotesSelectorDiagnostics.reset();
}
+
+bool Sema::captureSwiftVersionIndependentAPINotes() {
+ return getLangOpts().capturesVersionIndependentAPINotes(
+ PP.getPreprocessorOpts().GeneratePreamble);
+}
diff --git a/clang/test/APINotes/version-independent-translation-unit.c b/clang/test/APINotes/version-independent-translation-unit.c
new file mode 100644
index 0000000000000..f2d929ec6f3b0
--- /dev/null
+++ b/clang/test/APINotes/version-independent-translation-unit.c
@@ -0,0 +1,52 @@
+// A translation unit that does not build a module applies API notes normally
+// under -fswift-version-independent-apinotes. It has one Swift version, so
+// there is no module file to serve several versions and nothing to capture for.
+// The same checks run against the default mode.
+
+// RUN: rm -rf %t && split-file %s %t
+// RUN: %clang_cc1 -fapinotes -fswift-version-independent-apinotes -fapinotes-swift-version=4 -I %t %t/tu.c -ast-dump -ast-dump-filter tuRenamed -x c | FileCheck %s
+// RUN: %clang_cc1 -fapinotes -fapinotes-swift-version=4 -I %t %t/tu.c -ast-dump -ast-dump-filter tuRenamed -x c | FileCheck %s
+
+// The 4.0 slice is applied and the unversioned one superseded. No slice
+// markers, which only a capturing module build emits.
+// CHECK: Dumping tuRenamed:
+// CHECK: SwiftVersionedAdditionAttr {{.*}} Implicit 4.0 IsReplacedByActive 0{{$}}
+// CHECK-NEXT: SwiftNameAttr {{.*}} "tuUnversioned()"
+// CHECK-NEXT: SwiftNameAttr {{.*}} "tuV4()"
+// CHECK-NOT: SwiftVersionedSliceAttr
+
+// A precompiled preamble is the start of its translation unit, so it applies
+// API notes too, and its inline body can't use what they make unavailable.
+// RUN: env CINDEXTEST_EDITING=1 c-index-test -test-load-source-reparse 1 local %t/preamble.c -fapinotes -Xclang -fswift-version-independent-apinotes -Xclang -fapinotes-swift-version=5 -I %t 2>&1 | FileCheck --check-prefix=PREAMBLE %s
+// RUN: env CINDEXTEST_EDITING=1 c-index-test -test-load-source-reparse 1 local %t/preamble.c -fapinotes -Xclang -fapinotes-swift-version=5 -I %t 2>&1 | FileCheck --check-prefix=PREAMBLE %s
+// Once for the first parse, and once for the reparse, which uses the preamble.
+// PREAMBLE: Preamble.h:2:{{[0-9]+}}: error: 'preambleGone' is unavailable: gone
+// PREAMBLE: Preamble.h:2:{{[0-9]+}}: error: 'preambleGone' is unavailable: gone
+
+//--- APINotes.apinotes
+Name: TranslationUnit
+Functions:
+ - Name: tuRenamed
+ SwiftName: 'tuUnversioned()'
+ - Name: preambleGone
+ Availability: none
+ AvailabilityMsg: 'gone'
+SwiftVersions:
+ - Version: 4.0
+ Functions:
+ - Name: tuRenamed
+ SwiftName: 'tuV4()'
+
+//--- TranslationUnit.h
+void tuRenamed(void);
+
+//--- tu.c
+#include "TranslationUnit.h"
+
+//--- Preamble.h
+void preambleGone(void);
+static inline void preambleUser(void) { preambleGone(); }
+
+//--- preamble.c
+#include "Preamble.h"
+int main(void) { return 0; }
More information about the cfe-commits
mailing list