Files
swift-mirror/lib/AST/Evaluator.cpp
Hamish Knight 364eba482d [AST] Use CustomAttr's DeclContext for ResolveMacroRequest
Remove the DeclContext parameter from ResolveMacroRequest, we can now
retrieve the DeclContext either from the CustomAttr or macro expansion
expr/decl directly.
2025-10-16 11:21:54 +01:00

234 lines
7.0 KiB
C++

//===--- Evaluator.cpp - Request Evaluator Implementation -----------------===//
//
// 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 Evaluator class that evaluates and caches
// requests.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/Evaluator.h"
#include "swift/AST/DeclContext.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/LangOptions.h"
#include "swift/Basic/Range.h"
#include "swift/Basic/SourceManager.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/SaveAndRestore.h"
#include <vector>
using namespace swift;
AbstractRequestFunction *
Evaluator::getAbstractRequestFunction(uint8_t zoneID, uint8_t requestID) const {
for (const auto &zone : requestFunctionsByZone) {
if (zone.first == zoneID) {
if (requestID < zone.second.size())
return zone.second[requestID];
return nullptr;
}
}
return nullptr;
}
void Evaluator::registerRequestFunctions(
Zone zone,
ArrayRef<AbstractRequestFunction *> functions) {
uint8_t zoneID = static_cast<uint8_t>(zone);
#ifndef NDEBUG
for (const auto &zone : requestFunctionsByZone) {
assert(zone.first != zoneID);
}
#endif
requestFunctionsByZone.push_back({zoneID, functions});
}
Evaluator::Evaluator(DiagnosticEngine &diags, const LangOptions &opts)
: diags(diags),
debugDumpCycles(opts.DebugDumpCycles),
recorder(opts.RecordRequestReferences) {}
SourceLoc Evaluator::getInnermostSourceLoc(
llvm::function_ref<bool(SourceLoc)> fn) {
for (auto request : llvm::reverse(activeRequests)) {
SourceLoc loc = request.getNearestLoc();
if (fn(loc))
return loc;
}
return SourceLoc();
}
bool Evaluator::checkDependency(const ActiveRequest &request) {
// Record this as an active request.
if (activeRequests.insert(request)) {
return false;
}
// Diagnose cycle.
diagnoseCycle(request);
return true;
}
void Evaluator::finishedRequest(const ActiveRequest &request) {
assert(activeRequests.back() == request);
activeRequests.pop_back();
}
void Evaluator::diagnoseCycle(const ActiveRequest &request) {
if (debugDumpCycles) {
const auto printIndent = [](llvm::raw_ostream &OS, unsigned indent) {
OS.indent(indent);
OS << "`--";
};
unsigned indent = 1;
auto &OS = llvm::errs();
OS << "===CYCLE DETECTED===\n";
for (const auto &step : activeRequests) {
printIndent(OS, indent);
if (step == request) {
OS.changeColor(llvm::raw_ostream::GREEN);
simple_display(OS, step);
OS.resetColor();
} else {
simple_display(OS, step);
}
OS << "\n";
indent += 4;
}
printIndent(OS, indent);
OS.changeColor(llvm::raw_ostream::GREEN);
simple_display(OS, request);
OS.changeColor(llvm::raw_ostream::RED);
OS << " (cyclic dependency)";
OS.resetColor();
OS << "\n";
}
// Perform some filtering to avoid emitting duplicate 'through reference here'
// notes.
DiagnosticQueue cycleDiags(diags, /*emitOnDestruction*/ true);
SWIFT_DEFER {
auto isDefaultStepNote = [](const Diagnostic &diag) -> bool {
return diag.getID() == diag::circular_reference_through.ID;
};
// First populate seen locs for everything except default step notes. If
// we have a diagnostic or custom note at a given location we want to prefer
// that over the default step note.
llvm::DenseSet<SourceLoc> seenLocs;
cycleDiags.forEach([&](const Diagnostic &diag) {
if (isDefaultStepNote(diag))
return;
if (auto loc = diag.getLocOrDeclLoc())
seenLocs.insert(loc);
});
// Then we can filter out unnecessary default step notes.
cycleDiags.filter([&](const Diagnostic &diag) -> bool {
if (!isDefaultStepNote(diag))
return true;
auto loc = diag.getLocOrDeclLoc();
return loc && seenLocs.insert(loc).second;
});
};
request.diagnoseCycle(cycleDiags.getDiags());
for (const auto &step : llvm::reverse(activeRequests)) {
if (step == request) {
// Note that we diagnosed a cycle for the outermost step to ensure it
// also returns a cyclic result.
diagnosedActiveCycles.insert(step);
return;
}
step.noteCycleStep(cycleDiags.getDiags());
}
llvm_unreachable("Diagnosed a cycle but it wasn't represented in the stack");
}
void evaluator::DependencyRecorder::recordDependency(
const DependencyCollector::Reference &ref) {
if (activeRequestReferences.empty())
return;
activeRequestReferences.back().insert(ref);
}
evaluator::DependencyCollector::DependencyCollector(
evaluator::DependencyRecorder &parent) : parent(parent) {
#ifndef NDEBUG
assert(!parent.isRecording &&
"Probably not a good idea to allow nested recording");
parent.isRecording = true;
#endif
}
evaluator::DependencyCollector::~DependencyCollector() {
#ifndef NDEBUG
assert(parent.isRecording &&
"Should have been recording this whole time");
parent.isRecording = false;
#endif
}
void evaluator::DependencyCollector::addUsedMember(DeclContext *subject,
DeclBaseName name) {
assert(subject->isTypeContext());
return parent.recordDependency(Reference::usedMember(subject, name));
}
void evaluator::DependencyCollector::addPotentialMember(DeclContext *subject) {
assert(subject->isTypeContext());
return parent.recordDependency(Reference::potentialMember(subject));
}
void evaluator::DependencyCollector::addTopLevelName(DeclBaseName name) {
return parent.recordDependency(Reference::topLevel(name));
}
void evaluator::DependencyCollector::addDynamicLookupName(DeclBaseName name) {
return parent.recordDependency(Reference::dynamic(name));
}
void evaluator::DependencyRecorder::enumerateReferencesInFile(
const SourceFile *SF, ReferenceEnumerator f) const {
auto entry = fileReferences.find(SF);
if (entry == fileReferences.end()) {
return;
}
for (const auto &ref : entry->getSecond()) {
switch (ref.kind) {
case DependencyCollector::Reference::Kind::Empty:
case DependencyCollector::Reference::Kind::Tombstone:
llvm_unreachable("Cannot enumerate dead reference!");
case DependencyCollector::Reference::Kind::UsedMember:
case DependencyCollector::Reference::Kind::PotentialMember:
case DependencyCollector::Reference::Kind::TopLevel:
case DependencyCollector::Reference::Kind::Dynamic:
f(ref);
}
}
}