Skip to content

Add initial implementation of a consolidated availability logic - #1674

Merged
d-ronnqvist merged 14 commits into
swiftlang:mainfrom
d-ronnqvist:consolidated-availability
Sep 29, 2026
Merged

d-ronnqvist merged 14 commits into
swiftlang:mainfrom
d-ronnqvist:consolidated-availability

Conversation

@d-ronnqvist

Copy link
Copy Markdown
Contributor

Bug/issue #, if applicable: rdar://172280267

Summary

This PR adds an alternate implementation to DocC's availability logic that's consolidated in one place. This type isn't integrated yet, so it is effectively dead code.

Matching behavior

The new implementation tries quite hard to preserve all behaviors of the current implementation, even when those behaviors are inconsistent or are otherwise bugs. The two exceptions to this are (from AvailabilityTests.swift):

withKnownIssue("iPadOS availability should follow iOS availability (rdar://173704351)") { ...
// FIXME: Some availability logic only happens when symbols have declarations (rdar://172280267)

which were too quirky and inconsistent for me to be able to replicate their fully behaviors.

However, nothing changes until we integrate this new implementation.

No integration yet

With the goal of making this PR smaller and easier to review I haven't included the changes that integrate this new consolidated availability implementation throughout DocC.

However, there's also a risk that by omitting the integration, reviewers may have a harder time seeing the bigger picture.

Let me know if you prefer that I push the integration changes to this PR or to a follow-up PR.

Dependencies

None.

Testing

Nothing in particular unless we prefer to rescope this PR to also include the integration.

Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

  • Added tests
  • Ran the ./bin/test script and it succeeded
  • Updated documentation if necessary

@patshaughnessy patshaughnessy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Amazing work so far - can't wait to see where you go with this!

I assume unit tests will come later? Or will the existing unit test coverage test this automatically when we start using it?

}
}

func preservingBugThatArticlesDoNotDisplayDefaultAvailability() -> Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is the idea we will call this later, for articles only?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, the integration calls this for the common base value for articles.

