Skip to content

Commit cc1800d

Browse files
authored
Merge pull request #22460 from jrose-apple/i-cannot-contain-my-excitement
Extend transitive availability checking to initial value expressions (better version)
2 parents e3e69f7 + 19c1765 commit cc1800d

File tree

3 files changed

+140
-68
lines changed

3 files changed

+140
-68
lines changed

lib/AST/ASTVerifier.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,19 @@ class Verifier : public ASTWalker {
540540
void verifyParsed(Stmt *S) {}
541541
void verifyParsed(Pattern *P) {}
542542
void verifyParsed(Decl *D) {
543+
PrettyStackTraceDecl debugStack("verifying ", D);
543544
if (!D->getDeclContext()) {
544-
Out << "every Decl should have a DeclContext";
545-
PrettyStackTraceDecl debugStack("verifying DeclContext", D);
545+
Out << "every Decl should have a DeclContext\n";
546546
abort();
547547
}
548+
if (auto *DC = dyn_cast<DeclContext>(D)) {
549+
if (D->getDeclContext() != DC->getParent()) {
550+
Out << "Decl's DeclContext not in sync with DeclContext's parent\n";
551+
D->getDeclContext()->dumpContext();
552+
DC->getParent()->dumpContext();
553+
abort();
554+
}
555+
}
548556
}
549557
template<typename T>
550558
void verifyParsedBase(T ASTNode) {

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 43 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "TypeChecker.h"
1919
#include "MiscDiagnostics.h"
2020
#include "swift/AST/ASTWalker.h"
21+
#include "swift/AST/Initializer.h"
2122
#include "swift/AST/NameLookup.h"
2223
#include "swift/AST/Pattern.h"
2324
#include "swift/AST/TypeRefinementContext.h"
@@ -856,27 +857,41 @@ static Optional<ASTNode> findInnermostAncestor(
856857
static const Decl *findContainingDeclaration(SourceRange ReferenceRange,
857858
const DeclContext *ReferenceDC,
858859
const SourceManager &SM) {
859-
if (const Decl *D = ReferenceDC->getInnermostDeclarationDeclContext())
860+
auto ContainsReferenceRange = [&](const Decl *D) -> bool {
861+
if (ReferenceRange.isInvalid())
862+
return false;
863+
return SM.rangeContains(D->getSourceRange(), ReferenceRange);
864+
};
865+
866+
if (const Decl *D = ReferenceDC->getInnermostDeclarationDeclContext()) {
867+
// If we have an inner declaration context, see if we can narrow the search
868+
// down to one of its members. This is important for properties, which don't
869+
// count as DeclContexts of their own but which can still introduce
870+
// availability.
871+
if (auto *IDC = dyn_cast<IterableDeclContext>(D)) {
872+
auto BestMember = llvm::find_if(IDC->getMembers(),
873+
ContainsReferenceRange);
874+
if (BestMember != IDC->getMembers().end())
875+
return *BestMember;
876+
}
860877
return D;
878+
}
861879

862-
// We couldn't find a suitable node by climbing the DeclContext
863-
// hierarchy, so fall back to looking for a top-level declaration
864-
// that contains the reference range. We will hit this case for
865-
// top-level elements that do not themselves introduce DeclContexts,
866-
// such as extensions and global variables. If we don't have a reference
867-
// range, there is nothing we can do, so return null.
880+
// We couldn't find a suitable node by climbing the DeclContext hierarchy, so
881+
// fall back to looking for a top-level declaration that contains the
882+
// reference range. We will hit this case for top-level elements that do not
883+
// themselves introduce DeclContexts, such as global variables. If we don't
884+
// have a reference range, there is nothing we can do, so return null.
868885
if (ReferenceRange.isInvalid())
869886
return nullptr;
870887

871888
SourceFile *SF = ReferenceDC->getParentSourceFile();
872889
if (!SF)
873890
return nullptr;
874891

875-
for (Decl *D : SF->Decls) {
876-
if (SM.rangeContains(D->getSourceRange(), ReferenceRange)) {
877-
return D;
878-
}
879-
}
892+
auto BestTopLevelDecl = llvm::find_if(SF->Decls, ContainsReferenceRange);
893+
if (BestTopLevelDecl != SF->Decls.end())
894+
return *BestTopLevelDecl;
880895

881896
return nullptr;
882897
}
@@ -929,9 +944,13 @@ abstractSyntaxDeclForAvailableAttribute(const Decl *ConcreteSyntaxDecl) {
929944
// binding.
930945
ArrayRef<PatternBindingEntry> Entries = PBD->getPatternList();
931946
if (!Entries.empty()) {
932-
VarDecl *VD = Entries.front().getPattern()->getSingleVar();
933-
if (VD)
934-
return VD;
947+
const VarDecl *AnyVD = nullptr;
948+
// FIXME: This is wasteful; we only need the first variable.
949+
Entries.front().getPattern()->forEachVariable([&](const VarDecl *VD) {
950+
AnyVD = VD;
951+
});
952+
if (AnyVD)
953+
return AnyVD;
935954
}
936955
} else if (auto *ECD = dyn_cast<EnumCaseDecl>(ConcreteSyntaxDecl)) {
937956
// Similar to the PatternBindingDecl case above, we return the
@@ -1091,35 +1110,17 @@ static void findAvailabilityFixItNodes(SourceRange ReferenceRange,
10911110
FoundVersionCheckNode =
10921111
findInnermostAncestor(ReferenceRange, SM, SearchRoot, IsGuardable);
10931112

1094-
// Find some Decl that contains the reference range. We use this declaration
1095-
// as a starting place to climb the DeclContext hierarchy to find
1096-
// places to suggest adding @available() annotations.
1097-
InnermostAncestorFinder::MatchPredicate IsDeclaration = [](
1098-
ASTNode Node, ASTWalker::ParentTy Parent) { return Node.is<Decl *>(); };
1099-
1100-
Optional<ASTNode> FoundDeclarationNode =
1101-
findInnermostAncestor(ReferenceRange, SM, SearchRoot, IsDeclaration);
1102-
1103-
const Decl *ContainingDecl = nullptr;
1104-
if (FoundDeclarationNode.hasValue()) {
1105-
ContainingDecl = FoundDeclarationNode.getValue().get<Decl *>();
1106-
}
1107-
1108-
if (!ContainingDecl) {
1109-
ContainingDecl = ReferenceDC->getInnermostMethodContext();
1110-
}
1111-
11121113
// Try to find declarations on which @available attributes can be added.
11131114
// The heuristics for finding these declarations are biased towards deeper
11141115
// nodes in the AST to limit the scope of suggested availability regions
11151116
// and provide a better IDE experience (it can get jumpy if Fix-It locations
11161117
// are far away from the error needing the Fix-It).
1117-
if (ContainingDecl) {
1118+
if (DeclarationToSearch) {
11181119
FoundMemberLevelDecl =
1119-
ancestorMemberLevelDeclForAvailabilityFixit(ContainingDecl);
1120+
ancestorMemberLevelDeclForAvailabilityFixit(DeclarationToSearch);
11201121

11211122
FoundTypeLevelDecl =
1122-
ancestorTypeLevelDeclForAvailabilityFixit(ContainingDecl);
1123+
ancestorTypeLevelDeclForAvailabilityFixit(DeclarationToSearch);
11231124
}
11241125
}
11251126

@@ -1408,7 +1409,7 @@ someEnclosingDeclMatches(SourceRange ReferenceRange,
14081409
// Climb the DeclContext hierarchy to see if any of the containing
14091410
// declarations matches the predicate.
14101411
const DeclContext *DC = ReferenceDC;
1411-
do {
1412+
while (true) {
14121413
auto *D = DC->getInnermostDeclarationDeclContext();
14131414
if (!D)
14141415
break;
@@ -1424,20 +1425,15 @@ someEnclosingDeclMatches(SourceRange ReferenceRange,
14241425
return true;
14251426
}
14261427

1427-
DC = DC->getParent();
1428-
} while (DC);
1428+
DC = D->getDeclContext();
1429+
}
14291430

14301431
// Search the AST starting from our innermost declaration context to see if
14311432
// if the reference is inside a property declaration but not inside an
14321433
// accessor (this can happen for the TypeRepr for the declared type of a
14331434
// property, for example).
14341435
// We can't rely on the DeclContext hierarchy climb above because properties
1435-
// do not introduce a new DeclContext. This search is potentially slow, so we
1436-
// do it last and only if the reference declaration context is a
1437-
// type or global context.
1438-
1439-
if (!ReferenceDC->isTypeContext() && !ReferenceDC->isModuleScopeContext())
1440-
return false;
1436+
// do not introduce a new DeclContext.
14411437

14421438
// Don't search for a containing declaration if we don't have a source range.
14431439
if (ReferenceRange.isInvalid())
@@ -1448,28 +1444,11 @@ someEnclosingDeclMatches(SourceRange ReferenceRange,
14481444
findContainingDeclaration(ReferenceRange, ReferenceDC, Ctx.SourceMgr);
14491445

14501446
// We may not be able to find a declaration to search if the ReferenceRange
1451-
// is invalid (i.e., we are in synthesized code).
1447+
// isn't useful (i.e., we are in synthesized code).
14521448
if (!DeclToSearch)
14531449
return false;
14541450

1455-
InnermostAncestorFinder::MatchPredicate IsDeclaration =
1456-
[](ASTNode Node, ASTWalker::ParentTy Parent) {
1457-
return Node.is<Decl *>();
1458-
};
1459-
1460-
Optional<ASTNode> FoundDeclarationNode =
1461-
findInnermostAncestor(ReferenceRange, Ctx.SourceMgr,
1462-
const_cast<Decl *>(DeclToSearch), IsDeclaration);
1463-
1464-
if (FoundDeclarationNode.hasValue()) {
1465-
const Decl *D = FoundDeclarationNode.getValue().get<Decl *>();
1466-
D = abstractSyntaxDeclForAvailableAttribute(D);
1467-
if (Pred(D)) {
1468-
return true;
1469-
}
1470-
}
1471-
1472-
return false;
1451+
return Pred(abstractSyntaxDeclForAvailableAttribute(DeclToSearch));
14731452
}
14741453

14751454
/// Returns true if the reference or any of its parents is an

test/attr/attr_availability_transitive_osx.swift

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
// RUN: %target-typecheck-verify-swift
1+
// RUN: %target-typecheck-verify-swift -parse-as-library
22
// REQUIRES: OS=macosx
33

44
// Allow referencing unavailable API in situations where the caller is marked unavailable in the same circumstances.
55

66
@available(OSX, unavailable)
7-
func osx() {} // expected-note 2{{'osx()' has been explicitly marked unavailable here}}
7+
@discardableResult
8+
func osx() -> Int { return 0 } // expected-note * {{'osx()' has been explicitly marked unavailable here}}
89

910
@available(OSXApplicationExtension, unavailable)
1011
func osx_extension() {}
1112

13+
@available(OSX, unavailable)
14+
func osx_pair() -> (Int, Int) { return (0, 0) } // expected-note * {{'osx_pair()' has been explicitly marked unavailable here}}
15+
1216
func call_osx_extension() {
1317
osx_extension() // OK; osx_extension is only unavailable if -application-extension is passed.
1418
}
@@ -35,3 +39,84 @@ func osx_extension_call_osx_extension() {
3539
func osx_extension_call_osx() {
3640
osx() // expected-error {{'osx()' is unavailable}}
3741
}
42+
43+
@available(OSX, unavailable)
44+
var osx_init_osx = osx() // OK
45+
46+
@available(OSXApplicationExtension, unavailable)
47+
var osx_extension_init_osx = osx() // expected-error {{'osx()' is unavailable}}
48+
49+
@available(OSX, unavailable)
50+
var osx_inner_init_osx = { let inner_var = osx() } // OK
51+
52+
// FIXME: I'm not sure why this produces two errors instead of just one.
53+
@available(OSXApplicationExtension, unavailable)
54+
var osx_extension_inner_init_osx = { let inner_var = osx() } // expected-error 2 {{'osx()' is unavailable}}
55+
56+
struct Outer {
57+
@available(OSX, unavailable)
58+
var osx_init_osx = osx() // OK
59+
60+
@available(OSX, unavailable)
61+
lazy var osx_lazy_osx = osx() // OK
62+
63+
@available(OSXApplicationExtension, unavailable)
64+
var osx_extension_init_osx = osx() // expected-error {{'osx()' is unavailable}}
65+
66+
@available(OSXApplicationExtension, unavailable)
67+
var osx_extension_lazy_osx = osx() // expected-error {{'osx()' is unavailable}}
68+
69+
@available(OSX, unavailable)
70+
var osx_init_multi1_osx = osx(), osx_init_multi2_osx = osx() // OK
71+
72+
@available(OSXApplicationExtension, unavailable)
73+
var osx_extension_init_multi1_osx = osx(), osx_extension_init_multi2_osx = osx() // expected-error 2 {{'osx()' is unavailable}}
74+
75+
@available(OSX, unavailable)
76+
var (osx_init_deconstruct1_osx, osx_init_deconstruct2_osx) = osx_pair() // OK
77+
78+
@available(OSXApplicationExtension, unavailable)
79+
var (osx_extension_init_deconstruct1_osx, osx_extension_init_deconstruct2_osx) = osx_pair() // expected-error {{'osx_pair()' is unavailable}}
80+
81+
@available(OSX, unavailable)
82+
var (_, osx_init_deconstruct2_only_osx) = osx_pair() // OK
83+
84+
@available(OSXApplicationExtension, unavailable)
85+
var (_, osx_extension_init_deconstruct2_only_osx) = osx_pair() // expected-error {{'osx_pair()' is unavailable}}
86+
87+
@available(OSX, unavailable)
88+
var (osx_init_deconstruct1_only_osx, _) = osx_pair() // OK
89+
90+
@available(OSXApplicationExtension, unavailable)
91+
var (osx_extension_init_deconstruct1_only_osx, _) = osx_pair() // expected-error {{'osx_pair()' is unavailable}}
92+
93+
@available(OSX, unavailable)
94+
var osx_inner_init_osx = { let inner_var = osx() } // OK
95+
96+
// FIXME: I'm not sure why this produces two errors instead of just one.
97+
@available(OSXApplicationExtension, unavailable)
98+
var osx_extension_inner_init_osx = { let inner_var = osx() } // expected-error 2 {{'osx()' is unavailable}}
99+
}
100+
101+
extension Outer {
102+
@available(OSX, unavailable)
103+
static var also_osx_init_osx = osx() // OK
104+
105+
@available(OSXApplicationExtension, unavailable)
106+
static var also_osx_extension_init_osx = osx() // expected-error {{'osx()' is unavailable}}
107+
}
108+
109+
@available(OSX, unavailable)
110+
extension Outer {
111+
static var outer_osx_init_osx = osx() // OK
112+
}
113+
114+
@available(OSX, unavailable)
115+
struct NotOnOSX {
116+
var osx_init_osx = osx() // OK
117+
lazy var osx_lazy_osx = osx() // OK
118+
var osx_init_multi1_osx = osx(), osx_init_multi2_osx = osx() // OK
119+
var (osx_init_deconstruct1_osx, osx_init_deconstruct2_osx) = osx_pair() // OK
120+
var (_, osx_init_deconstruct2_only_osx) = osx_pair() // OK
121+
var (osx_init_deconstruct1_only_osx, _) = osx_pair() // OK
122+
}

0 commit comments

Comments
 (0)