Skip to content

Commit ecf7ac9

Browse files
authored
Merge pull request #77907 from tshortli/semantic-decl-availability
AST: Refactor semantic unavailability queries
2 parents b744793 + 64f9d5b commit ecf7ac9

12 files changed

+160
-87
lines changed

include/swift/AST/Decl.h

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,22 +1442,15 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
14421442
std::optional<std::pair<const AvailableAttr *, const Decl *>>
14431443
getSemanticAvailableRangeAttr() const;
14441444

1445-
/// Retrieve the @available attribute that makes this declaration unavailable,
1446-
/// if any. If \p ignoreAppExtensions is true then attributes for app
1447-
/// extension platforms are ignored.
1448-
///
1449-
/// This attribute may come from an enclosing decl since availability is
1450-
/// inherited. The second member of the returned pair is the decl that owns
1451-
/// the attribute.
1452-
///
1453-
/// Note that this notion of unavailability is broader than that which is
1454-
/// checked by \c isUnavailable().
1455-
std::optional<std::pair<const AvailableAttr *, const Decl *>>
1456-
getSemanticUnavailableAttr(bool ignoreAppExtensions = false) const;
1445+
/// Returns true if the decl is effectively always unavailable in the current
1446+
/// compilation context. This query differs from \c isUnavailable() because it
1447+
/// takes the availability of parent declarations into account.
1448+
bool isSemanticallyUnavailable() const;
14571449

14581450
/// Returns true if code associated with this declaration should be considerd
14591451
/// unreachable at runtime because the declaration is unavailable in all
1460-
/// execution contexts in which the code may run.
1452+
/// execution contexts in which the code may run. This result takes the
1453+
/// availability of parent declarations into account.
14611454
bool isUnreachableAtRuntime() const;
14621455

14631456
/// Returns true if this declaration should be considered available during

include/swift/AST/TypeCheckRequests.h

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4122,20 +4122,35 @@ class SemanticAvailableRangeAttrRequest
41224122
bool isCached() const { return true; }
41234123
};
41244124