for module in unifiedGraph.moduleData.values {
guard let knownPlatform = KnownPlatform(module.platform) else {
// We only care about tracking known values for symbol graph's platforms.
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we add a debug assertion here, for the case we ever receive an unknown platform from the unified symbol graph?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No. There can be unknown platforms but AFAICT the current implementation doesn't track them.

Comment thread Sources/SwiftDocC/Model/Availability.swift Outdated
// Any platform that's left as `havePlatformForInSymbolGraph` after this loop indicates a platform that the symbol was excluded from using conditionally compilation.
for selector in unifiedSymbol.allSelectors where selector.interfaceLanguage == languageFilter {
guard let selectorPlatform = KnownPlatform(selector) else {
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here: debug assertion?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. There can be unknown platforms but AFAICT the current implementation doesn't track them.

)
}
} else if let domainName = availability.domain?.rawValue {
customPlatformsByName[domainName] = if availability.isUnconditionallyUnavailable || availability.obsoletedVersion != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah so symbol graphs can contain unknown platforms?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The selector platform and the specified domain name of an in-source attribute are two different things.


/// Finished computing the combined availability information by applying "fallback" behaviors for iPadOS and Mac Catalyst.
mutating func finalizePlatformFallbacks() {
let valueToCopy = knownPlatforms[.iOS]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe add "iOS" into the identifier valuetToCopy to make the following code easier to follow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't know. For me it doesn't matter much to this where the value came from, only that it's the value that's copied for the other fallbacks.

Comment thread Sources/SwiftDocC/Model/Availability.swift Outdated
///
/// A page as a whole is considered to be deprecated if the API is deprecated on all the platforms that the API is available for.
var isDeprecated: Bool {
return knownPlatforms.allSatisfy {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will this work correctly when there are no platforms? Or if all the platforms are unavailable?

Should we add a guard similar to above in isBeta?

@d-ronnqvist d-ronnqvist Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If it doesn't we don't have any test coverage from that elsewhere in DocC.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a list of things we'd like to clean up about the availability behavior once this consolidated logic is merged? Maybe it's worth adding a test for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added two tests for this in cc6941f

Comment thread Sources/SwiftDocC/Model/Availability.swift Outdated
Comment thread Sources/SwiftDocC/Model/Availability.swift
@d-ronnqvist

Copy link
Copy Markdown
Contributor Author

[...] can't wait to see where you go with this!

Would it be easier or harder to review this new implementation if I also pushed the code that integrates it the same PR (compared to posting a follow up PR with the integration changes)?

I assume unit tests will come later? Or will the existing unit test coverage test this automatically when we start using it?

I've been adding tests covering the availability logic for months, primarily in

  • 0d8caef ("Add lots of test covering a variety of current availability behaviors")
  • c3c1b85 ("Add more availability tests")
  • b8a30e3 ("Add even more availability tests")

Like I mentioned in that first PR; the goal of those tests was to gain coverage of the observable behaviors of the current implementation so that the planned new implementation could keep the same behaviors. The idea is that by integrating this new code and using it to compute the values that are observable in the rendered output, we can verify that every behavior that those tests cover remain the same.

@d-ronnqvist
d-ronnqvist marked this pull request as ready for review September 24, 2026 11:36
@d-ronnqvist
d-ronnqvist requested a review from a team as a code owner September 24, 2026 11:36

@mayaepps mayaepps left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks good to me. I personally found it easier to review with just these changes in this PR, rather than also including the integration. You could consider opening a second PR targeting this branch with the integration, if you'd prefer to ultimately merge both into main at the same time.

///
/// - Parameter defaultAvailability: The decoded "default" availability, or `nil` if the inputs don't specify any "default" availability.
init(defaultAvailability: [DefaultAvailability.ModuleAvailability]?) {
knownPlatforms = [Information](repeating: Information(state: .unavailable, source: .initialValue), count: KnownPlatform.wildcard.rawValue + 1 /* once for all known platforms */)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is space-efficiency the reason you're not using a dictionary here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's part of the reason but I'm also trying to avoid unnecessary work of sorting the platforms when there's a known list of values and the expected order is known beforehand.

///
/// This is its own method because this computation only needs to be performed once per module.
/// Alternatively, if `addInSourceAvailability(from:languageFilter:)` was responsible for this task,
/// then the caller couldn't forget to perform this task but it would have to performed once per symbol instead once per module.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit:

Suggested change
/// then the caller couldn't forget to perform this task but it would have to performed once per symbol instead once per module.
/// then the caller couldn't forget to perform this task but it would have to be performed once per symbol instead once per module.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note also the parameter documentation below has an unfinished sentence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both fixed in 43cf666

///
/// - Parameters:
/// - unifiedSymbol: The symbol to read the unified in-source availability annotations from.
/// - languageFilter: The string identifier of a source language, that that container used to restrict the container to only add information that matches the provides which information the container reads from the symbol.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: I'm having a bit of trouble understanding this one, could you re-phrase?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I rephrased this in 43cf666

case "watchos": self = .watchOS
case "tvos": self = .tvOS
case "visionos": self = .visionOS
default: return nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we warn if there is a platform in the symbol graph that we don't know about?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. Similar to this question there can be unknown platforms but AFAIC the old platform didn't care about them. (We also have lots of tests that don't specify a platform for the symbol graph.)

patch = .init(clamping: version.patch)
}

///

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
///

@d-ronnqvist

Copy link
Copy Markdown
Contributor Author

@swift-ci please test

@d-ronnqvist
d-ronnqvist enabled auto-merge (squash) September 29, 2026 08:49
@d-ronnqvist

Copy link
Copy Markdown
Contributor Author

The integration can be merged after this. In the mean time, this will be "dead" / unused code.

@d-ronnqvist
d-ronnqvist merged commit f5511ec into swiftlang:main Sep 29, 2026
42 checks passed
@d-ronnqvist
d-ronnqvist deleted the consolidated-availability branch September 29, 2026 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants