Turn pretty-printing of a declaration into a request

The diagnostics engine has some code to pretty-print a declaration when
there is no source location for that declaration. The declaration is
pretty-printed into a source buffer, and a source location into that
buffer is synthesizes. This applies to synthesized declarations as well
as those imported from Swift modules (without source code) or from Clang.

Reimplement this pretty-printing for declarations as a request. In
doing so, change the manner in which we do the printing: the
diagnostics engine printed the entire enclosing type into a buffer
whose name was the module + that type. This meant that the buffer was
shared by every member of that type, but also meant that we would end
up deserializing a lot of declarations just for printing and
potentially doing a lot more work for these diagnostics.
This commit is contained in:
Doug Gregor
2024-10-01 15:49:15 -07:00
parent c9b0a2a43f
commit 5df96a7a6e
11 changed files with 216 additions and 177 deletions

View File

@@ -1052,10 +1052,6 @@ namespace swift {
/// been emitted due to an open transaction.
SmallVector<Diagnostic, 4> TentativeDiagnostics;
/// The set of declarations for which we have pretty-printed
/// results that we can point to on the command line.
llvm::DenseMap<const Decl *, SourceLoc> PrettyPrintedDeclarations;
llvm::BumpPtrAllocator TransactionAllocator;
/// A set of all strings involved in current transactional chain.
/// This is required because diagnostics are not directly emitted
@@ -1108,6 +1104,7 @@ namespace swift {
friend class CompoundDiagnosticTransaction;
friend class DiagnosticStateRAII;
friend class DiagnosticQueue;
friend class PrettyPrintDeclRequest;
public:
explicit DiagnosticEngine(SourceManager &SourceMgr)

View File

@@ -754,6 +754,27 @@ public:
void simple_display(llvm::raw_ostream &out, const KnownProtocolKind);
/// Pretty-print the given declaration into a buffer and return a source
/// location that refers to the declaration in that buffer.
class PrettyPrintDeclRequest
: public SimpleRequest<PrettyPrintDeclRequest,
SourceLoc(const Decl *),
RequestFlags::Cached>
{
public:
using SimpleRequest::SimpleRequest;
private:
friend SimpleRequest;
// Evaluation.
SourceLoc evaluate(Evaluator &eval, const Decl *d) const;
public:
// Caching
bool isCached() const { return true; }
};
// Find the type in the cache or look it up
class DefaultTypeRequest
: public SimpleRequest<DefaultTypeRequest,

View File

@@ -76,6 +76,8 @@ SWIFT_REQUEST(TypeChecker, DefaultDefinitionTypeRequest,
SWIFT_REQUEST(TypeChecker, DefaultTypeRequest,
Type(KnownProtocolKind, const DeclContext *), SeparatelyCached,
NoLocationInfo)
SWIFT_REQUEST(TypeChecker, PrettyPrintDeclRequest,
SourceLoc(const Decl *), Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, DifferentiableAttributeTypeCheckRequest,
IndexSubset *(DifferentiableAttr *),
SeparatelyCached, NoLocationInfo)

View File

@@ -29,6 +29,7 @@
#include "swift/AST/PrintOptions.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/Stmt.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/AST/TypeRepr.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/SourceManager.h"
@@ -1306,20 +1307,6 @@ void DiagnosticEngine::forwardTentativeDiagnosticsTo(
clearTentativeDiagnostics();
}
/// Returns the access level of the least accessible PrettyPrintedDeclarations
/// buffer that \p decl should appear in.
///
/// This is always \c Public unless \p decl is a \c ValueDecl and its
/// access level is below \c Public. (That can happen with @testable and
/// @_private imports.)
static AccessLevel getBufferAccessLevel(const Decl *decl) {
AccessLevel level = AccessLevel::Public;
if (auto *VD = dyn_cast<ValueDecl>(decl))
level = VD->getFormalAccessScope().accessLevelForDiagnostics();
if (level > AccessLevel::Public) level = AccessLevel::Public;
return level;
}
std::optional<DiagnosticInfo>
DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
auto behavior = state.determineBehavior(diagnostic);
@@ -1334,154 +1321,12 @@ DiagnosticEngine::diagnosticInfoForDiagnostic(const Diagnostic &diagnostic) {
// has a location we can point to, use that location.
loc = decl->getLoc();
// If the location of the decl is invalid still, try to pretty-print the
// declaration into a buffer and capture the source location there.
if (loc.isInvalid()) {
// There is no location we can point to. Pretty-print the declaration
// so we can point to it.
SourceLoc ppLoc = PrettyPrintedDeclarations[decl];
if (ppLoc.isInvalid()) {
class TrackingPrinter : public StreamPrinter {
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries;
AccessLevel bufferAccessLevel;
public:
TrackingPrinter(
SmallVectorImpl<std::pair<const Decl *, uint64_t>> &Entries,
raw_ostream &OS, AccessLevel bufferAccessLevel) :
StreamPrinter(OS), Entries(Entries),
bufferAccessLevel(bufferAccessLevel) {}
void printDeclLoc(const Decl *D) override {
if (getBufferAccessLevel(D) == bufferAccessLevel)
Entries.push_back({ D, OS.tell() });
}
};
SmallVector<std::pair<const Decl *, uint64_t>, 8> entries;
llvm::SmallString<128> buffer;
llvm::SmallString<128> bufferName;
const Decl *ppDecl = decl;
{
// The access level of the buffer we want to print. Declarations below
// this access level will be omitted from the buffer; declarations
// above it will be printed, but (except for Open declarations in the
// Public buffer) will not be recorded in PrettyPrintedDeclarations as
// the "true" SourceLoc for the declaration.
AccessLevel bufferAccessLevel = getBufferAccessLevel(decl);
// Figure out which declaration to print. It's the top-most
// declaration (not a module).
auto dc = decl->getDeclContext();
// FIXME: Horrible, horrible hackaround. We're not getting a
// DeclContext everywhere we should.
if (!dc) {
return std::nullopt;
}
while (!dc->isModuleContext()) {
switch (dc->getContextKind()) {
case DeclContextKind::Package:
llvm_unreachable("Not in a package context!");
break;
case DeclContextKind::Module:
llvm_unreachable("Not in a module context!");
break;
case DeclContextKind::FileUnit:
case DeclContextKind::TopLevelCodeDecl:
case DeclContextKind::SerializedTopLevelCodeDecl:
break;
case DeclContextKind::ExtensionDecl:
ppDecl = cast<ExtensionDecl>(dc);
break;
case DeclContextKind::GenericTypeDecl:
ppDecl = cast<GenericTypeDecl>(dc);
break;
case DeclContextKind::Initializer:
case DeclContextKind::AbstractClosureExpr:
case DeclContextKind::SerializedAbstractClosure:
case DeclContextKind::AbstractFunctionDecl:
case DeclContextKind::SubscriptDecl:
case DeclContextKind::EnumElementDecl:
case DeclContextKind::MacroDecl:
break;
}
dc = dc->getParent();
}
// Build the module name path (in reverse), which we use to
// build the name of the buffer.
SmallVector<StringRef, 4> nameComponents;
while (dc) {
auto publicName = cast<ModuleDecl>(dc)->
getPublicModuleName(/*onlyIfImported*/true);
nameComponents.push_back(publicName.str());
dc = dc->getParent();
}
for (unsigned i = nameComponents.size(); i; --i) {
bufferName += nameComponents[i-1];
bufferName += '.';
}
if (auto value = dyn_cast<ValueDecl>(ppDecl)) {
bufferName += value->getBaseName().userFacingName();
} else if (auto ext = dyn_cast<ExtensionDecl>(ppDecl)) {
bufferName += ext->getExtendedType().getString();
}
// If we're using a lowered access level, give the buffer a distinct
// name.
if (bufferAccessLevel != AccessLevel::Public) {
assert(bufferAccessLevel < AccessLevel::Public
&& "Above-public access levels should use public buffer");
bufferName += " (";
bufferName += getAccessLevelSpelling(bufferAccessLevel);
bufferName += ")";
}
// Pretty-print the declaration we've picked.
llvm::raw_svector_ostream out(buffer);
TrackingPrinter printer(entries, out, bufferAccessLevel);
llvm::SaveAndRestore<bool> isPrettyPrinting(
IsPrettyPrintingDecl, true);
ppDecl->print(
printer,
PrintOptions::printForDiagnostics(
bufferAccessLevel,
decl->getASTContext().TypeCheckerOpts.PrintFullConvention));
}
// Build a buffer with the pretty-printed declaration.
auto bufferID = SourceMgr.addMemBufferCopy(buffer, bufferName);
auto memBufferStartLoc = SourceMgr.getLocForBufferStart(bufferID);
SourceMgr.setGeneratedSourceInfo(
bufferID,
GeneratedSourceInfo{
GeneratedSourceInfo::PrettyPrinted,
CharSourceRange(),
CharSourceRange(memBufferStartLoc, buffer.size()),
ASTNode(const_cast<Decl *>(ppDecl)).getOpaqueValue(),
nullptr
}
);
// Go through all of the pretty-printed entries and record their
// locations.
for (auto entry : entries) {
PrettyPrintedDeclarations[entry.first] =
memBufferStartLoc.getAdvancedLoc(entry.second);
}
// Grab the pretty-printed location.
ppLoc = PrettyPrintedDeclarations[decl];
}
loc = ppLoc;
loc = evaluateOrDefault(
decl->getASTContext().evaluator, PrettyPrintDeclRequest{decl},
SourceLoc());
}
}

View File

@@ -26,6 +26,7 @@
#include "TypeCheckObjC.h"
#include "TypeCheckType.h"
#include "TypeChecker.h"
#include "swift/AST/ASTMangler.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/ASTWalker.h"
@@ -3209,3 +3210,176 @@ bool IsUnsafeRequest::evaluate(Evaluator &evaluator, Decl *decl) const {
return false;
}
//----------------------------------------------------------------------------//
// PrettyPrintDeclRequest
//----------------------------------------------------------------------------//
/// Returns the access level for pretty-printed declarations.
///
/// This is always \c Public unless \p decl is a \c ValueDecl and its
/// access level is below \c Public. (That can happen with @testable and
/// @_private imports.)
static AccessLevel getBufferAccessLevel(const Decl *decl) {
AccessLevel level = AccessLevel::Public;
if (auto *VD = dyn_cast<ValueDecl>(decl))
level = VD->getFormalAccessScope().accessLevelForDiagnostics();
if (level > AccessLevel::Public) level = AccessLevel::Public;
return level;
}
namespace {
/// Keep track of the offsets at which a given target declaration is printed.
class TrackingPrinter : public StreamPrinter {
const Decl *targetDecl;
public:
std::optional<uint64_t> targetDeclOffset;
TrackingPrinter(const Decl *targetDecl, raw_ostream &out)
: StreamPrinter(out), targetDecl(targetDecl) { }
void printDeclLoc(const Decl *D) override {
if (D == targetDecl)
targetDeclOffset = OS.tell();
}
};
}
SourceLoc PrettyPrintDeclRequest::evaluate(Evaluator &eval, const Decl *decl) const {
// Conjure a buffer name for this declaration.
SmallVector<std::string, 4> nameComponents;
DeclContext *dc;
if (auto valueDecl = dyn_cast<ValueDecl>(decl)) {
nameComponents.push_back(valueDecl->getBaseName().userFacingName().str());
dc = valueDecl->getDeclContext();
} else {
dc = decl->getInnermostDeclContext();
}
// Collect context information for the buffer name.
while (dc) {
switch (dc->getContextKind()) {
case DeclContextKind::Package:
break;
case DeclContextKind::Module:
nameComponents.push_back(
cast<ModuleDecl>(dc)->getPublicModuleName(/*onlyIfImported=*/true
).str().str());
break;
case DeclContextKind::FileUnit:
case DeclContextKind::TopLevelCodeDecl:
case DeclContextKind::SerializedTopLevelCodeDecl:
break;
case DeclContextKind::ExtensionDecl:
nameComponents.push_back(
cast<ExtensionDecl>(dc)->getExtendedType().getString());
break;
case DeclContextKind::GenericTypeDecl:
case DeclContextKind::Initializer:
case DeclContextKind::AbstractClosureExpr:
case DeclContextKind::SerializedAbstractClosure:
case DeclContextKind::AbstractFunctionDecl:
case DeclContextKind::SubscriptDecl:
case DeclContextKind::EnumElementDecl:
case DeclContextKind::MacroDecl:
if (auto valueDecl = dyn_cast_or_null<ValueDecl>(dc->getAsDecl())) {
nameComponents.push_back(
valueDecl->getBaseName().userFacingName().str());
}
break;
}
dc = dc->getParent();
}
std::string bufferName;
{
llvm::raw_string_ostream out(bufferName);
for (auto iter = nameComponents.rbegin(); iter != nameComponents.rend(); ++iter) {
out << *iter;
if (iter + 1 != nameComponents.rend())
out << ".";
}
}
// Compute the name of the enclosing type(s), if there is one. We'll embed the
// printed declaration in the type definition to establish the lexical
// context.
std::vector<std::string> enclosingTypes;
for (auto dc = decl->getDeclContext(); dc; dc = dc->getParent()) {
if (auto nominal = dc->getSelfNominalTypeDecl()) {
// The name of this enclosing type.
auto nominalKindName =
Decl::getDescriptiveKindName(nominal->getDescriptiveKind());
enclosingTypes.push_back(
(nominalKindName + " " +
nominal->getBaseName().userFacingName()).str());
// Jump from an extension over to the extended type.
dc = nominal;
}
}
std::reverse(enclosingTypes.begin(), enclosingTypes.end());
// Render the buffer contents.
ASTContext &ctx = decl->getASTContext();
llvm::SmallString<128> bufferContents;
uint64_t targetDeclOffsetInBuffer;
{
llvm::raw_svector_ostream out(bufferContents);
// Produce the enclosing types.
unsigned indent = 0;
for (const auto &enclosingType : enclosingTypes) {
out << std::string(indent, ' ') << enclosingType << " {\n";
indent += 2;
}
// Print this declaration.
TrackingPrinter printer(decl, out);
printer.setIndent(indent);
llvm::SaveAndRestore<bool> isPrettyPrinting(
ctx.Diags.IsPrettyPrintingDecl, true);
auto options = PrintOptions::printForDiagnostics(
getBufferAccessLevel(decl),
ctx.TypeCheckerOpts.PrintFullConvention);
decl->print(printer, options);
// Close all of the enclosing types.
for (const auto & enclosingType: enclosingTypes) {
(void)enclosingType;
indent -= 2;
out << std::string(indent, ' ') << "}\n";
}
assert(indent == 0);
if (!printer.targetDeclOffset)
return SourceLoc();
targetDeclOffsetInBuffer = *printer.targetDeclOffset;
}
// Build a buffer with the pretty-printed declaration.
SourceManager &sourceMgr = ctx.SourceMgr;
auto bufferID = sourceMgr.addMemBufferCopy(bufferContents, bufferName);
auto memBufferStartLoc = sourceMgr.getLocForBufferStart(bufferID);
// Note that this is a pretty-printed buffer.
sourceMgr.setGeneratedSourceInfo(
bufferID,
GeneratedSourceInfo{
GeneratedSourceInfo::PrettyPrinted,
CharSourceRange(),
CharSourceRange(memBufferStartLoc, bufferContents.size()),
ASTNode(const_cast<Decl *>(decl)).getOpaqueValue(),
nullptr
}
);
return memBufferStartLoc.getAdvancedLoc(targetDeclOffsetInBuffer);
}

View File

@@ -60,10 +60,10 @@ s.c = 5
// CHECK-NEXT: ctypes.h:{{[0-9]+}}:{{[0-9]+}}: note: built-in type 'Complex' not supported
// CHECK-NEXT: int _Complex c;
// CHECK-NEXT: ^
// CHECK-NEXT: ctypes.PartialImport:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'a'?
// CHECK-NEXT: ctypes.PartialImport.a:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'a'?
// CHECK-NEXT: public var a: Int32
// CHECK-NEXT: ^
// CHECK-NEXT: ctypes.PartialImport:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'b'?
// CHECK-NEXT: ctypes.PartialImport.b:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'b'?
// CHECK-NEXT: public var b: Int32
// CHECK-NEXT: ^

View File

@@ -19,8 +19,8 @@ _ = bar.methodReturningForwardDeclaredInterface()
let s: PartialImport
s.c = 5
// CHECK: experimental_diagnostics_opt_out.swift:{{[0-9]+}}:{{[0-9]+}}: error: value of type 'PartialImport' has no member 'c'
// CHECK: ctypes.PartialImport:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'a'?
// CHECK: ctypes.PartialImport:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'b'?
// CHECK: ctypes.PartialImport.a:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'a'?
// CHECK: ctypes.PartialImport.b:{{[0-9]+}}:{{[0-9]+}}: note: did you mean 'b'?
// CHECK-NOT: warning
// CHECK-NOT: error
// CHECK-NOT: note

View File

@@ -14,6 +14,6 @@ final class MyImage : Image {
// CHECK: non-@objc initializer 'init(imageLiteralResourceName:)' is declared in extension of 'Image' and cannot be overridden
// Make sure we aren't emitting a fixit into the extant module...
// CHECK-NOT: add '@objc' to make this declaration overridable
// CHECK: ImageInitializers.Image:{{.*}}: note: overridden declaration is here
// CHECK: ImageInitializers.Image.init:{{.*}}: note: overridden declaration is here
override required convenience init(imageLiteralResourceName name: String) { }
}

View File

@@ -8,7 +8,7 @@
var x = String.init // expected-error{{ambiguous use of 'init'}}
// CHECK: {{.*[/\\]}}serialized-diagnostics-prettyprint.swift:[[@LINE-1]]:16: error: ambiguous use of 'init'
// CHECK: Swift.String:2:23: note: found this candidate
// CHECK: CONTENTS OF FILE Swift.String:
// CHECK: extension String {
// CHECK: Swift.String.init:2:19: note: found this candidate
// CHECK: CONTENTS OF FILE Swift.String.init:
// CHECK: struct String {
// CHECK: public init(_ content: Substring.UnicodeScalarView)

View File

@@ -49,7 +49,7 @@ open class SubClass: ParentClass {
// Removed the underlying file, so should use the generated source instead
// RUN: mv %t/moda.swift %t/moda.swift-moved
// RUN: not %target-swift-frontend -typecheck -I %t/mods -D MODB %s 2>&1 | %FileCheck -check-prefix=CHECK-GENERATED %s
// CHECK-GENERATED: moda.ParentClass:{{.*}}: note:
// CHECK-GENERATED: moda.ParentClass.overridableMethodA:{{.*}}: note:
// Underlying file has changed, so the locations in .swiftsourceinfo may not
// make sense any more. Ignored for now (ie. it is used regardless)
@@ -65,7 +65,7 @@ open class SubClass: ParentClass {
// RUN: rm %t/moda.swift
// RUN: touch %t/moda.swift
// RUN: not %target-swift-frontend -typecheck -I %t/mods -D MODB %s 2>&1 | %FileCheck -check-prefix=CHECK-EMPTY %s
// CHECK-EMPTY: moda.ParentClass:{{.*}}: note:
// CHECK-EMPTY: moda.ParentClass.overridableMethodA:{{.*}}: note:
// The file and line from a location directive should be used whether or not it
// exists - the actual source still comes from the original file, so that's what

View File

@@ -9,5 +9,5 @@
ambiguous()
// CHECK: testable-printast-locations.swift:[[@LINE-2]]:1: error: ambiguous use of 'ambiguous()'
// CHECK: ModuleA.ambiguous (internal):1:15: note: found this candidate in module 'ModuleA'
// CHECK: ModuleB.ambiguous (internal):1:15: note: found this candidate in module 'ModuleB'
// CHECK: ModuleA.ambiguous:1:15: note: found this candidate in module 'ModuleA'
// CHECK: ModuleB.ambiguous:1:15: note: found this candidate in module 'ModuleB'