https://github.com/artemcm updated https://github.com/llvm/llvm-project/pull/147405
>From 1e1b8ed2a995f4969782c257dc03713b52839573 Mon Sep 17 00:00:00 2001 From: Artem Chikin <achi...@apple.com> Date: Mon, 7 Jul 2025 13:56:41 -0700 Subject: [PATCH] [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. --- .../include/clang/APINotes/APINotesManager.h | 9 + clang/include/clang/Basic/Attr.td | 20 ++ clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Driver/Options.td | 6 + clang/include/clang/Sema/Sema.h | 12 +- clang/lib/APINotes/APINotesManager.cpp | 3 +- clang/lib/Driver/ToolChains/Clang.cpp | 4 + clang/lib/Sema/SemaAPINotes.cpp | 213 +++++++++++------- .../APINotes/versioned-version-independent.m | 36 +++ 9 files changed, 226 insertions(+), 78 deletions(-) create mode 100644 clang/test/APINotes/versioned-version-independent.m diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h index 98592438e90ea..772fa5faa0f87 100644 --- a/clang/include/clang/APINotes/APINotesManager.h +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -50,6 +50,13 @@ class APINotesManager { /// source file from which an entity was declared. bool ImplicitAPINotes; + /// 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; @@ -167,6 +174,8 @@ 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/Attr.td b/clang/include/clang/Basic/Attr.td index 340f439a45bb9..2d6e144962119 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3055,6 +3055,26 @@ def Regparm : TypeAttr { let ASTNode = 0; } +def SwiftType : Attr { + // This attribute has no spellings as it is only ever created implicitly + // from API notes. + let Spellings = []; + let Args = [StringArgument<"TypeString">]; + let SemaHandler = 0; + let Documentation = [InternalOnly]; +} + +def SwiftNullability : Attr { + // This attribute has no spellings as it is only ever created implicitly + // from API notes. + let Spellings = []; + let Args = [EnumArgument<"Kind", "Kind", /*is_string=*/false, + ["non_null", "nullable", "unspecified", "nullable_result"], + ["NonNull", "Nullable", "Unspecified", "NullableResult"]>]; + let SemaHandler = 0; + let Documentation = [InternalOnly]; +} + def SwiftAsyncName : InheritableAttr { let Spellings = [GNU<"swift_async_name">]; let Args = [StringArgument<"Name">]; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 72321c204ce96..e43238ba683f2 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -390,6 +390,7 @@ LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, Compatible, "retain documentation LANGOPT(APINotes, 1, 0, NotCompatible, "use external API notes") LANGOPT(APINotesModules, 1, 0, NotCompatible, "use module-based external API notes") +LANGOPT(SwiftVersionIndependentAPINotes, 1, 0, NotCompatible, "use external API notes capturing all versions") LANGOPT(SanitizeAddressFieldPadding, 2, 0, NotCompatible, "controls how aggressive is ASan " "field padding (0: none, 1:least " diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 54c71b066f9d4..f4e86d2bd05ca 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1888,6 +1888,12 @@ defm apinotes_modules : BoolOption<"f", "apinotes-modules", NegFlag<SetFalse, [], [ClangOption], "Disable">, BothFlags<[], [ClangOption, CC1Option], " module-based external API notes support">>, Group<f_clang_Group>; +defm swift_version_independent_apinotes : BoolOption<"f", "swift-version-independent-apinotes", + LangOpts<"SwiftVersionIndependentAPINotes">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption], "Enable">, + NegFlag<SetFalse, [], [ClangOption], "Disable">, + BothFlags<[], [ClangOption, CC1Option], " version-independent external API notes support">>, + Group<f_clang_Group>; def fapinotes_swift_version : Joined<["-"], "fapinotes-swift-version=">, Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>, MetaVarName<"<version>">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index b281b1cfef96a..766acc6d7478f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1614,7 +1614,17 @@ class Sema final : public SemaBase { /// /// Triggered by declaration-attribute processing. void ProcessAPINotes(Decl *D); - + /// Apply the 'Nullability:' annotation to the specified declaration + void ApplyNullability(Decl *D, NullabilityKind Nullability); + /// Apply the 'Type:' annotation to the specified declaration + void ApplyAPINotesType(Decl *D, StringRef TypeString); + + /// 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(); + } ///@} // diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp index 4dc6ffd66bd53..60868ab104c46 100644 --- a/clang/lib/APINotes/APINotesManager.cpp +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -49,7 +49,8 @@ class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry { } // namespace APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts) - : SM(SM), ImplicitAPINotes(LangOpts.APINotes) {} + : SM(SM), ImplicitAPINotes(LangOpts.APINotes), + VersionIndependentSwift(LangOpts.SwiftVersionIndependentAPINotes) {} APINotesManager::~APINotesManager() { // Free the API notes readers. diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 7b8120da558f2..1efe3bd53fbfe 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7074,6 +7074,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("-fapinotes-modules"); Args.AddLastArg(CmdArgs, options::OPT_fapinotes_swift_version); + if (Args.hasFlag(options::OPT_fswift_version_independent_apinotes, + options::OPT_fno_swift_version_independent_apinotes, false)) + CmdArgs.push_back("-fswift-version-independent-apinotes"); + // -fblocks=0 is default. if (Args.hasFlag(options::OPT_fblocks, options::OPT_fno_blocks, TC.IsBlocksDefault()) || diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index f21cbbbdb44ee..044abb0ee08a8 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -52,63 +52,58 @@ static bool isIndirectPointerType(QualType Type) { Pointee->isMemberPointerType(); } -/// Apply nullability to the given declaration. -static void applyNullability(Sema &S, Decl *D, NullabilityKind Nullability, - VersionedInfoMetadata Metadata) { - if (!Metadata.IsActive) - return; +static void applyAPINotesType(Sema &S, Decl *decl, StringRef typeString, + VersionedInfoMetadata metadata) { + if (typeString.empty()) - auto GetModified = - [&](Decl *D, QualType QT, - NullabilityKind Nullability) -> std::optional<QualType> { - QualType Original = QT; - S.CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(), - isa<ParmVarDecl>(D), - /*OverrideExisting=*/true); - 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(S.Context.getFunctionType( - *Modified, proto->getParamTypes(), proto->getExtProtoInfo())); - else - Function->setType( - S.Context.getFunctionNoProtoType(*Modified, FnType->getExtInfo())); - } - } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) { - if (auto Modified = GetModified(D, Method->getReturnType(), Nullability)) { - Method->setReturnType(*Modified); + return; - // Make it a context-sensitive keyword if we can. - if (!isIndirectPointerType(*Modified)) - 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)) { - Value->setType(*Modified); + // Version-independent APINotes add "type" annotations + // with a versioned attribute for the client to select and apply. + if (S.captureSwiftVersionIndependentAPINotes()) { + auto *typeAttr = SwiftTypeAttr::CreateImplicit(S.Context, typeString); + auto *versioned = SwiftVersionedAdditionAttr::CreateImplicit( + S.Context, metadata.Version, typeAttr, metadata.IsReplacement); + decl->addAttr(versioned); + } else { + if (!metadata.IsActive) + return; + S.ApplyAPINotesType(decl, typeString); + } +} - // Make it a context-sensitive keyword if we can. - if (auto Parm = dyn_cast<ParmVarDecl>(D)) { - if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified)) - Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier( - Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability)); - } +/// Apply nullability to the given declaration. +static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability, + VersionedInfoMetadata metadata) { + // Version-independent APINotes add "nullability" annotations + // with a versioned attribute for the client to select and apply. + if (S.captureSwiftVersionIndependentAPINotes()) { + SwiftNullabilityAttr::Kind attrNullabilityKind; + switch (nullability) { + case NullabilityKind::NonNull: + attrNullabilityKind = SwiftNullabilityAttr::Kind::NonNull; + break; + case NullabilityKind::Nullable: + attrNullabilityKind = SwiftNullabilityAttr::Kind::Nullable; + break; + case NullabilityKind::Unspecified: + attrNullabilityKind = SwiftNullabilityAttr::Kind::Unspecified; + break; + case NullabilityKind::NullableResult: + attrNullabilityKind = SwiftNullabilityAttr::Kind::NullableResult; + break; } - } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) { - if (auto Modified = GetModified(D, Property->getType(), Nullability)) { - Property->setType(*Modified, Property->getTypeSourceInfo()); + auto *nullabilityAttr = + SwiftNullabilityAttr::CreateImplicit(S.Context, attrNullabilityKind); + auto *versioned = SwiftVersionedAdditionAttr::CreateImplicit( + S.Context, metadata.Version, nullabilityAttr, metadata.IsReplacement); + decl->addAttr(versioned); + return; + } else { + if (!metadata.IsActive) + return; - // Make it a property attribute if we can. - if (!isIndirectPointerType(*Modified)) - Property->setPropertyAttributes( - ObjCPropertyAttribute::kind_null_resettable); - } + S.ApplyNullability(decl, nullability); } } @@ -361,42 +356,99 @@ static bool checkAPINotesReplacementType(Sema &S, SourceLocation Loc, return false; } -/// Process API notes for a variable or property. -static void ProcessAPINotes(Sema &S, Decl *D, - const api_notes::VariableInfo &Info, - VersionedInfoMetadata Metadata) { - // Type override. - if (Metadata.IsActive && !Info.getType().empty() && - S.ParseTypeFromStringCallback) { - auto ParsedType = S.ParseTypeFromStringCallback( - Info.getType(), "<API Notes>", D->getLocation()); +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 = - S.Context.getTrivialTypeSourceInfo(Type, D->getLocation()); - + auto TypeInfo = Context.getTrivialTypeSourceInfo(Type, D->getLocation()); if (auto Var = dyn_cast<VarDecl>(D)) { // Make adjustments to parameter types. if (isa<ParmVarDecl>(Var)) { - Type = S.ObjC().AdjustParameterTypeForObjCAutoRefCount( + Type = ObjC().AdjustParameterTypeForObjCAutoRefCount( Type, D->getLocation(), TypeInfo); - Type = S.Context.getAdjustedParameterType(Type); + Type = Context.getAdjustedParameterType(Type); } - if (!checkAPINotesReplacementType(S, Var->getLocation(), Var->getType(), - Type)) { + if (!checkAPINotesReplacementType(*this, Var->getLocation(), + Var->getType(), Type)) { Var->setType(Type); Var->setTypeSourceInfo(TypeInfo); } - } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) { - if (!checkAPINotesReplacementType(S, Property->getLocation(), - Property->getType(), Type)) - Property->setType(Type, TypeInfo); - - } else + } else if (auto property = dyn_cast<ObjCPropertyDecl>(D)) { + if (!checkAPINotesReplacementType(*this, property->getLocation(), + property->getType(), Type)) { + property->setType(Type, TypeInfo); + } + } else { llvm_unreachable("API notes allowed a type on an unknown declaration"); + } + } + } +} + +void Sema::ApplyNullability(Decl *D, NullabilityKind Nullability) { + auto GetModified = + [&](class Decl *D, QualType QT, + NullabilityKind Nullability) -> std::optional<QualType> { + QualType Original = QT; + CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(), + isa<ParmVarDecl>(D), + /*OverrideExisting=*/true); + 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)) { + Method->setReturnType(*Modified); + + // Make it a context-sensitive keyword if we can. + if (!isIndirectPointerType(*Modified)) + 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)) { + Value->setType(*Modified); + + // Make it a context-sensitive keyword if we can. + 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)) { + Property->setType(*Modified, Property->getTypeSourceInfo()); + + // Make it a property attribute if we can. + if (!isIndirectPointerType(*Modified)) + Property->setPropertyAttributes( + ObjCPropertyAttribute::kind_null_resettable); } } +} + +/// Process API notes for a variable or property. +static void ProcessAPINotes(Sema &S, Decl *D, + const api_notes::VariableInfo &Info, + VersionedInfoMetadata Metadata) { + // Type override. + applyAPINotesType(S, D, Info.getType(), Metadata); // Nullability. if (auto Nullability = Info.getNullability()) @@ -814,7 +866,8 @@ static void ProcessVersionedAPINotes( Sema &S, SpecificDecl *D, const api_notes::APINotesReader::VersionedInfo<SpecificInfo> Info) { - maybeAttachUnversionedSwiftName(S, D, Info); + if (!S.captureSwiftVersionIndependentAPINotes()) + maybeAttachUnversionedSwiftName(S, D, Info); unsigned Selected = Info.getSelected().value_or(Info.size()); @@ -824,10 +877,18 @@ static void ProcessVersionedAPINotes( std::tie(Version, InfoSlice) = Info[i]; auto Active = (i == Selected) ? IsActive_t::Active : IsActive_t::Inactive; auto Replacement = IsSubstitution_t::Original; - if (Active == IsActive_t::Inactive && Version.empty()) { + + // When collection all APINotes as version-independent, + // capture all as inactive and defer to the client select the + // right one. + if (S.captureSwiftVersionIndependentAPINotes()) { + Active = IsActive_t::Inactive; + Replacement = IsSubstitution_t::Original; + } else if (Active == IsActive_t::Inactive && Version.empty()) { Replacement = IsSubstitution_t::Replacement; Version = Info[Selected].first; } + ProcessAPINotes(S, D, InfoSlice, VersionedInfoMetadata(Version, Active, Replacement)); } diff --git a/clang/test/APINotes/versioned-version-independent.m b/clang/test/APINotes/versioned-version-independent.m new file mode 100644 index 0000000000000..da8b34a1d9ba3 --- /dev/null +++ b/clang/test/APINotes/versioned-version-independent.m @@ -0,0 +1,36 @@ +// RUN: rm -rf %t && mkdir -p %t + +// Build and check the module file in version-independent mode. +// RUN: %clang_cc1 -fswift-version-independent-apinotes -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s +// RUN: %clang_cc1 -fswift-version-independent-apinotes -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'DUMP' &> %t/VersionedKit_AST_Dump.txt +// RUN: cat %t/VersionedKit_AST_Dump.txt | FileCheck -check-prefix=CHECK-VERSIONED-DUMP %s + +#import <VersionedKit/VersionedKit.h> + +// CHECK-VERSIONED-DUMP-LABEL: Dumping moveToPointDUMP +// CHECK-VERSIONED-DUMP: SwiftNameAttr {{.+}} "moveTo(x:y:)" +// CHECK-VERSIONED-DUMP-NEXT: SwiftVersionedAdditionAttr {{.+}} Implicit 3.0 +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} <<invalid sloc>> "moveTo(a:b:)" + +// CHECK-VERSIONED-DUMP-LABEL: Dumping unversionedRenameDUMP +// CHECK-VERSIONED-DUMP: SwiftNameAttr {{.+}} "unversionedRename_HEADER()" +// CHECK-VERSIONED-DUMP-NEXT: SwiftVersionedAdditionAttr {{.+}} Implicit 0 +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "unversionedRename_NOTES()" + +// CHECK-VERSIONED-DUMP-LABEL: Dumping TestGenericDUMP +// CHECK-VERSIONED-DUMP: SwiftVersionedAdditionAttr {{.+}} Implicit 3.0 +// CHECK-VERSIONED-DUMP-NEXT: SwiftImportAsNonGenericAttr {{.+}} <<invalid sloc>> + +// CHECK-VERSIONED-DUMP: Swift3RenamedOnlyDUMP +// CHECK-VERSIONED-DUMP: SwiftVersionedAdditionAttr {{.+}} Implicit 3.0 +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "SpecialSwift3Name" + +// CHECK-VERSIONED-DUMP: Swift3RenamedAlsoDUMP +// CHECK-VERSIONED-DUMP: SwiftNameAttr {{.+}} "Swift4Name" +// CHECK-VERSIONED-DUMP-NEXT: SwiftVersionedAdditionAttr {{.+}} Implicit 3.0 +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "SpecialSwift3Also" + +// CHECK-VERSIONED-DUMP: Swift4RenamedDUMP +// CHECK-VERSIONED-DUMP: SwiftVersionedAdditionAttr {{.+}} Implicit 4 +// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "SpecialSwift4Name" + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits