Author: Daniel Grumberg Date: 2024-08-15T14:19:49+01:00 New Revision: 026d963cb004689477d2b5798cbba5ad41c25a70
URL: https://github.com/llvm/llvm-project/commit/026d963cb004689477d2b5798cbba5ad41c25a70 DIFF: https://github.com/llvm/llvm-project/commit/026d963cb004689477d2b5798cbba5ad41c25a70.diff LOG: [clang][ExtractAPI] Compute inherited availability information (#103040) Additionally this computes availability information for all platforms ahead of possibly introducing a flag to enable this behavior. rdar://123513706 Added: clang/test/ExtractAPI/inherited_availability.m Modified: clang/include/clang/AST/Availability.h clang/lib/AST/Availability.cpp clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp Removed: ################################################################################ diff --git a/clang/include/clang/AST/Availability.h b/clang/include/clang/AST/Availability.h index 26ae622e5b4496..60ca1383f0a44e 100644 --- a/clang/include/clang/AST/Availability.h +++ b/clang/include/clang/AST/Availability.h @@ -97,6 +97,10 @@ struct AvailabilityInfo { return UnconditionallyUnavailable; } + /// Augments the existing information with additional constraints provided by + /// \c Other. + void mergeWith(AvailabilityInfo Other); + AvailabilityInfo(StringRef Domain, VersionTuple I, VersionTuple D, VersionTuple O, bool U, bool UD, bool UU) : Domain(Domain), Introduced(I), Deprecated(D), Obsoleted(O), diff --git a/clang/lib/AST/Availability.cpp b/clang/lib/AST/Availability.cpp index 238359a2dedfcf..cf040fc727d11f 100644 --- a/clang/lib/AST/Availability.cpp +++ b/clang/lib/AST/Availability.cpp @@ -16,33 +16,104 @@ #include "clang/AST/Decl.h" #include "clang/Basic/TargetInfo.h" -namespace clang { +namespace { + +/// Represents the availability of a symbol across platforms. +struct AvailabilitySet { + bool UnconditionallyDeprecated = false; + bool UnconditionallyUnavailable = false; + + void insert(clang::AvailabilityInfo &&Availability) { + auto *Found = getForPlatform(Availability.Domain); + if (Found) + Found->mergeWith(std::move(Availability)); + else + Availabilities.emplace_back(std::move(Availability)); + } + + clang::AvailabilityInfo *getForPlatform(llvm::StringRef Domain) { + auto *It = llvm::find_if(Availabilities, + [Domain](const clang::AvailabilityInfo &Info) { + return Domain.compare(Info.Domain) == 0; + }); + return It == Availabilities.end() ? nullptr : It; + } -AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *Decl) { - ASTContext &Context = Decl->getASTContext(); - StringRef PlatformName = Context.getTargetInfo().getPlatformName(); - AvailabilityInfo Availability; +private: + llvm::SmallVector<clang::AvailabilityInfo> Availabilities; +}; +static void createInfoForDecl(const clang::Decl *Decl, + AvailabilitySet &Availabilities) { // Collect availability attributes from all redeclarations. for (const auto *RD : Decl->redecls()) { - for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) { - if (A->getPlatform()->getName() != PlatformName) - continue; - Availability = AvailabilityInfo( + for (const auto *A : RD->specific_attrs<clang::AvailabilityAttr>()) { + Availabilities.insert(clang::AvailabilityInfo( A->getPlatform()->getName(), A->getIntroduced(), A->getDeprecated(), - A->getObsoleted(), A->getUnavailable(), false, false); - break; + A->getObsoleted(), A->getUnavailable(), false, false)); } - if (const auto *A = RD->getAttr<UnavailableAttr>()) + if (const auto *A = RD->getAttr<clang::UnavailableAttr>()) if (!A->isImplicit()) - Availability.UnconditionallyUnavailable = true; + Availabilities.UnconditionallyUnavailable = true; - if (const auto *A = RD->getAttr<DeprecatedAttr>()) + if (const auto *A = RD->getAttr<clang::DeprecatedAttr>()) if (!A->isImplicit()) - Availability.UnconditionallyDeprecated = true; + Availabilities.UnconditionallyDeprecated = true; } - return Availability; +} + +} // namespace + +namespace clang { + +void AvailabilityInfo::mergeWith(AvailabilityInfo Other) { + if (isDefault() && Other.isDefault()) + return; + + if (Domain.empty()) + Domain = Other.Domain; + + UnconditionallyUnavailable |= Other.UnconditionallyUnavailable; + UnconditionallyDeprecated |= Other.UnconditionallyDeprecated; + Unavailable |= Other.Unavailable; + + Introduced = std::max(Introduced, Other.Introduced); + + // Default VersionTuple is 0.0.0 so if both are non default let's pick the + // smallest version number, otherwise select the one that is non-zero if there + // is one. + if (!Deprecated.empty() && !Other.Deprecated.empty()) + Deprecated = std::min(Deprecated, Other.Deprecated); + else + Deprecated = std::max(Deprecated, Other.Deprecated); + + if (!Obsoleted.empty() && !Other.Obsoleted.empty()) + Obsoleted = std::min(Obsoleted, Other.Obsoleted); + else + Obsoleted = std::max(Obsoleted, Other.Obsoleted); +} + +AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *D) { + AvailabilitySet Availabilities; + // Walk DeclContexts upwards starting from D to find the combined availability + // of the symbol. + for (const auto *Ctx = D; Ctx; + Ctx = llvm::cast_or_null<Decl>(Ctx->getDeclContext())) + createInfoForDecl(Ctx, Availabilities); + + if (auto *Avail = Availabilities.getForPlatform( + D->getASTContext().getTargetInfo().getPlatformName())) { + Avail->UnconditionallyDeprecated = Availabilities.UnconditionallyDeprecated; + Avail->UnconditionallyUnavailable = + Availabilities.UnconditionallyUnavailable; + return std::move(*Avail); + } + + AvailabilityInfo Avail; + Avail.UnconditionallyDeprecated = Availabilities.UnconditionallyDeprecated; + Avail.UnconditionallyUnavailable = Availabilities.UnconditionallyUnavailable; + return Avail; } } // namespace clang diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp index 6e56ee5b573f66..84ed5467dd2fb9 100644 --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -171,22 +171,25 @@ std::optional<Array> serializeAvailability(const AvailabilityInfo &Avail) { UnconditionallyDeprecated["isUnconditionallyDeprecated"] = true; AvailabilityArray.emplace_back(std::move(UnconditionallyDeprecated)); } - Object Availability; - - Availability["domain"] = Avail.Domain; - - if (Avail.isUnavailable()) { - Availability["isUnconditionallyUnavailable"] = true; - } else { - serializeObject(Availability, "introduced", - serializeSemanticVersion(Avail.Introduced)); - serializeObject(Availability, "deprecated", - serializeSemanticVersion(Avail.Deprecated)); - serializeObject(Availability, "obsoleted", - serializeSemanticVersion(Avail.Obsoleted)); + + if (Avail.Domain.str() != "") { + Object Availability; + Availability["domain"] = Avail.Domain; + + if (Avail.isUnavailable()) { + Availability["isUnconditionallyUnavailable"] = true; + } else { + serializeObject(Availability, "introduced", + serializeSemanticVersion(Avail.Introduced)); + serializeObject(Availability, "deprecated", + serializeSemanticVersion(Avail.Deprecated)); + serializeObject(Availability, "obsoleted", + serializeSemanticVersion(Avail.Obsoleted)); + } + + AvailabilityArray.emplace_back(std::move(Availability)); } - AvailabilityArray.emplace_back(std::move(Availability)); return AvailabilityArray; } diff --git a/clang/test/ExtractAPI/inherited_availability.m b/clang/test/ExtractAPI/inherited_availability.m new file mode 100644 index 00000000000000..c24e7fa8e208f0 --- /dev/null +++ b/clang/test/ExtractAPI/inherited_availability.m @@ -0,0 +1,175 @@ +// RUN: rm -rf %t +// RUN: %clang_cc1 -extract-api --pretty-sgf --emit-sgf-symbol-labels-for-testing -triple arm64-apple-macosx \ +// RUN: -x objective-c-header %s -o %t/output.symbols.json -verify + + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix A +__attribute__((availability(macos, introduced=9.0, deprecated=12.0, obsoleted=20.0))) +@interface A +// A-LABEL: "!testLabel": "c:objc(cs)A" +// A: "availability": [ +// A-NEXT: { +// A-NEXT: "deprecated": { +// A-NEXT: "major": 12, +// A-NEXT: "minor": 0, +// A-NEXT: "patch": 0 +// A-NEXT: } +// A-NEXT: "domain": "macos" +// A-NEXT: "introduced": { +// A-NEXT: "major": 9, +// A-NEXT: "minor": 0, +// A-NEXT: "patch": 0 +// A-NEXT: } +// A-NEXT: "obsoleted": { +// A-NEXT: "major": 20, +// A-NEXT: "minor": 0, +// A-NEXT: "patch": 0 +// A-NEXT: } +// A-NEXT: } +// A-NEXT: ] + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CP +@property(class) int CP; +// CP-LABEL: "!testLabel": "c:objc(cs)A(cpy)CP" +// CP: "availability": [ +// CP-NEXT: { +// CP-NEXT: "deprecated": { +// CP-NEXT: "major": 12, +// CP-NEXT: "minor": 0, +// CP-NEXT: "patch": 0 +// CP-NEXT: } +// CP-NEXT: "domain": "macos" +// CP-NEXT: "introduced": { +// CP-NEXT: "major": 9, +// CP-NEXT: "minor": 0, +// CP-NEXT: "patch": 0 +// CP-NEXT: } +// CP-NEXT: "obsoleted": { +// CP-NEXT: "major": 20, +// CP-NEXT: "minor": 0, +// CP-NEXT: "patch": 0 +// CP-NEXT: } +// CP-NEXT: } +// CP-NEXT: ] + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix IP +@property int IP; +// IP-LABEL: "!testLabel": "c:objc(cs)A(py)IP" +// IP: "availability": [ +// IP-NEXT: { +// IP-NEXT: "deprecated": { +// IP-NEXT: "major": 12, +// IP-NEXT: "minor": 0, +// IP-NEXT: "patch": 0 +// IP-NEXT: } +// IP-NEXT: "domain": "macos" +// IP-NEXT: "introduced": { +// IP-NEXT: "major": 9, +// IP-NEXT: "minor": 0, +// IP-NEXT: "patch": 0 +// IP-NEXT: } +// IP-NEXT: "obsoleted": { +// IP-NEXT: "major": 20, +// IP-NEXT: "minor": 0, +// IP-NEXT: "patch": 0 +// IP-NEXT: } +// IP-NEXT: } +// IP-NEXT: ] + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix MR +@property int moreRestrictive __attribute__((availability(macos, introduced=10.0, deprecated=11.0, obsoleted=19.0))); +// MR-LABEL: "!testLabel": "c:objc(cs)A(py)moreRestrictive" +// MR: "availability": [ +// MR-NEXT: { +// MR-NEXT: "deprecated": { +// MR-NEXT: "major": 11, +// MR-NEXT: "minor": 0, +// MR-NEXT: "patch": 0 +// MR-NEXT: } +// MR-NEXT: "domain": "macos" +// MR-NEXT: "introduced": { +// MR-NEXT: "major": 10, +// MR-NEXT: "minor": 0, +// MR-NEXT: "patch": 0 +// MR-NEXT: } +// MR-NEXT: "obsoleted": { +// MR-NEXT: "major": 19, +// MR-NEXT: "minor": 0, +// MR-NEXT: "patch": 0 +// MR-NEXT: } +// MR-NEXT: } +// MR-NEXT: ] + +@end + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix B +__attribute__((deprecated("B is deprecated"))) +@interface B +// B-LABEL: "!testLabel": "c:objc(cs)B" +// B: "availability": [ +// B-NEXT: { +// B-NEXT: "domain": "*" +// B-NEXT: "isUnconditionallyDeprecated": true +// B-NEXT: } +// B-NEXT: ] + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix BIP +@property int BIP; +// BIP-LABEL: "!testLabel": "c:objc(cs)B(py)BIP" +// BIP: "availability": [ +// BIP-NEXT: { +// BIP-NEXT: "domain": "*" +// BIP-NEXT: "isUnconditionallyDeprecated": true +// BIP-NEXT: } +// BIP-NEXT: ] +@end + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix C +__attribute__((availability(macos, unavailable))) +@interface C +// C-LABEL: "!testLabel": "c:objc(cs)C" +// C: "availability": [ +// C-NEXT: { +// C-NEXT: "domain": "macos" +// C-NEXT: "isUnconditionallyUnavailable": true +// C-NEXT: } +// C-NEXT: ] + +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix CIP +@property int CIP; +// CIP-LABEL: "!testLabel": "c:objc(cs)C(py)CIP" +// CIP: "availability": [ +// CIP-NEXT: { +// CIP-NEXT: "domain": "macos" +// CIP-NEXT: "isUnconditionallyUnavailable": true +// CIP-NEXT: } +// CIP-NEXT: ] +@end + +@interface D +// RUN: FileCheck %s --input-file %t/output.symbols.json --check-prefix DIP +@property int DIP __attribute__((availability(macos, introduced=10.0, deprecated=11.0, obsoleted=19.0))); +// DIP-LABEL: "!testLabel": "c:objc(cs)D(py)DIP" +// DIP: "availability": [ +// DIP-NEXT: { +// DIP-NEXT: "deprecated": { +// DIP-NEXT: "major": 11, +// DIP-NEXT: "minor": 0, +// DIP-NEXT: "patch": 0 +// DIP-NEXT: } +// DIP-NEXT: "domain": "macos" +// DIP-NEXT: "introduced": { +// DIP-NEXT: "major": 10, +// DIP-NEXT: "minor": 0, +// DIP-NEXT: "patch": 0 +// DIP-NEXT: } +// DIP-NEXT: "obsoleted": { +// DIP-NEXT: "major": 19, +// DIP-NEXT: "minor": 0, +// DIP-NEXT: "patch": 0 +// DIP-NEXT: } +// DIP-NEXT: } +// DIP-NEXT: ] +@end + +// expected-no-diagnostics _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits