Skip to content

AST/Sema: Introduce AvailabilityContext #77083

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 2 commits into from
Oct 18, 2024
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
76 changes: 76 additions & 0 deletions include/swift/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#ifndef SWIFT_AST_AVAILABILITY_H
#define SWIFT_AST_AVAILABILITY_H

#include "swift/AST/PlatformKind.h"
#include "swift/AST/Type.h"
#include "swift/Basic/LLVM.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/Support/VersionTuple.h"
#include <optional>

Expand Down Expand Up @@ -174,6 +176,8 @@ class VersionRange {
return VersionRange(EndPoint);
}

void Profile(llvm::FoldingSetNodeID &ID) const;

private:
VersionRange(const llvm::VersionTuple &LowerEndpoint) {
setLowerEndpoint(LowerEndpoint);
Expand Down Expand Up @@ -331,6 +335,78 @@ class AvailabilityRange {
}
};

/// An `AvailabilityContext` summarizes the availability constraints for a
/// specific scope, such as within a declaration or at a particular source
/// location in a function body. This context is sufficient to determine whether
/// a declaration is available or not in that scope.
class AvailabilityContext : public llvm::FoldingSetNode {
/// Summarizes platform specific availability constraints.
struct PlatformInfo {
/// The introduction version.
AvailabilityRange Range;

/// Sets `Range` to `other` if `other` is more restrictive. Returns true if
/// any property changed as a result of adding this constraint.
bool constrainRange(const AvailabilityRange &other) {
if (!other.isContainedIn(Range))
return false;

Range = other;
return true;
}

/// Sets `Range` to the platform introduction range of `decl` if that range
/// is more restrictive. Returns true if
/// any property changed as a result of adding this constraint.
bool constrainRange(const Decl *decl);

void Profile(llvm::FoldingSetNodeID &ID) const {
Range.getRawVersionRange().Profile(ID);
}
};
PlatformInfo PlatformAvailability;

AvailabilityContext(const PlatformInfo &platformInfo)
: PlatformAvailability(platformInfo){};

static const AvailabilityContext *get(const PlatformInfo &platformInfo,
ASTContext &ctx);

public:
/// Retrieves the default `AvailabilityContext`, which is maximally available.
/// The platform availability range will be set to the deployment target (or
/// minimum inlining target when applicable).
static const AvailabilityContext *getDefault(ASTContext &ctx);

/// Retrieves a uniqued `AvailabilityContext` with the given platform
/// availability parameters.
static const AvailabilityContext *
get(const AvailabilityRange &platformAvailability, ASTContext &ctx) {
PlatformInfo platformInfo{platformAvailability};
return get(platformInfo, ctx);
}

/// Returns the range of platform versions which may execute code in the
/// availability context, starting at its introduction version.
AvailabilityRange getPlatformRange() const {
return PlatformAvailability.Range;
}

/// Returns the unique context that is the result of constraining the current
/// context's platform availability range with `platformRange`.
const AvailabilityContext *
constrainWithPlatformRange(const AvailabilityRange &platformRange,
ASTContext &ctx) const;

/// Returns the unique context that is the result of constraining the current
/// context both with the availability attributes of `decl` and with
/// `platformRange`.
const AvailabilityContext *constrainWithDeclAndPlatformRange(
Decl *decl, const AvailabilityRange &platformRange) const;

void Profile(llvm::FoldingSetNodeID &ID) const;
};

