Skip to content

Commit 9390283

Browse files
committed
AST: Allow overloads to be disambiguated by obsoletion version.
Previously, whether a declaration is unavailable because it is obsolete was determined based solely on the deployment target and not based on contextual availability. Taking contextual availability into account makes availability checking more internally consistent and allows library authors to evolve APIs by obsoleting the previous declaration while introducing a new declaration in the same version: ``` @available(macOS, obsoleted: 15) func foo(_ x: Int) { } @available(macOS, introduced: 15) func foo(_ x: Int, y: Int = 0) { } foo(42) // unambiguous, regardless of contextual version of macOS ``` This change primarily accepts more code that wasn't accepted previously, but it could also be source breaking for some code that was previously allowed to use obsoleted declarations in contexts that will always run on OS versions where the declaration is obsolete. That code was clearly taking advantage of an availabilty loophole, though, and in practice I don't expect it to be common. Resolves rdar://144647964.
1 parent 6e3361e commit 9390283

6 files changed

+88
-79
lines changed

lib/AST/AvailabilityConstraint.cpp

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,26 +205,23 @@ getAvailabilityConstraintForAttr(const Decl *decl,
205205
auto &ctx = decl->getASTContext();
206206
auto domain = attr.getDomain();
207207
auto deploymentRange = domain.getDeploymentRange(ctx);
208+
bool domainSupportsRefinement = domain.supportsContextRefinement();
209+
std::optional<AvailabilityRange> availableRange =
210+
domainSupportsRefinement ? context.getAvailabilityRange(domain, ctx)
211+
: deploymentRange;
208212

209-
// Is the decl obsoleted in the deployment context?
213+
// Is the decl obsoleted in this context?
210214
if (auto obsoletedRange = attr.getObsoletedRange(ctx)) {
211-
if (deploymentRange && deploymentRange->isContainedIn(*obsoletedRange))
215+
if (availableRange && availableRange->isContainedIn(*obsoletedRange))
212216
return AvailabilityConstraint::unavailableObsolete(attr);
213217
}
214218

215-
// Is the decl not yet introduced in the local context?
219+
// Is the decl not yet introduced in this context?
216220
if (auto introducedRange = attr.getIntroducedRange(ctx)) {
217-
if (domain.supportsContextRefinement()) {
218-
auto availableRange = context.getAvailabilityRange(domain, ctx);
219-
if (!availableRange || !availableRange->isContainedIn(*introducedRange))
220-
return AvailabilityConstraint::unintroduced(attr);
221-
222-
return std::nullopt;
223-
}
224-
225-
// Is the decl not yet introduced in the deployment context?
226-
if (deploymentRange && !deploymentRange->isContainedIn(*introducedRange))
227-
return AvailabilityConstraint::unavailableUnintroduced(attr);
221+
if (!availableRange || !availableRange->isContainedIn(*introducedRange))
222+
return domainSupportsRefinement
223+
? AvailabilityConstraint::unintroduced(attr)
224+
: AvailabilityConstraint::unavailableUnintroduced(attr);
228225
}
229226

230227
return std::nullopt;

lib/AST/AvailabilityContext.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,10 +355,13 @@ void AvailabilityContext::constrainWithDecl(const Decl *decl) {
355355
void AvailabilityContext::constrainWithDeclAndPlatformRange(
356356
const Decl *decl, const AvailabilityRange &otherPlatformRange) {
357357
auto &ctx = decl->getASTContext();
358+
359+
// Constrain the platform range first since this may have an effect on
360+
// whether the decl is considered obsolete.
361+
constrainWithPlatformRange(otherPlatformRange, ctx);
362+
358363
bool isConstrained = false;
359364
auto platformRange = storage->platformRange;
360-
isConstrained |= constrainRange(platformRange, otherPlatformRange);
361-
362365
bool isDeprecated = storage->isDeprecated;
363366
isConstrained |= constrainBool(isDeprecated, decl->isDeprecated());
364367

lib/AST/AvailabilityScopeBuilder.cpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "swift/AST/ASTContext.h"
2020
#include "swift/AST/ASTWalker.h"
21+
#include "swift/AST/AvailabilityConstraint.h"
2122
#include "swift/AST/AvailabilityInference.h"
2223
#include "swift/AST/AvailabilitySpec.h"
2324
#include "swift/AST/Decl.h"
@@ -461,10 +462,17 @@ class AvailabilityScopeBuilder : private ASTWalker {
461462
// As a convenience, explicitly unavailable decls are constrained to the
462463
// deployment target. There's not much benefit to checking these decls at a
463464
// lower availability version floor since they can't be invoked by clients.
464-
if (getCurrentScope()->getAvailabilityContext().isUnavailable() ||
465-
decl->isUnavailable())
465+
auto context = getCurrentScope()->getAvailabilityContext();
466+
if (context.isUnavailable())
466467
return true;
467468

469+
// Check whether the decl is unavailable relative to the current context.
470+
if (auto constraint = getAvailabilityConstraintsForDecl(decl, context)
471+
.getPrimaryConstraint()) {
472+
if (constraint->isUnavailable())
473+
return true;
474+
}
475+
468476
// To remain compatible with a lot of existing SPIs that are declared
469477
// without availability attributes, constrain them to the deployment target
470478
// too.
@@ -584,8 +592,7 @@ class AvailabilityScopeBuilder : private ASTWalker {
584592
}
585593

586594
void buildContextsForBodyOfDecl(Decl *decl) {
587-
// Are we already constrained by the deployment target and the declaration
588-
// doesn't explicitly allow unsafe constructs in its definition, adding
595+
// If we are already constrained by the deployment target then adding
589596
// new contexts won't change availability.
590597
if (isCurrentScopeContainedByDeploymentTarget())
591598
return;
@@ -599,8 +606,10 @@ class AvailabilityScopeBuilder : private ASTWalker {
599606
// Apply deployment-target availability if appropriate for this body.
600607
if (!isCurrentScopeContainedByDeploymentTarget() &&
601608
bodyIsDeploymentTarget(decl)) {
602-
availability.constrainWithPlatformRange(
603-
AvailabilityRange::forDeploymentTarget(Context), Context);
609+
// Also constrain availability with the decl itself to handle the case
610+
// where the decl becomes obsolete at the deployment target.
611+
availability.constrainWithDeclAndPlatformRange(
612+
decl, AvailabilityRange::forDeploymentTarget(Context));
604613
}
605614

606615
nodesAndScopes.push_back(

0 commit comments

Comments
 (0)