Files
swift-mirror/lib/AST/AvailabilityScope.cpp
2025-10-24 16:14:49 -07:00

586 lines
19 KiB
C++

//===--- AvailabilityScope.cpp - Swift Availability Scopes ----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements the AvailabilityScope class.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/AvailabilityScope.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/AvailabilityConstraint.h"
#include "swift/AST/AvailabilitySpec.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Expr.h"
#include "swift/AST/Module.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/Stmt.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/SourceManager.h"
using namespace swift;
AvailabilityScope::IntroNode::IntroNode(SourceFile *SF)
: IntroReason(Reason::Root), DC(SF), SF(SF) {}
AvailabilityScope::IntroNode::IntroNode(Decl *D, Reason introReason)
: IntroReason(introReason), DC(D->getDeclContext()), D(D) {
(void)getAsDecl(); // check that assertion succeeds
}
AvailabilityScope::AvailabilityScope(ASTContext &Ctx, IntroNode Node,
AvailabilityScope *Parent,
SourceRange SrcRange,
const AvailabilityContext Info)
: Node(Node), SrcRange(SrcRange), AvailabilityInfo(Info) {
if (Parent) {
ASSERT(SrcRange.isValid());
Parent->addChild(this, Ctx);
DEBUG_ASSERT(Info.isContainedIn(Parent->getAvailabilityContext()));
}
Ctx.addDestructorCleanup(Children);
}
AvailabilityScope *
AvailabilityScope::createForSourceFile(SourceFile *SF,
const AvailabilityContext Info) {
ASSERT(SF);
ASTContext &Ctx = SF->getASTContext();
SourceRange range;
AvailabilityScope *parentContext = nullptr;
switch (SF->Kind) {
case SourceFileKind::MacroExpansion:
case SourceFileKind::DefaultArgument: {
// Look up the parent context in the enclosing file that this file's
// root context should be nested under.
auto enclosingSF = SF->getEnclosingSourceFile();
if (!enclosingSF)
break;
if (auto parentScope = enclosingSF->getAvailabilityScope()) {
auto charRange = Ctx.SourceMgr.getRangeForBuffer(SF->getBufferID());
range = SourceRange(charRange.getStart(), charRange.getEnd());
auto originalNode = SF->getNodeInEnclosingSourceFile();
parentContext = parentScope->findMostRefinedSubContext(
originalNode.getStartLoc(), Ctx);
}
break;
}
case SourceFileKind::Library:
case SourceFileKind::Main:
case SourceFileKind::Interface:
break;
case SourceFileKind::SIL:
llvm_unreachable("unexpected SourceFileKind");
}
return new (Ctx) AvailabilityScope(
Ctx, SF, parentContext, range,
parentContext ? parentContext->getAvailabilityContext() : Info);
}
AvailabilityScope *AvailabilityScope::createForDecl(
ASTContext &Ctx, Decl *D, AvailabilityScope *Parent,
const AvailabilityContext Info, SourceRange SrcRange) {
ASSERT(D);
ASSERT(Parent);
return new (Ctx) AvailabilityScope(Ctx, D, Parent, SrcRange, Info);
}
AvailabilityScope *AvailabilityScope::createForDeclImplicit(
ASTContext &Ctx, Decl *D, AvailabilityScope *Parent,
const AvailabilityContext Info, SourceRange SrcRange) {
ASSERT(D);
ASSERT(Parent);
return new (Ctx) AvailabilityScope(Ctx, IntroNode(D, Reason::DeclImplicit),
Parent, SrcRange, Info);
}
AvailabilityScope *AvailabilityScope::createForIfStmtThen(
ASTContext &Ctx, IfStmt *S, const DeclContext *DC,
AvailabilityScope *Parent, const AvailabilityContext Info) {
ASSERT(S);
ASSERT(Parent);
return new (Ctx)
AvailabilityScope(Ctx, IntroNode(S, DC, /*IsThen=*/true), Parent,
S->getThenStmt()->getSourceRange(), Info);
}
AvailabilityScope *AvailabilityScope::createForIfStmtElse(
ASTContext &Ctx, IfStmt *S, const DeclContext *DC,
AvailabilityScope *Parent, const AvailabilityContext Info) {
ASSERT(S);
ASSERT(Parent);
return new (Ctx)
AvailabilityScope(Ctx, IntroNode(S, DC, /*IsThen=*/false), Parent,
S->getElseStmt()->getSourceRange(), Info);
}
AvailabilityScope *AvailabilityScope::createForConditionFollowingQuery(
ASTContext &Ctx, PoundAvailableInfo *PAI,
const StmtConditionElement &LastElement, const DeclContext *DC,
AvailabilityScope *Parent, const AvailabilityContext Info) {
ASSERT(PAI);
ASSERT(Parent);
SourceRange Range(PAI->getEndLoc(), LastElement.getEndLoc());
return new (Ctx)
AvailabilityScope(Ctx, IntroNode(PAI, DC), Parent, Range, Info);
}
AvailabilityScope *AvailabilityScope::createForGuardStmtFallthrough(
ASTContext &Ctx, GuardStmt *RS, BraceStmt *ContainingBraceStmt,
const DeclContext *DC, AvailabilityScope *Parent,
const AvailabilityContext Info) {
ASSERT(RS);
ASSERT(ContainingBraceStmt);
ASSERT(Parent);
SourceRange Range(RS->getEndLoc(), ContainingBraceStmt->getEndLoc());
return new (Ctx) AvailabilityScope(
Ctx, IntroNode(RS, DC, /*IsFallthrough=*/true), Parent, Range, Info);
}
AvailabilityScope *AvailabilityScope::createForGuardStmtElse(
ASTContext &Ctx, GuardStmt *RS, const DeclContext *DC,
AvailabilityScope *Parent, const AvailabilityContext Info) {
ASSERT(RS);
ASSERT(Parent);
return new (Ctx)
AvailabilityScope(Ctx, IntroNode(RS, DC, /*IsFallthrough=*/false), Parent,
RS->getBody()->getSourceRange(), Info);
}
AvailabilityScope *AvailabilityScope::createForWhileStmtBody(
ASTContext &Ctx, WhileStmt *S, const DeclContext *DC,
AvailabilityScope *Parent, const AvailabilityContext Info) {
ASSERT(S);
ASSERT(Parent);
return new (Ctx) AvailabilityScope(Ctx, IntroNode(S, DC), Parent,
S->getBody()->getSourceRange(), Info);
}
void AvailabilityScope::addChild(AvailabilityScope *Child, ASTContext &Ctx) {
bool validSourceRange = Child->getSourceRange().isValid();
ASSERT(validSourceRange);
// Handle the first child.
if (Children.empty()) {
Children.push_back(Child);
return;
}
// Handle a child that is ordered after the existing children (this should be
// the common case).
auto &srcMgr = Ctx.SourceMgr;
if (srcMgr.isBefore(Children.back()->getSourceRange().Start,
Child->getSourceRange().Start)) {
Children.push_back(Child);
return;
}
// Insert the child amongst the existing sorted children.
auto iter = std::upper_bound(
Children.begin(), Children.end(), Child,
[&srcMgr](AvailabilityScope *lhs, AvailabilityScope *rhs) {
return srcMgr.isBefore(lhs->getSourceRange().Start,
rhs->getSourceRange().Start);
});
Children.insert(iter, Child);
}
AvailabilityScope *
AvailabilityScope::findMostRefinedSubContext(SourceLoc Loc, ASTContext &Ctx) {
DEBUG_ASSERT(Loc.isValid());
if (SrcRange.isValid() && !Ctx.SourceMgr.containsTokenLoc(SrcRange, Loc))
return nullptr;
(void)evaluateOrDefault(Ctx.evaluator,
ExpandChildAvailabilityScopesRequest{this}, {});
DEBUG_ASSERT(!getNeedsExpansion());
// Do a binary search to find the first child with a source range that
// ends after the given location.
auto iter = std::lower_bound(Children.begin(), Children.end(), Loc,
[&Ctx](AvailabilityScope *scope, SourceLoc loc) {
return Ctx.SourceMgr.isBefore(
scope->getSourceRange().End, loc);
});
// Check whether the matching child or any of its descendants contain
// the given location.
if (iter != Children.end()) {
if (auto found = (*iter)->findMostRefinedSubContext(Loc, Ctx))
return found;
}
// The location is in this context's range but not in any child's, so this
// context must be the innermost context.
return this;
}
void AvailabilityScope::dump(SourceManager &SrcMgr) const {
dump(llvm::errs(), SrcMgr);
}
void AvailabilityScope::dump(raw_ostream &OS, SourceManager &SrcMgr) const {
print(OS, SrcMgr, 0);
OS << '\n';
}
SourceLoc AvailabilityScope::getIntroductionLoc() const {
switch (getReason()) {
case Reason::Decl:
case Reason::DeclImplicit:
return Node.getAsDecl()->getLoc();
case Reason::IfStmtThenBranch:
case Reason::IfStmtElseBranch:
return Node.getAsIfStmt()->getIfLoc();
case Reason::ConditionFollowingAvailabilityQuery:
return Node.getAsPoundAvailableInfo()->getStartLoc();
case Reason::GuardStmtFallthrough:
case Reason::GuardStmtElseBranch:
return Node.getAsGuardStmt()->getGuardLoc();
case Reason::WhileStmtBody:
return Node.getAsWhileStmt()->getStartLoc();
case Reason::Root:
return SourceLoc();
}
llvm_unreachable("Unhandled Reason in switch.");
}
static SourceRange getAvailabilityConditionVersionSourceRange(
const PoundAvailableInfo *PAI, const DeclContext *ReferenceDC,
AvailabilityDomain Domain, const llvm::VersionTuple &Version) {
SourceRange Range;
for (auto Spec : PAI->getSemanticAvailabilitySpecs(ReferenceDC)) {
if (Spec.getDomain() == Domain && Spec.getVersion() == Version) {
// More than one: return invalid range, no unique choice.
if (Range.isValid())
return SourceRange();
else
Range = Spec.getParsedSpec()->getVersionSrcRange();
}
}
return Range;
}
static SourceRange getAvailabilityConditionVersionSourceRange(
const MutableArrayRef<StmtConditionElement> &Conds,
const DeclContext *ReferenceDC, AvailabilityDomain Domain,
const llvm::VersionTuple &Version) {
SourceRange Range;
for (auto const &C : Conds) {
if (C.getKind() == StmtConditionElement::CK_Availability) {
SourceRange R = getAvailabilityConditionVersionSourceRange(
C.getAvailability(), ReferenceDC, Domain, Version);
// More than one: return invalid range.
if (Range.isValid())
return SourceRange();
else
Range = R;
}
}
return Range;
}
static SourceRange
getAvailabilityConditionVersionSourceRange(const Decl *D,
AvailabilityDomain Domain,
const llvm::VersionTuple &Version) {
SourceRange Range;
for (auto Attr : D->getSemanticAvailableAttrs()) {
if (Attr.getIntroduced().has_value() &&
Attr.getIntroduced().value() == Version && Attr.getDomain() == Domain) {
// More than one: return invalid range.
if (Range.isValid())
return SourceRange();
else
Range = Attr.getIntroducedSourceRange();
}
}
return Range;
}
SourceRange AvailabilityScope::getAvailabilityConditionVersionSourceRange(
AvailabilityDomain Domain, const llvm::VersionTuple &Version) const {
switch (getReason()) {
case Reason::Decl:
return ::getAvailabilityConditionVersionSourceRange(Node.getAsDecl(),
Domain, Version);
case Reason::IfStmtThenBranch:
case Reason::IfStmtElseBranch:
return ::getAvailabilityConditionVersionSourceRange(
Node.getAsIfStmt()->getCond(), Node.getDeclContext(), Domain, Version);
case Reason::ConditionFollowingAvailabilityQuery:
return ::getAvailabilityConditionVersionSourceRange(
Node.getAsPoundAvailableInfo(), Node.getDeclContext(), Domain, Version);
case Reason::GuardStmtFallthrough:
case Reason::GuardStmtElseBranch:
return ::getAvailabilityConditionVersionSourceRange(
Node.getAsGuardStmt()->getCond(), Node.getDeclContext(), Domain,
Version);
case Reason::WhileStmtBody:
return ::getAvailabilityConditionVersionSourceRange(
Node.getAsWhileStmt()->getCond(), Node.getDeclContext(), Domain,
Version);
case Reason::Root:
case Reason::DeclImplicit:
return SourceRange();
}
llvm_unreachable("Unhandled Reason in switch.");
}
std::optional<const AvailabilityRange>
AvailabilityScope::getExplicitAvailabilityRange(AvailabilityDomain domain,
ASTContext &ctx) const {
switch (getReason()) {
case Reason::Root:
return std::nullopt;
case Reason::Decl: {
auto decl = Node.getAsDecl();
if (auto constraint = swift::getAvailabilityConstraintForDeclInDomain(
decl, AvailabilityContext::forAlwaysAvailable(ctx), domain))
return constraint->getAttr().getIntroducedRange(ctx);
return std::nullopt;
}
case Reason::DeclImplicit:
return std::nullopt;
case Reason::IfStmtThenBranch:
case Reason::IfStmtElseBranch:
case Reason::ConditionFollowingAvailabilityQuery:
case Reason::GuardStmtFallthrough:
case Reason::GuardStmtElseBranch:
case Reason::WhileStmtBody:
// Availability is inherently explicit for all of these nodes.
return getAvailabilityContext().getAvailabilityRange(domain, ctx);
}
llvm_unreachable("Unhandled Reason in switch.");
}
void AvailabilityScope::print(raw_ostream &OS, SourceManager &SrcMgr,
unsigned Indent) const {
OS.indent(Indent);
OS << "(" << getReasonName(getReason());
OS << " ";
AvailabilityInfo.print(OS);
if (getReason() == Reason::Decl || getReason() == Reason::DeclImplicit) {
Decl *D = Node.getAsDecl();
OS << " decl=";
if (auto VD = dyn_cast<ValueDecl>(D)) {
OS << VD->getName();
} else if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
OS << "extension." << ED->getExtendedType().getString();
} else if (isa<TopLevelCodeDecl>(D)) {
OS << "<top-level-code>";
} else if (auto PBD = dyn_cast<PatternBindingDecl>(D)) {
if (auto VD = PBD->getAnchoringVarDecl(0)) {
OS << VD->getName();
}
} else if (auto ECD = dyn_cast<EnumCaseDecl>(D)) {
if (auto EED = ECD->getFirstElement()) {
OS << EED->getName();
}
}
}
auto R = getSourceRange();
if (R.isValid()) {
OS << " src_range=";
if (getReason() != Reason::Root) {
R.print(OS, SrcMgr, /*PrintText=*/false);
} else if (auto info = SrcMgr.getGeneratedSourceInfo(
Node.getAsSourceFile()->getBufferID())) {
info->originalSourceRange.print(OS, SrcMgr, /*PrintText=*/false);
} else {
OS << "<unknown>";
}
}
if (getReason() == Reason::Root) {
if (auto info = SrcMgr.getGeneratedSourceInfo(
Node.getAsSourceFile()->getBufferID())) {
OS << " generated_kind=" << GeneratedSourceInfo::kindToString(info->kind);
} else {
OS << " file=" << Node.getAsSourceFile()->getFilename().str();
}
}
for (AvailabilityScope *Child : Children) {
OS << '\n';
Child->print(OS, SrcMgr, Indent + 2);
}
OS.indent(Indent);
OS << ")";
}
AvailabilityScope::Reason AvailabilityScope::getReason() const {
return Node.getReason();
}
StringRef AvailabilityScope::getReasonName(Reason R) {
switch (R) {
case Reason::Root:
return "root";
case Reason::Decl:
return "decl";
case Reason::DeclImplicit:
return "decl_implicit";
case Reason::IfStmtThenBranch:
return "if_then";
case Reason::IfStmtElseBranch:
return "if_else";
case Reason::ConditionFollowingAvailabilityQuery:
return "condition_following_availability";
case Reason::GuardStmtFallthrough:
return "guard_fallthrough";
case Reason::GuardStmtElseBranch:
return "guard_else";
case Reason::WhileStmtBody:
return "while_body";
}
llvm_unreachable("Unhandled Reason in switch.");
}
void swift::simple_display(llvm::raw_ostream &out,
const AvailabilityScope *scope) {
out << "Scope @" << scope;
}
std::optional<evaluator::SideEffect>
ExpandChildAvailabilityScopesRequest::getCachedResult() const {
auto *scope = std::get<0>(getStorage());
if (scope->getNeedsExpansion())
return std::nullopt;
return evaluator::SideEffect();
}
void ExpandChildAvailabilityScopesRequest::cacheResult(
evaluator::SideEffect sideEffect) const {
auto *scope = std::get<0>(getStorage());
scope->setNeedsExpansion(false);
}
/// Emit an error message, dump each context with its corresponding label, and
/// abort.
static void verificationError(
ASTContext &ctx, llvm::StringRef msg,
std::initializer_list<std::pair<const char *, const AvailabilityScope *>>
labelsAndNodes) {
ABORT([&](auto &out) {
out << msg << "\n";
for (auto pair : labelsAndNodes) {
auto label = std::get<0>(pair);
auto scope = std::get<1>(pair);
out << label << ":\n";
scope->print(out, ctx.SourceMgr);
out << "\n";
}
});
}
void AvailabilityScope::verify(const AvailabilityScope *parent,
ASTContext &ctx) const {
// Verify the children first.
for (auto child : Children) {
child->verify(this, ctx);
}
// Verify that the children are in sorted order and that their source ranges
// do not overlap.
auto &srcMgr = ctx.SourceMgr;
if (Children.size() > 1) {
auto const *previous = Children.front();
for (auto const *current : ArrayRef(Children).drop_front()) {
if (!srcMgr.isAtOrBefore(previous->getSourceRange().Start,
current->getSourceRange().Start))
verificationError(
ctx, "out of order children",
{{"child 1", previous}, {"child 2", current}, {"parent", this}});
if (srcMgr.containsLoc(previous->getSourceRange(),
current->getSourceRange().Start))
verificationError(
ctx, "overlapping children",
{{"child 1", previous}, {"child 2", current}, {"parent", this}});
previous = current;
}
}
// Only root nodes are allowed to have no parent.
if (!parent) {
if (getReason() != Reason::Root)
verificationError(ctx, "interior node without parent", {{"node", this}});
return;
}
// All nodes with a parent must have a valid source range.
if (!SrcRange.isValid())
verificationError(ctx, "invalid source range", {{"node", this}});
// Child nodes must be contained by their parents in all dimensions (source
// range, introduction version, etc).
if (getReason() != Reason::Root) {
auto parentRange = parent->SrcRange;
if (parentRange.isValid() &&
!(srcMgr.isAtOrBefore(parentRange.Start, SrcRange.Start) &&
srcMgr.isAtOrBefore(SrcRange.End, parentRange.End)))
verificationError(ctx, "child source range not contained",
{{"child", this}, {"parent", parent}});
}
auto context = getAvailabilityContext();
if (!context.verify(ctx))
verificationError(ctx, "context is invalid", {{"node", this}});
if (!context.isContainedIn(parent->getAvailabilityContext()))
verificationError(ctx, "child availability range not contained",
{{"child", this}, {"parent", parent}});
}
void AvailabilityScope::verify(ASTContext &ctx) {
verify(nullptr, ctx);
}