Skip to content

Commit 8ab7f54

Browse files
committed
AST/Sema: Favor AvailabilityContext over VersionTuple/VersionRange.
For the purposes of availability calculations, direct use of `llvm::VersionTuple` and `VersionRange` is discouraged, since these fundamental version representations are divorced from their context. For example, comparing an iOS platform version to a visionOS platform version is invalid since the versioning systems of the two platforms differ. Although visionOS inherits avialability from iOS, an iOS version must be converted to a visionOS version prior to comparison. In the future, `AvailabilityContext` can be enriched to carry the information necessary to verify that its algebraic operations are being performed on compatible values. NFC.
1 parent 0bf3aa9 commit 8ab7f54

File tree

8 files changed

+104
-102
lines changed

8 files changed

+104
-102
lines changed

include/swift/AST/Availability.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,20 @@ class AvailabilityContext {
238238
}
239239

240240
/// Returns the range of possible versions required by this context.
241-
VersionRange getVersionRange() const { return Range; }
241+
VersionRange getRawVersionRange() const { return Range; }
242+
243+
/// Returns true if there is a version tuple for this context.
244+
bool hasMinimumVersion() const { return Range.hasLowerEndpoint(); }
245+
246+
/// Returns the minimum version required by this context. This convenience
247+
/// is meant for debugging, diagnostics, serialization, etc. Use of the set
248+
/// algebra operations on `AvailabilityContext` should be preferred over
249+
/// direct comparison of raw versions.
250+
///
251+
/// Only call when `hasMinimumVersion()` returns true.
252+
llvm::VersionTuple getRawMinimumVersion() const {
253+
return Range.getLowerEndpoint();
254+
}
242255

243256
/// Returns true if \p other makes stronger guarantees than this context.
244257
///

lib/ClangImporter/ImportDecl.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ static void applyAvailableAttribute(Decl *decl, AvailabilityContext &info,
541541
/*Message=*/StringRef(),
542542
/*Rename=*/StringRef(),
543543
/*RenameDecl=*/nullptr,
544-
info.getVersionRange().getLowerEndpoint(),
544+
info.getRawMinimumVersion(),
545545
/*IntroducedRange*/SourceRange(),
546546
/*Deprecated=*/noVersion,
547547
/*DeprecatedRange*/SourceRange(),
@@ -2798,13 +2798,13 @@ namespace {
27982798
if (auto classDecl = dyn_cast<ClassDecl>(result)) {
27992799
validateForeignReferenceType(decl, classDecl);
28002800

2801-
auto ctx = Impl.SwiftContext.getSwift58Availability();
2802-
if (!ctx.isAlwaysAvailable()) {
2803-
assert(ctx.getVersionRange().hasLowerEndpoint());
2801+
auto availability = Impl.SwiftContext.getSwift58Availability();
2802+
if (!availability.isAlwaysAvailable()) {
2803+
assert(availability.hasMinimumVersion());
28042804
auto AvAttr = new (Impl.SwiftContext) AvailableAttr(
28052805
SourceLoc(), SourceRange(),
28062806
targetPlatform(Impl.SwiftContext.LangOpts), "", "",
2807-
/*RenameDecl=*/nullptr, ctx.getVersionRange().getLowerEndpoint(),
2807+
/*RenameDecl=*/nullptr, availability.getRawMinimumVersion(),
28082808
/*IntroducedRange=*/SourceRange(), {},
28092809
/*DeprecatedRange=*/SourceRange(), {},
28102810
/*ObsoletedRange=*/SourceRange(),
@@ -6615,9 +6615,9 @@ bool SwiftDeclConverter::existingConstructorIsWorse(
66156615
if (!introduced.empty())
66166616
return false;
66176617
} else {
6618-
VersionRange existingIntroduced = existingAvailability.getVersionRange();
6619-
if (introduced != existingIntroduced.getLowerEndpoint()) {
6620-
return introduced < existingIntroduced.getLowerEndpoint();
6618+
auto existingIntroduced = existingAvailability.getRawMinimumVersion();
6619+
if (introduced != existingIntroduced) {
6620+
return introduced < existingIntroduced;
66216621
}
66226622
}
66236623

lib/IRGen/GenMeta.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5116,8 +5116,8 @@ diagnoseUnsupportedObjCImplLayout(IRGenModule &IGM, ClassDecl *classDecl,
51165116
field.getVarDecl(),
51175117
diag::attr_objc_implementation_resilient_property_deployment_target,
51185118
prettyPlatformString(targetPlatform(ctx.LangOpts)),
5119-
currentAvailability.getVersionRange().getLowerEndpoint(),
5120-
requiredAvailability.getVersionRange().getLowerEndpoint());
5119+
currentAvailability.getRawMinimumVersion(),
5120+
requiredAvailability.getRawMinimumVersion());
51215121
else
51225122
diags.diagnose(
51235123
field.getVarDecl(),

lib/Sema/TypeCheckAttr.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,7 @@ visitObjCImplementationAttr(ObjCImplementationAttr *attr) {
16851685
attr->getLocation(),
16861686
diag::attr_objc_implementation_raise_minimum_deployment_target,
16871687
prettyPlatformString(targetPlatform(Ctx.LangOpts)),
1688-
Ctx.getSwift50Availability().getVersionRange().getLowerEndpoint());
1688+
Ctx.getSwift50Availability().getRawMinimumVersion());
16891689
if (attr->isEarlyAdopter()) {
16901690
diag.wrapIn(diag::wrap_objc_implementation_will_become_error);
16911691
}
@@ -2210,11 +2210,11 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) {
22102210
diag::availability_implicit_decl_here,
22112211
D->getDescriptiveKind(),
22122212
prettyPlatformString(targetPlatform(Ctx.LangOpts)),
2213-
AttrRange.getVersionRange().getLowerEndpoint());
2213+
AttrRange.getRawMinimumVersion());
22142214
diagnose(enclosingDecl->getLoc(),
22152215
diag::availability_decl_more_than_enclosing_here,
22162216
prettyPlatformString(targetPlatform(Ctx.LangOpts)),
2217-
EnclosingAnnotatedRange->getVersionRange().getLowerEndpoint());
2217+
EnclosingAnnotatedRange->getRawMinimumVersion());
22182218
}
22192219
}
22202220
}

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 50 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
11191119
}
11201120

