Skip to content

Commit 229ca24

Browse files
authored
Merge pull request #77362 from tshortli/replace-check-declaration-availability
Sema: Introduce `getUnmetAvailabilityRequirement()`
2 parents 798f0e6 + c044321 commit 229ca24

File tree

10 files changed

+165
-208
lines changed

10 files changed

+165
-208
lines changed

include/swift/AST/Availability.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,78 @@ class AvailabilityRange {
335335
}
336336
};
337337

338+
/// Represents the reason a declaration is considered unavailable in a certain
339+
/// context.
340+
class UnmetAvailabilityRequirement {
341+
public:
342+
enum class Kind {
343+
/// The declaration is referenced in a context in which it is
344+
/// generally unavailable. For example, a reference to a declaration that is
345+
/// unavailable on macOS from a context that may execute on macOS has this
346+
/// unmet requirement.
347+
AlwaysUnavailable,
348+
349+
/// The declaration is referenced in a context in which it is considered
350+
/// obsolete. For example, a reference to a declaration that is obsolete in
351+
/// macOS 13 from a context that may execute on macOS 13 or later has this
352+
/// unmet requirement.
353+
Obsoleted,
354+
355+
/// The declaration is only available in a different version. For example,
356+
/// the declaration might only be introduced in the Swift 6 language mode
357+
/// while the module is being compiled in the Swift 5 language mode.
358+
RequiresVersion,
359+
360+
/// The declaration is referenced in a context that does not have an
361+
/// adequate minimum version constraint. For example, a reference to a
362+
/// declaration that is introduced in macOS 13 from a context that may
363+
/// execute on earlier versions of macOS has this unmet requirement. This
364+
/// kind of unmet requirement can be addressed by tightening the minimum
365+
/// version of the context with `if #available(...)` or by adding or
366+
/// adjusting an `@available` attribute.
367+
IntroducedInNewerVersion,
368+
};
369+
370+
private:
371+
Kind kind;
372+
const AvailableAttr *attr;
373+
374+
UnmetAvailabilityRequirement(Kind kind, const AvailableAttr *attr)
375+
: kind(kind), attr(attr){};
376+
377+
public:
378+
static UnmetAvailabilityRequirement
379+
forAlwaysUnavailable(const AvailableAttr *attr) {
380+
return UnmetAvailabilityRequirement(Kind::AlwaysUnavailable, attr);
381+
}
382+
383+
static UnmetAvailabilityRequirement forObsoleted(const AvailableAttr *attr) {
384+
return UnmetAvailabilityRequirement(Kind::Obsoleted, attr);
385+
}
386+
387+
static UnmetAvailabilityRequirement
388+
forRequiresVersion(const AvailableAttr *attr) {
389+
return UnmetAvailabilityRequirement(Kind::RequiresVersion, attr);
390+
}
391+
392+
static UnmetAvailabilityRequirement
393+
forIntroducedInNewerVersion(const AvailableAttr *attr) {
394+
return UnmetAvailabilityRequirement(Kind::IntroducedInNewerVersion, attr);
395+
}
396+
397+
Kind getKind() const { return kind; }
398+
const AvailableAttr *getAttr() const { return attr; }
399+
400+
/// Returns the required range for `IntroducedInNewerVersion` requirements, or
401+
/// `std::nullopt` otherwise.
402+
std::optional<AvailabilityRange>
403+
getRequiredNewerAvailabilityRange(ASTContext &ctx) const;
404+
405+
/// Returns true if this unmet requirement can be satisfied by introducing an
406+
/// `if #available(...)` condition in source.
407+
bool isConditionallySatisfiable() const;
408+
};
409+
338410
class AvailabilityInference {
339411
public:
340412
/// Returns the decl that should be considered the parent decl of the given

lib/AST/Availability.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,30 @@ AvailabilityRange AvailabilityRange::forRuntimeTarget(const ASTContext &Ctx) {
6464
return AvailabilityRange(VersionRange::allGTE(Ctx.LangOpts.RuntimeVersion));
6565
}
6666

67+
std::optional<AvailabilityRange>
68+
UnmetAvailabilityRequirement::getRequiredNewerAvailabilityRange(
69+
ASTContext &ctx) const {
70+
switch (kind) {
71+
case Kind::AlwaysUnavailable:
72+
case Kind::RequiresVersion:
73+
case Kind::Obsoleted:
74+
return std::nullopt;
75+
case Kind::IntroducedInNewerVersion:
76+
return AvailabilityInference::availableRange(attr, ctx);
77+
}
78+
}
79+
80+
bool UnmetAvailabilityRequirement::isConditionallySatisfiable() const {
81+
switch (kind) {
82+
case Kind::AlwaysUnavailable:
83+
case Kind::RequiresVersion:
84+
case Kind::Obsoleted:
85+
return false;
86+
case Kind::IntroducedInNewerVersion:
87+
return true;
88+
}
89+
}
90+
6791
namespace {
6892

6993
/// The inferred availability required to access a group of declarations

lib/Sema/BuilderTransform.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,12 +1321,8 @@ ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
13211321
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
13221322

13231323
auto isUnavailable = [&](Decl *D) -> bool {
1324-
if (AvailableAttr::isUnavailable(D))
1325-
return true;
1326-
13271324
auto loc = extractNearestSourceLoc(dc);
1328-
auto context = ExportContext::forFunctionBody(dc, loc);
1329-
return TypeChecker::checkDeclarationAvailability(D, context).has_value();
1325+
return getUnmetDeclAvailabilityRequirement(D, dc, loc).has_value();
13301326
};
13311327

13321328
bool foundMatch = false;

lib/Sema/CSSyntacticElement.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,10 +2475,9 @@ class ResultBuilderRewriter : public SyntacticElementSolutionApplication {
24752475
if (!nominal)
24762476
return false;
24772477

2478-
ExportContext where =
2479-
ExportContext::forFunctionBody(context.getAsDeclContext(), loc);
2480-
if (auto reason =
2481-
TypeChecker::checkDeclarationAvailability(nominal, where)) {
2478+
auto unmetRequirement = getUnmetDeclAvailabilityRequirement(
2479+
nominal, context.getAsDeclContext(), loc);
2480+
if (unmetRequirement && unmetRequirement->isConditionallySatisfiable()) {
24822481
auto &ctx = getASTContext();
24832482
ctx.Diags.diagnose(loc,
24842483
diag::result_builder_missing_limited_availability,

lib/Sema/ConstraintSystem.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4682,8 +4682,7 @@ bool ConstraintSystem::isDeclUnavailable(const Decl *D,
46824682
loc = getLoc(anchor);
46834683
}
46844684

4685-
auto availabilityContext = TypeChecker::availabilityAtLocation(loc, DC);
4686-
return checkDeclarationAvailability(D, DC, availabilityContext).has_value();
4685+
return getUnmetDeclAvailabilityRequirement(D, DC, loc).has_value();
46874686
}
46884687

46894688
bool ConstraintSystem::isConformanceUnavailable(ProtocolConformanceRef conformance,
@@ -4898,12 +4897,12 @@ bool ConstraintSystem::isReadOnlyKeyPathComponent(
48984897
// If the setter is unavailable, then the keypath ought to be read-only
48994898
// in this context.
49004899
if (auto setter = storage->getOpaqueAccessor(AccessorKind::Set)) {
4901-
ExportContext where = ExportContext::forFunctionBody(DC, referenceLoc);
4902-
auto maybeUnavail =
4903-
TypeChecker::checkDeclarationAvailability(setter, where);
4904-
if (maybeUnavail.has_value()) {
4900+
// FIXME: Fully unavailable setters should cause the key path to be
4901+
// readonly too.
4902+
auto unmetRequirement =
4903+
getUnmetDeclAvailabilityRequirement(setter, DC, referenceLoc);
4904+
if (unmetRequirement && unmetRequirement->isConditionallySatisfiable())
49054905
return true;
4906-
}
49074906
}
49084907

49094908
return false;

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 43 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -316,19 +316,6 @@ ExportContext::getExportabilityReason() const {
316316
return std::nullopt;
317317
}
318318

319-
std::optional<AvailabilityRange>
320-
UnmetAvailabilityRequirement::getRequiredNewerAvailabilityRange(
321-
ASTContext &ctx) const {
322-
switch (kind) {
323-
case Kind::AlwaysUnavailable:
324-
case Kind::RequiresVersion:
325-
case Kind::Obsoleted:
326-
return std::nullopt;
327-
case Kind::IntroducedInNewerVersion:
328-
return AvailabilityInference::availableRange(attr, ctx);
329-
}
330-
}
331-
332319
/// Returns the first availability attribute on the declaration that is active
333320
/// on the target platform.
334321
static const AvailableAttr *getActiveAvailableAttribute(const Decl *D,
@@ -1478,62 +1465,6 @@ AvailabilityRange TypeChecker::overApproximateAvailabilityAtLocation(
14781465
return availabilityAtLocation(loc, DC, MostRefined).getPlatformRange();
14791466
}
14801467

1481-
bool TypeChecker::isDeclarationUnavailable(
1482-
const Decl *D, const DeclContext *referenceDC,
1483-
llvm::function_ref<AvailabilityRange()> getAvailabilityRange) {
1484-
ASTContext &Context = referenceDC->getASTContext();
1485-
if (Context.LangOpts.DisableAvailabilityChecking) {
1486-
return false;
1487-
}
1488-
1489-
if (!referenceDC->getParentSourceFile()) {
1490-
// We only check availability if this reference is in a source file; we do
1491-
// not check in other kinds of FileUnits.
1492-
return false;
1493-
}
1494-
1495-
AvailabilityRange safeRangeUnderApprox{
1496-
AvailabilityInference::availableRange(D)};
1497-
1498-
if (safeRangeUnderApprox.isAlwaysAvailable())
1499-
return false;
1500-
1501-
AvailabilityRange runningOSOverApprox = getAvailabilityRange();
1502-
1503-
// The reference is safe if an over-approximation of the running OS
1504-
// versions is fully contained within an under-approximation
1505-
// of the versions on which the declaration is available. If this
1506-
// containment cannot be guaranteed, we say the reference is
1507-
// not available.
1508-
return !runningOSOverApprox.isContainedIn(safeRangeUnderApprox);
1509-
}
1510-
1511-
std::optional<AvailabilityRange>
1512-
TypeChecker::checkDeclarationAvailability(const Decl *D,
1513-
const ExportContext &Where) {
1514-
// Skip computing potential unavailability if the declaration is explicitly
1515-
// unavailable and the context is also unavailable.
1516-
if (const AvailableAttr *Attr = AvailableAttr::isUnavailable(D))
1517-
if (isInsideCompatibleUnavailableDeclaration(D, Where.getAvailability(),
1518-
Attr))
1519-
return std::nullopt;
1520-
1521-
if (isDeclarationUnavailable(D, Where.getDeclContext(), [&Where] {
1522-
return Where.getAvailabilityRange();
1523-
})) {
1524-
return AvailabilityInference::availableRange(D);
1525-
}
1526-
1527-
return std::nullopt;
1528-
}
1529-
1530-
std::optional<AvailabilityRange>
1531-
TypeChecker::checkConformanceAvailability(const RootProtocolConformance *conf,
1532-
const ExtensionDecl *ext,
1533-
const ExportContext &where) {
1534-
return checkDeclarationAvailability(ext, where);
1535-
}
1536-
15371468
/// A class that walks the AST to find the innermost (i.e., deepest) node that
15381469
/// contains a target SourceRange and matches a particular criterion.
15391470
/// This class finds the innermost nodes of interest by walking
@@ -2275,12 +2206,14 @@ behaviorLimitForExplicitUnavailability(
22752206

22762207
/// Emits a diagnostic for a protocol conformance that is potentially
22772208
/// unavailable at the given source location.
2278-
static void
2209+
static bool
22792210
diagnosePotentialUnavailability(const RootProtocolConformance *rootConf,
22802211
const ExtensionDecl *ext, SourceLoc loc,
22812212
const DeclContext *dc,
22822213
const AvailabilityRange &availability) {
22832214
ASTContext &ctx = dc->getASTContext();
2215+
if (ctx.LangOpts.DisableAvailabilityChecking)
2216+
return false;
22842217

22852218
{
22862219
auto type = rootConf->getType();
@@ -2296,10 +2229,11 @@ diagnosePotentialUnavailability(const RootProtocolConformance *rootConf,
22962229
// Direct a fixit to the error if an existing guard is nearly-correct
22972230
if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc, availability, ctx,
22982231
err))
2299-
return;
2232+
return true;
23002233
}
23012234

23022235
fixAvailability(loc, dc, availability, ctx);
2236+
return true;
23032237
}
23042238

23052239
/// Returns the availability attribute indicating deprecation of the
@@ -3099,9 +3033,9 @@ bool diagnoseExplicitUnavailability(
30993033
}
31003034

31013035
std::optional<UnmetAvailabilityRequirement>
3102-
swift::checkDeclarationAvailability(const Decl *decl,
3103-
const DeclContext *declContext,
3104-
AvailabilityContext availabilityContext) {
3036+
swift::getUnmetDeclAvailabilityRequirement(
3037+
const Decl *decl, const DeclContext *declContext,
3038+
AvailabilityContext availabilityContext) {
31053039
auto &ctx = declContext->getASTContext();
31063040

31073041
// Generic parameters are always available.
@@ -3140,6 +3074,14 @@ swift::checkDeclarationAvailability(const Decl *decl,
31403074
return std::nullopt;
31413075
}
31423076

3077+
std::optional<UnmetAvailabilityRequirement>
3078+
swift::getUnmetDeclAvailabilityRequirement(const Decl *decl,
3079+
const DeclContext *referenceDC,
3080+
SourceLoc referenceLoc) {
3081+
return getUnmetDeclAvailabilityRequirement(
3082+
decl, referenceDC,
3083+
TypeChecker::availabilityAtLocation(referenceLoc, referenceDC));
3084+
}
31433085

31443086
/// Check if this is a subscript declaration inside String or
31453087
/// Substring that returns String, and if so return true.
@@ -4153,13 +4095,9 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
41534095
auto &ctx = DC->getASTContext();
41544096

41554097
auto unmetRequirement =
4156-
checkDeclarationAvailability(D, DC, Where.getAvailability());
4157-
auto requiredRange =
4158-
unmetRequirement
4159-
? unmetRequirement->getRequiredNewerAvailabilityRange(ctx)
4160-
: std::nullopt;
4098+
getUnmetDeclAvailabilityRequirement(D, DC, Where.getAvailability());
41614099

4162-
if (unmetRequirement && !requiredRange) {
4100+
if (unmetRequirement && !unmetRequirement->isConditionallySatisfiable()) {
41634101
// FIXME: diagnoseExplicitUnavailability should take an unmet requirement
41644102
if (diagnoseExplicitUnavailability(D, R, Where, call, Flags))
41654103
return true;
@@ -4183,6 +4121,11 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
41834121
&& isa<ProtocolDecl>(D))
41844122
return false;
41854123

4124+
if (!unmetRequirement)
4125+
return false;
4126+
4127+
auto requiredRange = unmetRequirement->getRequiredNewerAvailabilityRange(ctx);
4128+
41864129
// Diagnose (and possibly signal) for potential unavailability
41874130
if (!requiredRange)
41884131
return false;
@@ -4630,6 +4573,7 @@ swift::diagnoseConformanceAvailability(SourceLoc loc,
46304573
// Diagnose "missing" conformances where we needed a conformance but
46314574
// didn't have one.
46324575
auto *DC = where.getDeclContext();
4576+
auto &ctx = DC->getASTContext();
46334577
if (auto builtinConformance = dyn_cast<BuiltinProtocolConformance>(rootConf)){
46344578
if (builtinConformance->isMissing()) {
46354579
diagnoseMissingConformance(loc, builtinConformance->getType(),
@@ -4643,7 +4587,6 @@ swift::diagnoseConformanceAvailability(SourceLoc loc,
46434587

46444588
Type selfTy = rootConf->getProtocol()->getSelfInterfaceType();
46454589
if (!depTy->isEqual(selfTy)) {
4646-
auto &ctx = DC->getASTContext();
46474590
ctx.Diags.diagnose(
46484591
loc,
46494592
diag::assoc_conformance_from_implementation_only_module,
@@ -4658,20 +4601,26 @@ swift::diagnoseConformanceAvailability(SourceLoc loc,
46584601
return true;
46594602
}
46604603

4661-
if (diagnoseExplicitUnavailability(loc, rootConf, ext, where,
4662-
warnIfConformanceUnavailablePreSwift6)) {
4663-
maybeEmitAssociatedTypeNote();
4664-
return true;
4665-
}
4604+
auto unmetRequirement = getUnmetDeclAvailabilityRequirement(
4605+
ext, where.getDeclContext(), where.getAvailability());
4606+
if (unmetRequirement) {
4607+
// FIXME: diagnoseExplicitUnavailability() should take unmet requirement
4608+
if (diagnoseExplicitUnavailability(
4609+
loc, rootConf, ext, where,
4610+
warnIfConformanceUnavailablePreSwift6)) {
4611+
maybeEmitAssociatedTypeNote();
4612+
return true;
4613+
}
46664614

4667-
// Diagnose (and possibly signal) for potential unavailability
4668-
auto maybeUnavail = TypeChecker::checkConformanceAvailability(
4669-
rootConf, ext, where);
4670-
if (maybeUnavail.has_value()) {
4671-
diagnosePotentialUnavailability(rootConf, ext, loc, DC,
4672-
maybeUnavail.value());
4673-
maybeEmitAssociatedTypeNote();
4674-
return true;
4615+
// Diagnose (and possibly signal) for potential unavailability
4616+
if (auto requiredRange =
4617+
unmetRequirement->getRequiredNewerAvailabilityRange(ctx)) {
4618+
if (diagnosePotentialUnavailability(rootConf, ext, loc, DC,
4619+
*requiredRange)) {
4620+
maybeEmitAssociatedTypeNote();
4621+
return true;
4622+
}
4623+
}
46754624
}
46764625

46774626
// Diagnose for deprecation

0 commit comments

Comments
 (0)