Skip to content

AST/Sema: Sink AvailabilityContext lookup queries from Sema to AST #80040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions include/swift/AST/AvailabilityContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
namespace swift {
class ASTContext;
class AvailableAttr;
class AvailabilityScope;
class Decl;
class DeclContext;

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

/// Returns the most refined `AvailabilityContext` for the given source
/// location. If `refinedScope` is not `nullptr`, it will be set to the most
/// refined scope that contains the location.
static AvailabilityContext
forLocation(SourceLoc loc, const DeclContext *declContext,
const AvailabilityScope **refinedScope = nullptr);

/// Returns the availability context of the signature (rather than the body)
/// of the given declaration.
static AvailabilityContext forDeclSignature(const Decl *decl);

/// Returns the range of platform versions which may execute code in the
/// availability context, starting at its introduction version.
// FIXME: [availability] Remove; superseded by getAvailableRange().
Expand Down
9 changes: 0 additions & 9 deletions include/swift/AST/AvailabilityInference.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,6 @@ class AvailabilityInference {
AvailabilityDomain &domain, llvm::VersionTuple &platformVer);
};

// FIXME: This should become a utility on Decl.

/// Given a declaration upon which an availability attribute would appear in
/// concrete syntax, return a declaration to which the parser
/// actually attaches the attribute in the abstract syntax tree. We use this
/// function to determine whether the concrete syntax already has an
/// availability attribute.
const Decl *abstractSyntaxDeclForAvailableAttribute(const Decl *D);

} // end namespace swift

#endif
4 changes: 4 additions & 0 deletions include/swift/AST/AvailabilityScope.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ class AvailabilityScope : public ASTAllocated<AvailabilityScope> {
SourceRange SrcRange, const AvailabilityContext Info);

public:
/// Constructs the root availability scope for the given file and builds out
/// the scope tree for the top level contents of the file.
static AvailabilityScope *getOrBuildForSourceFile(SourceFile &SF);

/// Create the root availability scope for the given SourceFile.
static AvailabilityScope *createForSourceFile(SourceFile *SF,
const AvailabilityContext Info);
Expand Down
10 changes: 10 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,16 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl>, public Swi
/// Returns true if this declaration has any `@backDeployed` attributes.
bool hasBackDeployedAttr() const;

/// Return the declaration upon which the attributes for this declaration
/// would appear in concrete syntax. This function is necessary because for
/// semantic analysis, the parser attaches attributes to declarations other
/// than those on which they, concretely, appear.
const Decl *getConcreteSyntaxDeclForAttributes() const;

/// Return the declaration to which the parser actually attaches attributes in
/// the abstract syntax tree (see `getConcreteSyntaxDeclForAttributes()`).
const Decl *getAbstractSyntaxDeclForAttributes() const;

/// Apply the specified function to decls that should be placed _next_ to
/// this decl when constructing AST.
void forEachDeclToHoist(llvm::function_ref<void(Decl *)> callback) const;
Expand Down
12 changes: 12 additions & 0 deletions include/swift/AST/DeclExportabilityVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ class DeclExportabilityVisitor

#undef UNINTERESTING
};

/// Check if a declaration is exported as part of a module's external interface.
/// This includes public and @usableFromInline decls.
/// FIXME: This is legacy that should be subsumed by `DeclExportabilityVisitor`
bool isExported(const Decl *D);

/// A specialization of `isExported` for `ValueDecl`.
bool isExported(const ValueDecl *VD);

/// A specialization of `isExported` for `ExtensionDecl`.
bool isExported(const ExtensionDecl *ED);

} // end namespace swift