11211121
AvailabilityContext NewConstraint = contextForSpec(Spec, false);
1122-
Query->setAvailableRange(contextForSpec(Spec, true).getVersionRange());
1122+
Query->setAvailableRange(contextForSpec(Spec, true).getRawVersionRange());
11231123

11241124
// When compiling zippered for macCatalyst, we need to collect both
11251125
// a macOS version (the target version) and an iOS/macCatalyst version
@@ -1130,7 +1130,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
11301130
AvailabilitySpec *VariantSpec =
11311131
bestActiveSpecForQuery(Query, /*ForTargetVariant*/ true);
11321132
VersionRange VariantRange =
1133-
contextForSpec(VariantSpec, true).getVersionRange();
1133+
contextForSpec(VariantSpec, true).getRawVersionRange();
11341134
Query->setVariantAvailableRange(VariantRange);
11351135
}
11361136

@@ -1857,9 +1857,10 @@ static void findAvailabilityFixItNodes(
18571857

18581858
/// Emit a diagnostic note and Fix-It to add an @available attribute
18591859
/// on the given declaration for the given version range.
1860-
static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
1861-
const VersionRange &RequiredRange,
1862-
ASTContext &Context) {
1860+
static void
1861+
fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
1862+
const AvailabilityContext &RequiredAvailability,
1863+
ASTContext &Context) {
18631864
assert(D);
18641865

18651866
// Don't suggest adding an @available() to a declaration where we would
@@ -1897,8 +1898,7 @@ static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
18971898
D->diagnose(diag::availability_add_attribute, KindForDiagnostic)
18981899
.fixItInsert(InsertLoc, diag::insert_available_attr,
18991900
platformString(Target),
1900-
RequiredRange.getLowerEndpoint().getAsString(),
1901-
OriginalIndent);
1901+
RequiredAvailability.getVersionString(), OriginalIndent);
19021902
}
19031903

