Files
swift-mirror/lib/AST/AvailabilityScope.cpp
Hamish Knight 588a5f1981 [Sema] Fix availability scope source range for SwitchStmt
Ensure we extend the end loc of the range, same as we do for the case
scope. This ensures the case scope is contained by the parent.
2026-06-09 23:49:23 +01:00

639 lines
21 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/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"
#include "swift/Parse/Lexer.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::SyntheticMacro:
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);
}
AvailabilityScope *AvailabilityScope::createForSwitchStmt(
ASTContext &Ctx, SwitchStmt *S, const DeclContext *DC,
AvailabilityScope *Parent, const AvailabilityContext Info) {
ASSERT(S);
ASSERT(Parent);
// Use the brace range so that the subject expression (which sits between
// the `switch` keyword and the opening brace) isn't covered by this
// scope's source range. The subject is type-checked before the case label
// items, so excluding it avoids triggering this scope's lazy expansion
// before the patterns are ready.
//
// Widen the source range to the end of its last token so it contains
// child availability scopes that extend their ranges similarly (such as
// implicit decl scopes).
SourceRange range(S->getLBraceLoc(), S->getRBraceLoc());
if (range.End.isValid())
range.End = Lexer::getLocForEndOfToken(Ctx.SourceMgr, range.End);
return new (Ctx)
AvailabilityScope(Ctx, IntroNode(S, DC), Parent, range, Info);
}
AvailabilityScope *AvailabilityScope::createForSwitchStmtCaseBody(
ASTContext &Ctx, CaseStmt *S, const DeclContext *DC,
AvailabilityScope *Parent, const AvailabilityContext Info) {
ASSERT(S);
ASSERT(Parent);
// Widen the body source range to the end of its last token so it contains
// child availability scopes that extend their ranges similarly (such as
// implicit decl scopes).
SourceRange range = S->getBody()->getSourceRange();
if (range.End.isValid())
range.End = Lexer::getLocForEndOfToken(Ctx.SourceMgr, range.End);
return new (Ctx)
AvailabilityScope(Ctx, IntroNode(S, DC), Parent, range, 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::SwitchStmt:
return Node.getAsSwitchStmt()->getStartLoc();
case Reason::SwitchStmtCaseBody:
return Node.getAsCaseStmt()->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::SwitchStmt:
case Reason::SwitchStmtCaseBody:
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:
case Reason::SwitchStmt:
case Reason::SwitchStmtCaseBody:
// 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";
case Reason::SwitchStmt:
return "switch_stmt";
case Reason::SwitchStmtCaseBody:
return "switch_case_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);
}