Skip to content

Commit da31340

Browse files
authored
Merge pull request #80040 from tshortli/sink-availability-scope-builder
AST/Sema: Sink `AvailabilityContext` lookup queries from Sema to AST
2 parents e33bf2a + 2dd544a commit da31340

23 files changed

+1424
-1415
lines changed

include/swift/AST/AvailabilityContext.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
namespace swift {
2929
class ASTContext;
3030
class AvailableAttr;
31+
class AvailabilityScope;
3132
class Decl;
33+
class DeclContext;
3234

3335
/// An `AvailabilityContext` summarizes the availability constraints for a
3436
/// specific scope, such as within a declaration or at a particular source
@@ -62,6 +64,17 @@ class AvailabilityContext {
6264
/// set to the deployment target.
6365
static AvailabilityContext forDeploymentTarget(const ASTContext &ctx);
6466

67+
/// Returns the most refined `AvailabilityContext` for the given source
68+
/// location. If `refinedScope` is not `nullptr`, it will be set to the most
69+
/// refined scope that contains the location.
70+
static AvailabilityContext
71+
forLocation(SourceLoc loc, const DeclContext *declContext,
72+
const AvailabilityScope **refinedScope = nullptr);
73+
74+
/// Returns the availability context of the signature (rather than the body)
75+
/// of the given declaration.
76+
static AvailabilityContext forDeclSignature(const Decl *decl);
77+
6578
/// Returns the range of platform versions which may execute code in the
6679
/// availability context, starting at its introduction version.
6780
// FIXME: [availability] Remove; superseded by getAvailableRange().

include/swift/AST/AvailabilityInference.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,6 @@ class AvailabilityInference {
9494
AvailabilityDomain &domain, llvm::VersionTuple &platformVer);
9595
};
9696

97-
// FIXME: This should become a utility on Decl.
98-
99-
/// Given a declaration upon which an availability attribute would appear in
100-
/// concrete syntax, return a declaration to which the parser
101-
/// actually attaches the attribute in the abstract syntax tree. We use this
102-
/// function to determine whether the concrete syntax already has an
103-
/// availability attribute.
104-
const Decl *abstractSyntaxDeclForAvailableAttribute(const Decl *D);
105-
10697
} // end namespace swift
10798

10899
#endif

include/swift/AST/AvailabilityScope.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ class AvailabilityScope : public ASTAllocated<AvailabilityScope> {
185185
SourceRange SrcRange, const AvailabilityContext Info);
186186

187187
public:
188+
/// Constructs the root availability scope for the given file and builds out
189+
/// the scope tree for the top level contents of the file.
190+
static AvailabilityScope *getOrBuildForSourceFile(SourceFile &SF);
191+
188192
/// Create the root availability scope for the given SourceFile.
189193
static AvailabilityScope *createForSourceFile(SourceFile *SF,
190194
const AvailabilityContext Info);

include/swift/AST/Decl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,16 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
15101510
/// Returns true if this declaration has any `@backDeployed` attributes.
15111511
bool hasBackDeployedAttr() const;
15121512

1513+
/// Return the declaration upon which the attributes for this declaration
1514+
/// would appear in concrete syntax. This function is necessary because for
1515+
/// semantic analysis, the parser attaches attributes to declarations other
1516+
/// than those on which they, concretely, appear.
1517+
const Decl *getConcreteSyntaxDeclForAttributes() const;
1518+
1519+
/// Return the declaration to which the parser actually attaches attributes in
1520+
/// the abstract syntax tree (see `getConcreteSyntaxDeclForAttributes()`).
1521+
const Decl *getAbstractSyntaxDeclForAttributes() const;
1522+
15131523
/// Apply the specified function to decls that should be placed _next_ to
15141524
/// this decl when constructing AST.
15151525
void forEachDeclToHoist(llvm::function_ref<void(Decl *)> callback) const;

include/swift/AST/DeclExportabilityVisitor.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ class DeclExportabilityVisitor
178178

179179
#undef UNINTERESTING
180180
};
181+
182+
/// Check if a declaration is exported as part of a module's external interface.
183+
/// This includes public and @usableFromInline decls.
184+
/// FIXME: This is legacy that should be subsumed by `DeclExportabilityVisitor`
185+
bool isExported(const Decl *D);
186+
187+
/// A specialization of `isExported` for `ValueDecl`.
188+
bool isExported(const ValueDecl *VD);
189+
190+
/// A specialization of `isExported` for `ExtensionDecl`.
191+
bool isExported(const ExtensionDecl *ED);
192+
181193
} // end namespace swift
182194

183195
#endif