#endif
2 changes: 1 addition & 1 deletion include/swift/AST/SourceFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ class SourceFile final : public FileUnit {

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

Expand Down
105 changes: 79 additions & 26 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/AvailabilityRange.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DeclExportabilityVisitor.h"
// FIXME: [availability] Remove this when possible
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/DiagnosticsSema.h"
Expand Down Expand Up @@ -382,7 +383,7 @@ static std::optional<SemanticAvailableAttr>
getDeclAvailableAttrForPlatformIntroduction(const Decl *D) {
std::optional<SemanticAvailableAttr> bestAvailAttr;

D = abstractSyntaxDeclForAvailableAttribute(D);
D = D->getAbstractSyntaxDeclForAttributes();

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

// FIXME: Rename abstractSyntaxDeclForAvailableAttribute since it's useful
// for more attributes than `@available`.
const Decl *
swift::abstractSyntaxDeclForAvailableAttribute(const Decl *ConcreteSyntaxDecl) {
// This function needs to be kept in sync with its counterpart,
// concreteSyntaxDeclForAvailableAttribute().

if (auto *PBD = dyn_cast<PatternBindingDecl>(ConcreteSyntaxDecl)) {
// Existing @available attributes in the AST are attached to VarDecls
// rather than PatternBindingDecls, so we return the first VarDecl for
// the pattern binding declaration.
// This is safe, even though there may be multiple VarDecls, because
// all parsed attribute that appear in the concrete syntax upon on the
// PatternBindingDecl are added to all of the VarDecls for the pattern
// binding.
for (auto index : range(PBD->getNumPatternEntries())) {
if (auto VD = PBD->getAnchoringVarDecl(index))
return VD;
}
} else if (auto *ECD = dyn_cast<EnumCaseDecl>(ConcreteSyntaxDecl)) {
// Similar to the PatternBindingDecl case above, we return the
// first EnumElementDecl.
if (auto *Elem = ECD->getFirstElement()) {
return Elem;
bool swift::isExported(const Decl *D) {
if (auto *VD = dyn_cast<ValueDecl>(D)) {
return isExported(VD);
}
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
for (unsigned i = 0, e = PBD->getNumPatternEntries(); i < e; ++i) {
if (auto *VD = PBD->getAnchoringVarDecl(i))
return isExported(VD);
}

return false;
}
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
return isExported(ED);
}

return true;
}

bool swift::isExported(const ValueDecl *VD) {
if (VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return false;
if (VD->isObjCMemberImplementation())
return false;

// Is this part of the module's API or ABI?
AccessScope accessScope =
VD->getFormalAccessScope(nullptr,
/*treatUsableFromInlineAsPublic*/ true);
if (accessScope.isPublic())
return true;

// Is this a stored property in a @frozen struct or class?
if (auto *property = dyn_cast<VarDecl>(VD))
if (property->isLayoutExposedToClients())
return true;

return false;
}

static bool hasConformancesToPublicProtocols(const ExtensionDecl *ED) {
auto nominal = ED->getExtendedNominal();
if (!nominal)
return false;

// Extensions of protocols cannot introduce additional conformances.
if (isa<ProtocolDecl>(nominal))
return false;

auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
for (const ProtocolDecl *PD : protocols) {
AccessScope scope =
PD->getFormalAccessScope(/*useDC*/ nullptr,
/*treatUsableFromInlineAsPublic*/ true);
if (scope.isPublic())
return true;
}

return false;
}

bool swift::isExported(const ExtensionDecl *ED) {
// An extension can only be exported if it extends an exported type.
if (auto *NTD = ED->getExtendedNominal()) {
if (!isExported(NTD))
return false;
}

// If there are any exported members then the extension is exported.
for (const Decl *D : ED->getMembers()) {
if (isExported(D))
return true;
}

return ConcreteSyntaxDecl;
// If the extension declares a conformance to a public protocol then the
// extension is exported.
if (hasConformancesToPublicProtocols(ED))
return true;

return false;
}
2 changes: 1 addition & 1 deletion lib/AST/AvailabilityConstraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ swift::getAvailabilityConstraintsForDecl(const Decl *decl,
if (isa<GenericTypeParamDecl>(decl))
return DeclAvailabilityConstraints();

decl = abstractSyntaxDeclForAvailableAttribute(decl);
decl = decl->getAbstractSyntaxDeclForAttributes();

getAvailabilityConstraintsForDecl(constraints, decl, context);

Expand Down
61 changes: 61 additions & 0 deletions lib/AST/AvailabilityContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#include "swift/AST/AvailabilityConstraint.h"
#include "swift/AST/AvailabilityContextStorage.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/AvailabilityScope.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Module.h"
#include "swift/Basic/Assertions.h"

using namespace swift;
Expand Down Expand Up @@ -153,6 +155,65 @@ AvailabilityContext::forDeploymentTarget(const ASTContext &ctx) {
AvailabilityRange::forDeploymentTarget(ctx), ctx);
}

AvailabilityContext
AvailabilityContext::forLocation(SourceLoc loc, const DeclContext *declContext,
const AvailabilityScope **refinedScope) {
auto &ctx = declContext->getASTContext();
SourceFile *sf =
loc.isValid()
? declContext->getParentModule()->getSourceFileContainingLocation(loc)
: declContext->getParentSourceFile();

// If our source location is invalid (this may be synthesized code), climb the
// decl context hierarchy until we find a location that is valid, merging
// availability contexts on the way up.
//
// Because we are traversing decl contexts, we will miss availability scopes
// in synthesized code that are introduced by AST elements that are themselves
// not decl contexts, such as `#available(..)` and property declarations.
// Therefore a reference with an invalid source location that is contained
// inside an `#available()` and with no intermediate decl context will not be
// refined. For now, this is fine, but if we ever synthesize #available(),
// this could become a problem..

// We can assume we are running on at least the minimum inlining target.
auto baseAvailability = AvailabilityContext::forInliningTarget(ctx);
auto isInvalidLoc = [sf](SourceLoc loc) {
return sf ? loc.isInvalid() : true;
};
while (declContext && isInvalidLoc(loc)) {
const Decl *decl = declContext->getInnermostDeclarationDeclContext();
if (!decl)
break;

baseAvailability.constrainWithDecl(decl);
loc = decl->getLoc();
declContext = decl->getDeclContext();
}

if (!sf || loc.isInvalid())
return baseAvailability;

auto *rootScope = AvailabilityScope::getOrBuildForSourceFile(*sf);
if (!rootScope)
return baseAvailability;

auto *scope = rootScope->findMostRefinedSubContext(loc, ctx);
if (!scope)
return baseAvailability;

if (refinedScope)
*refinedScope = scope;

auto availability = scope->getAvailabilityContext();
availability.constrainWithContext(baseAvailability, ctx);
return availability;
}

AvailabilityContext AvailabilityContext::forDeclSignature(const Decl *decl) {
return forLocation(decl->getLoc(), decl->getInnermostDeclContext());
}

AvailabilityRange AvailabilityContext::getPlatformRange() const {
return storage->platformRange;
}
Expand Down
Loading