class AvailabilityInference {
public:
/// Returns the decl that should be considered the parent decl of the given
Expand Down
33 changes: 19 additions & 14 deletions include/swift/AST/TypeRefinementContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {

/// A canonical availability info for this context, computed top-down from the
/// root context.
AvailabilityRange AvailabilityInfo;
const AvailabilityContext *AvailabilityInfo;

std::vector<TypeRefinementContext *> Children;

Expand All @@ -184,33 +184,33 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {

TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
TypeRefinementContext *Parent, SourceRange SrcRange,
const AvailabilityRange &Info);
const AvailabilityContext *Info);

public:
/// Create the root refinement context for the given SourceFile.
static TypeRefinementContext *
createForSourceFile(SourceFile *SF, const AvailabilityRange &Info);
createForSourceFile(SourceFile *SF, const AvailabilityContext *Info);

/// Create a refinement context for the given declaration.
static TypeRefinementContext *createForDecl(ASTContext &Ctx, Decl *D,
TypeRefinementContext *Parent,
const AvailabilityRange &Info,
const AvailabilityContext *Info,
SourceRange SrcRange);

/// Create a refinement context for the given declaration.
static TypeRefinementContext *
createForDeclImplicit(ASTContext &Ctx, Decl *D, TypeRefinementContext *Parent,
const AvailabilityRange &Info, SourceRange SrcRange);
const AvailabilityContext *Info, SourceRange SrcRange);

/// Create a refinement context for the Then branch of the given IfStmt.
static TypeRefinementContext *
createForIfStmtThen(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,
const AvailabilityRange &Info);
const AvailabilityContext *Info);

/// Create a refinement context for the Else branch of the given IfStmt.
static TypeRefinementContext *
createForIfStmtElse(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,
const AvailabilityRange &Info);
const AvailabilityContext *Info);

/// Create a refinement context for the true-branch control flow to
/// further StmtConditionElements following a #available() query in
Expand All @@ -219,24 +219,24 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
createForConditionFollowingQuery(ASTContext &Ctx, PoundAvailableInfo *PAI,
const StmtConditionElement &LastElement,
TypeRefinementContext *Parent,
const AvailabilityRange &Info);
const AvailabilityContext *Info);

/// Create a refinement context for the fallthrough of a GuardStmt.
static TypeRefinementContext *createForGuardStmtFallthrough(
ASTContext &Ctx, GuardStmt *RS, BraceStmt *ContainingBraceStmt,
TypeRefinementContext *Parent, const AvailabilityRange &Info);
TypeRefinementContext *Parent, const AvailabilityContext *Info);

/// Create a refinement context for the else branch of a GuardStmt.
static TypeRefinementContext *
createForGuardStmtElse(ASTContext &Ctx, GuardStmt *RS,
TypeRefinementContext *Parent,
const AvailabilityRange &Info);
const AvailabilityContext *Info);

/// Create a refinement context for the body of a WhileStmt.
static TypeRefinementContext *
createForWhileStmtBody(ASTContext &Ctx, WhileStmt *WS,
TypeRefinementContext *Parent,
const AvailabilityRange &Info);
const AvailabilityContext *Info);