4125-
class SemanticUnavailableAttrRequest
4126-
: public SimpleRequest<SemanticUnavailableAttrRequest,
4127-
std::optional<AvailableAttrDeclPair>(
4128-
const Decl *decl, bool ignoreAppExtensions),
4125+
enum class SemanticDeclAvailability : uint8_t {
4126+
/// The decl is potentially available in some contexts and/or under certain
4127+
/// deployment conditions.
4128+
PotentiallyAvailable,
4129+
4130+
/// The decl is always unavailable in the current compilation context.
4131+
/// However, it may still be used at runtime by other modules with different
4132+
/// settings. For example a decl that is obsolete in Swift 5 is still
4133+
/// available to other modules compiled for an earlier language mode.
4134+
ConditionallyUnavailable,
4135+
4136+
/// The decl is universally unavailable. For example, when compiling for macOS
4137+
/// a decl with `@available(macOS, unavailable)` can never be used (except in
4138+
/// contexts that are also completely unavailable on macOS).
4139+
CompletelyUnavailable,
4140+
};
4141+
4142+
class SemanticDeclAvailabilityRequest
4143+
: public SimpleRequest<SemanticDeclAvailabilityRequest,
4144+
SemanticDeclAvailability(const Decl *decl),
41294145
RequestFlags::Cached> {
41304146
public:
41314147
using SimpleRequest::SimpleRequest;
41324148

41334149
private:
41344150
friend SimpleRequest;
41354151

4136-
std::optional<AvailableAttrDeclPair>
4137-
evaluate(Evaluator &evaluator, const Decl *decl,
4138-
bool ignoreAppExtensions) const;
4152+
SemanticDeclAvailability evaluate(Evaluator &evaluator,
4153+
const Decl *decl) const;
41394154

41404155
public:
41414156
bool isCached() const { return true; }

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,8 @@ SWIFT_REQUEST(TypeChecker, RenamedDeclRequest,
472472
SWIFT_REQUEST(TypeChecker, SemanticAvailableRangeAttrRequest,
473473
Optional<AvailableAttrDeclPair>(const Decl *),
474474
Cached, NoLocationInfo)
475-
SWIFT_REQUEST(TypeChecker, SemanticUnavailableAttrRequest,
476-
Optional<AvailableAttrDeclPair>(const Decl *),
475+
SWIFT_REQUEST(TypeChecker, SemanticDeclAvailabilityRequest,
476+
SemanticDeclAvailability(const Decl *),
477477
Cached, NoLocationInfo)
478478
SWIFT_REQUEST(TypeChecker, ClosureEffectsRequest,
479479
FunctionType::ExtInfo(ClosureExpr *),

lib/AST/Availability.cpp

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -689,60 +689,72 @@ const AvailableAttr *Decl::getUnavailableAttr(bool ignoreAppExtensions) const {
689689
return nullptr;
690690
}
691691

692-
std::optional<AvailableAttrDeclPair>
693-
SemanticUnavailableAttrRequest::evaluate(Evaluator &evaluator, const Decl *decl,
694-
bool ignoreAppExtensions) const {
695-
// Directly marked unavailable.
696-
if (auto attr = decl->getUnavailableAttr(ignoreAppExtensions))
697-
return std::make_pair(attr, decl);
698-
699-
if (auto *parent =
700-
AvailabilityInference::parentDeclForInferredAvailability(decl))
701-
return parent->getSemanticUnavailableAttr(ignoreAppExtensions);
702-
703-
return std::nullopt;
704-
}
705-
706-
std::optional<AvailableAttrDeclPair>
707-
Decl::getSemanticUnavailableAttr(bool ignoreAppExtensions) const {
708-
auto &eval = getASTContext().evaluator;
709-
return evaluateOrDefault(
710-
eval, SemanticUnavailableAttrRequest{this, ignoreAppExtensions},
711-
std::nullopt);
712-
}
713-
714-
bool Decl::isUnreachableAtRuntime() const {
692+
static bool isDeclCompletelyUnavailable(const Decl *decl) {
715693
// Don't trust unavailability on declarations from clang modules.
716-
if (isa<ClangModuleUnit>(getDeclContext()->getModuleScopeContext()))
694+
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
717695
return false;
718696

719-
auto unavailableAttrAndDecl =
720-
getSemanticUnavailableAttr(/*ignoreAppExtensions=*/true);
721-
if (!unavailableAttrAndDecl)
697+
auto *unavailableAttr =
698+
decl->getUnavailableAttr(/*ignoreAppExtensions=*/true);
699+
if (!unavailableAttr)
722700
return false;
723701

724-
// getSemanticUnavailableAttr() can return an @available attribute that makes
725-
// its declaration unavailable conditionally due to deployment target. Only
726-
// stub or skip a declaration that is unavailable regardless of deployment
727-
// target.
728-
auto *unavailableAttr = unavailableAttrAndDecl->first;
702+
// getUnavailableAttr() can return an @available attribute that is
703+
// obsoleted for certain deployment targets or language modes. These decls
704+
// can still be reached by code in other modules that is compiled with
705+
// a different deployment target or language mode.
729706
if (!unavailableAttr->isUnconditionallyUnavailable())
730707
return false;
731708

732-
// Universally unavailable declarations are always unreachable.
709+
// Universally unavailable declarations are always completely unavailable.
733710
if (unavailableAttr->getPlatform() == PlatformKind::none)
734711
return true;
735712

736713
// FIXME: Support zippered frameworks (rdar://125371621)
737714
// If we have a target variant (e.g. we're building a zippered macOS
738715
// framework) then the decl is only unreachable if it is unavailable for both
739716
// the primary target and the target variant.
740-
if (getASTContext().LangOpts.TargetVariant.has_value())
717+
if (decl->getASTContext().LangOpts.TargetVariant.has_value())
741718
return false;
742719

743720
return true;
744721
}
745722

723+
SemanticDeclAvailability
724+
SemanticDeclAvailabilityRequest::evaluate(Evaluator &evaluator,
725+
const Decl *decl) const {
726+
auto inherited = SemanticDeclAvailability::PotentiallyAvailable;
727+
if (auto *parent =
728+
AvailabilityInference::parentDeclForInferredAvailability(decl)) {
729+
inherited = evaluateOrDefault(
730+
evaluator, SemanticDeclAvailabilityRequest{parent}, inherited);
731+
}
732+
733+
if (inherited == SemanticDeclAvailability::CompletelyUnavailable ||
734+
isDeclCompletelyUnavailable(decl))
735+
return SemanticDeclAvailability::CompletelyUnavailable;
736+
737+
if (inherited == SemanticDeclAvailability::ConditionallyUnavailable ||
738+
decl->isUnavailable())
739+
return SemanticDeclAvailability::ConditionallyUnavailable;
740+
741+
return SemanticDeclAvailability::PotentiallyAvailable;
742+
}
743+
744+
bool Decl::isSemanticallyUnavailable() const {
745+
auto availability = evaluateOrDefault(
746+
getASTContext().evaluator, SemanticDeclAvailabilityRequest{this},
747+
SemanticDeclAvailability::PotentiallyAvailable);
748+
return availability != SemanticDeclAvailability::PotentiallyAvailable;
749+
}
750+
751+
bool Decl::isUnreachableAtRuntime() const {
752+
auto availability = evaluateOrDefault(
753+
getASTContext().evaluator, SemanticDeclAvailabilityRequest{this},
754+
SemanticDeclAvailability::PotentiallyAvailable);
755+
return availability == SemanticDeclAvailability::CompletelyUnavailable;
756+
}
757+
746758
static UnavailableDeclOptimization
747759
getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
748760
if (ctx.LangOpts.UnavailableDeclOptimizationMode.has_value())

lib/AST/Decl.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,8 +1300,7 @@ bool Decl::isAlwaysWeakImported() const {
13001300

13011301
// FIXME: Weak linking on Windows is not yet supported
13021302
// https://github.com/apple/swift/issues/53303
1303-
if (getSemanticUnavailableAttr() &&
1304-
!getASTContext().LangOpts.Target.isOSWindows())
1303+
if (isUnavailable() && !getASTContext().LangOpts.Target.isOSWindows())
13051304
return true;
13061305

13071306
if (auto *accessor = dyn_cast<AccessorDecl>(this))

lib/SIL/IR/SILProfiler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ static bool shouldProfile(SILDeclRef Constant) {
117117

118118
if (auto *D = DC->getInnermostDeclarationDeclContext()) {
119119
// Do not profile AST nodes in unavailable contexts.
120-
if (D->getSemanticUnavailableAttr()) {
120+
if (D->isSemanticallyUnavailable()) {
121121
LLVM_DEBUG(llvm::dbgs() << "Skipping ASTNode: unavailable context\n");
122122
return false;
123123
}

lib/Sema/TypeCheckAttr.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4796,10 +4796,12 @@ void AttributeChecker::checkBackDeployedAttrs(
47964796
if (Attr != ActiveAttr)
47974797
continue;
47984798

4799+
auto availability =
4800+
TypeChecker::availabilityAtLocation(D->getLoc(), D->getDeclContext());
4801+
47994802
// Unavailable decls cannot be back deployed.
4800-
if (auto unavailableAttrPair = VD->getSemanticUnavailableAttr()) {
4801-
auto unavailableAttr = unavailableAttrPair.value().first;
4802-
if (!inheritsAvailabilityFromPlatform(unavailableAttr->getPlatform(),
4803+
if (auto unavailablePlatform = availability.getUnavailablePlatformKind()) {
4804+
if (!inheritsAvailabilityFromPlatform(*unavailablePlatform,
48034805
Attr->Platform)) {
48044806
auto platformString = prettyPlatformString(Attr->Platform);
48054807
llvm::VersionTuple ignoredVersion;
@@ -4809,9 +4811,21 @@ void AttributeChecker::checkBackDeployedAttrs(
48094811

48104812
diagnose(AtLoc, diag::attr_has_no_effect_on_unavailable_decl, Attr, VD,
48114813
platformString);
4812-
diagnose(unavailableAttr->AtLoc, diag::availability_marked_unavailable,
4813-
VD)
4814-
.highlight(unavailableAttr->getRange());
4814+
4815+
// Find the attribute that makes the declaration unavailable.
4816+
const Decl *attrDecl = D;
4817+
do {
4818+
if (auto *unavailableAttr = attrDecl->getUnavailableAttr()) {
4819+
diagnose(unavailableAttr->AtLoc,
4820+
diag::availability_marked_unavailable, VD)
4821+
.highlight(unavailableAttr->getRange());
4822+
break;
4823+
}
4824+
4825+
attrDecl = AvailabilityInference::parentDeclForInferredAvailability(
4826+
attrDecl);
4827+
} while (attrDecl);
4828+
48154829
continue;
48164830
}
48174831
}
@@ -5008,7 +5022,7 @@ TypeChecker::diagnosticIfDeclCannotBeUnavailable(const Decl *D) {
50085022
auto parentIsUnavailable = [](const Decl *D) -> bool {
50095023
if (auto *parent =
50105024
AvailabilityInference::parentDeclForInferredAvailability(D)) {
5011-
return parent->getSemanticUnavailableAttr() != std::nullopt;
5025+
return parent->isSemanticallyUnavailable();
50125026
}
50135027
return false;
50145028
};

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -814,11 +814,17 @@ class AvailabilityScopeBuilder : private ASTWalker {
814814
if (D->getDeclContext()->isLocalContext())
815815
return false;
816816

817-
// As a convenience, SPI decls and explicitly unavailable decls are
818-
// constrained to the deployment target. There's not much benefit to
819-
// checking these declarations at a lower availability version floor since
820-
// neither can be used by API clients.
821-
if (D->isSPI() || D->getSemanticUnavailableAttr())
817+
// As a convenience, explicitly unavailable decls are constrained to the
818+
// deployment target. There's not much benefit to checking these decls at a
819+
// lower availability version floor since they can't be invoked by clients.
820+
if (getCurrentScope()->getAvailabilityContext().isUnavailable() ||
821+
D->isUnavailable())
822+
return true;
823+
824+
// To remain compatible with a lot of existing SPIs that are declared
825+
// without availability attributes, constrain them to the deployment target
826+
// too.
827+
if (D->isSPI())
822828
return true;
823829

824830
return !::isExported(D);

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,7 @@ checkOverrideUnavailability(ValueDecl *override, ValueDecl *base) {
19241924
if (auto *overrideParent = override->getDeclContext()->getAsDecl()) {
19251925
// If the parent of the override is unavailable, then the unavailability of
19261926
// the override decl is irrelevant.
1927-
if (overrideParent->getSemanticUnavailableAttr())
1927+
if (overrideParent->isSemanticallyUnavailable())
19281928
return {OverrideUnavailabilityStatus::Ignored, nullptr};
19291929
}
19301930

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1883,7 +1883,7 @@ RequirementCheck WitnessChecker::checkWitness(ValueDecl *requirement,
18831883
}
18841884

18851885
if (auto adoptingNominal = DC->getSelfNominalTypeDecl()) {
1886-
if (adoptingNominal->getSemanticUnavailableAttr())
1886+
if (adoptingNominal->isSemanticallyUnavailable())
18871887
return true;
18881888
}
18891889

test/IRGen/unavailable_decl_optimization_complete_nested_type.swift

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
1-
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=none %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-NO-STRIP
1+
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=none %s -emit-ir | %FileCheck %s --check-prefixes=CHECK-NO-STRIP
22

3-
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=complete %s -emit-ir | %FileCheck %s --check-prefixes=CHECK,CHECK-STRIP
3+
// RUN: %target-swift-frontend -parse-as-library -module-name Test -validate-tbd-against-ir=missing -unavailable-decl-optimization=complete %s -emit-ir | %FileCheck %s --implicit-check-not=unavailableFuncWithNestedObsoleteType --implicit-check-not=unavailableFuncWithNestedType --implicit-check-not=NestedInExtension
4+
5+
// CHECK-NO-STRIP-DAG: s4Test29unavailableFuncWithNestedType
6+
// CHECK-NO-STRIP-DAG: s4Test29unavailableFuncWithNestedTypeyyF0E10InFunction
47

5-
// CHECK-NO-STRIP: s4Test29unavailableFuncWithNestedTypeyyF
6-
// CHECK-STRIP-NOT: s4Test29unavailableFuncWithNestedTypeyyF
78
@available(*, unavailable)
89
public func unavailableFuncWithNestedType() {
910
struct NestedInFunction {
10-
// s4Test29unavailableFuncWithNestedTypeyyF0E10InFunctionL_VADycfC
1111
init() {}
1212
}
1313

1414
_ = NestedInFunction()
1515
}
1616

17-
// CHECK-NO-STRIP: s4Test29unavailableFuncWithNestedTypeyyF0E10InFunctionL_VADycfC
18-
// CHECK-STRIP-NOT: s4Test29unavailableFuncWithNestedTypeyyF0E10InFunctionL_VADycfC
17+
// CHECK-NO-STRIP-DAG: s4Test37unavailableFuncWithNestedObsoleteType
18+
// CHECK-NO-STRIP-DAG: s4Test37unavailableFuncWithNestedObsoleteTypeyyF0E10InFunction
19+
20+
@available(*, unavailable)
21+
public func unavailableFuncWithNestedObsoleteType() {
22+
@available(swift, obsoleted: 4)
23+
struct NestedInFunction {
24+
init() {}
25+
}
26+
}
1927

2028
public struct S {}
2129

30+
// CHECK-NO-STRIP-DAG: s4Test1SV17NestedInExtension
31+
2232
extension S {
2333
@available(*, unavailable)
2434
public struct NestedInExtension {
25-
// CHECK-NO-STRIP: s4Test1SV17NestedInExtensionV6methodyyF
26-
// CHECK-STRIP-NOT: s4Test1SV17NestedInExtensionV6methodyyF
2735
public func method() {}
2836
}
2937
}
3038

31-
// CHECK-NO-STRIP: s4Test1SV17NestedInExtensionVMa
32-
// CHECK-STRIP-NOT: s4Test1SV17NestedInExtensionVMa
33-
34-
// CHECK-NO-STRIP: s4Test29unavailableFuncWithNestedTypeyyF0E10InFunctionL_VMa
35-
// CHECK-STRIP-NOT: s4Test29unavailableFuncWithNestedTypeyyF0E10InFunctionL_VMa

test/SILGen/unavailable_decl_optimization_stub.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,37 @@ public func unavailableFunc() -> S {
1212
return S()
1313
}
1414

15+
// CHECK-LABEL: sil{{.*}}@$s4Test025unavailableFuncWithNestedC0yyF
16+
// CHECK: [[FNREF:%.*]] = function_ref @$[[DIAGNOSEFN:(ss31_diagnoseUnavailableCodeReacheds5NeverOyF|ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb)]] : $@convention(thin) () -> Never
17+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
18+
// CHECK: function_ref @$s4Test025unavailableFuncWithNestedC0yyF6nestedL_SiyF
19+
// CHECK: } // end sil function '$s4Test025unavailableFuncWithNestedC0yyF'
20+
@available(*, unavailable)
21+
public func unavailableFuncWithNestedFunc() {
22+
func nested() -> Int { 1 }
23+
_ = nested()
24+
}
25+
26+
// CHECK-LABEL: sil{{.*}}@$s4Test025unavailableFuncWithNestedC0yyF6nestedL_SiyF
27+
// CHECK: [[FNREF:%.*]] = function_ref @$[[DIAGNOSEFN:(ss31_diagnoseUnavailableCodeReacheds5NeverOyF|ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb)]] : $@convention(thin) () -> Never
28+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
29+
// CHECK: } // end sil function '$s4Test025unavailableFuncWithNestedC0yyF6nestedL_SiyF'
30+
31+
// CHECK-LABEL: sil{{.*}}@$s4Test033unavailableFuncWithObsoleteNestedC0yyF
32+
// CHECK: [[FNREF:%.*]] = function_ref @$[[DIAGNOSEFN:(ss31_diagnoseUnavailableCodeReacheds5NeverOyF|ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb)]] : $@convention(thin) () -> Never
33+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
34+
// CHECK: } // end sil function '$s4Test033unavailableFuncWithObsoleteNestedC0yyF'
35+
@available(*, unavailable)
36+
public func unavailableFuncWithObsoleteNestedFunc() {
37+
@available(swift, obsoleted: 1)
38+
func nested() -> Int { 1 }
39+
}
40+
41+
// CHECK-LABEL: sil{{.*}}@$s4Test033unavailableFuncWithObsoleteNestedC0yyF6nestedL_SiyF
42+
// CHECK: [[FNREF:%.*]] = function_ref @$[[DIAGNOSEFN:(ss31_diagnoseUnavailableCodeReacheds5NeverOyF|ss31_diagnoseUnavailableCodeReacheds5NeverOyFTwb)]] : $@convention(thin) () -> Never
43+
// CHECK-NEXT: [[APPLY:%.*]] = apply [[FNREF]]()
44+
// CHECK: } // end sil function '$s4Test033unavailableFuncWithObsoleteNestedC0yyF6nestedL_SiyF'
45+
1546
enum SomeError: Error { case generic }
1647

1748
// CHECK-LABEL: sil{{.*}}@$s4Test23unavailableThrowingFuncyyKF

0 commit comments

Comments
 (0)