19041904
/// In the special case of being in an existing, nontrivial type refinement
@@ -1907,29 +1907,26 @@ static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
19071907
/// version), emit a diagnostic and fixit that narrows the existing TRC
19081908
/// condition to the required range.
19091909
static bool fixAvailabilityByNarrowingNearbyVersionCheck(
1910-
SourceRange ReferenceRange,
1911-
const DeclContext *ReferenceDC,
1912-
const VersionRange &RequiredRange,
1913-
ASTContext &Context,
1910+
SourceRange ReferenceRange, const DeclContext *ReferenceDC,
1911+
const AvailabilityContext &RequiredAvailability, ASTContext &Context,
19141912
InFlightDiagnostic &Err) {
19151913
const TypeRefinementContext *TRC = nullptr;
19161914
(void)TypeChecker::overApproximateAvailabilityAtLocation(ReferenceRange.Start,
19171915
ReferenceDC, &TRC);
19181916
if (!TRC)
19191917
return false;
1920-
VersionRange RunningRange =
1921-
TRC->getExplicitAvailabilityInfo().getVersionRange();
1922-
if (RunningRange.hasLowerEndpoint() &&
1923-
RequiredRange.hasLowerEndpoint() &&
1918+
1919+
AvailabilityContext AvailabilityAtLoc = TRC->getExplicitAvailabilityInfo();
1920+
if (!AvailabilityAtLoc.isAlwaysAvailable() &&
1921+
!RequiredAvailability.isAlwaysAvailable() &&
19241922
TRC->getReason() != TypeRefinementContext::Reason::Root &&
1925-
AvailabilityContext(RequiredRange).isContainedIn(
1926-
AvailabilityContext(RunningRange))) {
1923+
RequiredAvailability.isContainedIn(AvailabilityAtLoc)) {
19271924

19281925
// Only fix situations that are "nearby" versions, meaning
19291926
// disagreement on a minor-or-less version (subminor-or-less version for
19301927
// macOS 10.x.y).
1931-
auto RunningVers = RunningRange.getLowerEndpoint();
1932-
auto RequiredVers = RequiredRange.getLowerEndpoint();
1928+
auto RunningVers = AvailabilityAtLoc.getRawMinimumVersion();
1929+
auto RequiredVers = RequiredAvailability.getRawMinimumVersion();
19331930
auto Platform = targetPlatform(Context.LangOpts);
19341931
if (RunningVers.getMajor() != RequiredVers.getMajor())
19351932
return false;
@@ -1947,7 +1944,7 @@ static bool fixAvailabilityByNarrowingNearbyVersionCheck(
19471944
if (!FixRange.isValid())
19481945
return false;
19491946
// Have found a nontrivial type refinement context-introducer to narrow.
1950-
Err.fixItReplace(FixRange, RequiredVers.getAsString());
1947+
Err.fixItReplace(FixRange, RequiredAvailability.getVersionString());
19511948
return true;
19521949
}
19531950
return false;
@@ -1956,7 +1953,7 @@ static bool fixAvailabilityByNarrowingNearbyVersionCheck(
19561953
/// Emit a diagnostic note and Fix-It to add an if #available(...) { } guard
19571954
/// that checks for the given version range around the given node.
19581955
static void fixAvailabilityByAddingVersionCheck(
1959-
ASTNode NodeToWrap, const VersionRange &RequiredRange,
1956+
ASTNode NodeToWrap, const AvailabilityContext &RequiredAvailability,
19601957
SourceRange ReferenceRange, ASTContext &Context) {
19611958
// If this is an implicit variable that wraps an expression,
19621959
// let's point to it's initializer. For example, result builder
@@ -2010,9 +2007,8 @@ static void fixAvailabilityByAddingVersionCheck(
20102007
basePlatformForExtensionPlatform(Target))
20112008
Target = *TargetRemovingAppExtension;
20122009

2013-
Out << "if #available(" << platformString(Target)
2014-
<< " " << RequiredRange.getLowerEndpoint().getAsString()
2015-
<< ", *) {\n";
2010+
Out << "if #available(" << platformString(Target) << " "
2011+
<< RequiredAvailability.getVersionString() << ", *) {\n";
20162012

20172013
Out << OriginalIndent << ExtraIndent << GuardedText << "\n";
20182014

@@ -2032,7 +2028,7 @@ static void fixAvailabilityByAddingVersionCheck(
20322028
/// requiting the given OS version range.
20332029
static void fixAvailability(SourceRange ReferenceRange,
20342030
const DeclContext *ReferenceDC,
2035-
const VersionRange &RequiredRange,
2031+
const AvailabilityContext &RequiredAvailability,
20362032
ASTContext &Context) {
20372033
if (ReferenceRange.isInvalid())
20382034
return;
@@ -2048,18 +2044,19 @@ static void fixAvailability(SourceRange ReferenceRange,
20482044
// Suggest wrapping in if #available(...) { ... } if possible.
20492045
if (NodeToWrapInVersionCheck.has_value()) {
20502046
fixAvailabilityByAddingVersionCheck(NodeToWrapInVersionCheck.value(),
2051-
RequiredRange, ReferenceRange, Context);
2047+
RequiredAvailability, ReferenceRange,
2048+
Context);
20522049
}
20532050

20542051
// Suggest adding availability attributes.
20552052
if (FoundMemberDecl) {
2056-
fixAvailabilityForDecl(ReferenceRange, FoundMemberDecl, RequiredRange,
2057-
Context);
2053+
fixAvailabilityForDecl(ReferenceRange, FoundMemberDecl,
2054+
RequiredAvailability, Context);
20582055
}
20592056

20602057
if (FoundTypeLevelDecl) {
2061-
fixAvailabilityForDecl(ReferenceRange, FoundTypeLevelDecl, RequiredRange,
2062-
Context);
2058+
fixAvailabilityForDecl(ReferenceRange, FoundTypeLevelDecl,
2059+
RequiredAvailability, Context);
20632060
}
20642061
}
20652062

@@ -2068,20 +2065,18 @@ void TypeChecker::diagnosePotentialUnavailability(
20682065
const DeclContext *ReferenceDC, const AvailabilityContext &Availability) {
20692066
ASTContext &Context = ReferenceDC->getASTContext();
20702067

2071-
auto RequiredRange = Availability.getVersionRange();
20722068
{
2073-
auto Err =
2074-
Context.Diags.diagnose(
2075-
ReferenceRange.Start, Diag,
2076-
prettyPlatformString(targetPlatform(Context.LangOpts)),
2077-
RequiredRange.getLowerEndpoint());
2069+
auto Err = Context.Diags.diagnose(
2070+
ReferenceRange.Start, Diag,
2071+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2072+
Availability.getRawMinimumVersion());
20782073

20792074
// Direct a fixit to the error if an existing guard is nearly-correct
20802075
if (fixAvailabilityByNarrowingNearbyVersionCheck(
2081-
ReferenceRange, ReferenceDC, RequiredRange, Context, Err))
2076+
ReferenceRange, ReferenceDC, Availability, Context, Err))
20822077
return;
20832078
}
2084-
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
2079+
fixAvailability(ReferenceRange, ReferenceDC, Availability, Context);
20852080
}
20862081

20872082
bool TypeChecker::checkAvailability(SourceRange ReferenceRange,
@@ -2128,7 +2123,6 @@ static Diagnostic getPotentialUnavailabilityDiagnostic(
21282123
bool &IsError) {
21292124
ASTContext &Context = ReferenceDC->getASTContext();
21302125
auto Platform = prettyPlatformString(targetPlatform(Context.LangOpts));
2131-
auto Version = Availability.getVersionRange().getLowerEndpoint();
21322126

21332127
if (requiresDeploymentTargetOrEarlier(Availability, Context)) {
21342128
// The required OS version is at or before the deployment target so this
@@ -2139,12 +2133,13 @@ static Diagnostic getPotentialUnavailabilityDiagnostic(
21392133
return Diagnostic(
21402134
IsError ? diag::availability_decl_only_version_newer_for_clients
21412135
: diag::availability_decl_only_version_newer_for_clients_warn,
2142-
D, Platform, Version, ReferenceDC->getParentModule());
2136+
D, Platform, Availability.getRawMinimumVersion(),
2137+
ReferenceDC->getParentModule());
21432138
}
21442139

21452140
IsError = true;
21462141
return Diagnostic(diag::availability_decl_only_version_newer, D, Platform,
2147-
Version);
2142+
Availability.getRawMinimumVersion());
21482143
}
21492144

21502145
bool TypeChecker::diagnosePotentialUnavailability(
@@ -2153,7 +2148,6 @@ bool TypeChecker::diagnosePotentialUnavailability(
21532148
bool WarnBeforeDeploymentTarget = false) {
21542149
ASTContext &Context = ReferenceDC->getASTContext();
21552150

2156-
auto RequiredRange = Availability.getVersionRange();
21572151
bool IsError;
21582152
{
21592153
auto Diag = Context.Diags.diagnose(
@@ -2163,11 +2157,11 @@ bool TypeChecker::diagnosePotentialUnavailability(
21632157

21642158
// Direct a fixit to the error if an existing guard is nearly-correct
21652159
if (fixAvailabilityByNarrowingNearbyVersionCheck(
2166-
ReferenceRange, ReferenceDC, RequiredRange, Context, Diag))
2160+
ReferenceRange, ReferenceDC, Availability, Context, Diag))
21672161
return IsError;
21682162
}
21692163

2170-
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
2164+
fixAvailability(ReferenceRange, ReferenceDC, Availability, Context);
21712165
return IsError;
21722166
}
21732167

@@ -2182,23 +2176,19 @@ void TypeChecker::diagnosePotentialAccessorUnavailability(
21822176
auto &diag = ForInout ? diag::availability_inout_accessor_only_version_newer
21832177
: diag::availability_decl_only_version_newer;
21842178

2185-
auto RequiredRange = Availability.getVersionRange();
21862179
{
2187-
auto Err =
2188-
Context.Diags.diagnose(
2189-
ReferenceRange.Start, diag, Accessor,
2190-
prettyPlatformString(targetPlatform(Context.LangOpts)),
2191-
RequiredRange.getLowerEndpoint());
2192-
2180+
auto Err = Context.Diags.diagnose(
2181+
ReferenceRange.Start, diag, Accessor,
2182+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2183+
Availability.getRawMinimumVersion());
21932184

21942185
// Direct a fixit to the error if an existing guard is nearly-correct
2195-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
2196-
ReferenceDC,
2197-
RequiredRange, Context, Err))
2186+
if (fixAvailabilityByNarrowingNearbyVersionCheck(
2187+
ReferenceRange, ReferenceDC, Availability, Context, Err))
21982188
return;
21992189
}
22002190

2201-
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
2191+
fixAvailability(ReferenceRange, ReferenceDC, Availability, Context);
22022192
}
22032193

22042194
static DiagnosticBehavior
@@ -2226,25 +2216,24 @@ void TypeChecker::diagnosePotentialUnavailability(
22262216
const AvailabilityContext &availability) {
22272217
ASTContext &ctx = dc->getASTContext();
22282218

2229-
auto requiredRange = availability.getVersionRange();
22302219
{
22312220
auto type = rootConf->getType();
22322221
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
22332222
auto err = ctx.Diags.diagnose(
22342223
loc, diag::conformance_availability_only_version_newer, type, proto,
22352224
prettyPlatformString(targetPlatform(ctx.LangOpts)),
2236-
requiredRange.getLowerEndpoint());
2225+
availability.getRawMinimumVersion());
22372226

22382227
err.warnUntilSwiftVersion(6);
22392228
err.limitBehavior(behaviorLimitForExplicitUnavailability(rootConf, dc));
22402229

22412230
// Direct a fixit to the error if an existing guard is nearly-correct
2242-
if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc,
2243-
requiredRange, ctx, err))
2231+
if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc, availability, ctx,
2232+
err))
22442233
return;
22452234
}
22462235

2247-
fixAvailability(loc, dc, requiredRange, ctx);
2236+
fixAvailability(loc, dc, availability, ctx);
22482237
}
22492238

22502239
const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {
@@ -4635,7 +4624,7 @@ static bool declNeedsExplicitAvailability(const Decl *decl) {
46354624

46364625
// Warn on decls without an introduction version.
46374626
auto safeRangeUnderApprox = AvailabilityInference::availableRange(decl, ctx);
4638-
return !safeRangeUnderApprox.getVersionRange().hasLowerEndpoint();
4627+
return safeRangeUnderApprox.isAlwaysAvailable();
46394628
}
46404629

46414630
void swift::checkExplicitAvailability(Decl *decl) {

0 commit comments

Comments
 (0)