Decl *getDeclOrNull() const {
auto IntroReason = getReason();
Expand Down Expand Up @@ -276,12 +276,17 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
/// Returns the source range on which this context refines types.
SourceRange getSourceRange() const { return SrcRange; }

/// Returns the information on what can be assumed present at run time when
/// running code contained in this context.
const AvailabilityRange &getAvailabilityInfo() const {
/// Returns the availability context of code contained in this context.
const AvailabilityContext *getAvailabilityContext() const {
return AvailabilityInfo;
}

/// Returns the platform version range that can be assumed present at run
/// time when running code contained in this context.
const AvailabilityRange getPlatformAvailabilityRange() const {
return AvailabilityInfo->getPlatformRange();
}

/// Adds a child refinement context.
void addChild(TypeRefinementContext *Child, ASTContext &Ctx);

Expand Down
24 changes: 24 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,9 @@ struct ASTContext::Implementation {
/// The set of substitution maps (uniqued by their storage).
llvm::FoldingSet<SubstitutionMap::Storage> SubstitutionMaps;

/// The set of unique AvailabilityContexts.
llvm::FoldingSet<AvailabilityContext> AvailabilityContexts;

~Arena() {
for (auto &conformance : SpecializedConformances)
conformance.~SpecializedProtocolConformance();
Expand Down Expand Up @@ -3301,6 +3304,7 @@ void ASTContext::Implementation::Arena::dump(llvm::raw_ostream &os) const {
SIZE_AND_BYTES(BuiltinConformances);
SIZE(PackConformances);
SIZE(SubstitutionMaps);
SIZE(AvailabilityContexts);

#undef SIZE
#undef SIZE_AND_BYTES
Expand Down Expand Up @@ -5577,6 +5581,26 @@ SubstitutionMap::Storage *SubstitutionMap::Storage::get(
return result;
}

const AvailabilityContext *
AvailabilityContext::get(const PlatformInfo &platformInfo, ASTContext &ctx) {
llvm::FoldingSetNodeID id;
platformInfo.Profile(id);

auto &foldingSet =
ctx.getImpl().getArena(AllocationArena::Permanent).AvailabilityContexts;
void *insertPos;
auto *existing = foldingSet.FindNodeOrInsertPos(id, insertPos);
if (existing)
return existing;

void *mem =
ctx.Allocate(sizeof(AvailabilityContext), alignof(AvailabilityContext));
auto *newNode = ::new (mem) AvailabilityContext(platformInfo);
foldingSet.InsertNode(newNode, insertPos);

return newNode;
}

void GenericSignatureImpl::Profile(llvm::FoldingSetNodeID &ID,
ArrayRef<GenericTypeParamType *> genericParams,
ArrayRef<Requirement> requirements) {
Expand Down
61 changes: 61 additions & 0 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@

using namespace swift;

void VersionRange::Profile(llvm::FoldingSetNodeID &id) const {
id.AddBoolean(hasLowerEndpoint());
if (!hasLowerEndpoint()) {
id.AddBoolean(isAll());
return;
}

auto profileVersionComponent = [&id](std::optional<unsigned> component) {
id.AddBoolean(component.has_value());
if (component)
id.AddInteger(*component);
};

auto lowerEndpoint = getLowerEndpoint();
id.AddInteger(lowerEndpoint.getMajor());
profileVersionComponent(lowerEndpoint.getMinor());
profileVersionComponent(lowerEndpoint.getSubminor());
profileVersionComponent(lowerEndpoint.getBuild());
}

AvailabilityRange
AvailabilityRange::forDeploymentTarget(const ASTContext &Ctx) {
return AvailabilityRange(
Expand All @@ -44,6 +64,47 @@ AvailabilityRange AvailabilityRange::forRuntimeTarget(const ASTContext &Ctx) {
return AvailabilityRange(VersionRange::allGTE(Ctx.LangOpts.RuntimeVersion));
}

bool AvailabilityContext::PlatformInfo::constrainRange(const Decl *decl) {
if (auto range = AvailabilityInference::annotatedAvailableRange(decl))
return constrainRange(*range);

return false;
}

const AvailabilityContext *AvailabilityContext::getDefault(ASTContext &ctx) {
PlatformInfo platformInfo{AvailabilityRange::forInliningTarget(ctx)};
return AvailabilityContext::get(platformInfo, ctx);
}

/// Returns the unique context that is the result of constraining the current
/// context's platform availability range with `platformRange`.
const AvailabilityContext *AvailabilityContext::constrainWithPlatformRange(
const AvailabilityRange &platformRange, ASTContext &ctx) const {
PlatformInfo platformAvailability{PlatformAvailability};
if (platformAvailability.constrainRange(platformRange))
return get(platformAvailability, ctx);

return this;
}

const AvailabilityContext *
AvailabilityContext::constrainWithDeclAndPlatformRange(
Decl *decl, const AvailabilityRange &platformRange) const {
PlatformInfo platformAvailability{PlatformAvailability};
bool isConstrained = false;
isConstrained |= platformAvailability.constrainRange(decl);
isConstrained |= platformAvailability.constrainRange(platformRange);

if (!isConstrained)
return this;

return get(platformAvailability, decl->getASTContext());
}

void AvailabilityContext::Profile(llvm::FoldingSetNodeID &id) const {
PlatformAvailability.Profile(id);
}

namespace {

/// The inferred availability required to access a group of declarations
Expand Down
Loading