include/swift/AST/SourceFile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ class SourceFile final : public FileUnit {
776776

777777
/// Get the root availability scope for the file. The root scope may be
778778
/// null if the scope tree has not been built yet. Use
779-
/// TypeChecker::getOrBuildAvailabilityScope() to get a built
779+
/// `AvailabilityScope::getOrBuildForSourceFile()` to get a built
780780
/// root of the tree.
781781
AvailabilityScope *getAvailabilityScope() const;
782782

lib/AST/Availability.cpp

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/AST/AvailabilityInference.h"
2323
#include "swift/AST/AvailabilityRange.h"
2424
#include "swift/AST/Decl.h"
25+
#include "swift/AST/DeclExportabilityVisitor.h"
2526
// FIXME: [availability] Remove this when possible
2627
#include "swift/AST/DiagnosticsParse.h"
2728
#include "swift/AST/DiagnosticsSema.h"
@@ -382,7 +383,7 @@ static std::optional<SemanticAvailableAttr>
382383
getDeclAvailableAttrForPlatformIntroduction(const Decl *D) {
383384
std::optional<SemanticAvailableAttr> bestAvailAttr;
384385

385-
D = abstractSyntaxDeclForAvailableAttribute(D);
386+
D = D->getAbstractSyntaxDeclForAttributes();
386387

387388
for (auto attr : D->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
388389
if (!attr.isPlatformSpecific() || !attr.getIntroduced())
@@ -1053,32 +1054,84 @@ bool ASTContext::supportsVersionedAvailability() const {
10531054
return minimumAvailableOSVersionForTriple(LangOpts.Target).has_value();
10541055
}
10551056

1056-
// FIXME: Rename abstractSyntaxDeclForAvailableAttribute since it's useful
1057-
// for more attributes than `@available`.
1058-
const Decl *
1059-
swift::abstractSyntaxDeclForAvailableAttribute(const Decl *ConcreteSyntaxDecl) {
1060-
// This function needs to be kept in sync with its counterpart,
1061-
// concreteSyntaxDeclForAvailableAttribute().
1062-
1063-
if (auto *PBD = dyn_cast<PatternBindingDecl>(ConcreteSyntaxDecl)) {
1064-
// Existing @available attributes in the AST are attached to VarDecls
1065-
// rather than PatternBindingDecls, so we return the first VarDecl for
1066-
// the pattern binding declaration.
1067-
// This is safe, even though there may be multiple VarDecls, because
1068-
// all parsed attribute that appear in the concrete syntax upon on the
1069-
// PatternBindingDecl are added to all of the VarDecls for the pattern
1070-
// binding.
1071-
for (auto index : range(PBD->getNumPatternEntries())) {
1072-
if (auto VD = PBD->getAnchoringVarDecl(index))
1073-
return VD;
1074-
}
1075-
} else if (auto *ECD = dyn_cast<EnumCaseDecl>(ConcreteSyntaxDecl)) {
1076-
// Similar to the PatternBindingDecl case above, we return the
1077-
// first EnumElementDecl.
1078-
if (auto *Elem = ECD->getFirstElement()) {
1079-
return Elem;
1057+
bool swift::isExported(const Decl *D) {
1058+
if (auto *VD = dyn_cast<ValueDecl>(D)) {
1059+
return isExported(VD);
1060+
}
1061+
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
1062+
for (unsigned i = 0, e = PBD->getNumPatternEntries(); i < e; ++i) {
1063+
if (auto *VD = PBD->getAnchoringVarDecl(i))
1064+
return isExported(VD);
10801065
}
1066+
1067+
return false;
1068+
}
1069+
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
1070+
return isExported(ED);
1071+
}
1072+
1073+
return true;
1074+
}
1075+
1076+
bool swift::isExported(const ValueDecl *VD) {
1077+
if (VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
1078+
return false;
1079+
if (VD->isObjCMemberImplementation())
1080+
return false;
1081+
1082+
// Is this part of the module's API or ABI?
1083+
AccessScope accessScope =
1084+
VD->getFormalAccessScope(nullptr,
1085+
/*treatUsableFromInlineAsPublic*/ true);
1086+
if (accessScope.isPublic())
1087+
return true;
1088+
1089+
// Is this a stored property in a @frozen struct or class?
1090+
if (auto *property = dyn_cast<VarDecl>(VD))
1091+
if (property->isLayoutExposedToClients())
1092+
return true;
1093+
1094+
return false;
1095+
}
1096+
1097+
static bool hasConformancesToPublicProtocols(const ExtensionDecl *ED) {
1098+
auto nominal = ED->getExtendedNominal();
1099+
if (!nominal)
1100+
return false;
1101+
1102+
// Extensions of protocols cannot introduce additional conformances.
1103+
if (isa<ProtocolDecl>(nominal))
1104+
return false;
1105+
1106+
auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
1107+
for (const ProtocolDecl *PD : protocols) {
1108+
AccessScope scope =
1109+
PD->getFormalAccessScope(/*useDC*/ nullptr,
1110+
/*treatUsableFromInlineAsPublic*/ true);
1111+
if (scope.isPublic())
1112+
return true;
1113+
}
1114+
1115+
return false;
1116+
}
1117+
1118+
bool swift::isExported(const ExtensionDecl *ED) {
1119+
// An extension can only be exported if it extends an exported type.
1120+
if (auto *NTD = ED->getExtendedNominal()) {
1121+
if (!isExported(NTD))
1122+
return false;
1123+
}
1124+
1125+
// If there are any exported members then the extension is exported.
1126+
for (const Decl *D : ED->getMembers()) {
1127+
if (isExported(D))
1128+
return true;
10811129
}
10821130

1083-
return ConcreteSyntaxDecl;
1131+
// If the extension declares a conformance to a public protocol then the
1132+
// extension is exported.
1133+
if (hasConformancesToPublicProtocols(ED))
1134+
return true;
1135+
1136+
return false;
10841137
}

lib/AST/AvailabilityConstraint.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ swift::getAvailabilityConstraintsForDecl(const Decl *decl,
232232
if (isa<GenericTypeParamDecl>(decl))
233233
return DeclAvailabilityConstraints();
234234

235-
decl = abstractSyntaxDeclForAvailableAttribute(decl);
235+
decl = decl->getAbstractSyntaxDeclForAttributes();
236236

237237
getAvailabilityConstraintsForDecl(constraints, decl, context);
238238

lib/AST/AvailabilityContext.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
#include "swift/AST/AvailabilityConstraint.h"
1616
#include "swift/AST/AvailabilityContextStorage.h"
1717
#include "swift/AST/AvailabilityInference.h"
18+
#include "swift/AST/AvailabilityScope.h"
1819
#include "swift/AST/Decl.h"
20+
#include "swift/AST/Module.h"
1921
#include "swift/Basic/Assertions.h"
2022

2123
using namespace swift;
@@ -153,6 +155,65 @@ AvailabilityContext::forDeploymentTarget(const ASTContext &ctx) {
153155
AvailabilityRange::forDeploymentTarget(ctx), ctx);
154156
}
155157

158+
AvailabilityContext
159+
AvailabilityContext::forLocation(SourceLoc loc, const DeclContext *declContext,
160+
const AvailabilityScope **refinedScope) {
161+
auto &ctx = declContext->getASTContext();
162+
SourceFile *sf =
163+
loc.isValid()
164+
? declContext->getParentModule()->getSourceFileContainingLocation(loc)
165+
: declContext->getParentSourceFile();
166+
167+
// If our source location is invalid (this may be synthesized code), climb the
168+
// decl context hierarchy until we find a location that is valid, merging
169+
// availability contexts on the way up.
170+
//
171+
// Because we are traversing decl contexts, we will miss availability scopes
172+
// in synthesized code that are introduced by AST elements that are themselves
173+
// not decl contexts, such as `#available(..)` and property declarations.
174+
// Therefore a reference with an invalid source location that is contained
175+
// inside an `#available()` and with no intermediate decl context will not be
176+
// refined. For now, this is fine, but if we ever synthesize #available(),
177+
// this could become a problem..
178+
179+
// We can assume we are running on at least the minimum inlining target.
180+
auto baseAvailability = AvailabilityContext::forInliningTarget(ctx);
181+
auto isInvalidLoc = [sf](SourceLoc loc) {
182+
return sf ? loc.isInvalid() : true;
183+
};
184+
while (declContext && isInvalidLoc(loc)) {
185+
const Decl *decl = declContext->getInnermostDeclarationDeclContext();
186+
if (!decl)
187+
break;
188+
189+
baseAvailability.constrainWithDecl(decl);
190+
loc = decl->getLoc();
191+
declContext = decl->getDeclContext();
192+
}
193+
194+
if (!sf || loc.isInvalid())
195+
return baseAvailability;
196+
197+
auto *rootScope = AvailabilityScope::getOrBuildForSourceFile(*sf);
198+
if (!rootScope)
199+
return baseAvailability;
200+
201+
auto *scope = rootScope->findMostRefinedSubContext(loc, ctx);
202+
if (!scope)
203+
return baseAvailability;
204+
205+
if (refinedScope)
206+
*refinedScope = scope;
207+
208+
auto availability = scope->getAvailabilityContext();
209+
availability.constrainWithContext(baseAvailability, ctx);
210+
return availability;
211+
}
212+
213+
AvailabilityContext AvailabilityContext::forDeclSignature(const Decl *decl) {
214+
return forLocation(decl->getLoc(), decl->getInnermostDeclContext());
215+
}
216+
156217
AvailabilityRange AvailabilityContext::getPlatformRange() const {
157218
return storage->platformRange;
158219
}

0 commit comments

Comments
 (0)