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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
