Files
swift-mirror/lib/AST/Evaluator.cpp
Doug Gregor f96240d2d8 [Macros] Do not look into macro expansions while resolving macro arguments.
Macros introduced a significant wrinkle into Swift's name lookup mechanism.
Specifically, when resolving names (and, really, anything else) within the
arguments to a macro expansion, name lookup must not try to expand any
macros, because doing so trivially creates a cyclic dependency amongst the
macro expansions that will be detected by the request-evaluator.

Our lookup requests don't always have enough information to answer the
question "is this part of an argument to a macro?", so we do a much simpler,
more efficient, and not-entirely-sound hack based on the request-evaluator.
Specifically, if we are in the process of resolving a macro (which is
determined by checking for the presence of a `ResolveMacroRequest` in the
request-evaluator stack), then we adjust the options used for the name
lookup request we are forming to exclude macro expansions. The evaluation
of that request will then avoid expanding any macros, and not produce any
results that involve entries in already-expanded macros. By adjusting the
request itself, we still distinguish between requests that can and cannot
look into macro expansions, so it doesn't break caching for those immediate
requests.

Over time, we should seek to replace this heuristic with a location-based
check, where we use ASTScope to determine whether we are inside a macro
argument. This existing check might still be useful because it's going to
be faster than a location-based query, but the location-based query can be
fully correct.

This addresses a class of cyclic dependencies that we've been seeing
with macros, and aligns the lookup behavior for module-level lookups
with that specified in the macros proposals. It is not fully complete
because lookup until nominal types does not yet support excluding
results from macro expansions.
2023-04-06 16:04:41 -07:00

193 lines
5.6 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/TypeCheckRequests.h" // for ResolveMacroRequest
#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) {}
bool Evaluator::checkDependency(const ActiveRequest &request) {
// Record this as an active request.
if (activeRequests.insert(request)) {
if (request.getAs<ResolveMacroRequest>())
++numActiveResolveMacroRequests;
return false;
}
// Diagnose cycle.
diagnoseCycle(request);
return true;
}
void Evaluator::finishedRequest(const ActiveRequest &request) {
if (request.getAs<ResolveMacroRequest>())
--numActiveResolveMacroRequests;
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";
}
request.diagnoseCycle(diags);
for (const auto &step : llvm::reverse(activeRequests)) {
if (step == request) return;
step.noteCycleStep(diags);
}
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);
}
}
}