Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/IRGen/APIGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ static void serialize(llvm::json::OStream &OS, APIAvailability availability) {
OS.attribute("obsoleted", availability.obsoleted);
if (availability.unavailable)
OS.attribute("unavailable", availability.unavailable);
if (availability.spiAvailable)
OS.attribute("SPIAvailable", availability.spiAvailable);
}

static void serialize(llvm::json::OStream &OS, APILinkage linkage) {
Expand Down
4 changes: 3 additions & 1 deletion lib/IRGen/APIGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ struct APIAvailability {
std::string introduced;
std::string obsoleted;
bool unavailable = false;
bool spiAvailable = false;

bool empty() {
return introduced.empty() && obsoleted.empty() && !unavailable;
return introduced.empty() && obsoleted.empty() && !unavailable &&
!spiAvailable;
}
};

Expand Down
18 changes: 12 additions & 6 deletions lib/IRGen/TBDGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ void swift::writeTBDFile(ModuleDecl *M, llvm::raw_ostream &os,
}

class APIGenRecorder final : public APIRecorder {
static bool isSPI(const Decl *decl) {
bool isSPI(const Decl *decl) {
assert(decl);

if (auto value = dyn_cast<ValueDecl>(decl)) {
Expand All @@ -700,7 +700,7 @@ class APIGenRecorder final : public APIRecorder {
return true;
}

return decl->isSPI() || decl->isAvailableAsSPI();
return decl->isSPI() || getAvailability(decl).spiAvailable;
}

public:
Expand Down Expand Up @@ -805,25 +805,31 @@ class APIGenRecorder final : public APIRecorder {
llvm::DenseMap<CategoryNameKey, unsigned> CategoryCounts;

apigen::APIAvailability getAvailability(const Decl *decl) {
std::optional<bool> unavailable;
std::optional<bool> unavailable, spiAvailable;
std::string introduced, obsoleted;
bool hasFallbackUnavailability = false;
bool hasFallbackUnavailability = false, hasFallbackSPIAvailability = false;
auto platform = targetPlatform(module->getASTContext().LangOpts);
for (auto attr : decl->getSemanticAvailableAttrs()) {
const Decl *declForAvailability = decl->getInnermostDeclWithAvailability();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be fine for now, but FWIW getInnermostDeclWithAvailability() is probably not going to give you correct information for 100% of cases. For example:

@available(macOS 26, *)
public struct Outer {
  @available(iOS 26, *)
  public struct Inner {
    public func f()
  }
}

f() is implicitly introduced in macOS 26 but getInnermostDeclWithAvailability() is going to find Inner which only has an attribute for iOS. Correctly finding the attribute that is responsible for the platform availability of a decl on the current platform is a pretty tricky task to get right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's a really good point. Forgot that we are also matching platforms here. But the current code doesn't correctly handle this case as well.
I think we'll have to traverse up until we hit an availability attribute with the matching platform, or the top level context right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, to be clear this seems like an improvement over the existing approach and would be fine to land as-is if you want to make incremental progress. To really make it as accurate as possible, you'll probably need to re-implement something like getSemanticAvailableRangeDeclAndAttr() here. The other (existing) problem with this code is that it doesn't handle the case where availability is inherited from an attribute for a different platform:

// Implicitly @available(visionOS 26, *) too
@available(iOS 26, *)
public struct Outer { }

if (!declForAvailability)
return {};
for (auto attr : declForAvailability->getSemanticAvailableAttrs()) {
if (!attr.isPlatformSpecific()) {
hasFallbackUnavailability = attr.isUnconditionallyUnavailable();
hasFallbackSPIAvailability = attr.isSPI();
continue;
}
if (attr.getPlatform() != platform)
continue;
unavailable = attr.isUnconditionallyUnavailable();
spiAvailable = attr.isSPI();
if (attr.getIntroduced())
introduced = attr.getIntroduced()->getAsString();
if (attr.getObsoleted())
obsoleted = attr.getObsoleted()->getAsString();
}
return {introduced, obsoleted,
unavailable.value_or(hasFallbackUnavailability)};
unavailable.value_or(hasFallbackUnavailability),
spiAvailable.value_or(hasFallbackSPIAvailability)};
}

StringRef getSelectorName(SILDeclRef method, SmallString<128> &buffer) {
Expand Down
70 changes: 47 additions & 23 deletions test/APIJSON/apigen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,15 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: "name": "_$s8MyModule4TestC7method1yyFTj",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule4TestC7method1yyFTq",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule4TestC7method2yyFZTj",
Expand All @@ -153,25 +155,29 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: "name": "_$s8MyModule4TestC7nonObjcyyFTj",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule4TestC7nonObjcyyFTq",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule4TestCACycfC",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule4TestCACycfc",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule4TestCMa",
Expand Down Expand Up @@ -212,7 +218,8 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: "name": "_$s8MyModule4TestCfD",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule5Test2CMa",
Expand Down Expand Up @@ -350,31 +357,36 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC15inheritlyPublicyyF",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlyACSi_tcfC",
// CHECK-NEXT: "access": "private",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlyACSi_tcfCTj",
// CHECK-NEXT: "access": "private",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlyACSi_tcfCTq",
// CHECK-NEXT: "access": "private",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlyACSi_tcfc",
// CHECK-NEXT: "access": "private",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlySivMTj",
Expand All @@ -394,43 +406,50 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlySivgTj",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlySivgTq",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlySivpMV",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlySivsTj",
// CHECK-NEXT: "access": "private",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedC8readOnlySivsTq",
// CHECK-NEXT: "access": "private",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedCACycfC",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedCACycfc",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "_$s8MyModule7DerivedCMa",
Expand Down Expand Up @@ -471,7 +490,8 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: "name": "_$s8MyModule7DerivedCfD",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "linkage": "exported"
// CHECK-NEXT: "linkage": "exported",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "interfaces": [
Expand All @@ -486,12 +506,14 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: {
// CHECK-NEXT: "name": "method1",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift"
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "init",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift"
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "classMethods": [
Expand Down Expand Up @@ -539,12 +561,14 @@ public var myGlobalVar: Int = 42
// CHECK-NEXT: {
// CHECK-NEXT: "name": "method1",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift"
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: },
// CHECK-NEXT: {
// CHECK-NEXT: "name": "init",
// CHECK-NEXT: "access": "public",
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift"
// CHECK-NEXT: "file": "SOURCE_DIR/test/APIJSON/apigen.swift",
// CHECK-NEXT: "introduced": "10.13"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "classMethods": []
Expand Down
Loading